Skip to content
Na tej stronie

Rezultat i kod źródłowy gry imitującej Tetrisa

Ta część strony została oznaczona, jakoby była w trakcie konstrukcji. To oznacza, że treść w miejscu tego komunikatu zostanie uzupełniona w najbliższym czasie.
python
from enum import Enum
import random
import copy


class TetrisTileKind(Enum):
    EMPTY = ' '
    RED = 'R'
    YELLOW = 'Y'
    GREEN = 'G'


class TetrisTile:
    def __init__(self):
        self.kind = TetrisTileKind.EMPTY


class TetrisStructureKind(Enum):
    L1 = [[True, False],
          [True, False],
          [True, True]]
    L2 = [
        [False, False, True],
        [True, True, True]]
    L3 = [
        [True, True],
        [False, True],
        [False, True]]
    L4 = [
        [True, True, True],
        [True, False, False]]
    I1 = [[True, True, True]]
    I2 = [[True], [True], [True]]
    Sq = [[True, True],
          [True, True]]


class TetrisBoard:
    def __init__(self):
        self.width = 8
        self.height = 12
        self.resetState()

    def resetState(self):
        self.state = [[TetrisTile()
                       for _ in range(self.width)] for _ in range(self.height)]

    def is_empty_at(self, row, column):
        return row < 0 or column < 0 or row >= self.height or column >= self.width or self.state[row][column].kind.name == "EMPTY"


class TetrisStructure:
    def __init__(self, **kwargs):
        board_width = kwargs["board_width"]

        self.kind = random.choice(list(TetrisTileKind)[1:])
        self.structure_kind = random.choice(list(TetrisStructureKind))
        self.pos_row = self.height() * (-1)
        self.pos_col = int(board_width / 2) - \
            int(self.width() / 2)

    def is_overlap(self, board, offset_row_col=(0, 0)):
        shape = self.structure_kind.value
        for i in range(len(shape)):
            for j in range(len(shape[0])):
                if shape[i][j] and not board.is_empty_at(i+offset_row_col[0]+self.pos_row, j+offset_row_col[1]+self.pos_col):
                    return True
        return False

    def is_colliding_bottom(self, board):
        return self.pos_row >= board.height-len(self.structure_kind.value) or self.is_overlap(board, (1, 0))

    def is_colliding_left(self, board):
        return self.pos_col <= 0 or self.is_overlap(board, (0, -1))

    def is_colliding_right(self, board):
        return self.pos_col >= board.width - len(self.structure_kind.value[0]) or self.is_overlap(board, (0, 1))

    def write_to_board(self, board):
        shape = self.structure_kind.value
        for i in range(len(shape)):
            for j in range(len(shape[0])):
                if shape[i][j] and (i+self.pos_row) >= 0:
                    board.state[i+self.pos_row][j +
                                                self.pos_col].kind = self.kind

    def width(self):
        return len(self.structure_kind.value[0])

    def height(self):
        return len(self.structure_kind.value)

    kindRotationMap = {
        "L1": "L2",
        "L2": "L3",
        "L3": "L4",
        "L4": "L1",
        "Sq": "Sq",
        "I1": "I2",
        "I2": "I1"
    }


class TetrisEventKind(Enum):
    Void = 0
    Updated = 1
    LevelFinished = 2


def is_row_filled(row):
    for col in row:
        if col.kind.name == "EMPTY":
            return False
    return True


