refactor(game): pass mypy

This commit is contained in:
Kristofers Solo 2024-01-06 03:59:13 +02:00
parent bb80400ad7
commit 0afe1ed3cb
12 changed files with 156 additions and 67 deletions

View File

@ -67,10 +67,10 @@ def main(args: argparse.ArgumentParser) -> None:
if args.train is not None:
ai.log.debug("Training the AI")
# ai.train(*args.train)
game.Main(GameMode.AI_TRAINING).run()
game.Game(GameMode.AI_TRAINING).run()
else:
game.log.debug("Running the game")
game.Main(GameMode.PLAYER).run()
game.Game(GameMode.PLAYER).run()
if __name__ == "__main__":

View File

@ -1,20 +1,9 @@
from .block import Block
from .game import Game
from .log import log
from .main import Main
from .preview import Preview
from .score import Score
from .tetromino import Tetromino
from .timer import Timer, Timers
from .screens import Game, Menu, Tetris
__all__ = [
"log",
"Main",
"Game",
"Score",
"Block",
"Tetromino",
"Preview",
"Timer",
"Timers",
]

View 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
View 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."""

View File

@ -3,16 +3,18 @@ import sys
import pygame
from utils import CONFIG, Figure, GameMode
from .game import Game
from .log import log
from game.log import log
from .base import BaseScreen
from .preview import Preview
from .score import Score
from .tetris import Tetris
from .tetromino import Tetromino
class Main:
class Game(BaseScreen):
"""
Main class for the game.
Game class.
Attributes:
display_surface: Pygame display surface.
@ -46,11 +48,12 @@ class Main:
self.draw()
self.handle_events()
self.game.run()
self.tetris.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)
def handle_events(self) -> None:
@ -71,14 +74,14 @@ class Main:
"""Initialize game-related components."""
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.preview = Preview()
def mute(self) -> None:
"""Mute the game."""
self.music.set_volume(0)
self.game.mute()
self.tetris.mute()
def _update_score(self, lines: int, score: int, level: int) -> None:
"""

7
src/game/screens/menu.py Normal file
View File

@ -0,0 +1,7 @@
from game.log import log
from .base import BaseScreen
class Menu(BaseScreen):
pass

View File

@ -1,8 +1,10 @@
import pygame
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.
@ -17,15 +19,25 @@ class Preview:
self._initialize_surface()
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:
next_figures (list[Figure]): List of upcoming figures.
next_figures: Next figure.
"""
self.dispaly_surface.blit(self.surface, self.rect)
self._draw_preview(next_figure)
self.next_figure = 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:
"""Draw the border around the preview surface."""
@ -37,7 +49,7 @@ class Preview:
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.
@ -45,23 +57,12 @@ class Preview:
figure (Figure): The upcoming figure to draw.
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
y = self.surface.get_height() / 2
rect = figure_surface.get_rect(center=(x, y))
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:
"""Draw the background of the preview."""
self.surface.fill(CONFIG.colors.bg_sidebar)
@ -79,3 +80,7 @@ class Preview:
CONFIG.window.padding,
)
)
def _update_diplaysurface(self) -> None:
"""Update the display surface."""
self.dispaly_surface.blit(self.surface, self.rect)

View File

@ -1,8 +1,10 @@
import pygame
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.
@ -24,7 +26,7 @@ class Score:
def run(self) -> None:
"""Display the score on the game surface."""
self.dispaly_surface.blit(self.surface, self.rect)
self._update_diplaysurface()
self.draw()
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
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.
@ -101,3 +103,7 @@ class Score:
def _initialize_increment_height(self) -> None:
"""Initialize the increment height for positioning text elements."""
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)

View File

@ -1,16 +1,18 @@
from typing import Callable, Optional
from typing import Any, Callable, Optional
import numpy as np
import pygame
from utils import CONFIG, Direction, Field, Figure, Rotation
from .block import Block
from .log import log
from .tetromino import Tetromino
from .timer import Timer, Timers
from game.log import log
from game.sprites.block import Block
from game.sprites.tetromino import Tetromino
from game.timer import Timer, Timers
from .base import BaseScreen
class Game:
class Tetris(BaseScreen):
"""
Game class for managing the game state.
@ -79,7 +81,7 @@ class Game:
def handle_event(self) -> None:
"""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_rotation_keys(keys)
@ -215,9 +217,11 @@ class Game:
for block in self.sprites:
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."""
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:
"""Calculate and update the game score."""
@ -308,7 +312,7 @@ class Game:
"""Fill the game surface with background color."""
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.
@ -326,7 +330,7 @@ class Game:
self.move_right()
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.
@ -353,7 +357,7 @@ class Game:
self.rotate_reverse()
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]."""
down_keys = keys[pygame.K_DOWN] or keys[pygame.K_s] or keys[pygame.K_j]
if not self.down_pressed and down_keys:
@ -364,7 +368,7 @@ class Game:
self.down_pressed = False
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]."""
drop_keys = keys[pygame.K_SPACE]

View File

@ -0,0 +1,4 @@
from .block import Block
from .tetromino import Tetromino
__all__ = ["Block", "Tetromino"]

View File

@ -1,3 +1,5 @@
from typing import Any
import numpy as np
import pygame
from utils import CONFIG, Rotation, Size
@ -22,7 +24,7 @@ class Block(pygame.sprite.Sprite):
self,
/,
*,
group: pygame.sprite.Group,
group: pygame.sprite.Group[Any],
pos: pygame.Vector2,
color: str,
) -> None:
@ -32,9 +34,13 @@ class Block(pygame.sprite.Sprite):
def update(self) -> None:
"""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.
@ -47,7 +53,7 @@ class Block(pygame.sprite.Sprite):
"""
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.
@ -91,4 +97,5 @@ class Block(pygame.sprite.Sprite):
pos: Initial position of the block.
"""
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)

View File

@ -1,4 +1,4 @@
from typing import Callable, Optional
from typing import Any, Callable, Optional
import numpy as np
import pygame
@ -29,9 +29,9 @@ class Tetromino:
def __init__(
self,
group: pygame.sprite.Group,
group: pygame.sprite.Group[Any],
create_new: Callable[[], None],
field: np.ndarray,
field: np.ndarray[int, Any],
shape: Optional[Figure] = None,
) -> None:
self.figure: Figure = self._generate_figure(shape)
@ -156,7 +156,7 @@ class Tetromino:
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.