mirror of
https://github.com/kristoferssolo/Tetris.git
synced 2025-10-21 20:00:35 +00:00
Merge pull request #7 from kristoferssolo/feature/pause
Implement pause feature
This commit is contained in:
commit
63fdf72cc8
@ -1,15 +1,17 @@
|
||||
from typing import Any
|
||||
|
||||
import pygame
|
||||
from loguru import logger
|
||||
from utils import CONFIG, Figure, GameMode
|
||||
|
||||
from .base import BaseScreen
|
||||
from .base import BaseScreen, SceenElement
|
||||
from .pause import Pause
|
||||
from .preview import Preview
|
||||
from .score import Score
|
||||
from .tetris import Tetris
|
||||
|
||||
|
||||
class Game(BaseScreen):
|
||||
class Game(BaseScreen, SceenElement):
|
||||
"""
|
||||
Game class.
|
||||
|
||||
@ -32,15 +34,16 @@ class Game(BaseScreen):
|
||||
def __init__(self, game_mode: GameMode, settings: dict[str, Any]) -> None:
|
||||
self.game_mode = game_mode
|
||||
self.settings = settings
|
||||
self._initialize_surface()
|
||||
self._initialize_rect()
|
||||
self._initialize_game_components()
|
||||
self._start_background_music()
|
||||
self.paused = False
|
||||
|
||||
def draw(self) -> None:
|
||||
"""
|
||||
Raises:
|
||||
NotImplementedError: Not implemented yet.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
"""Draw the score on the score surface."""
|
||||
self._update_display_surface()
|
||||
self._draw_background()
|
||||
|
||||
def update(self) -> None:
|
||||
"""
|
||||
@ -51,12 +54,15 @@ class Game(BaseScreen):
|
||||
|
||||
def run(self) -> None:
|
||||
"""Run a single iteration of the game loop."""
|
||||
|
||||
self.draw()
|
||||
self.tetris.run()
|
||||
self.score.run()
|
||||
self.preview.update(self.next_figure)
|
||||
self.preview.run()
|
||||
|
||||
if self.paused:
|
||||
self.pause_screen.draw()
|
||||
|
||||
self.clock.tick(CONFIG.game.fps)
|
||||
|
||||
def mute(self) -> None:
|
||||
@ -64,6 +70,19 @@ class Game(BaseScreen):
|
||||
self.music.set_volume(0)
|
||||
self.tetris.mute()
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Pause the game."""
|
||||
if self.paused:
|
||||
logger.debug("Unpause")
|
||||
self.paused = False
|
||||
self.tetris.unfreeze()
|
||||
self.music.play(-1, fade_ms=100)
|
||||
else:
|
||||
logger.debug("Pause")
|
||||
self.paused = True
|
||||
self.tetris.freeze()
|
||||
self.music.fadeout(100)
|
||||
|
||||
def _initialize_game_components(self) -> None:
|
||||
"""Initialize game-related components."""
|
||||
self.clock = pygame.time.Clock()
|
||||
@ -72,6 +91,7 @@ class Game(BaseScreen):
|
||||
self.tetris = Tetris(self._get_next_figure, self._update_score, self.game_mode, self.settings)
|
||||
self.score = Score(self.game_mode)
|
||||
self.preview = Preview()
|
||||
self.pause_screen = Pause()
|
||||
|
||||
def _update_score(self, lines: int, score: int, level: int) -> None:
|
||||
"""
|
||||
@ -110,3 +130,28 @@ class Game(BaseScreen):
|
||||
self.music = pygame.mixer.Sound(CONFIG.music.background)
|
||||
self.music.set_volume(self.settings["Volume"]["Music"]["level"])
|
||||
self.music.play(-1)
|
||||
|
||||
def _initialize_surface(self) -> None:
|
||||
"""Initialize the pause screen surface."""
|
||||
self.surface = pygame.Surface(CONFIG.window.size)
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
|
||||
def _initialize_rect(self) -> None:
|
||||
"""Initialize the score rectangle."""
|
||||
self.rect = self.surface.get_rect(topleft=(0, 0))
|
||||
|
||||
def _draw_background(self) -> None:
|
||||
"""Draw the background."""
|
||||
self.surface.fill(CONFIG.colors.bg)
|
||||
|
||||
def _update_display_surface(self) -> None:
|
||||
"""Update the display surface."""
|
||||
self.display_surface.blit(self.surface, self.rect)
|
||||
|
||||
def _draw_border(self) -> None:
|
||||
"""Draw the border.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: Not implemented yet.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@ -8,6 +8,7 @@ from utils import CONFIG, GameMode, read_settings
|
||||
from .base import BaseScreen, SceenElement, TextScreen
|
||||
from .button import Button
|
||||
from .game import Game
|
||||
from .tetris import get_keys
|
||||
|
||||
|
||||
class Main(BaseScreen, SceenElement, TextScreen):
|
||||
@ -45,8 +46,10 @@ class Main(BaseScreen, SceenElement, TextScreen):
|
||||
if event.type == pygame.QUIT:
|
||||
self.exit()
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
if event.key in [pygame.key.key_code(key) for key in self.settings["General"]["quit"]]:
|
||||
if event.key in get_keys(self.settings["General"]["quit"]):
|
||||
self.exit()
|
||||
elif event.key in get_keys(self.settings["General"]["pause"]) and self.game:
|
||||
self.game.pause()
|
||||
|
||||
if not self.game:
|
||||
for button in self.buttons:
|
||||
@ -60,14 +63,13 @@ class Main(BaseScreen, SceenElement, TextScreen):
|
||||
|
||||
def run_game_loop(self) -> None:
|
||||
"""Run a single iteration of the game loop."""
|
||||
if not self.game:
|
||||
if self.game:
|
||||
self.game.run()
|
||||
else:
|
||||
self.draw()
|
||||
|
||||
self.handle_events()
|
||||
|
||||
if self.game:
|
||||
self.game.run()
|
||||
|
||||
self.update()
|
||||
|
||||
def exit(self) -> None:
|
||||
|
||||
71
src/game/screens/pause.py
Normal file
71
src/game/screens/pause.py
Normal file
@ -0,0 +1,71 @@
|
||||
import pygame
|
||||
from utils import CONFIG
|
||||
|
||||
from .base import BaseScreen, SceenElement, TextScreen
|
||||
|
||||
|
||||
class Pause(BaseScreen, SceenElement, TextScreen):
|
||||
def __init__(self) -> None:
|
||||
self._initialize_surface()
|
||||
self._initialize_rect()
|
||||
self._initialize_font()
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Raises:
|
||||
NotImplementedError: Not implemented yet.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def update(self) -> None:
|
||||
self._update_display_surface()
|
||||
|
||||
def draw(self) -> None:
|
||||
"""Update the display."""
|
||||
self.update()
|
||||
self._draw_background()
|
||||
self._draw_text()
|
||||
|
||||
def _draw_text(self) -> None:
|
||||
"""Draw the text."""
|
||||
self._display_text("Paused")
|
||||
|
||||
def _display_text(self, text: str) -> None:
|
||||
"""Display the text."""
|
||||
text_surface = self.font.render(text, True, CONFIG.colors.fg_float)
|
||||
text_rect = text_surface.get_rect(center=self.rect.center)
|
||||
self.text_surface.blit(text_surface, text_rect)
|
||||
|
||||
def _draw_background(self) -> None:
|
||||
"""Draw the background."""
|
||||
self.surface.fill(CONFIG.colors.bg_float)
|
||||
self.surface.set_alpha(100)
|
||||
self.text_surface.set_colorkey("#000000")
|
||||
self.text_surface.set_alpha(255)
|
||||
|
||||
def _initialize_surface(self) -> None:
|
||||
"""Initialize the pause screen surface."""
|
||||
self.surface = pygame.Surface(CONFIG.window.size)
|
||||
self.display_surface = pygame.display.get_surface()
|
||||
self.text_surface = pygame.Surface(CONFIG.window.size)
|
||||
|
||||
def _initialize_rect(self) -> None:
|
||||
"""Initialize the score rectangle."""
|
||||
self.rect = self.surface.get_rect(topleft=(0, 0))
|
||||
|
||||
def _initialize_font(self) -> None:
|
||||
"""Initialize the font used to display the text."""
|
||||
self.font = pygame.font.Font(CONFIG.font.family, CONFIG.font.size * 2)
|
||||
|
||||
def _update_display_surface(self) -> None:
|
||||
"""Update the display surface."""
|
||||
self.display_surface.blit(self.surface, self.rect)
|
||||
self.display_surface.blit(self.text_surface, self.rect)
|
||||
|
||||
def _draw_border(self) -> None:
|
||||
"""Draw the border.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: Not implemented yet.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@ -91,12 +91,11 @@ class Tetris(BaseScreen):
|
||||
|
||||
def handle_events(self) -> None:
|
||||
"""Handle player input events."""
|
||||
keys: pygame.key.ScancodeWrapper = pygame.key.get_pressed()
|
||||
|
||||
self._handle_movement_keys(keys)
|
||||
self._handle_rotation_keys(keys)
|
||||
self._handle_down_key(keys)
|
||||
self._handle_drop_key(keys)
|
||||
if not self.paused:
|
||||
self._handle_movement_keys()
|
||||
self._handle_rotation_keys()
|
||||
self._handle_down_key()
|
||||
self._handle_drop_key()
|
||||
|
||||
def move_down(self) -> bool:
|
||||
"""
|
||||
@ -161,6 +160,25 @@ class Tetris(BaseScreen):
|
||||
"""
|
||||
return self.tetromino.drop()
|
||||
|
||||
def restart(self) -> None:
|
||||
"""Restart the game."""
|
||||
logger.info(f"Restarting the game. Score was {self.score}")
|
||||
self._reset_game_state()
|
||||
|
||||
def freeze(self) -> None:
|
||||
"""Freeze all timers."""
|
||||
self.timers.freeze()
|
||||
self.paused = True
|
||||
|
||||
def unfreeze(self) -> None:
|
||||
"""Unfreeze all timers."""
|
||||
self.timers.unfreeze()
|
||||
self.paused = False
|
||||
|
||||
def mute(self) -> None:
|
||||
"""Mute the game."""
|
||||
self.landing_sound.set_volume(0)
|
||||
|
||||
def create_new_tetromino(self, shape: Optional[Figure] = None) -> Optional[Tetromino]:
|
||||
"""Create a new tetromino and perform necessary actions."""
|
||||
self._play_landing_sound()
|
||||
@ -204,15 +222,6 @@ class Tetris(BaseScreen):
|
||||
return True
|
||||
return False
|
||||
|
||||
def restart(self) -> None:
|
||||
"""Restart the game."""
|
||||
logger.info(f"Restarting the game. Score was {self.score}")
|
||||
self._reset_game_state()
|
||||
|
||||
def mute(self) -> None:
|
||||
"""Mute the game."""
|
||||
self.landing_sound.set_volume(0)
|
||||
|
||||
def _draw_grid(self) -> None:
|
||||
"""Draw the grid on the game surface."""
|
||||
for col in range(1, CONFIG.game.columns):
|
||||
@ -299,8 +308,8 @@ class Tetris(BaseScreen):
|
||||
def _level_up(self) -> None:
|
||||
"""Level up."""
|
||||
self.level += 1
|
||||
self.initial_block_speed *= 0.5
|
||||
self.increased_block_speed *= 0.5
|
||||
self.initial_block_speed *= 0.8 # the larger the multiplier, the slower the game
|
||||
self.increased_block_speed *= 0.8
|
||||
self.timers.vertical.duration = self.initial_block_speed
|
||||
|
||||
def _draw_components(self) -> None:
|
||||
@ -365,13 +374,14 @@ class Tetris(BaseScreen):
|
||||
def _initialize_game_state(self) -> None:
|
||||
"""Initialize the game state."""
|
||||
self.initial_block_speed = CONFIG.game.initial_speed
|
||||
self.increased_block_speed = self.initial_block_speed * 0.4
|
||||
self.increased_block_speed = self.initial_block_speed * 0.5
|
||||
self.down_pressed = False
|
||||
self.drop_pressed = False
|
||||
self.level: int = 1
|
||||
self.score: int = 0
|
||||
self.lines: int = 0
|
||||
self.game_over = False
|
||||
self.paused = False
|
||||
|
||||
def _initialize_sound(self) -> None:
|
||||
"""Initialize game sounds."""
|
||||
@ -397,17 +407,17 @@ class Tetris(BaseScreen):
|
||||
"""Fill the game surface with background color."""
|
||||
self.surface.fill(CONFIG.colors.bg_float)
|
||||
|
||||
def _handle_movement_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||
def _handle_movement_keys(self) -> None:
|
||||
"""
|
||||
Handle movement keys.
|
||||
|
||||
See `settings.toml` for the default key bindings.
|
||||
"""
|
||||
right_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["right"]]
|
||||
right_key_pressed = any(keys[key] for key in right_keys)
|
||||
right_keys = get_keys(self.settings["Movement"]["right"])
|
||||
right_key_pressed = is_key_pressed(right_keys)
|
||||
|
||||
left_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["left"]]
|
||||
left_key_pressed = any(keys[key] for key in left_keys)
|
||||
left_keys = get_keys(self.settings["Movement"]["left"])
|
||||
left_key_pressed = is_key_pressed(left_keys)
|
||||
|
||||
if not self.timers.horizontal.active:
|
||||
if left_key_pressed:
|
||||
@ -417,17 +427,18 @@ class Tetris(BaseScreen):
|
||||
self.move_right()
|
||||
self.timers.horizontal.activate()
|
||||
|
||||
def _handle_rotation_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||
def _handle_rotation_keys(self) -> None:
|
||||
"""
|
||||
Handle rotation keys.
|
||||
|
||||
See `settings.toml` for the default key bindings.
|
||||
"""
|
||||
cw_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Rotation"]["cw"]]
|
||||
cw_key_pressed = any(keys[key] for key in cw_keys)
|
||||
|
||||
ccw_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Rotation"]["ccw"]]
|
||||
ccw_key_pressed = any(keys[key] for key in ccw_keys)
|
||||
cw_keys = get_keys(self.settings["Rotation"]["cw"])
|
||||
cw_key_pressed = is_key_pressed(cw_keys)
|
||||
|
||||
ccw_keys = get_keys(self.settings["Rotation"]["ccw"])
|
||||
ccw_key_pressed = is_key_pressed(ccw_keys)
|
||||
|
||||
if not self.timers.rotation.active:
|
||||
if cw_key_pressed:
|
||||
@ -438,14 +449,14 @@ class Tetris(BaseScreen):
|
||||
self.rotate_reverse()
|
||||
self.timers.rotation.activate()
|
||||
|
||||
def _handle_down_key(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||
def _handle_down_key(self) -> None:
|
||||
"""
|
||||
Handle the down key.
|
||||
|
||||
See `settings.toml` for the default key bindings.
|
||||
"""
|
||||
down_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["down"]]
|
||||
down_key_pressed = any(keys[key] for key in down_keys)
|
||||
down_keys = get_keys(self.settings["Movement"]["down"])
|
||||
down_key_pressed = is_key_pressed(down_keys)
|
||||
if not self.down_pressed and down_key_pressed:
|
||||
self.down_pressed = True
|
||||
self.timers.vertical.duration = self.increased_block_speed
|
||||
@ -454,14 +465,14 @@ class Tetris(BaseScreen):
|
||||
self.down_pressed = False
|
||||
self.timers.vertical.duration = self.initial_block_speed
|
||||
|
||||
def _handle_drop_key(self, keys: pygame.key.ScancodeWrapper) -> None:
|
||||
def _handle_drop_key(self) -> None:
|
||||
"""
|
||||
Handle the drop key.
|
||||
|
||||
See `settings.toml` for the default key bindings.
|
||||
"""
|
||||
drop_keys = [pygame.key.key_code(key) for key in self.settings["Action"]["drop"]]
|
||||
drop_key_pressed = any(keys[key] for key in drop_keys)
|
||||
drop_keys = get_keys(self.settings["Action"]["drop"])
|
||||
drop_key_pressed = is_key_pressed(drop_keys)
|
||||
|
||||
if not self.timers.drop.active and drop_key_pressed:
|
||||
self.drop()
|
||||
@ -494,3 +505,14 @@ class Tetris(BaseScreen):
|
||||
(self.grid_surface.get_width(), y),
|
||||
CONFIG.game.line_width,
|
||||
)
|
||||
|
||||
|
||||
def get_keys(keys: dict[str, str]) -> list[int]:
|
||||
"""Get the key codes for the specified keys."""
|
||||
return [pygame.key.key_code(key) for key in keys]
|
||||
|
||||
|
||||
def is_key_pressed(keys: list[int]) -> bool:
|
||||
"""Check if any of the specified keys is pressed."""
|
||||
keys_pressed = pygame.key.get_pressed()
|
||||
return any(keys_pressed[key] for key in keys)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable, NamedTuple, Optional
|
||||
from typing import Any, Callable, Iterator, Optional
|
||||
|
||||
import pygame
|
||||
from attrs import define, field
|
||||
@ -55,7 +55,8 @@ class Timer:
|
||||
self.activate()
|
||||
|
||||
|
||||
class Timers(NamedTuple):
|
||||
@define
|
||||
class Timers:
|
||||
"""
|
||||
NamedTuple for grouping different timers.
|
||||
|
||||
@ -70,3 +71,17 @@ class Timers(NamedTuple):
|
||||
horizontal: Timer
|
||||
rotation: Timer
|
||||
drop: Timer
|
||||
|
||||
def __iter__(self) -> Iterator[Timer]:
|
||||
"""Returns an iterator over the timers."""
|
||||
return iter((self.vertical, self.horizontal, self.rotation, self.drop))
|
||||
|
||||
def freeze(self) -> None:
|
||||
"""Freezes all timers."""
|
||||
for timer in self:
|
||||
timer.deactivate()
|
||||
|
||||
def unfreeze(self) -> None:
|
||||
"""Unfreezes all timers."""
|
||||
for timer in self:
|
||||
timer.activate()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user