class Tetris:
    def __init__(self):
        # Wypełniamy bufor board pustymi blokami
        self.board = TetrisBoard()
        self.dropping_structure = None
        self.update_task_check_winning = False
        self.is_finished = False
        self.score = 0

    def update(self):
        if self.is_finished:
            return

        if self.update_task_check_winning:
            self.delete_winning_rows()
            self.update_task_check_winning = False
            return

        if self.dropping_structure is None:
            self.dropping_structure = TetrisStructure(
                board_width=self.board.width)
            if self.dropping_structure.is_overlap(self.board) or self.dropping_structure.is_colliding_bottom(self.board):
                self.is_finished = True
                return

        self.go_down()
        self.check_and_write_dropping_structure()

    def check_and_write_dropping_structure(self):
        if self.dropping_structure is not None and self.dropping_structure.is_colliding_bottom(self.board):
            self.dropping_structure.write_to_board(self.board)
            self.dropping_structure = None
            self.update_task_check_winning = True
            if random.choice([True, False]):
                self.score += 1
            elif random.choice([True, False]):
                self.score += 2

    def delete_winning_rows(self):
        rows = self.board.state
        i = 1
        if is_row_filled(rows[0]):
            return

        # logika grawitacji
        while i < len(rows):
            if is_row_filled(rows[i]):
                if random.choice([True, False]):
                    self.score = int(self.score * 1.25 + 15)
                else:
                    self.score = int(self.score * 1.25 + 10)

                i2 = i
                while i2 > 0:
                    rows[i2] = rows[i2-1]
                    i2 -= 1
                rows[0] = []
                for m in range(self.board.width):
                    rows[0].append(TetrisTile())

            i += 1

    def go_left(self):
        if self.dropping_structure is None or self.dropping_structure.is_colliding_left(self.board):
            return
        self.dropping_structure.pos_col -= 1

    def go_right(self):
        if self.dropping_structure is None or self.dropping_structure.is_colliding_right(self.board):
            return
        self.dropping_structure.pos_col += 1

    def go_down(self):
        if self.dropping_structure is not None and not self.dropping_structure.is_colliding_bottom(self.board):
            self.dropping_structure.pos_row += 1

    def go_rotate(self):
        if self.dropping_structure is None:
            return

        target = copy.deepcopy(self.dropping_structure)
        target.structure_kind = TetrisStructureKind[
            TetrisStructure.kindRotationMap[target.structure_kind.name]]

        offset_bottom_board = self.board.height - target.pos_row - target.height() + 1
        if offset_bottom_board < 0:
            target.pos_row += offset_bottom_board

        offset_right = self.board.width - target.pos_col - target.width()
        if offset_right < 0:
            target.pos_col += offset_right

        offset_left = target.pos_col
        if offset_left < 0:
            target.pos_col -= offset_left

        if self.dropping_structure.is_overlap(self.board):
            return

        self.dropping_structure = target
from enum import Enum
import random
import copy


class TetrisTileKind(Enum):
    EMPTY = ' '
    RED = 'R'
    YELLOW = 'Y'
    GREEN = 'G'


class TetrisTile:
    def __init__(self):
        self.kind = TetrisTileKind.EMPTY


class TetrisStructureKind(Enum):
    L1 = [[True, False],
          [True, False],
          [True, True]]
    L2 = [
        [False, False, True],
        [True, True, True]]
    L3 = [
        [True, True],
        [False, True],
        [False, True]]
    L4 = [
        [True, True, True],
        [True, False, False]]
    I1 = [[True, True, True]]
    I2 = [[True], [True], [True]]
    Sq = [[True, True],
          [True, True]]


class TetrisBoard:
    def __init__(self):
        self.width = 8
        self.height = 12
        self.resetState()

    def resetState(self):
        self.state = [[TetrisTile()
                       for _ in range(self.width)] for _ in range(self.height)]

    def is_empty_at(self, row, column):
        return row < 0 or column < 0 or row >= self.height or column >= self.width or self.state[row][column].kind.name == "EMPTY"


class TetrisStructure:
    def __init__(self, **kwargs):
        board_width = kwargs["board_width"]

        self.kind = random.choice(list(TetrisTileKind)[1:])
        self.structure_kind = random.choice(list(TetrisStructureKind))
        self.pos_row = self.height() * (-1)
        self.pos_col = int(board_width / 2) - \
            int(self.width() / 2)

    def is_overlap(self, board, offset_row_col=(0, 0)):
        shape = self.structure_kind.value
        for i in range(len(shape)):
            for j in range(len(shape[0])):
                if shape[i][j] and not board.is_empty_at(i+offset_row_col[0]+self.pos_row, j+offset_row_col[1]+self.pos_col):
                    return True
        return False

    def is_colliding_bottom(self, board):
        return self.pos_row >= board.height-len(self.structure_kind.value) or self.is_overlap(board, (1, 0))

    def is_colliding_left(self, board):
        return self.pos_col <= 0 or self.is_overlap(board, (0, -1))

    def is_colliding_right(self, board):
        return self.pos_col >= board.width - len(self.structure_kind.value[0]) or self.is_overlap(board, (0, 1))

    def write_to_board(self, board):
        shape = self.structure_kind.value
        for i in range(len(shape)):
            for j in range(len(shape[0])):
                if shape[i][j] and (i+self.pos_row) >= 0:
                    board.state[i+self.pos_row][j +
                                                self.pos_col].kind = self.kind

    def width(self):
        return len(self.structure_kind.value[0])

    def height(self):
        return len(self.structure_kind.value)

    kindRotationMap = {
        "L1": "L2",
        "L2": "L3",
        "L3": "L4",
        "L4": "L1",
        "Sq": "Sq",
        "I1": "I2",
        "I2": "I1"
    }


