From 87efe0915ad74ab31ed05ba1620b2a15e11b287c Mon Sep 17 00:00:00 2001 From: Giovani Rodriguez Date: Thu, 8 Jul 2021 01:20:33 -0400 Subject: [PATCH] feat: add single player scene --- config.yaml | 2 +- main.py | 8 +- tetri5/entity.py | 257 +++++++++++++++++++++-------------------------- tetri5/game.py | 112 +++++---------------- tetri5/scene.py | 105 ++++++++++++++++--- 5 files changed, 231 insertions(+), 253 deletions(-) diff --git a/config.yaml b/config.yaml index 7b325e9..766c5e9 100644 --- a/config.yaml +++ b/config.yaml @@ -28,7 +28,7 @@ engine: piece-set-time: 600 piece-gravity-increase: 56 lines-per-level: 5 - points-per-lines-completed: + points-table: - 0 # 0 line - 40 # 1 line - 100 # 2 lines diff --git a/main.py b/main.py index 19828d3..394244b 100644 --- a/main.py +++ b/main.py @@ -9,13 +9,11 @@ from tetri5.util import ConfigurationManager def main() -> None: ConfigurationManager.init() - - game = Game() - game.init() + Game.init() while True: - game.update() - game.draw() + Game.update() + Game.draw() if __name__ == "__main__": main() \ No newline at end of file diff --git a/tetri5/entity.py b/tetri5/entity.py index fab7c74..2526c4e 100644 --- a/tetri5/entity.py +++ b/tetri5/entity.py @@ -1,47 +1,43 @@ import copy import random import pygame -from typing import List, Tuple from pygame import mixer +from typing import List, Tuple +from types import FunctionType from tetri5.util import ConfigurationManager from tetri5.util import Controller -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from tetri5.game import Game - """ TODO description """ class Entity: def __init__(self, points: Tuple, color: str, border_color: str = None): - self.points = points - self.color = color - self.border_color = border_color - self.elapsed_time = 0 + self._tile_size = ConfigurationManager.get("engine", "tile-size") + self._points = points + self._color = color + self._border_color = border_color + self._elapsed_time = 0 def update(self, elapsed_time: int) -> None: - self.elapsed_time += elapsed_time + self._elapsed_time += elapsed_time def draw(self, surface: pygame.Surface) -> None: - tile_size = ConfigurationManager.get("engine", "tile-size") - - for square in self.points: - pygame.draw.polygon(surface, pygame.Color(self.color), square, 0) - if self.border_color: - pygame.draw.polygon(surface, pygame.Color(self.border_color), square, max(tile_size // 6, 1)) + for square in self._points: + pygame.draw.polygon(surface, pygame.Color(self._color), square, 0) + if self._border_color: + pygame.draw.polygon(surface, pygame.Color(self._border_color), square, max(self._tile_size // 6, 1)) def collide(self, entity: "Entity") -> bool: # TODO figure out how to do type hint for entity param of type Entity - for square_one in self.points: - for square_two in entity.points: + for square_one in self._points: + for square_two in entity._points: for i in range(4): # 4 vertices if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: return True return False def _collide(self, points: List) -> bool: - for square_one in self.points: + for square_one in self._points: for square_two in points: for i in range(4): # 4 vertices if square_one[i][0] == square_two[i][0] and square_one[i][1] == square_two[i][1]: @@ -57,17 +53,17 @@ class Well(Entity): HEIGHT = 20 # standard tetris well height, should not be changed def __init__(self, position: Tuple, color: str, border_color: str): - super().__init__(self._get_points(position), color, border_color) + super().__init__(Well._get_points(position), color, border_color) - def _get_points(self, position: Tuple) -> List: + @classmethod + def _get_points(cls, position: Tuple) -> List: tile_size = ConfigurationManager.get("engine", "tile-size") - shape = [] - for i in range(self.WIDTH + 2): - for j in range(self.HEIGHT + 2): - if i in [0, self.WIDTH + 1]: + for i in range(cls.WIDTH + 2): + for j in range(cls.HEIGHT + 2): + if i in [0, cls.WIDTH + 1]: shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) - elif j in [0, self.HEIGHT + 1]: + elif j in [0, cls.HEIGHT + 1]: shape.append(((i, j), (i + 1, j), (i + 1, j + 1), (i, j + 1))) points = [] @@ -77,7 +73,6 @@ class Well(Entity): point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] sub_points.append(point) points.append(sub_points) - return points ''' @@ -87,52 +82,49 @@ class Well(Entity): class Piece(Entity): def __init__(self, shape: Tuple, position: Tuple, color: str, inner_border_color: str, border_color: str): - super().__init__(self._get_points(shape, position), color, border_color) - - self.inner_border_color = inner_border_color - self.center = self._get_center(shape, position) - self.piece_set_sound = mixer.Channel(2) - self.previous_points = None - self.previous_center = None + super().__init__(Piece._get_points(shape, position), color, border_color) + self._inner_border_color = inner_border_color + self._center = self._get_center(shape, position) + self._piece_set_sound = mixer.Channel(2) + self._previous_points = None + self._previous_center = None # Gravity - self.gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") - self.current_gravity_time = 0 - self.applying_gravity = True + self._gravity_time = ConfigurationManager.get("engine", "piece-gravity-time") + self._current_gravity_time = 0 + self._applying_gravity = True # Set - self.set_time = ConfigurationManager.get("engine", "piece-set-time") - self.current_set_time = 0 - self.applying_set = False + self._set_time = ConfigurationManager.get("engine", "piece-set-time") + self._current_set_time = 0 + self._applying_set = False - def update(self, elapsed_time: int, game: "Game") -> None: + def update(self, elapsed_time: int, well: Well, stack: "Stack", level: int, clear_current_piece: FunctionType) -> None: super().update(elapsed_time) - tile_size = ConfigurationManager.get("engine", "tile-size") - - if self.applying_gravity: - self.applying_gravity = self._apply_gravity(elapsed_time, game.well, game.stack) - self.applying_set = not self.applying_gravity + if self._applying_gravity: + self._applying_gravity = self._apply_gravity(elapsed_time, well, stack) + self._applying_set = not self._applying_gravity """ For more information on the piece set logic go here: https://strategywiki.org/wiki/Tetris/Features#Lock_delay """ - if self.applying_set: - self.applying_set = self._apply_set(elapsed_time, game) - self.applying_gravity = not self.applying_set + if self._applying_set: + self._applying_set = self._apply_set(elapsed_time, well, stack, clear_current_piece) + self._applying_gravity = not self._applying_set # handle rotation, left and right movement if Controller.key_down(pygame.K_SPACE): self.rotate() - if game.well and self.collide(game.well) or game.stack and self.collide(game.stack): + if well and self.collide(well) or stack and self.collide(stack): self.revert() if Controller.key_down(pygame.K_LEFT): - self.move((-tile_size, 0)) - if game.well and self.collide(game.well) or game.stack and self.collide(game.stack): + self.move((-self._tile_size, 0)) + if well and self.collide(well) or stack and self.collide(stack): self.revert() if Controller.key_down(pygame.K_RIGHT): - self.move((tile_size, 0)) - if game.well and self.collide(game.well) or game.stack and self.collide(game.stack): + self.move((self._tile_size, 0)) + if well and self.collide(well) or stack and self.collide(stack): self.revert() # handle soft drop movement and gravity based on level @@ -141,41 +133,37 @@ class Piece(Entity): gravity_increase = ConfigurationManager.get("engine", "piece-gravity-increase") if Controller.key_pressed(pygame.K_DOWN): - self.gravity_time = max(10, (gravity_time - (game.get_level() * gravity_increase)) // 10) - self.set_time = max(10, set_time // 10) + self._gravity_time = max(10, (gravity_time - (level * gravity_increase)) // 10) + self._set_time = max(10, set_time // 10) if not Controller.key_pressed(pygame.K_DOWN): - self.gravity_time = gravity_time - (game.get_level() * gravity_increase) - self.set_time = set_time + self._gravity_time = gravity_time - (level * gravity_increase) + self._set_time = set_time def draw(self, surface: pygame.Surface, well: Well = None, stack: "Stack" = None) -> None: - tile_size = ConfigurationManager.get("engine", "tile-size") - # ghost piece if well and stack: for square in self._get_ghost_piece_points(well, stack): - pygame.draw.polygon(surface, pygame.Color("#FFFFFF"), square, max(tile_size // 6, 1)) # TODO add white to the yaml - + pygame.draw.polygon(surface, pygame.Color("#FFFFFF"), square, max(self._tile_size // 6, 1)) # TODO add white to the yaml super().draw(surface) # inner border piece - for square in self.points: - if self.inner_border_color: - vertex_one = (square[0][0] + (tile_size // 10), square[0][1] + (tile_size // 10)) - vertex_two = (square[1][0] - (tile_size // 10), square[1][1] + (tile_size // 10)) - vertex_three = (square[2][0] - (tile_size // 10), square[2][1] - (tile_size // 10)) - vertex_four = (square[3][0] + (tile_size // 10), square[3][1] - (tile_size // 10)) + for square in self._points: + if self._inner_border_color: + vertex_one = (square[0][0] + (self._tile_size // 10), square[0][1] + (self._tile_size // 10)) + vertex_two = (square[1][0] - (self._tile_size // 10), square[1][1] + (self._tile_size // 10)) + vertex_three = (square[2][0] - (self._tile_size // 10), square[2][1] - (self._tile_size // 10)) + vertex_four = (square[3][0] + (self._tile_size // 10), square[3][1] - (self._tile_size // 10)) new_square = (vertex_one, vertex_two, vertex_three, vertex_four) - - pygame.draw.polygon(surface, pygame.Color(self.inner_border_color), new_square, max(tile_size // 6, 1)) + pygame.draw.polygon(surface, pygame.Color(self._inner_border_color), new_square, max(self._tile_size // 6, 1)) def move(self, vector: Tuple) -> None: - self.previous_points = copy.deepcopy(self.points) - self.previous_center = copy.deepcopy(self.center) + self._previous_points = copy.deepcopy(self._points) + self._previous_center = copy.deepcopy(self._center) - self.center[0] += vector[0] - self.center[1] += vector[1] + self._center[0] += vector[0] + self._center[1] += vector[1] - for square in self.points: + for square in self._points: for vertex in square: vertex[0] += vector[0] vertex[1] += vector[1] @@ -185,28 +173,28 @@ class Piece(Entity): https://gamedev.stackexchange.com/questions/17974/how-to-rotate-blocks-in-tetris ''' def rotate(self) -> None: - self.previous_points = copy.deepcopy(self.points) - self.previous_center = copy.deepcopy(self.center) + self._previous_points = copy.deepcopy(self._points) + self._previous_center = copy.deepcopy(self._center) new_points = [] - for square in self.points: + for square in self._points: for vertex in square: - h = vertex[0] - self.center[0] - k = vertex[1] - self.center[1] + h = vertex[0] - self._center[0] + k = vertex[1] - self._center[1] - vertex[0] = (k * -1) + self.center[0] - vertex[1] = h + self.center[1] + vertex[0] = (k * -1) + self._center[0] + vertex[1] = h + self._center[1] new_points.append([square[-1]] + square[0:-1]) - self.points = new_points + self._points = new_points def revert(self) -> None: - if self.previous_points and self.previous_center: - self.points = self.previous_points - self.center = self.previous_center + if self._previous_points and self._previous_center: + self._points = self._previous_points + self._center = self._previous_center + @classmethod def _get_points(self, shape: Tuple, position: Tuple) -> List: - tile_size = ConfigurationManager.get("engine", "tile-size") - + tile_size = ConfigurationManager.get("engine", "tile-size") points = [] for square in shape[:-1]: sub_points = [] @@ -214,45 +202,36 @@ class Piece(Entity): point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]] sub_points.append(point) points.append(sub_points) - return points def _get_center(self, shape: Tuple, position: Tuple) -> List: - tile_size = ConfigurationManager.get("engine", "tile-size") center = shape[-1] - - # cast to int and avoid exception from pygame (center can be a floating point) - return [int(center[0] * tile_size + position[0]), int(center[1] * tile_size + position[1])] + return [int(center[0] * self._tile_size + position[0]), int(center[1] * self._tile_size + position[1])] def _apply_gravity(self, elapsed_time: int, well: Well, stack: "Stack") -> bool: - tile_size = ConfigurationManager.get("engine", "tile-size") - self.current_gravity_time += elapsed_time - - if self.current_gravity_time >= self.gravity_time: - self.current_gravity_time = 0 + self._current_gravity_time += elapsed_time + if self._current_gravity_time >= self._gravity_time: + self._current_gravity_time = 0 if not self._entity_is_below(well) and not self._entity_is_below(stack): - self.move((0, tile_size)) + self.move((0, self._tile_size)) else: return False - return True - def _apply_set(self, elapsed_time: int, game: "Game") -> bool: - self.current_set_time += elapsed_time - - if self.current_set_time >= self.set_time: - self.current_set_time = 0 - if self._entity_is_below(game.well) or self._entity_is_below(game.stack): + def _apply_set(self, elapsed_time: int, well: Well, stack: "Stack", clear_current_piece: FunctionType) -> bool: + self._current_set_time += elapsed_time + if self._current_set_time >= self._set_time: + self._current_set_time = 0 + if self._entity_is_below(well) or self._entity_is_below(stack): self._play_piece_set_sound() - game.stack.add_piece(self) # TODO do on tetris object level? - game.current_piece = None # TODO turn into a method + stack.add_piece(self) + clear_current_piece() else: return False - return True def _mimic_move(self, vector: Tuple) -> List: - mimic_points = copy.deepcopy(self.points) + mimic_points = copy.deepcopy(self._points) for square in mimic_points: for vertex in square: @@ -263,24 +242,19 @@ class Piece(Entity): def _play_piece_set_sound(self) -> None: piece_set_sound_file = ConfigurationManager.get("sound", "piece-set") - self.piece_set_sound.play(mixer.Sound(piece_set_sound_file)) + self._piece_set_sound.play(mixer.Sound(piece_set_sound_file)) def _entity_is_below(self, entity: Entity) -> bool: - tile_size = ConfigurationManager.get("engine", "tile-size") - mimic_points = self._mimic_move((0, tile_size)) + mimic_points = self._mimic_move((0, self._tile_size)) return entity and entity._collide(mimic_points) def _get_ghost_piece_points(self, well: Well, stack: "Stack") -> List: - tile_size = ConfigurationManager.get("engine", "tile-size") - prior_points = [] - current_points = copy.deepcopy(self.points) - + current_points = copy.deepcopy(self._points) while not well._collide(current_points) and not stack._collide(current_points): prior_points = copy.deepcopy(current_points) for square in current_points: for vertex in square: - vertex[1] += 1 * tile_size - + vertex[1] += 1 * self._tile_size return prior_points # shape attributes @@ -299,26 +273,22 @@ class Stack(Entity): def __init__(self, color: str, border_color: str): super().__init__([], color, border_color) - self.lines_completed_count = 0 + self.total_lines = 0 + self.lines_completed_last = 0 self.line_completed_sound = mixer.Channel(1) - def update(self, elapsed_time: int, game: "Game") -> None: + def update(self, elapsed_time: int) -> None: # TODO remove scene argument super().update(elapsed_time) - - lines_completed = self._complete_rows() - current_level = game.get_level() - points_per_lines_completed = ConfigurationManager.get("engine", "points-per-lines-completed") - - game.score += points_per_lines_completed[lines_completed] * (current_level + 1) - self.lines_completed_count += lines_completed - + self.lines_completed_last = self._complete_rows() + self.total_lines += self.lines_completed_last + def add_piece(self, piece: Piece) -> None: - self.points += piece.points + self._points += piece._points # TODO refactor into multiple functions def _complete_rows(self) -> int: squares_by_row = {} - for square in self.points: + for square in self._points: top_left_vertex = square[0] if top_left_vertex[1] not in squares_by_row: squares_by_row[top_left_vertex[1]] = [] @@ -327,28 +297,27 @@ class Stack(Entity): squares_to_exclude = [] rows_completed = [] - for key in squares_by_row: + for key, value in squares_by_row.items(): if len(squares_by_row[key]) == Well.WIDTH: - squares_to_exclude += squares_by_row[key] + squares_to_exclude += value rows_completed.append(key) - if len(squares_to_exclude) == 0: + if not squares_to_exclude: return 0 self._play_line_completed_sound() - - tile_size = ConfigurationManager.get("engine", "tile-size") new_points = [] - for square in self.points: + for square in self._points: if square not in squares_to_exclude: for vertex in square: - distance_to_move = 0 - for row_completed in rows_completed: - if vertex[1] <= row_completed: - distance_to_move += 1 - vertex[1] += tile_size * distance_to_move + distance_to_move = sum( + vertex[1] <= row_completed + for row_completed in rows_completed + ) + + vertex[1] += self._tile_size * distance_to_move new_points.append(square) - self.points = new_points + self._points = new_points return len(rows_completed) @@ -398,9 +367,7 @@ class PieceGenerator: def _get_piece_color() -> Tuple: random_number = random.randint(1, 3) - base_color = ConfigurationManager.get("color", "piece-" + str(random_number)) inner_border_color = None if random_number != 3 else ConfigurationManager.get("color", "piece-inner-border-1") outer_border_color = ConfigurationManager.get("color", "piece-outer-border-1") - return (base_color, inner_border_color, outer_border_color) \ No newline at end of file diff --git a/tetri5/game.py b/tetri5/game.py index adb9f5d..c0976b2 100644 --- a/tetri5/game.py +++ b/tetri5/game.py @@ -1,126 +1,60 @@ import sys import pygame -from pygame import mixer from tetri5.util import ConfigurationManager from tetri5.util import TextGenerator -from tetri5.entity import PieceGenerator -from tetri5.entity import Well -from tetri5.entity import Stack -from tetri5.online import MultiplayerService -from tetri5.scene import TitleScene +from tetri5.scene import Scene, TitleScene # TODO improve game assets https://www.spriters-resource.com/nes/tetris/ # TODO should be a singleton and refactor the whole file? class Game: - def __init__(self): - self.fps = -1 - self.tile_size = -1 - self.screen = None - self.clock = None - self.current_scene = TitleScene() - - # In Game # - self.current_piece = None - self.next_piece = None - self.well = None - self.stack = None - self.main_music = None - self.score = -1 + _current_scene = None - def init(self) -> None: + @classmethod + def change_scene(cls, scene: Scene) -> None: + cls._current_scene = scene + + @classmethod + def init(cls) -> None: pygame.init() TextGenerator.init(ConfigurationManager.get("image", "font"), (20, 20)) + cls._current_scene = TitleScene(Game.change_scene) win_width = ConfigurationManager.get("window", "width") win_height = ConfigurationManager.get("window", "height") win_title = ConfigurationManager.get("window", "title") win_icon = ConfigurationManager.get("image", "window-icon") - self.fps = ConfigurationManager.get("engine", "fps") - self.tile_size = ConfigurationManager.get("engine", "tile-size") - self.screen = pygame.display.set_mode((win_width, win_height)) - self.clock = pygame.time.Clock() + cls.fps = ConfigurationManager.get("engine", "fps") + cls.tile_size = ConfigurationManager.get("engine", "tile-size") + cls.screen = pygame.display.set_mode((win_width, win_height)) + cls.clock = pygame.time.Clock() pygame.display.set_caption(win_title) pygame.display.set_icon(pygame.image.load(win_icon)) - - - self.main_music = mixer.Channel(0) - self.well = Well((280, 80), ConfigurationManager.get("color", "well-1"), ConfigurationManager.get("color", "well-border-1")) # TODO calculate position later and redo color config for well - self.stack = Stack(ConfigurationManager.get("color", "stack-1"), ConfigurationManager.get("color", "stack-border-1")) - self.score = 0 - self.main_music.set_volume(0.7) # TODO add volume to the config - self.main_music.play(mixer.Sound(ConfigurationManager.get("sound", "main-music")), -1) - # gets called from the games main loop - def update(self) -> None: + @classmethod + def update(cls) -> None: # TODO write not initialized exception - elapsed_time = self.clock.tick(self.fps) - - if self.current_scene: - self.current_scene.update(elapsed_time) - else: - if not self.next_piece: - self.next_piece = PieceGenerator.get_piece((620, 160)) - - if self.current_piece: - self.current_piece.update(elapsed_time, self) - else: - self.current_piece = self.next_piece - self.current_piece.move((360 - 620, 100 - 160)) # TODO calculate spawn position correctly - self.next_piece = PieceGenerator.get_piece((620, 160)) # (360, 100) - if self.stack and self.current_piece.collide(self.stack): # TODO game over redo - pygame.quit() - MultiplayerService.quit() - sys.exit() - - if self.stack: - self.stack.update(elapsed_time, self) + elapsed_time = cls.clock.tick(cls.fps) + if cls._current_scene: + cls._current_scene.update(elapsed_time) + for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() - MultiplayerService.quit() sys.exit() - def draw(self) -> None: + @classmethod + def draw(cls) -> None: # TODO write not initialized exception - if self.current_scene: - self.current_scene.draw(self.screen) - else: - # draw window bg - bg_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) - self.screen.fill(bg_color) - - # draw all game objects - if self.next_piece: - self.next_piece.draw(self.screen) - if self.well: - self.well.draw(self.screen) - if self.stack: - self.stack.draw(self.screen) - if self.current_piece: - self.current_piece.draw(self.screen, self.well, self.stack) - - score = str(self.score).zfill(6) - lines = str(self.stack.lines_completed_count).zfill(4) - level = str(self.get_level()).zfill(2) - - TextGenerator.draw("Top", (80, 120), self.screen) - TextGenerator.draw("000000", (80, 140), self.screen) - TextGenerator.draw("Score", (80, 180), self.screen) - TextGenerator.draw(score, (80, 200), self.screen) - TextGenerator.draw("Lines " + lines, (300, 40), self.screen) - TextGenerator.draw("Next", (600, 120), self.screen) - TextGenerator.draw("LVL " + level, (600, 220), self.screen) + if cls._current_scene: + cls._current_scene.draw(cls.screen) # update display pygame.display.update() - def get_level(self) -> int: - lines_per_level = ConfigurationManager.get("engine", "lines-per-level") - return 0 if not self.stack else self.stack.lines_completed_count // lines_per_level \ No newline at end of file diff --git a/tetri5/scene.py b/tetri5/scene.py index a9a84b7..039a9b0 100644 --- a/tetri5/scene.py +++ b/tetri5/scene.py @@ -1,19 +1,32 @@ +import sys import pygame +from pygame import mixer +from types import FunctionType from tetri5.util import ConfigurationManager from tetri5.util import TextGenerator from tetri5.util import Controller +from tetri5.entity import Well +from tetri5.entity import Stack +from tetri5.entity import PieceGenerator """ TODO """ -class TitleScene: +class Scene: + pass + +""" + TODO +""" +class TitleScene(Scene): # Title screen options ONE_PLAYER = "1 PLAYER" TWO_PLAYER = "2 PLAYER" - def __init__(self) -> None: + def __init__(self, change_scene: FunctionType) -> None: self._tile_size = ConfigurationManager.get("engine", "tile-size") + self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) self._logo_image = pygame.image.load(ConfigurationManager.get("image", "title-screen")) self._cursor_position_one = ConfigurationManager.get("position", "cursor-option-one") self._cursor_position_two = ConfigurationManager.get("position", "cursor-option-two") @@ -22,12 +35,13 @@ class TitleScene: self._cursor_blink_time = 0 self._cursor_color = pygame.Color(ConfigurationManager.get("color", "cursor")) self._cursor_off = False - self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) self._logo_position = ConfigurationManager.get("position", "title-logo") self._option_one_position = ConfigurationManager.get("position", "option-one") self._option_two_position = ConfigurationManager.get("position", "option-two") self._is_multiplayer = False + self._change_scence = change_scene + def draw(self, surface: pygame.Surface) -> None: surface.fill(self._background_color) surface.blit(self._logo_image, self._logo_position) @@ -38,7 +52,7 @@ class TitleScene: if self._cursor_off: pygame.draw.circle(surface, self._cursor_color, self._cursor_position, self._tile_size // 3) - def update(self, elapsed_time) -> None: + def update(self, elapsed_time: int) -> None: if Controller.key_pressed(pygame.K_UP): self._cursor_position = self._cursor_position_one self._is_multiplayer = False @@ -51,18 +65,83 @@ class TitleScene: self._cursor_blink_time = 0 self._cursor_off = not self._cursor_off -""" - TODO -""" -class SinglePlayerScene: - pass + if Controller.key_pressed(pygame.K_RETURN): + self._change_scence(SinglePlayerScene(self._change_scence)) """ TODO """ -class MultiPlayerScene: - pass - +class SinglePlayerScene(Scene): + def __init__(self, change_scene: FunctionType) -> None: + self._tile_size = ConfigurationManager.get("engine", "tile-size") + self._background_color = pygame.Color(ConfigurationManager.get("color", "window-bg")) + self._score = 0 + self._level = 0 + self._well = Well((280, 80), ConfigurationManager.get("color", "well-1"), ConfigurationManager.get("color", "well-border-1")) # TODO calculate position later and redo color config for well + self._stack = Stack(ConfigurationManager.get("color", "stack-1"), ConfigurationManager.get("color", "stack-border-1")) + self._current_piece = None + self._next_piece = PieceGenerator.get_piece((620, 160)) + self._main_music = mixer.Channel(0) + self._main_music.set_volume(0.7) # TODO add volume to the config + self._main_music.play(mixer.Sound(ConfigurationManager.get("sound", "main-music")), -1) + self._points_table = ConfigurationManager.get("engine", "points-table") + + self._change_scence = change_scene + + def draw(self, surface: pygame.Surface) -> None: + surface.fill(self._background_color) + + if self._next_piece: + self._next_piece.draw(surface) + if self._well: + self._well.draw(surface) + if self._stack: + self._stack.draw(surface) + if self._current_piece: + self._current_piece.draw(surface, self._well, self._stack) - \ No newline at end of file + score = str(self._score).zfill(6) + lines = str(self._stack.total_lines).zfill(4) + level = str(self._get_level()).zfill(2) + + TextGenerator.draw("Top", (80, 120), surface) + TextGenerator.draw("000000", (80, 140), surface) + TextGenerator.draw("Score", (80, 180), surface) + TextGenerator.draw(score, (80, 200), surface) + TextGenerator.draw("Lines " + lines, (300, 40), surface) + TextGenerator.draw("Next", (600, 120), surface) + TextGenerator.draw("LVL " + level, (600, 220), surface) + + def update(self, elapsed_time: int) -> None: + if self._current_piece: + self._current_piece.update(elapsed_time,\ + self._well,\ + self._stack,\ + self._get_level(),\ + self._clear_current_piece) + else: + self._current_piece = self._next_piece + self._current_piece.move((360 - 620, 100 - 160)) # TODO calculate spawn position correctly + self._next_piece = PieceGenerator.get_piece((620, 160)) # (360, 100) + if self._stack and self._current_piece.collide(self._stack): # TODO game over redo + pygame.quit() + sys.exit() + + if self._stack: + self._stack.update(elapsed_time) + + self._score += self._points_table[self._stack.lines_completed_last] * (self._get_level() + 1) + + def _get_level(self) -> int: + lines_per_level = ConfigurationManager.get("engine", "lines-per-level") + return 0 if not self._stack else self._stack.total_lines // lines_per_level + + def _clear_current_piece(self) -> None: + self._current_piece = None + +""" + TODO +""" +class MultiPlayerScene(Scene): + pass