mirror of
https://github.com/kristoferssolo/Tetris.git
synced 2025-10-21 20:00:35 +00:00
refactor(game): pass mypy
This commit is contained in:
parent
bb80400ad7
commit
0afe1ed3cb
4
main.py
4
main.py
@ -67,10 +67,10 @@ def main(args: argparse.ArgumentParser) -> None:
|
|||||||
if args.train is not None:
|
if args.train is not None:
|
||||||
ai.log.debug("Training the AI")
|
ai.log.debug("Training the AI")
|
||||||
# ai.train(*args.train)
|
# ai.train(*args.train)
|
||||||
game.Main(GameMode.AI_TRAINING).run()
|
game.Game(GameMode.AI_TRAINING).run()
|
||||||
else:
|
else:
|
||||||
game.log.debug("Running the game")
|
game.log.debug("Running the game")
|
||||||
game.Main(GameMode.PLAYER).run()
|
game.Game(GameMode.PLAYER).run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,20 +1,9 @@
|
|||||||
from .block import Block
|
|
||||||
from .game import Game
|
|
||||||
from .log import log
|
from .log import log
|
||||||
from .main import Main
|
from .screens import Game, Menu, Tetris
|
||||||
from .preview import Preview
|
|
||||||
from .score import Score
|
|
||||||
from .tetromino import Tetromino
|
|
||||||
from .timer import Timer, Timers
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"log",
|
"log",
|
||||||
"Main",
|
"Main",
|
||||||
"Game",
|
"Game",
|
||||||
"Score",
|
|
||||||
"Block",
|
|
||||||
"Tetromino",
|
|
||||||
"Preview",
|
"Preview",
|
||||||
"Timer",
|
|
||||||
"Timers",
|
|
||||||
]
|
]
|
||||||
|
|||||||
7
src/game/screens/__init__.py
Normal file
7
src/game/screens/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .game import Game
|
||||||
|
from .menu import Menu
|
||||||
|
from .preview import Preview
|
||||||
|
from .score import Score
|
||||||
|
from .tetris import Tetris
|
||||||
|
|
||||||
|
__all__ = ["Tetris", "Game", "Preview", "Score", "Menu"]
|
||||||
57
src/game/screens/base.py
Normal file
57
src/game/screens/base.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from abc import ABC, ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
|
||||||
|
|
||||||
|
class BaseScreen(ABC, metaclass=ABCMeta):
|
||||||
|
"""Base screen class."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def update(self, *args, **kwargs) -> None:
|
||||||
|
"""Update the screen."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def draw(self, *args, **kwargs) -> None:
|
||||||
|
"""Draw the screen."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def run(self, *args, **kwargs) -> None:
|
||||||
|
"""Run the screen."""
|
||||||
|
|
||||||
|
|
||||||
|
class SceenElement(ABC, metaclass=ABCMeta):
|
||||||
|
@abstractmethod
|
||||||
|
def _draw_background(self) -> None:
|
||||||
|
"""Draw the background."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _draw_border(self) -> None:
|
||||||
|
"""Draw the border."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _initialize_surface(self) -> None:
|
||||||
|
"""Initialize the surface."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _initialize_rect(self) -> None:
|
||||||
|
"""Initialize the rectangle."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _update_diplaysurface(self) -> None:
|
||||||
|
"""Update the display surface."""
|
||||||
|
|
||||||
|
|
||||||
|
class TextScreen(ABC, metaclass=ABCMeta):
|
||||||
|
"""Base screen class for text."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _initialize_font(self) -> None:
|
||||||
|
"""Initialize the font."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _draw_text(self) -> None:
|
||||||
|
"""Draw the text on the surface."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def _display_text(self, *args, **kwargs) -> None:
|
||||||
|
"""Display the text."""
|
||||||
@ -3,16 +3,18 @@ import sys
|
|||||||
import pygame
|
import pygame
|
||||||
from utils import CONFIG, Figure, GameMode
|
from utils import CONFIG, Figure, GameMode
|
||||||
|
|
||||||
from .game import Game
|
from game.log import log
|
||||||
from .log import log
|
|
||||||
|
from .base import BaseScreen
|
||||||
from .preview import Preview
|
from .preview import Preview
|
||||||
from .score import Score
|
from .score import Score
|
||||||
|
from .tetris import Tetris
|
||||||
from .tetromino import Tetromino
|
from .tetromino import Tetromino
|
||||||
|
|
||||||
|
|
||||||
class Main:
|
class Game(BaseScreen):
|
||||||
"""
|
"""
|
||||||
Main class for the game.
|
Game class.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
display_surface: Pygame display surface.
|
display_surface: Pygame display surface.
|
||||||
@ -46,11 +48,12 @@ class Main:
|
|||||||
self.draw()
|
self.draw()
|
||||||
self.handle_events()
|
self.handle_events()
|
||||||
|
|
||||||
self.game.run()
|
self.tetris.run()
|
||||||
self.score.run()
|
self.score.run()
|
||||||
self.preview.run(self.next_figure)
|
self.preview.update(self.next_figure)
|
||||||
|
self.preview.run()
|
||||||
|
|
||||||
pygame.display.update()
|
self.draw()
|
||||||
self.clock.tick(CONFIG.fps)
|
self.clock.tick(CONFIG.fps)
|
||||||
|
|
||||||
def handle_events(self) -> None:
|
def handle_events(self) -> None:
|
||||||
@ -71,14 +74,14 @@ class Main:
|
|||||||
"""Initialize game-related components."""
|
"""Initialize game-related components."""
|
||||||
self.next_figure: Figure = self._generate_next_figure()
|
self.next_figure: Figure = self._generate_next_figure()
|
||||||
|
|
||||||
self.game = Game(self._get_next_figure, self._update_score)
|
self.tetris = Tetris(self._get_next_figure, self._update_score)
|
||||||
self.score = Score()
|
self.score = Score()
|
||||||
self.preview = Preview()
|
self.preview = Preview()
|
||||||
|
|
||||||
def mute(self) -> None:
|
def mute(self) -> None:
|
||||||
"""Mute the game."""
|
"""Mute the game."""
|
||||||
self.music.set_volume(0)
|
self.music.set_volume(0)
|
||||||
self.game.mute()
|
self.tetris.mute()
|
||||||
|
|
||||||
def _update_score(self, lines: int, score: int, level: int) -> None:
|
def _update_score(self, lines: int, score: int, level: int) -> None:
|
||||||
"""
|
"""
|
||||||
7
src/game/screens/menu.py
Normal file
7
src/game/screens/menu.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from game.log import log
|
||||||
|
|
||||||
|
from .base import BaseScreen
|
||||||
|
|
||||||
|
|
||||||
|
class Menu(BaseScreen):
|
||||||
|
pass
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import pygame
|
import pygame
|
||||||
from utils import CONFIG, Figure, Size
|
from utils import CONFIG, Figure, Size
|
||||||
|
|
||||||
|
from .base import BaseScreen, SceenElement
|
||||||
|
|
||||||
class Preview:
|
|
||||||
|
class Preview(BaseScreen, SceenElement):
|
||||||
"""
|
"""
|
||||||
Class representing the preview of upcoming figures on the sidebar.
|
Class representing the preview of upcoming figures on the sidebar.
|
||||||
|
|
||||||
@ -17,15 +19,25 @@ class Preview:
|
|||||||
self._initialize_surface()
|
self._initialize_surface()
|
||||||
self._initialize_rect()
|
self._initialize_rect()
|
||||||
|
|
||||||
def run(self, next_figure: Figure) -> None:
|
def run(self) -> None:
|
||||||
|
"""Run the preview by updating the display and drawing next figure."""
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def update(self, next_figure: Figure) -> None:
|
||||||
"""
|
"""
|
||||||
Run the preview by updating the display and drawing next figures.
|
Update the preview information.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
next_figures (list[Figure]): List of upcoming figures.
|
next_figures: Next figure.
|
||||||
"""
|
"""
|
||||||
self.dispaly_surface.blit(self.surface, self.rect)
|
self.next_figure = next_figure
|
||||||
self._draw_preview(next_figure)
|
|
||||||
|
def draw(self) -> None:
|
||||||
|
"""Draw the preview on the preview surface."""
|
||||||
|
self._update_diplaysurface()
|
||||||
|
self._draw_background()
|
||||||
|
self._draw_border()
|
||||||
|
self._draw_figure()
|
||||||
|
|
||||||
def _draw_border(self) -> None:
|
def _draw_border(self) -> None:
|
||||||
"""Draw the border around the preview surface."""
|
"""Draw the border around the preview surface."""
|
||||||
@ -37,7 +49,7 @@ class Preview:
|
|||||||
CONFIG.game.border_radius,
|
CONFIG.game.border_radius,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _draw_figure(self, figure: Figure) -> None:
|
def _draw_figure(self) -> None:
|
||||||
"""
|
"""
|
||||||
Draw a single upcoming figure on the preview surface.
|
Draw a single upcoming figure on the preview surface.
|
||||||
|
|
||||||
@ -45,23 +57,12 @@ class Preview:
|
|||||||
figure (Figure): The upcoming figure to draw.
|
figure (Figure): The upcoming figure to draw.
|
||||||
idx (int): Index of the figure in the list.
|
idx (int): Index of the figure in the list.
|
||||||
"""
|
"""
|
||||||
figure_surface = figure.value.image
|
figure_surface = self.next_figure.value.image
|
||||||
x = self.surface.get_width() / 2
|
x = self.surface.get_width() / 2
|
||||||
y = self.surface.get_height() / 2
|
y = self.surface.get_height() / 2
|
||||||
rect = figure_surface.get_rect(center=(x, y))
|
rect = figure_surface.get_rect(center=(x, y))
|
||||||
self.surface.blit(figure_surface, rect)
|
self.surface.blit(figure_surface, rect)
|
||||||
|
|
||||||
def _draw_preview(self, next_figure: Figure) -> None:
|
|
||||||
"""
|
|
||||||
Draw the preview with the background, border, and next figure.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
next_figures (list[Figure]): List of upcoming figures.
|
|
||||||
"""
|
|
||||||
self._draw_background()
|
|
||||||
self._draw_border()
|
|
||||||
self._draw_figure(next_figure)
|
|
||||||
|
|
||||||
def _draw_background(self) -> None:
|
def _draw_background(self) -> None:
|
||||||
"""Draw the background of the preview."""
|
"""Draw the background of the preview."""
|
||||||
self.surface.fill(CONFIG.colors.bg_sidebar)
|
self.surface.fill(CONFIG.colors.bg_sidebar)
|
||||||
@ -79,3 +80,7 @@ class Preview:
|
|||||||
CONFIG.window.padding,
|
CONFIG.window.padding,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _update_diplaysurface(self) -> None:
|
||||||
|
"""Update the display surface."""
|
||||||
|
self.dispaly_surface.blit(self.surface, self.rect)
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import pygame
|
import pygame
|
||||||
from utils import CONFIG, Size
|
from utils import CONFIG, Size
|
||||||
|
|
||||||
|
from .base import BaseScreen, SceenElement, TextScreen
|
||||||
|
|
||||||
class Score:
|
|
||||||
|
class Score(BaseScreen, SceenElement, TextScreen):
|
||||||
"""
|
"""
|
||||||
Class representing the score on the sidebar.
|
Class representing the score on the sidebar.
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ class Score:
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Display the score on the game surface."""
|
"""Display the score on the game surface."""
|
||||||
self.dispaly_surface.blit(self.surface, self.rect)
|
self._update_diplaysurface()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def update(self, lines: int, score: int, level: int) -> None:
|
def update(self, lines: int, score: int, level: int) -> None:
|
||||||
@ -55,7 +57,7 @@ class Score:
|
|||||||
y = self.increment_height / 2 + idx * self.increment_height
|
y = self.increment_height / 2 + idx * self.increment_height
|
||||||
self._display_text(text, (x, y))
|
self._display_text(text, (x, y))
|
||||||
|
|
||||||
def _display_text(self, text: tuple[str, int], pos: tuple[int, int]) -> None:
|
def _display_text(self, text: tuple[str, int], pos: tuple[float, float]) -> None:
|
||||||
"""
|
"""
|
||||||
Display a single text element on the score surface.
|
Display a single text element on the score surface.
|
||||||
|
|
||||||
@ -101,3 +103,7 @@ class Score:
|
|||||||
def _initialize_increment_height(self) -> None:
|
def _initialize_increment_height(self) -> None:
|
||||||
"""Initialize the increment height for positioning text elements."""
|
"""Initialize the increment height for positioning text elements."""
|
||||||
self.increment_height = self.surface.get_height() / 3
|
self.increment_height = self.surface.get_height() / 3
|
||||||
|
|
||||||
|
def _update_diplaysurface(self) -> None:
|
||||||
|
"""Update the display surface."""
|
||||||
|
self.dispaly_surface.blit(self.surface, self.rect)
|
||||||
@ -1,16 +1,18 @@
|
|||||||
from typing import Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pygame
|
import pygame
|
||||||
from utils import CONFIG, Direction, Field, Figure, Rotation
|
from utils import CONFIG, Direction, Field, Figure, Rotation
|
||||||
|
|
||||||
from .block import Block
|
from game.log import log
|
||||||
from .log import log
|
from game.sprites.block import Block
|
||||||
from .tetromino import Tetromino
|
from game.sprites.tetromino import Tetromino
|
||||||
from .timer import Timer, Timers
|
from game.timer import Timer, Timers
|
||||||
|
|
||||||
|
from .base import BaseScreen
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Tetris(BaseScreen):
|
||||||
"""
|
"""
|
||||||
Game class for managing the game state.
|
Game class for managing the game state.
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ class Game:
|
|||||||
|
|
||||||
def handle_event(self) -> None:
|
def handle_event(self) -> None:
|
||||||
"""Handle player input events."""
|
"""Handle player input events."""
|
||||||
keys: list[bool] = pygame.key.get_pressed()
|
keys: pygame.key.ScancodeWrapper = pygame.key.get_pressed()
|
||||||
|
|
||||||
self._handle_movement_keys(keys)
|
self._handle_movement_keys(keys)
|
||||||
self._handle_rotation_keys(keys)
|
self._handle_rotation_keys(keys)
|
||||||
@ -215,9 +217,11 @@ class Game:
|
|||||||
for block in self.sprites:
|
for block in self.sprites:
|
||||||
self.field[int(block.pos.y), int(block.pos.x)] = block
|
self.field[int(block.pos.y), int(block.pos.x)] = block
|
||||||
|
|
||||||
def _generate_empty_field(self) -> np.ndarray:
|
def _generate_empty_field(self) -> np.ndarray[Field, Any]:
|
||||||
"""Generate an empty game field."""
|
"""Generate an empty game field."""
|
||||||
return np.full((CONFIG.game.rows, CONFIG.game.columns), None, dtype=Field)
|
return np.full(
|
||||||
|
(CONFIG.game.rows, CONFIG.game.columns), Field.EMPTY, dtype=Field
|
||||||
|
)
|
||||||
|
|
||||||
def _calculate_score(self, rows_deleted: int) -> None:
|
def _calculate_score(self, rows_deleted: int) -> None:
|
||||||
"""Calculate and update the game score."""
|
"""Calculate and update the game score."""
|
||||||
@ -308,7 +312,7 @@ class Game:
|
|||||||
"""Fill the game surface with background color."""
|
"""Fill the game surface with background color."""
|
||||||
self.surface.fill(CONFIG.colors.bg_float)
|
self.surface.fill(CONFIG.colors.bg_float)
|
||||||
|
|
||||||
def _handle_movement_keys(self, keys: list[bool]) -> None:
|
def _handle_movement_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||||
"""
|
"""
|
||||||
Handle movement keys.
|
Handle movement keys.
|
||||||
|
|
||||||
@ -326,7 +330,7 @@ class Game:
|
|||||||
self.move_right()
|
self.move_right()
|
||||||
self.timers.horizontal.activate()
|
self.timers.horizontal.activate()
|
||||||
|
|
||||||
def _handle_rotation_keys(self, keys: list[bool]) -> None:
|
def _handle_rotation_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||||
"""
|
"""
|
||||||
Handle rotation keys.
|
Handle rotation keys.
|
||||||
|
|
||||||
@ -353,7 +357,7 @@ class Game:
|
|||||||
self.rotate_reverse()
|
self.rotate_reverse()
|
||||||
self.timers.rotation.activate()
|
self.timers.rotation.activate()
|
||||||
|
|
||||||
def _handle_down_key(self, keys: list[bool]) -> None:
|
def _handle_down_key(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||||
"""Handle the down key [K_DOWN, K_s, K_j]."""
|
"""Handle the down key [K_DOWN, K_s, K_j]."""
|
||||||
down_keys = keys[pygame.K_DOWN] or keys[pygame.K_s] or keys[pygame.K_j]
|
down_keys = keys[pygame.K_DOWN] or keys[pygame.K_s] or keys[pygame.K_j]
|
||||||
if not self.down_pressed and down_keys:
|
if not self.down_pressed and down_keys:
|
||||||
@ -364,7 +368,7 @@ class Game:
|
|||||||
self.down_pressed = False
|
self.down_pressed = False
|
||||||
self.timers.vertical.duration = self.initial_block_speed
|
self.timers.vertical.duration = self.initial_block_speed
|
||||||
|
|
||||||
def _handle_drop_key(self, keys: list[bool]) -> None:
|
def _handle_drop_key(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||||
"""Handle the drop key [K_SPACE]."""
|
"""Handle the drop key [K_SPACE]."""
|
||||||
drop_keys = keys[pygame.K_SPACE]
|
drop_keys = keys[pygame.K_SPACE]
|
||||||
|
|
||||||
4
src/game/sprites/__init__.py
Normal file
4
src/game/sprites/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from .block import Block
|
||||||
|
from .tetromino import Tetromino
|
||||||
|
|
||||||
|
__all__ = ["Block", "Tetromino"]
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pygame
|
import pygame
|
||||||
from utils import CONFIG, Rotation, Size
|
from utils import CONFIG, Rotation, Size
|
||||||
@ -22,7 +24,7 @@ class Block(pygame.sprite.Sprite):
|
|||||||
self,
|
self,
|
||||||
/,
|
/,
|
||||||
*,
|
*,
|
||||||
group: pygame.sprite.Group,
|
group: pygame.sprite.Group[Any],
|
||||||
pos: pygame.Vector2,
|
pos: pygame.Vector2,
|
||||||
color: str,
|
color: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -32,9 +34,13 @@ class Block(pygame.sprite.Sprite):
|
|||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
"""Updates the block's position on the screen."""
|
"""Updates the block's position on the screen."""
|
||||||
self.rect.topleft = self.pos * CONFIG.game.cell.width
|
if self.rect:
|
||||||
|
self.rect.topleft = (
|
||||||
|
self.pos.x * CONFIG.game.cell.width,
|
||||||
|
self.pos.y * CONFIG.game.cell.width,
|
||||||
|
)
|
||||||
|
|
||||||
def vertical_collision(self, x: int, field: np.ndarray) -> bool:
|
def vertical_collision(self, x: int, field: np.ndarray[int, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks for vertical collision with the game field.
|
Checks for vertical collision with the game field.
|
||||||
|
|
||||||
@ -47,7 +53,7 @@ class Block(pygame.sprite.Sprite):
|
|||||||
"""
|
"""
|
||||||
return not 0 <= x < CONFIG.game.columns or field[int(self.pos.y), x]
|
return not 0 <= x < CONFIG.game.columns or field[int(self.pos.y), x]
|
||||||
|
|
||||||
def horizontal_collision(self, y: int, field: np.ndarray) -> bool:
|
def horizontal_collision(self, y: int, field: np.ndarray[int, Any]) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks for horizontal collision with the game field.
|
Checks for horizontal collision with the game field.
|
||||||
|
|
||||||
@ -91,4 +97,5 @@ class Block(pygame.sprite.Sprite):
|
|||||||
pos: Initial position of the block.
|
pos: Initial position of the block.
|
||||||
"""
|
"""
|
||||||
self.pos = pygame.Vector2(pos) + CONFIG.game.offset
|
self.pos = pygame.Vector2(pos) + CONFIG.game.offset
|
||||||
self.rect = self.image.get_rect(topleft=self.pos * CONFIG.game.cell.width)
|
if self.image:
|
||||||
|
self.rect = self.image.get_rect(topleft=self.pos * CONFIG.game.cell.width)
|
||||||
@ -1,4 +1,4 @@
|
|||||||
from typing import Callable, Optional
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pygame
|
import pygame
|
||||||
@ -29,9 +29,9 @@ class Tetromino:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
group: pygame.sprite.Group,
|
group: pygame.sprite.Group[Any],
|
||||||
create_new: Callable[[], None],
|
create_new: Callable[[], None],
|
||||||
field: np.ndarray,
|
field: np.ndarray[int, Any],
|
||||||
shape: Optional[Figure] = None,
|
shape: Optional[Figure] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.figure: Figure = self._generate_figure(shape)
|
self.figure: Figure = self._generate_figure(shape)
|
||||||
@ -156,7 +156,7 @@ class Tetromino:
|
|||||||
for pos in new_positions
|
for pos in new_positions
|
||||||
)
|
)
|
||||||
|
|
||||||
def _initialize_blocks(self, group: pygame.sprite.Group) -> list[Block]:
|
def _initialize_blocks(self, group: pygame.sprite.Group[Any]) -> list[Block]:
|
||||||
"""
|
"""
|
||||||
Initializes Tetromino blocks.
|
Initializes Tetromino blocks.
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user