class TetrisEventKind(Enum):
    Void = 0
    Updated = 1
    LevelFinished = 2


def is_row_filled(row):
    for col in row:
        if col.kind.name == "EMPTY":
            return False
    return True


class Tetris:
    def __init__(self):
        # Wypełniamy bufor board pustymi blokami
        self.board = TetrisBoard()
        self.dropping_structure = None
        self.update_task_check_winning = False
        self.is_finished = False
        self.score = 0

    def update(self):
        if self.is_finished:
            return

        if self.update_task_check_winning:
            self.delete_winning_rows()
            self.update_task_check_winning = False
            return

        if self.dropping_structure is None:
            self.dropping_structure = TetrisStructure(
                board_width=self.board.width)
            if self.dropping_structure.is_overlap(self.board) or self.dropping_structure.is_colliding_bottom(self.board):
                self.is_finished = True
                return

        self.go_down()
        self.check_and_write_dropping_structure()

    def check_and_write_dropping_structure(self):
        if self.dropping_structure is not None and self.dropping_structure.is_colliding_bottom(self.board):
            self.dropping_structure.write_to_board(self.board)
            self.dropping_structure = None
            self.update_task_check_winning = True
            if random.choice([True, False]):
                self.score += 1
            elif random.choice([True, False]):
                self.score += 2

    def delete_winning_rows(self):
        rows = self.board.state
        i = 1
        if is_row_filled(rows[0]):
            return

        # logika grawitacji
        while i < len(rows):
            if is_row_filled(rows[i]):
                if random.choice([True, False]):
                    self.score = int(self.score * 1.25 + 15)
                else:
                    self.score = int(self.score * 1.25 + 10)

                i2 = i
                while i2 > 0:
                    rows[i2] = rows[i2-1]
                    i2 -= 1
                rows[0] = []
                for m in range(self.board.width):
                    rows[0].append(TetrisTile())

            i += 1

    def go_left(self):
        if self.dropping_structure is None or self.dropping_structure.is_colliding_left(self.board):
            return
        self.dropping_structure.pos_col -= 1

    def go_right(self):
        if self.dropping_structure is None or self.dropping_structure.is_colliding_right(self.board):
            return
        self.dropping_structure.pos_col += 1

    def go_down(self):
        if self.dropping_structure is not None and not self.dropping_structure.is_colliding_bottom(self.board):
            self.dropping_structure.pos_row += 1

    def go_rotate(self):
        if self.dropping_structure is None:
            return

        target = copy.deepcopy(self.dropping_structure)
        target.structure_kind = TetrisStructureKind[
            TetrisStructure.kindRotationMap[target.structure_kind.name]]

        offset_bottom_board = self.board.height - target.pos_row - target.height() + 1
        if offset_bottom_board < 0:
            target.pos_row += offset_bottom_board

        offset_right = self.board.width - target.pos_col - target.width()
        if offset_right < 0:
            target.pos_col += offset_right

        offset_left = target.pos_col
        if offset_left < 0:
            target.pos_col -= offset_left

        if self.dropping_structure.is_overlap(self.board):
            return

        self.dropping_structure = target
python
import random
import pygame
import sys
import datetime
from pygame.locals import *

from tetris import *

TileKindToColorMap = {
    ' ': (50, 50, 70),
    'R': (220, 30, 90),
    'Y': (220, 220, 30),
    'G': (50, 220, 70),
}


