Compare commits

..

No commits in common. "main" and "v0.1.0" have entirely different histories.
main ... v0.1.0

22 changed files with 155 additions and 289 deletions

25
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Release
on:
push:
tags:
- "v*.*.*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: dist/*.tar.gz

1
.gitignore vendored
View File

@ -162,4 +162,3 @@ debug
.logs/ .logs/
checkpoints checkpoints
assets/highscore assets/highscore
typst/*.pdf

View File

@ -40,7 +40,7 @@ cd Tetris
3. Install the required dependencies: 3. Install the required dependencies:
```bash ```bash
pip install -e . pip install .
``` ```
4. Run the game: 4. Run the game:
@ -54,7 +54,7 @@ python -m tetris
The `settings.toml` file is a configuration file for customizing various aspects of the game. The `settings.toml` file is a configuration file for customizing various aspects of the game.
### General Settings ### General Settings
- `pause`: Defines the keys to pause the game. Currently a work in progress. - `pause`: Defines the keys to pause the game. Currently a work in progress (WIP[^WIP]).
- `quit`: Defines the key(s) to quit the game. - `quit`: Defines the key(s) to quit the game.
- `colorscheme`: Specifies the color scheme for the game interface. Options include: - `colorscheme`: Specifies the color scheme for the game interface. Options include:
- `tokyonight-night` - `tokyonight-night`
@ -72,7 +72,7 @@ The `settings.toml` file is a configuration file for customizing various aspects
- `ccw (counter-clockwise)`: Defines the keys to rotate the tetromino in a counter-clockwise direction. - `ccw (counter-clockwise)`: Defines the keys to rotate the tetromino in a counter-clockwise direction.
### Action Settings ### Action Settings
- `hold`: Defines the keys to hold the tetromino (WIP[^WIP]). - `hold`: Defines the keys to hold the tetromino (WIP).
- `drop`: Defines the keys to instantly drop the tetromino. - `drop`: Defines the keys to instantly drop the tetromino.
### Volume Settings ### Volume Settings

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 320 B

View File

@ -4,26 +4,26 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "tetris" name = "tetris"
version = "0.1.3" version = "0.1.0"
description = "Tetris game" description = "Tetris game"
authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }] authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }]
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
license = { file = "LICENSE" } license = { file = "LICENSE" }
dependencies = [ dependencies = [
"attrs==23.1.0", "attrs==23.1.0",
"loguru==0.7.2", "loguru==0.7.2",
"numpy==1.26.3", "numpy==1.26.3",
"pygame-ce==2.4.0", "pygame-ce==2.4.0",
"toml==0.10.2", "toml==0.10.2",
] ]
keywords = ["tetris", "game", "pygame"] keywords = ["tetris", "game", "pygame"]
classifiers = [ classifiers = [
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"License :: OSI Approved :: GPLv3 License", "License :: OSI Approved :: GPLv3 License",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
] ]
[project.urls] [project.urls]
@ -45,29 +45,29 @@ warn_unused_configs = true
[tool.ruff] [tool.ruff]
extend-select = [ extend-select = [
"B", "B",
"BLE", "BLE",
"C4", "C4",
"ERA", "ERA",
"I", "I",
"ICN", "ICN",
"INP", "INP",
"ISC", "ISC",
"N", "N",
"NPY", "NPY",
"PGH", "PGH",
"PIE", "PIE",
# "PTH", # "PTH",
"Q", "Q",
"RET", "RET",
"RSE", "RSE",
"RUF", "RUF",
"S", "S",
"SIM", "SIM",
"T20", "T20",
"TCH", "TCH",
"TID", "TID",
"YTT", "YTT",
] ]
ignore = ["E741"] ignore = ["E741"]
show-fixes = true show-fixes = true

View File

@ -1,5 +1,5 @@
[General] [General]
pause = ["escape", "F1"] pause = ["escape", "F1"] # WIP
quit = ["q"] quit = ["q"]
colorscheme = "tokyonight-night" # tokyonight-night / tokyonight-storm / tokyonight-day / tokyonight-moon colorscheme = "tokyonight-night" # tokyonight-night / tokyonight-storm / tokyonight-day / tokyonight-moon
@ -11,16 +11,16 @@ down = ["down", "keypad 2"]
[Rotation] [Rotation]
cw = ["x", "up", "keypad 1", "keypad 5", "keypad 9"] # clockwise cw = ["x", "up", "keypad 1", "keypad 5", "keypad 9"] # clockwise
ccw = [ ccw = [
"left ctrl", "left ctrl",
"right ctrl", "right ctrl",
"z", "z",
"keypad 3", "keypad 3",
"keypad 7", "keypad 7",
] # counter-clockwise ] # counter-clockwise
[Action] [Action]
hold = ["left shift", "right shift", "c", "keypad 0"] # WIP hold = ["left shift", "right shift", "c", "keypad 0"] # WIP
drop = ["space", "keypad 8"] drop = ["space", "keypad 5"]
[Volume.Music] [Volume.Music]
enabled = true enabled = true

View File

@ -1,17 +1,15 @@
from typing import Any from typing import Any
import pygame import pygame
from loguru import logger
from utils import CONFIG, Figure, GameMode from utils import CONFIG, Figure, GameMode
from .base import BaseScreen, SceenElement from .base import BaseScreen
from .pause import Pause
from .preview import Preview from .preview import Preview
from .score import Score from .score import Score
from .tetris import Tetris from .tetris import Tetris
class Game(BaseScreen, SceenElement): class Game(BaseScreen):
""" """
Game class. Game class.
@ -34,16 +32,15 @@ class Game(BaseScreen, SceenElement):
def __init__(self, game_mode: GameMode, settings: dict[str, Any]) -> None: def __init__(self, game_mode: GameMode, settings: dict[str, Any]) -> None:
self.game_mode = game_mode self.game_mode = game_mode
self.settings = settings self.settings = settings
self._initialize_surface()
self._initialize_rect()
self._initialize_game_components() self._initialize_game_components()
self._start_background_music() self._start_background_music()
self.paused = False
def draw(self) -> None: def draw(self) -> None:
"""Draw the score on the score surface.""" """
self._update_display_surface() Raises:
self._draw_background() NotImplementedError: Not implemented yet.
"""
raise NotImplementedError
def update(self) -> None: def update(self) -> None:
""" """
@ -54,15 +51,12 @@ class Game(BaseScreen, SceenElement):
def run(self) -> None: def run(self) -> None:
"""Run a single iteration of the game loop.""" """Run a single iteration of the game loop."""
self.draw()
self.tetris.run() self.tetris.run()
self.score.run() self.score.run()
self.preview.update(self.next_figure) self.preview.update(self.next_figure)
self.preview.run() self.preview.run()
if self.paused:
self.pause_screen.draw()
self.clock.tick(CONFIG.game.fps) self.clock.tick(CONFIG.game.fps)
def mute(self) -> None: def mute(self) -> None:
@ -70,19 +64,6 @@ class Game(BaseScreen, SceenElement):
self.music.set_volume(0) self.music.set_volume(0)
self.tetris.mute() 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: def _initialize_game_components(self) -> None:
"""Initialize game-related components.""" """Initialize game-related components."""
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
@ -91,7 +72,6 @@ class Game(BaseScreen, SceenElement):
self.tetris = Tetris(self._get_next_figure, self._update_score, self.game_mode, self.settings) self.tetris = Tetris(self._get_next_figure, self._update_score, self.game_mode, self.settings)
self.score = Score(self.game_mode) self.score = Score(self.game_mode)
self.preview = Preview() self.preview = Preview()
self.pause_screen = Pause()
def _update_score(self, lines: int, score: int, level: int) -> None: def _update_score(self, lines: int, score: int, level: int) -> None:
""" """
@ -130,28 +110,3 @@ class Game(BaseScreen, SceenElement):
self.music = pygame.mixer.Sound(CONFIG.music.background) self.music = pygame.mixer.Sound(CONFIG.music.background)
self.music.set_volume(self.settings["Volume"]["Music"]["level"]) self.music.set_volume(self.settings["Volume"]["Music"]["level"])
self.music.play(-1) 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

View File

@ -8,7 +8,6 @@ from utils import CONFIG, GameMode, read_settings
from .base import BaseScreen, SceenElement, TextScreen from .base import BaseScreen, SceenElement, TextScreen
from .button import Button from .button import Button
from .game import Game from .game import Game
from .tetris import get_keys
class Main(BaseScreen, SceenElement, TextScreen): class Main(BaseScreen, SceenElement, TextScreen):
@ -46,10 +45,8 @@ class Main(BaseScreen, SceenElement, TextScreen):
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
self.exit() self.exit()
elif event.type == pygame.KEYDOWN: elif event.type == pygame.KEYDOWN:
if event.key in get_keys(self.settings["General"]["quit"]): if event.key in [pygame.key.key_code(key) for key in self.settings["General"]["quit"]]:
self.exit() self.exit()
elif event.key in get_keys(self.settings["General"]["pause"]) and self.game:
self.game.pause()
if not self.game: if not self.game:
for button in self.buttons: for button in self.buttons:
@ -63,13 +60,14 @@ class Main(BaseScreen, SceenElement, TextScreen):
def run_game_loop(self) -> None: def run_game_loop(self) -> None:
"""Run a single iteration of the game loop.""" """Run a single iteration of the game loop."""
if self.game: if not self.game:
self.game.run()
else:
self.draw() self.draw()
self.handle_events() self.handle_events()
if self.game:
self.game.run()
self.update() self.update()
def exit(self) -> None: def exit(self) -> None:

View File

@ -1,71 +0,0 @@
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

View File

@ -30,7 +30,7 @@ class Preview(BaseScreen, SceenElement):
Args: Args:
next_figures: Next figure. next_figures: Next figure.
""" """
self.next_figure: Figure = next_figure self.next_figure = next_figure
def draw(self) -> None: def draw(self) -> None:
"""Draw the preview on the preview surface.""" """Draw the preview on the preview surface."""
@ -51,10 +51,9 @@ class Preview(BaseScreen, SceenElement):
def _draw_figure(self) -> None: def _draw_figure(self) -> None:
"""Draw a single upcoming figure on the preview surface.""" """Draw a single upcoming figure on the preview surface."""
figure_surface: pygame.Surface = self.next_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
figure_surface.fill(self.next_figure.value.color, special_flags=pygame.BLEND_RGB_MULT)
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)

View File

@ -91,11 +91,12 @@ class Tetris(BaseScreen):
def handle_events(self) -> None: def handle_events(self) -> None:
"""Handle player input events.""" """Handle player input events."""
if not self.paused: keys: pygame.key.ScancodeWrapper = pygame.key.get_pressed()
self._handle_movement_keys()
self._handle_rotation_keys() self._handle_movement_keys(keys)
self._handle_down_key() self._handle_rotation_keys(keys)
self._handle_drop_key() self._handle_down_key(keys)
self._handle_drop_key(keys)
def move_down(self) -> bool: def move_down(self) -> bool:
""" """
@ -160,25 +161,6 @@ class Tetris(BaseScreen):
""" """
return self.tetromino.drop() 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]: def create_new_tetromino(self, shape: Optional[Figure] = None) -> Optional[Tetromino]:
"""Create a new tetromino and perform necessary actions.""" """Create a new tetromino and perform necessary actions."""
self._play_landing_sound() self._play_landing_sound()
@ -222,6 +204,15 @@ class Tetris(BaseScreen):
return True return True
return False 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: def _draw_grid(self) -> None:
"""Draw the grid on the game surface.""" """Draw the grid on the game surface."""
for col in range(1, CONFIG.game.columns): for col in range(1, CONFIG.game.columns):
@ -308,8 +299,8 @@ class Tetris(BaseScreen):
def _level_up(self) -> None: def _level_up(self) -> None:
"""Level up.""" """Level up."""
self.level += 1 self.level += 1
self.initial_block_speed *= 0.8 # the larger the multiplier, the slower the game self.initial_block_speed *= 0.5
self.increased_block_speed *= 0.8 self.increased_block_speed *= 0.5
self.timers.vertical.duration = self.initial_block_speed self.timers.vertical.duration = self.initial_block_speed
def _draw_components(self) -> None: def _draw_components(self) -> None:
@ -374,14 +365,13 @@ class Tetris(BaseScreen):
def _initialize_game_state(self) -> None: def _initialize_game_state(self) -> None:
"""Initialize the game state.""" """Initialize the game state."""
self.initial_block_speed = CONFIG.game.initial_speed self.initial_block_speed = CONFIG.game.initial_speed
self.increased_block_speed = self.initial_block_speed * 0.5 self.increased_block_speed = self.initial_block_speed * 0.4
self.down_pressed = False self.down_pressed = False
self.drop_pressed = False self.drop_pressed = False
self.level: int = 1 self.level: int = 1
self.score: int = 0 self.score: int = 0
self.lines: int = 0 self.lines: int = 0
self.game_over = False self.game_over = False
self.paused = False
def _initialize_sound(self) -> None: def _initialize_sound(self) -> None:
"""Initialize game sounds.""" """Initialize game sounds."""
@ -407,17 +397,17 @@ class Tetris(BaseScreen):
"""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) -> None: def _handle_movement_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
""" """
Handle movement keys. Handle movement keys.
See `settings.toml` for the default key bindings. See `settings.toml` for the default key bindings.
""" """
right_keys = get_keys(self.settings["Movement"]["right"]) right_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["right"]]
right_key_pressed = is_key_pressed(right_keys) right_key_pressed = any(keys[key] for key in right_keys)
left_keys = get_keys(self.settings["Movement"]["left"]) left_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["left"]]
left_key_pressed = is_key_pressed(left_keys) left_key_pressed = any(keys[key] for key in left_keys)
if not self.timers.horizontal.active: if not self.timers.horizontal.active:
if left_key_pressed: if left_key_pressed:
@ -427,18 +417,17 @@ class Tetris(BaseScreen):
self.move_right() self.move_right()
self.timers.horizontal.activate() self.timers.horizontal.activate()
def _handle_rotation_keys(self) -> None: def _handle_rotation_keys(self, keys: pygame.key.ScancodeWrapper) -> None:
""" """
Handle rotation keys. Handle rotation keys.
See `settings.toml` for the default key bindings. 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)
cw_keys = get_keys(self.settings["Rotation"]["cw"]) ccw_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Rotation"]["ccw"]]
cw_key_pressed = is_key_pressed(cw_keys) ccw_key_pressed = any(keys[key] for key in ccw_keys)
ccw_keys = get_keys(self.settings["Rotation"]["ccw"])
ccw_key_pressed = is_key_pressed(ccw_keys)
if not self.timers.rotation.active: if not self.timers.rotation.active:
if cw_key_pressed: if cw_key_pressed:
@ -449,14 +438,14 @@ class Tetris(BaseScreen):
self.rotate_reverse() self.rotate_reverse()
self.timers.rotation.activate() self.timers.rotation.activate()
def _handle_down_key(self) -> None: def _handle_down_key(self, keys: pygame.key.ScancodeWrapper) -> None:
""" """
Handle the down key. Handle the down key.
See `settings.toml` for the default key bindings. See `settings.toml` for the default key bindings.
""" """
down_keys = get_keys(self.settings["Movement"]["down"]) down_keys: list[int] = [pygame.key.key_code(key) for key in self.settings["Movement"]["down"]]
down_key_pressed = is_key_pressed(down_keys) down_key_pressed = any(keys[key] for key in down_keys)
if not self.down_pressed and down_key_pressed: if not self.down_pressed and down_key_pressed:
self.down_pressed = True self.down_pressed = True
self.timers.vertical.duration = self.increased_block_speed self.timers.vertical.duration = self.increased_block_speed
@ -465,14 +454,14 @@ class Tetris(BaseScreen):
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) -> None: def _handle_drop_key(self, keys: pygame.key.ScancodeWrapper) -> None:
""" """
Handle the drop key. Handle the drop key.
See `settings.toml` for the default key bindings. See `settings.toml` for the default key bindings.
""" """
drop_keys = get_keys(self.settings["Action"]["drop"]) drop_keys = [pygame.key.key_code(key) for key in self.settings["Action"]["drop"]]
drop_key_pressed = is_key_pressed(drop_keys) drop_key_pressed = any(keys[key] for key in drop_keys)
if not self.timers.drop.active and drop_key_pressed: if not self.timers.drop.active and drop_key_pressed:
self.drop() self.drop()
@ -505,14 +494,3 @@ class Tetris(BaseScreen):
(self.grid_surface.get_width(), y), (self.grid_surface.get_width(), y),
CONFIG.game.line_width, 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)

View File

@ -1,4 +1,4 @@
from typing import Any, Callable, Iterator, Optional from typing import Any, Callable, NamedTuple, Optional
import pygame import pygame
from attrs import define, field from attrs import define, field
@ -55,8 +55,7 @@ class Timer:
self.activate() self.activate()
@define class Timers(NamedTuple):
class Timers:
""" """
NamedTuple for grouping different timers. NamedTuple for grouping different timers.
@ -71,17 +70,3 @@ class Timers:
horizontal: Timer horizontal: Timer
rotation: Timer rotation: Timer
drop: 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()

View File

@ -1,6 +1,6 @@
from .config import CONFIG from .config import CONFIG
from .enum import Direction, GameMode, Rotation from .enum import Direction, GameMode, Rotation
from .figure import Figure from .figure import Figure, FigureConfig
from .path import BASE_PATH from .path import BASE_PATH
from .settings import read_settings, save_settings from .settings import read_settings, save_settings
from .tuples import Size from .tuples import Size
@ -10,6 +10,7 @@ __all__ = [
"CONFIG", "CONFIG",
"Size", "Size",
"Figure", "Figure",
"FigureConfig",
"Direction", "Direction",
"Rotation", "Rotation",
"GameMode", "GameMode",

View File

@ -1,33 +1,30 @@
import random import random
from enum import Enum from enum import Enum
from typing import NamedTuple
import pygame import pygame
from attrs import define
from pygame import Vector2 as Vec2 from pygame import Vector2 as Vec2
from .config import CONFIG from .colors import TokyoNightNight
from .path import BASE_PATH from .path import BASE_PATH
@define class FigureConfig(NamedTuple):
class FigureParams:
""" """
Attributes: Attributes:
shape: The shape of the figure. shape: The shape of the figure.
color: The color of the figure. color: The color of the figure.
filename: The filename of the image of the figure. image: The image of the figure.
""" """
shape: list[Vec2] shape: list[Vec2]
color: str color: str
filename: str image: pygame.Surface
def image(self) -> pygame.Surface:
""" def _load_image(filename: str) -> pygame.Surface:
Returns: return pygame.image.load(BASE_PATH / "assets" / "figures" / filename) # TODO: add `.convert_alpha()``
The image of the figure. # TODO: change colors of images
"""
return pygame.image.load(BASE_PATH / "assets" / "figures" / self.filename).convert_alpha()
class Figure(Enum): class Figure(Enum):
@ -42,76 +39,76 @@ class Figure(Enum):
L: The L figure. L: The L figure.
""" """
I = FigureParams( # type: ignore I = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(0, -2), Vec2(0, -2),
Vec2(0, 1), Vec2(0, 1),
], ],
CONFIG.colors.cyan, TokyoNightNight().cyan,
"I.png", _load_image("I.png"),
) )
O = FigureParams( # type: ignore O = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(1, 0), Vec2(1, 0),
Vec2(1, -1), Vec2(1, -1),
], ],
CONFIG.colors.yellow, TokyoNightNight().yellow,
"O.png", _load_image("O.png"),
) )
T = FigureParams( # type: ignore T = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(-1, 0), Vec2(-1, 0),
Vec2(1, 0), Vec2(1, 0),
Vec2(0, -1), Vec2(0, -1),
], ],
CONFIG.colors.purple, TokyoNightNight().purple,
"T.png", _load_image("T.png"),
) )
S = FigureParams( # type: ignore S = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(-1, 0), Vec2(-1, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(1, -1), Vec2(1, -1),
], ],
CONFIG.colors.green, TokyoNightNight().green,
"S.png", _load_image("S.png"),
) )
Z = FigureParams( # type: ignore Z = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(1, 0), Vec2(1, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(-1, -1), Vec2(-1, -1),
], ],
CONFIG.colors.red, TokyoNightNight().red,
"Z.png", _load_image("Z.png"),
) )
J = FigureParams( # type: ignore J = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(0, 1), Vec2(0, 1),
Vec2(-1, 1), Vec2(-1, 1),
], ],
CONFIG.colors.blue, TokyoNightNight().blue,
"J.png", _load_image("J.png"),
) )
L = FigureParams( # type: ignore L = FigureConfig(
[ [
Vec2(0, 0), Vec2(0, 0),
Vec2(0, -1), Vec2(0, -1),
Vec2(0, 1), Vec2(0, 1),
Vec2(1, 1), Vec2(1, 1),
], ],
CONFIG.colors.orange, TokyoNightNight().orange,
"L.png", _load_image("L.png"),
) )
@classmethod @classmethod

Binary file not shown.

View File

@ -99,7 +99,7 @@ cd Tetris
3. Instalējiet nepieciešamās atkarības: 3. Instalējiet nepieciešamās atkarības:
```bash ```bash
pip install -e . pip install .
``` ```
4. Palaidiet spēli: 4. Palaidiet spēli:
@ -116,7 +116,7 @@ python -m tetris
=== Vispārīgi iestatījumi === Vispārīgi iestatījumi
#par(first-line-indent: 0cm, [ #par(first-line-indent: 0cm, [
/ `pause`: definē taustiņu(-s), lai apturētu spēli. / `pause`: definē taustiņu(-s), lai apturētu spēli (WIP #footnote[WIP (Work In Progress) -- nepabeigts darbs: darbs vai produkts, kas ir sākts, bet nav pabeigts vai gatavs.]<WIP>).
/ `quit`: definē taustiņu(-s), lai izietu no spēles. / `quit`: definē taustiņu(-s), lai izietu no spēles.
/ `colorscheme`: norāda spēles saskarnes krāsu shēmu. Iespējas ietver: / `colorscheme`: norāda spēles saskarnes krāsu shēmu. Iespējas ietver:
- `tokyonight-day` - `tokyonight-day`
@ -140,7 +140,7 @@ python -m tetris
=== Papildus darbību iestatījumi === Papildus darbību iestatījumi
#par(first-line-indent: 0cm, [ #par(first-line-indent: 0cm, [
/ `hold`: definē taustiņu(-s), lai uzglabātu tetromino (WIP #footnote[WIP (Work In Progress) -- nepabeigts darbs: darbs vai produkts, kas ir sākts, bet nav pabeigts vai gatavs.]<WIP>). / `hold`: definē taustiņu(-s), lai uzglabātu tetromino (WIP @WIP).
/ `drop`: definē taustiņ(-s), lai nekavējoties nomestu tetromino. / `drop`: definē taustiņ(-s), lai nekavējoties nomestu tetromino.
]) ])