mirror of
https://github.com/kristoferssolo/Tetris.git
synced 2025-10-21 20:00:35 +00:00
186 lines
5.6 KiB
Python
186 lines
5.6 KiB
Python
from typing import Any, Callable, Optional
|
|
|
|
import numpy as np
|
|
import pygame
|
|
from utils import CONFIG, Direction, Field, Figure, Rotation, Size
|
|
|
|
from game.log import log
|
|
|
|
from .block import Block
|
|
|
|
|
|
class Tetromino:
|
|
"""
|
|
Class representing a Tetromino.
|
|
|
|
Args:
|
|
group: Sprite group for managing blocks.
|
|
create_new: Callback function to create a new Tetromino.
|
|
field: 2D array representing the game field.
|
|
shape: Initial shape of the Tetromino (default is None).
|
|
|
|
Attributes:
|
|
figure: Tetromino figure.
|
|
block_positions: List of block positions.
|
|
color: Color of the Tetromino.
|
|
create_new: Callback function to create a new Tetromino.
|
|
field: 2D array representing the game field.
|
|
blocks: List of Tetromino blocks.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
group: pygame.sprite.Group,
|
|
create_new: Callable[[], None],
|
|
field: np.ndarray[Field, Any],
|
|
shape: Optional[Figure] = None,
|
|
) -> 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.blocks = self._initialize_blocks(group)
|
|
|
|
def move_down(self) -> None:
|
|
"""
|
|
Moves the Tetromino down.
|
|
|
|
If there is a collision, the Tetromino is placed on the field, and a new one is created.
|
|
"""
|
|
if not self._check_horizontal_collision(self.blocks, Direction.DOWN):
|
|
for block in self.blocks:
|
|
block.pos.y += 1
|
|
else:
|
|
for block in self.blocks:
|
|
self.field[int(block.pos.y), int(block.pos.x)] = block
|
|
self.create_new()
|
|
|
|
def move_horizontal(self, direction: Direction) -> None:
|
|
"""
|
|
Moves the Tetromino horizontally.
|
|
|
|
Args:
|
|
direction: Direction to move (LEFT or RIGHT).
|
|
"""
|
|
if not self._check_vertical_collision(self.blocks, direction):
|
|
for block in self.blocks:
|
|
block.pos.x += direction.value
|
|
|
|
def rotate(self, rotation: Rotation = Rotation.CLOCKWISE) -> None:
|
|
"""
|
|
Rotates the Tetromino clockwise.
|
|
|
|
Does not rotate if the Tetromino is an O-shaped (square) figure.
|
|
|
|
Args:
|
|
rotation: Rotation to perform (CLOCKWISE or COUNTER_CLOCKWISE).
|
|
"""
|
|
if self.figure == Figure.O:
|
|
return
|
|
|
|
pivot: pygame.Vector2 = self.blocks[0].pos
|
|
|
|
new_positions: list[pygame.Vector2] = [
|
|
block.rotate(pivot, rotation) for block in self.blocks
|
|
]
|
|
|
|
if self._are_new_positions_valid(new_positions):
|
|
self._update_block_positions(new_positions)
|
|
|
|
def drop(self) -> None:
|
|
"""Drops the Tetromino to the bottom of the game field."""
|
|
while not self._check_horizontal_collision(self.blocks, Direction.DOWN):
|
|
for block in self.blocks:
|
|
block.pos.y += 1
|
|
|
|
def _check_vertical_collision(
|
|
self, blocks: list[Block], direction: Direction
|
|
) -> bool:
|
|
"""
|
|
Checks for vertical collision.
|
|
|
|
Args:
|
|
blocks: List of blocks to check for collision.
|
|
direction: Direction of movement.
|
|
|
|
Returns:
|
|
True if there is a vertical collision, False otherwise.
|
|
"""
|
|
return any(
|
|
block.vertical_collision(int(block.pos.x + direction.value), self.field)
|
|
for block in self.blocks
|
|
)
|
|
|
|
def _check_horizontal_collision(
|
|
self, blocks: list[Block], direction: Direction
|
|
) -> bool:
|
|
"""
|
|
Checks for horizontal collision.
|
|
|
|
Args:
|
|
blocks: List of blocks to check for collision.
|
|
direction: Direction of movement.
|
|
|
|
Returns:
|
|
True if there is a horizontal collision, False otherwise.
|
|
"""
|
|
return any(
|
|
block.horizontal_collision(int(block.pos.y + direction.value), self.field)
|
|
for block in self.blocks
|
|
)
|
|
|
|
def _update_block_positions(self, new_positions: list[pygame.Vector2]) -> None:
|
|
"""
|
|
Updates the positions of Tetromino blocks.
|
|
|
|
Args:
|
|
new_positions: New positions for the blocks.
|
|
"""
|
|
for block, new_pos in zip(self.blocks, new_positions):
|
|
block.pos = new_pos
|
|
|
|
def _are_new_positions_valid(self, new_positions: list[pygame.Vector2]) -> bool:
|
|
"""
|
|
Checks if the new positions are valid within the game field.
|
|
|
|
Args:
|
|
new_positions: New positions to check.
|
|
|
|
Returns:
|
|
True if all positions are valid, False otherwise.
|
|
"""
|
|
return all(
|
|
0 <= pos.x < CONFIG.game.columns
|
|
and 0 <= pos.y < CONFIG.game.rows
|
|
and not self.field[int(pos.y), int(pos.x)]
|
|
for pos in new_positions
|
|
)
|
|
|
|
def _initialize_blocks(self, group: pygame.sprite.Group) -> list[Block]:
|
|
"""
|
|
Initializes Tetromino blocks.
|
|
|
|
Args:
|
|
group: Sprite group for managing blocks.
|
|
|
|
Returns:
|
|
List of initialized blocks.
|
|
"""
|
|
return [
|
|
Block(group=group, pos=pos, color=self.color)
|
|
for pos in self.block_positions
|
|
]
|
|
|
|
def _generate_figure(self, shape: Optional[Figure]) -> Figure:
|
|
"""
|
|
Generates a Tetromino figure.
|
|
|
|
Args:
|
|
shape: Initial shape of the Tetromino (default is None).
|
|
|
|
Returns:
|
|
Generated Tetromino figure.
|
|
"""
|
|
return shape if shape else Figure.random()
|