class GameScene:
    def __init__(self, **kwargs):
        self.full_score = 0
        pass

    def resetState(self, globalState):
        self.globalState = globalState

        # ustawienie początkowego stanu gry
        self.tetris = Tetris()

        self.tileSize = 30
        self.boardOffset = (int((self.globalState.window_width -
                            self.tileSize*self.tetris.board.width)/2), int((self.globalState.window_height - self.tileSize*self.tetris.board.height)/2))

        self.font = pygame.font.SysFont(
            self.globalState.default_font_name, 24)

        self.frame_freeze_count = 0
        self.key_repeat_frame = 0

        self.is_score_recorded = False
        self.best_scores_text = None

    def onEvent(self, event):
        if event.type == pygame.KEYUP and self.tetris.is_finished:
            self.resetState(self.globalState)
        if event.type == pygame.KEYUP:
            self.key_repeat_frame = 0

    def save_tetris_score(self):
        with open('wyniki.txt', 'a') as f:
            f.write(str(self.tetris.score*10) + ';' +
                    datetime.datetime.now().strftime("%B %d, %Y %H:%M") + '\n')

    def read_tetris_scores(self):
        scores = []
        with open('wyniki.txt', 'r') as f:
            for line in f:
                line = line.strip()
                columns = line.split(';')
                scores.append({
                    "wynik": columns[0],
                    "data": columns[1],
                })

        return scores

    def tetris_scores_text(self):
        text = "Najlepsze wyniki:\n"
        items = sorted(self.read_tetris_scores(),
                       key=lambda i: int(i["wynik"]), reverse=True)
        for i, item in enumerate(items[:4]):
            text += str(i+1) + ". Wynik " + \
                item["wynik"] + " punktów (" + item["data"] + ")\n"
        return text

    def onFrame(self):

        if self.tetris.is_finished and not self.is_score_recorded:
            self.is_score_recorded = True
            self.save_tetris_score()

        if self.tetris.is_finished:
            return

        # logika odbioru zdarzenia wciśnięcia przycisku klawiatury i jego powtarzania
        keys = pygame.key.get_pressed()
        key_handled = False
        if self.key_repeat_frame == 0:
            if keys[pygame.K_LEFT]:
                self.tetris.go_left()
            if keys[pygame.K_RIGHT]:
                self.tetris.go_right()
            if keys[pygame.K_DOWN]:
                self.tetris.go_down()
            if keys[pygame.K_UP]:
                self.tetris.go_rotate()
        elif self.key_repeat_frame > 1:
            self.key_repeat_frame = -1

        if keys[pygame.K_LEFT] or keys[pygame.K_RIGHT] or keys[pygame.K_DOWN] or keys[pygame.K_UP]:
            self.key_repeat_frame += 1

        # logika aktualizacji stanu gry
        if self.frame_freeze_count > self.globalState.fps:
            self.frame_freeze_count = 0
            self.tetris.update()

        self.frame_freeze_count += 1

    def draw(self, surface):
        board = self.tetris.board
        if self.tetris.is_finished:
            surface.fill((60, 30, 40))
            if self.best_scores_text is None:
                self.best_scores_text = self.tetris_scores_text()

            text = self.best_scores_text
            for i, line in enumerate(text.splitlines()):
                obj_message_left = self.font.render(
                    line, True, self.globalState.color_fg)
                obj_message_left_rect = obj_message_left.get_rect()
                obj_message_left_rect.topleft = (
                    25, int((self.globalState.window_height-100) - (obj_message_left_rect.height / 2) + (obj_message_left_rect.height + 3)*i))
                surface.blit(obj_message_left, obj_message_left_rect)

        obj_message = self.font.render(
            'Punkty: ' + str(self.tetris.score*10), True, self.globalState.color_fg)
        obj_message_rect = obj_message.get_rect()
        obj_message_rect.topleft = (
            self.globalState.window_width - obj_message_rect.width - 20, self.globalState.window_height - obj_message_rect.height - 10)
        surface.blit(obj_message, obj_message_rect)

        self.draw_board(surface)
        if self.tetris.dropping_structure is not None:
            self.draw_dropping_structure(surface)

        obj_message = self.font.render(
            'Punkty: ' + str(self.tetris.score*10), True, self.globalState.color_fg)
        obj_message_rect = obj_message.get_rect()
        obj_message_rect.topleft = (
            self.globalState.window_width - obj_message_rect.width - 20, self.globalState.window_height - obj_message_rect.height - 10)
        surface.blit(obj_message, obj_message_rect)

    def draw_tile(self, surface, tile, position):
        color = TileKindToColorMap[tile.kind.value]
        if self.tetris.is_finished and tile.kind.name == "EMPTY":
            color = (90, 50, 60)
        pygame.draw.rect(surface, color,
                         pygame.Rect(position[0]+2, position[1]+2, self.tileSize-4, self.tileSize-4))

    def draw_tile_alt(self, surface, tile, position):
        pygame.draw.rect(surface, TileKindToColorMap[tile.kind.value],
                         pygame.Rect(position[0]+2, position[1]+2, self.tileSize-4, self.tileSize-4))
        pygame.draw.rect(surface, self.globalState.color_bg,
                         pygame.Rect(position[0]+4, position[1]+4, self.tileSize-8, self.tileSize-8))

    def draw_board(self, surface):
        tetrisBoard = self.tetris.board
        for i, row in enumerate(tetrisBoard.state):
            for j, tile in enumerate(row):
                self.draw_tile(
                    surface, tile, (j*self.tileSize+self.boardOffset[0], i*self.tileSize+self.boardOffset[1]))

    def draw_dropping_structure(self, surface):
        dropping_structure = self.tetris.dropping_structure
        pos_col = dropping_structure.pos_col
        pos_row = dropping_structure.pos_row
        for i, row in enumerate(dropping_structure.structure_kind.value):
            for j, value in enumerate(row):
                if value:
                    self.draw_tile_alt(
                        surface, dropping_structure, ((j-dropping_structure.width()-1)*self.tileSize+self.boardOffset[0], (i+1)*self.tileSize+self.boardOffset[1]))
                if j+pos_col >= 0 and i+pos_row >= 0 and value:
                    self.draw_tile(
                        surface, dropping_structure, ((j+pos_col)*self.tileSize+self.boardOffset[0], (i+pos_row)*self.tileSize+self.boardOffset[1]))
