From 4bd4ff00adeefc80e07ddeea6ebe4dca9a509fba Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Sun, 7 Jan 2024 18:13:01 +0200 Subject: [PATCH] feat(game): add phantoms --- src/game/screens/main.py | 2 +- src/game/screens/tetris.py | 47 ++++++++++++++++++++++++++++++++--- src/game/sprites/block.py | 6 +++++ src/game/sprites/tetromino.py | 21 +++++++++------- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/game/screens/main.py b/src/game/screens/main.py index fbd1590..726683b 100644 --- a/src/game/screens/main.py +++ b/src/game/screens/main.py @@ -22,8 +22,8 @@ class Main(BaseScreen, SceenElement, TextScreen): self._set_buttons() self._initialize_increment_height() self.settings = read_settings() - self.game_mode = mode self.game: Optional[Game] = None + self.game_mode = mode self.settings_screen: Optional[Settings] = None def draw(self) -> None: diff --git a/src/game/screens/tetris.py b/src/game/screens/tetris.py index 1972df0..99da10e 100644 --- a/src/game/screens/tetris.py +++ b/src/game/screens/tetris.py @@ -59,6 +59,7 @@ class Tetris(BaseScreen): self._initialize_grid_surface() self._initialize_field_and_tetromino() self.tetromino: Tetromino + self.phantom_tetromino: Tetromino self._initialize_game_state() self._initialize_timers() @@ -76,12 +77,14 @@ class Tetris(BaseScreen): self.update() self._draw_background() self.sprites.draw(self.surface) + self.phantom_sprites.draw(self.surface) self._draw_border() self._draw_grid() def update(self) -> None: self._update_display_surface() self.sprites.update() + self.phantom_sprites.update() def handle_events(self) -> None: """Handle player input events.""" @@ -108,7 +111,9 @@ class Tetris(BaseScreen): Returns: True if the movement was successful, False otherwise. """ - return self.tetromino.move_horizontal(Direction.LEFT) + moved = self.tetromino.move_horizontal(Direction.LEFT) + self._update_phantom_position() + return moved def move_right(self) -> bool: """ @@ -117,7 +122,10 @@ class Tetris(BaseScreen): Returns: True if the movement was successful, False otherwise. """ - return self.tetromino.move_horizontal(Direction.RIGHT) + + moved = self.tetromino.move_horizontal(Direction.RIGHT) + self._update_phantom_position() + return moved def rotate(self) -> bool: """ @@ -126,7 +134,9 @@ class Tetris(BaseScreen): Returns: True if the rotation was successful, False otherwise. """ - return self.tetromino.rotate() + rotated = self.tetromino.rotate() + self._update_phantom_position() + return rotated def rotate_reverse(self) -> bool: """ @@ -135,7 +145,9 @@ class Tetris(BaseScreen): Returns: True if the rotation was successful, False otherwise. """ - return self.tetromino.rotate(Rotation.COUNTER_CLOCKWISE) + rotated = self.tetromino.rotate(Rotation.COUNTER_CLOCKWISE) + self._update_phantom_position() + return rotated def drop(self) -> bool: """ @@ -150,6 +162,7 @@ class Tetris(BaseScreen): """Create a new tetromino and perform necessary actions.""" self._play_landing_sound() self._check_finished_rows() + self.phantom_tetromino.kill() self.game_over: bool = self._check_game_over() if self.game_over: @@ -162,6 +175,15 @@ class Tetris(BaseScreen): shape or self.get_next_figure(), ) + self.phantom_tetromino = Tetromino( + self.phantom_sprites, + None, + self.field, + self.tetromino.figure, + True, + ) + self.phantom_tetromino.drop() + return self.tetromino def _check_game_over(self) -> bool: @@ -281,6 +303,7 @@ class Tetris(BaseScreen): def _draw_components(self) -> None: """Draw additional components like borders and grid.""" self.sprites.draw(self.surface) + self.phantom_sprites.draw(self.surface) self._draw_border() self._draw_grid() @@ -296,6 +319,7 @@ class Tetris(BaseScreen): def _initialize_sprites(self) -> None: """Initialize the game sprites.""" self.sprites: pygame.sprite.Group[Block] = pygame.sprite.Group() + self.phantom_sprites: pygame.sprite.Group[Block] = pygame.sprite.Group() def _initialize_grid_surface(self) -> None: """Initialize the grid surface.""" @@ -314,6 +338,15 @@ class Tetris(BaseScreen): self.field, ) + self.phantom_tetromino = Tetromino( + self.phantom_sprites, + None, + self.field, + self.tetromino.figure, + True, + ) + self.phantom_tetromino.drop() + def _initialize_timers(self) -> None: """Initialize game timers.""" self.timers = Timers( @@ -356,6 +389,11 @@ class Tetris(BaseScreen): """Update the display surface.""" self.dispaly_surface.blit(self.surface, CONFIG.game.pos) + def _update_phantom_position(self) -> None: + new_positions = [block.pos.copy() for block in self.tetromino.blocks] + self.phantom_tetromino.update_block_positions(new_positions) + self.phantom_tetromino.drop() + def _draw_background(self) -> None: """Fill the game surface with background color.""" self.surface.fill(CONFIG.colors.bg_float) @@ -445,6 +483,7 @@ class Tetris(BaseScreen): def _reset_game_state(self) -> None: """Reset the game state.""" self.sprites.empty() + self.phantom_sprites.empty() self._initialize_field_and_tetromino() self._initialize_game_state() self.update_score(self.lines, self.score, self.level) diff --git a/src/game/sprites/block.py b/src/game/sprites/block.py index 33ec164..57285a9 100644 --- a/src/game/sprites/block.py +++ b/src/game/sprites/block.py @@ -4,6 +4,8 @@ import numpy as np import pygame from utils import CONFIG, Rotation, Size +from game.log import log + class Block(pygame.sprite.Sprite): """ @@ -27,8 +29,10 @@ class Block(pygame.sprite.Sprite): group: pygame.sprite.Group, pos: pygame.Vector2, color: str, + phantom: bool = False, ) -> None: super().__init__(group) + self.phantom = phantom self._initialize_image(color) self._initialize_positions(pos) @@ -91,6 +95,8 @@ class Block(pygame.sprite.Sprite): color: Color of the block. """ self.image = pygame.Surface(CONFIG.game.cell) + if self.phantom: + self.image.set_alpha(75) self.image.fill(color) def _initialize_positions(self, pos: pygame.Vector2) -> None: diff --git a/src/game/sprites/tetromino.py b/src/game/sprites/tetromino.py index 9863d84..559a0a9 100644 --- a/src/game/sprites/tetromino.py +++ b/src/game/sprites/tetromino.py @@ -31,15 +31,17 @@ class Tetromino: def __init__( self, group: pygame.sprite.Group, - create_new: Callable[[Optional[Figure]], "Tetromino"], + create_new: Optional[Callable[[Optional[Figure]], "Tetromino"]], field: np.ndarray[Optional[Block], Any], shape: Optional[Figure] = None, + phantom: bool = False, ) -> None: self.figure: Figure = self._generate_figure(shape) self.block_positions: list[pygame.Vector2] = self.figure.value.shape self.color: str = self.figure.value.color self.create_new = create_new self.field = field + self.phantom = phantom self.blocks = self._initialize_blocks(group) def move_down(self) -> bool: @@ -59,7 +61,8 @@ class Tetromino: for block in self.blocks: self.field[int(block.pos.y), int(block.pos.x)] = block - self.create_new(None) + if self.create_new: + self.create_new(None) return False @@ -102,7 +105,7 @@ class Tetromino: ] if self._are_new_positions_valid(new_positions): - self._update_block_positions(new_positions) + self.update_block_positions(new_positions) return True if any(pos.x < 0 for pos in new_positions): @@ -112,10 +115,6 @@ class Tetromino: return False - def next_rotation(self) -> "Tetromino": - self.rotate() - return self - def drop(self) -> bool: """ Drops the Tetromino to the bottom of the game field. @@ -130,6 +129,10 @@ class Tetromino: return True + def kill(self) -> None: + for block in self.blocks: + block.kill() + def check_collision(self, direction: Direction) -> bool: """ Checks if there is a collision in the given direction. @@ -181,7 +184,7 @@ class Tetromino: for block in self.blocks ) - def _update_block_positions(self, new_positions: list[pygame.Vector2]) -> None: + def update_block_positions(self, new_positions: list[pygame.Vector2]) -> None: """ Updates the positions of Tetromino blocks. @@ -219,7 +222,7 @@ class Tetromino: List of initialized blocks. """ return [ - Block(group=group, pos=pos, color=self.color) + Block(group=group, pos=pos, color=self.color, phantom=self.phantom) for pos in self.block_positions ]