diff --git a/src/game/block.py b/src/game/block.py index bb2b2df..ecd2f50 100644 --- a/src/game/block.py +++ b/src/game/block.py @@ -27,3 +27,6 @@ class Block(pygame.sprite.Sprite): def horizontal_collision(self, y: int, field: np.ndarray) -> bool: return y >= CONFIG.game.rows or (y >= 0 and field[y, int(self.pos.x)]) + + def rotate(self, pivot: pygame.Vector2) -> pygame.Vector2: + return pivot + (self.pos - pivot).rotate(90) diff --git a/src/game/game.py b/src/game/game.py index f43a7cd..873cd97 100644 --- a/src/game/game.py +++ b/src/game/game.py @@ -31,6 +31,7 @@ class Game: self.timers = Timers( Timer(CONFIG.game.initial_speed, True, self.move_down), Timer(CONFIG.game.movment_delay), + Timer(CONFIG.game.rotation_delay), ) self.timers.vertical.activate() @@ -64,6 +65,17 @@ class Game: self.move_right() self.timers.horizontal.activate() + if not self.timers.rotation.active: + if ( + keys[pygame.K_SPACE] + or keys[pygame.K_r] + or keys[pygame.K_UP] + or keys[pygame.K_w] + or keys[pygame.K_k] + ): + self.tetromino.rotate() + self.timers.rotation.activate() + def move_down(self) -> None: self.tetromino.move_down() @@ -131,13 +143,15 @@ class Game: self._delete_rows(delete_rows) def _delete_rows(self, delete_rows: list[int]) -> None: - if delete_rows: - for row in delete_rows: - for block in self.field[row]: - block.kill() + if not delete_rows: + return - self._move_rows_down(row) - self._rebuild_field() + for row in delete_rows: + for block in self.field[row]: + block.kill() + + self._move_rows_down(row) + self._rebuild_field() def _move_rows_down(self, deleted_row: int) -> None: for row in self.field: diff --git a/src/game/tetromino.py b/src/game/tetromino.py index a6a951d..6701fc8 100644 --- a/src/game/tetromino.py +++ b/src/game/tetromino.py @@ -2,7 +2,7 @@ from typing import Callable, Optional import numpy as np import pygame -from utils import CONFIG, Direction, Figure, FigureConfig, Size +from utils import CONFIG, Direction, Figure, Size from .block import Block from .log import log @@ -16,9 +16,9 @@ class Tetromino: field: np.ndarray, shape: Optional[Figure] = None, ) -> None: - self.figure: FigureConfig = shape.value if shape else Figure.random().value - self.block_positions: list[pygame.Vector2] = self.figure.shape - self.color: str = self.figure.color + self.figure: Figure = shape if shape else Figure.random() + self.block_positions: list[pygame.Vector2] = self.figure.value.shape + self.color: str = self.figure.value.color self.create_new = func self.field = field @@ -41,6 +41,21 @@ class Tetromino: for block in self.blocks: block.pos.x += direction.value + def rotate(self) -> None: + if self.figure == Figure.O: + return + + pivot: pygame.Vector2 = self.blocks[0].pos + + new_positions: list[pygame.Vector2] = [ + block.rotate(pivot) for block in self.blocks + ] + + if not self._are_new_positions_valid(new_positions): + return + + self._update_block_positions(new_positions) + def _check_vertical_collision( self, blocks: list[Block], direction: Direction ) -> bool: @@ -56,3 +71,17 @@ class Tetromino: block.horizontal_collision(int(block.pos.y + direction.value), self.field) for block in self.blocks ) + + def _are_new_positions_valid(self, new_positions: list[pygame.Vector2]) -> bool: + for pos in new_positions: + if not ( + 0 <= pos.x < CONFIG.game.columns and 0 <= pos.y <= CONFIG.game.rows + ): + return False + if self.field[int(pos.y), int(pos.x)]: + return False + return True + + def _update_block_positions(self, new_positions: list[pygame.Vector2]) -> None: + for block, new_pos in zip(self.blocks, new_positions): + block.pos = new_pos diff --git a/src/game/timer.py b/src/game/timer.py index c46e670..49bf4fc 100644 --- a/src/game/timer.py +++ b/src/game/timer.py @@ -35,3 +35,4 @@ class Timer: class Timers(NamedTuple): vertical: Timer horizontal: Timer + rotation: Timer