import random
import pygame
import sys
import datetime
from pygame.locals import *

from tetris import *

TileKindToColorMap = {
    ' ': (50, 50, 70),
    'R': (220, 30, 90),
    'Y': (220, 220, 30),
    'G': (50, 220, 70),
}


class GameScene:
    def __init__(self, **kwargs):
        self.full_score = 0
        pass

    def resetState(self, globalState):
        self.globalState = globalState

        # ustawienie początkowego stanu gry
        self.tetris = Tetris()

        self.tileSize = 30
        self.boardOffset = (int((self.globalState.window_width -
                            self.tileSize*self.tetris.board.width)/2), int((self.globalState.window_height - self.tileSize*self.tetris.board.height)/2))

        self.font = pygame.font.SysFont(
            self.globalState.default_font_name, 24)

        self.frame_freeze_count = 0
        self.key_repeat_frame = 0

        self.is_score_recorded = False
        self.best_scores_text = None

    def onEvent(self, event):
        if event.type == pygame.KEYUP and self.tetris.is_finished:
            self.resetState(self.globalState)
        if event.type == pygame.KEYUP:
            self.key_repeat_frame = 0

    def save_tetris_score(self):
        with open('wyniki.txt', 'a') as f:
            f.write(str(self.tetris.score*10) + ';' +
                    datetime.datetime.now().strftime("%B %d, %Y %H:%M") + '\n')

    def read_tetris_scores(self):
        scores = []
        with open('wyniki.txt', 'r') as f:
            for line in f:
                line = line.strip()
                columns = line.split(';')
                scores.append({
                    "wynik": columns[0],
                    "data": columns[1],
                })

        return scores

    def tetris_scores_text(self):
        text = "Najlepsze wyniki:\n"
        items = sorted(self.read_tetris_scores(),
                       key=lambda i: int(i["wynik"]), reverse=True)
        for i, item in enumerate(items[:4]):
            text += str(i+1) + ". Wynik " + \
                item["wynik"] + " punktów (" + item["data"] + ")\n"
        return text

    def onFrame(self):

        if self.tetris.is_finished and not self.is_score_recorded:
            self.is_score_recorded = True
            self.save_tetris_score()

        if self.tetris.is_finished:
            return

        # logika odbioru zdarzenia wciśnięcia przycisku klawiatury i jego powtarzania
        keys = pygame.key.get_pressed()
        key_handled = False
        if self.key_repeat_frame == 0:
            if keys[pygame.K_LEFT]:
                self.tetris.go_left()
            if keys[pygame.K_RIGHT]:
                self.tetris.go_right()
            if keys[pygame.K_DOWN]:
                self.tetris.go_down()
            if keys[pygame.K_UP]:
                self.tetris.go_rotate()
        elif self.key_repeat_frame > 1:
            self.key_repeat_frame = -1

        if keys[pygame.K_LEFT] or keys[pygame.K_RIGHT] or keys[pygame.K_DOWN] or keys[pygame.K_UP]:
            self.key_repeat_frame += 1

        # logika aktualizacji stanu gry
        if self.frame_freeze_count > self.globalState.fps:
            self.frame_freeze_count = 0
            self.tetris.update()

        self.frame_freeze_count += 1

    def draw(self, surface):
        board = self.tetris.board
        if self.tetris.is_finished:
            surface.fill((60, 30, 40))
            if self.best_scores_text is None:
                self.best_scores_text = self.tetris_scores_text()

            text = self.best_scores_text
            for i, line in enumerate(text.splitlines()):
                obj_message_left = self.font.render(
                    line, True, self.globalState.color_fg)
                obj_message_left_rect = obj_message_left.get_rect()
                obj_message_left_rect.topleft = (
                    25, int((self.globalState.window_height-100) - (obj_message_left_rect.height / 2) + (obj_message_left_rect.height + 3)*i))
                surface.blit(obj_message_left, obj_message_left_rect)

        obj_message = self.font.render(
            'Punkty: ' + str(self.tetris.score*10), True, self.globalState.color_fg)
        obj_message_rect = obj_message.get_rect()
        obj_message_rect.topleft = (
            self.globalState.window_width - obj_message_rect.width - 20, self.globalState.window_height - obj_message_rect.height - 10)
        surface.blit(obj_message, obj_message_rect)

        self.draw_board(surface)
        if self.tetris.dropping_structure is not None:
            self.draw_dropping_structure(surface)

        obj_message = self.font.render(
            'Punkty: ' + str(self.tetris.score*10), True, self.globalState.color_fg)
        obj_message_rect = obj_message.get_rect()
        obj_message_rect.topleft = (
            self.globalState.window_width - obj_message_rect.width - 20, self.globalState.window_height - obj_message_rect.height - 10)
        surface.blit(obj_message, obj_message_rect)

    def draw_tile(self, surface, tile, position):
        color = TileKindToColorMap[tile.kind.value]
        if self.tetris.is_finished and tile.kind.name == "EMPTY":
            color = (90, 50, 60)
        pygame.draw.rect(surface, color,
                         pygame.Rect(position[0]+2, position[1]+2, self.tileSize-4, self.tileSize-4))

    def draw_tile_alt(self, surface, tile, position):
        pygame.draw.rect(surface, TileKindToColorMap[tile.kind.value],
                         pygame.Rect(position[0]+2, position[1]+2, self.tileSize-4, self.tileSize-4))
        pygame.draw.rect(surface, self.globalState.color_bg,
                         pygame.Rect(position[0]+4, position[1]+4, self.tileSize-8, self.tileSize-8))

    def draw_board(self, surface):
        tetrisBoard = self.tetris.board
        for i, row in enumerate(tetrisBoard.state):
            for j, tile in enumerate(row):
                self.draw_tile(
                    surface, tile, (j*self.tileSize+self.boardOffset[0], i*self.tileSize+self.boardOffset[1]))

    def draw_dropping_structure(self, surface):
        dropping_structure = self.tetris.dropping_structure
        pos_col = dropping_structure.pos_col
        pos_row = dropping_structure.pos_row
        for i, row in enumerate(dropping_structure.structure_kind.value):
            for j, value in enumerate(row):
                if value:
                    self.draw_tile_alt(
                        surface, dropping_structure, ((j-dropping_structure.width()-1)*self.tileSize+self.boardOffset[0], (i+1)*self.tileSize+self.boardOffset[1]))
                if j+pos_col >= 0 and i+pos_row >= 0 and value:
                    self.draw_tile(
                        surface, dropping_structure, ((j+pos_col)*self.tileSize+self.boardOffset[0], (i+pos_row)*self.tileSize+self.boardOffset[1]))
python
import random
import pygame
import sys
from pygame.locals import *
from game import *
from dataclasses import dataclass

pygame.init()


@dataclass
class GlobalState:
    window_width: int = 800
    window_height: int = 600
    color_bg: tuple = (30, 30, 35)
    color_fg: tuple = (230, 230, 240)
    default_font_name: str = "sans-serif"
    fps: int = 60
    display: object = object()
    extra: object = object()


class SimpleTitleScene:
    def __init__(self, **kwargs):
        self.next_scene = kwargs["next_scene"] if "next_scene" in kwargs else None
        self.title_message = kwargs["title_message"] if "title_message" in kwargs else None
        self.message = kwargs["message"] if "message" in kwargs else None

    def resetState(self, globalState):
        self.globalState = globalState
        self.font = pygame.font.SysFont(self.globalState.default_font_name, 24)
        self.title_font = pygame.font.SysFont(
            self.globalState.default_font_name, 90)

        self.go_to_next_scene = False
        self.next_scene.resetState(globalState)

    def onEvent(self, event):
        if event.type == pygame.KEYUP or event.type == pygame.MOUSEBUTTONUP:
            self.go_to_next_scene = True

    def onFrame(self):
        if self.go_to_next_scene and self.next_scene is not None:
            self.globalState.display.scene = self.next_scene

    def draw(self, surface):

        self.obj_message = self.font.render(
            'Nacisnąć dowolny przycisk', True, self.globalState.color_fg)
        self.obj_message_rect = self.obj_message.get_rect()
        self.obj_message_rect.topleft = (
            self.globalState.window_width - self.obj_message_rect.width - 20, self.globalState.window_height - self.obj_message_rect.height - 10)
        surface.blit(self.obj_message, self.obj_message_rect)

        if self.title_message:
            obj_message_title = self.title_font.render(
                self.title_message, True, self.globalState.color_fg)
            obj_message_title_rect = obj_message_title.get_rect()
            obj_message_title_rect.topleft = (
                int((self.globalState.window_width/2) - (obj_message_title_rect.width / 2)), int((self.globalState.window_height/2) - (obj_message_title_rect.height / 2)))
            surface.blit(obj_message_title, obj_message_title_rect)

        if self.message:
            for i, line in enumerate(self.message.splitlines()):
                obj_message_left = self.font.render(
                    line, True, self.globalState.color_fg)
                obj_message_left_rect = obj_message_left.get_rect()
                obj_message_left_rect.topleft = (
                    25, int((self.globalState.window_height*0.75) - (obj_message_left_rect.height / 2) + (obj_message_left_rect.height + 3)*i))
                surface.blit(obj_message_left, obj_message_left_rect)


class Display:
    def __init__(self, **kwargs):
        self.globalState = kwargs["global_state"] in kwargs if "global_state" in kwargs else GlobalState(
        )
        self.globalState.display = self
        self.scene = kwargs["scene"] if "scene" in kwargs else SimpleTitleScene(
            next_scene=None,
        )
        self.scene.resetState(self.globalState)

        self.clock = pygame.time.Clock()
        self.surface = pygame.display.set_mode(
            (self.globalState.window_width, self.globalState.window_height))

    def frame(self):
        # Zbieramy zdarzenia użytkownika
        for event in pygame.event.get():
            if event.type == QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
                sys.exit(0)

            # Wywołujemy odbiorcę zdarzeń
            self.scene.onEvent(event)

        self.surface.fill(self.globalState.color_bg)

        # Wywołujemy funkcję w każdej klatce
        self.scene.onFrame()

        # Wywołujemy rysowanie klatki
        self.scene.draw(self.surface)

        pygame.display.update()
        self.clock.tick(self.globalState.fps)
import random
import pygame
import sys
from pygame.locals import *
from game import *
from dataclasses import dataclass

pygame.init()


@dataclass
class GlobalState:
    window_width: int = 800
    window_height: int = 600
    color_bg: tuple = (30, 30, 35)
    color_fg: tuple = (230, 230, 240)
    default_font_name: str = "sans-serif"
    fps: int = 60
    display: object = object()
    extra: object = object()


class SimpleTitleScene:
    def __init__(self, **kwargs):
        self.next_scene = kwargs["next_scene"] if "next_scene" in kwargs else None
        self.title_message = kwargs["title_message"] if "title_message" in kwargs else None
        self.message = kwargs["message"] if "message" in kwargs else None

    def resetState(self, globalState):
        self.globalState = globalState
        self.font = pygame.font.SysFont(self.globalState.default_font_name, 24)
        self.title_font = pygame.font.SysFont(
            self.globalState.default_font_name, 90)

        self.go_to_next_scene = False
        self.next_scene.resetState(globalState)

    def onEvent(self, event):
        if event.type == pygame.KEYUP or event.type == pygame.MOUSEBUTTONUP:
            self.go_to_next_scene = True

    def onFrame(self):
        if self.go_to_next_scene and self.next_scene is not None:
            self.globalState.display.scene = self.next_scene

    def draw(self, surface):

        self.obj_message = self.font.render(
            'Nacisnąć dowolny przycisk', True, self.globalState.color_fg)
        self.obj_message_rect = self.obj_message.get_rect()
        self.obj_message_rect.topleft = (
            self.globalState.window_width - self.obj_message_rect.width - 20, self.globalState.window_height - self.obj_message_rect.height - 10)
        surface.blit(self.obj_message, self.obj_message_rect)

        if self.title_message:
            obj_message_title = self.title_font.render(
                self.title_message, True, self.globalState.color_fg)
            obj_message_title_rect = obj_message_title.get_rect()
            obj_message_title_rect.topleft = (
                int((self.globalState.window_width/2) - (obj_message_title_rect.width / 2)), int((self.globalState.window_height/2) - (obj_message_title_rect.height / 2)))
            surface.blit(obj_message_title, obj_message_title_rect)

        if self.message:
            for i, line in enumerate(self.message.splitlines()):
                obj_message_left = self.font.render(
                    line, True, self.globalState.color_fg)
                obj_message_left_rect = obj_message_left.get_rect()
                obj_message_left_rect.topleft = (
                    25, int((self.globalState.window_height*0.75) - (obj_message_left_rect.height / 2) + (obj_message_left_rect.height + 3)*i))
                surface.blit(obj_message_left, obj_message_left_rect)


class Display:
    def __init__(self, **kwargs):
        self.globalState = kwargs["global_state"] in kwargs if "global_state" in kwargs else GlobalState(
        )
        self.globalState.display = self
        self.scene = kwargs["scene"] if "scene" in kwargs else SimpleTitleScene(
            next_scene=None,
        )
        self.scene.resetState(self.globalState)

        self.clock = pygame.time.Clock()
        self.surface = pygame.display.set_mode(
            (self.globalState.window_width, self.globalState.window_height))

    def frame(self):
        # Zbieramy zdarzenia użytkownika
        for event in pygame.event.get():
            if event.type == QUIT or (event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE):
                sys.exit(0)

            # Wywołujemy odbiorcę zdarzeń
            self.scene.onEvent(event)

        self.surface.fill(self.globalState.color_bg)

        # Wywołujemy funkcję w każdej klatce
        self.scene.onFrame()

        # Wywołujemy rysowanie klatki
        self.scene.draw(self.surface)

        pygame.display.update()
        self.clock.tick(self.globalState.fps)
python
from display import Display, SimpleTitleScene
from game import GameScene
import pygame


def main():
    pygame.init()
    pygame.display.set_caption('TERTIS')

    display = Display(scene=SimpleTitleScene(
        title_message="TERTIS",
        message='''
Strzałka w lewo, prawo porusza strukturą
Strzałka w dół wykonuje dodatkowy krok w dół
Strzałka w górę obraca strukturą
Esc wychodzi z gry
''',
        next_scene=GameScene()
    ))
    while True:
        display.frame()


if __name__ == '__main__':
    main()
from display import Display, SimpleTitleScene
from game import GameScene
import pygame


def main():
    pygame.init()
    pygame.display.set_caption('TERTIS')

    display = Display(scene=SimpleTitleScene(
        title_message="TERTIS",
        message='''
Strzałka w lewo, prawo porusza strukturą
Strzałka w dół wykonuje dodatkowy krok w dół
Strzałka w górę obraca strukturą
Esc wychodzi z gry
''',
        next_scene=GameScene()
    ))
    while True:
        display.frame()


if __name__ == '__main__':
    main()