Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b69cb4d9ff | ||
|
|
f7b0bc8da4 | ||
|
|
8298aef8b4 | ||
|
|
e9ee2e31fb | ||
|
|
3299732976 | ||
|
|
1631ff8894 | ||
|
|
ac2e568a26 | ||
|
|
714c145316 | ||
|
|
782bf70531 | ||
|
|
c3358a7295 | ||
|
|
f8b18f60fe | ||
|
|
c0e998b882 | ||
|
|
5a78d14c41 | ||
|
|
63fdf72cc8 | ||
|
|
280c6be84d | ||
|
|
a136a6ebf7 | ||
|
|
36edbacbbc | ||
|
|
4eed4a18e9 | ||
|
|
68452de415 | ||
|
|
b8604f41c3 | ||
|
|
ae5b2fc0df | ||
|
|
c35f4798e9 | ||
|
|
79e6fd3a07 | ||
|
|
a8748a69a2 | ||
|
|
38301b0688 | ||
|
|
75d873d1eb | ||
|
|
5200a1458e | ||
|
|
ef3dbad2d2 | ||
|
|
9e47d1bf2d | ||
|
|
a578731b65 | ||
|
|
822dd60bc1 | ||
|
|
3ae98435c2 | ||
|
|
040a879868 |
25
.github/workflows/release.yml
vendored
@ -1,25 +0,0 @@
|
||||
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
@ -162,3 +162,4 @@ debug
|
||||
.logs/
|
||||
checkpoints
|
||||
assets/highscore
|
||||
typst/*.pdf
|
||||
|
||||
@ -40,7 +40,7 @@ cd Tetris
|
||||
|
||||
3. Install the required dependencies:
|
||||
```bash
|
||||
pip install .
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### General Settings
|
||||
- `pause`: Defines the keys to pause the game. Currently a work in progress (WIP[^WIP]).
|
||||
- `pause`: Defines the keys to pause the game. Currently a work in progress.
|
||||
- `quit`: Defines the key(s) to quit the game.
|
||||
- `colorscheme`: Specifies the color scheme for the game interface. Options include:
|
||||
- `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.
|
||||
|
||||
### Action Settings
|
||||
- `hold`: Defines the keys to hold the tetromino (WIP).
|
||||
- `hold`: Defines the keys to hold the tetromino (WIP[^WIP]).
|
||||
- `drop`: Defines the keys to instantly drop the tetromino.
|
||||
|
||||
### Volume Settings
|
||||
|
||||
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 262 B After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 320 B After Width: | Height: | Size: 4.3 KiB |
@ -4,26 +4,26 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tetris"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
description = "Tetris game"
|
||||
authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"attrs==23.1.0",
|
||||
"loguru==0.7.2",
|
||||
"numpy==1.26.3",
|
||||
"pygame-ce==2.4.0",
|
||||
"toml==0.10.2",
|
||||
"attrs==23.1.0",
|
||||
"loguru==0.7.2",
|
||||
"numpy==1.26.3",
|
||||
"pygame-ce==2.4.0",
|
||||
"toml==0.10.2",
|
||||
]
|
||||
keywords = ["tetris", "game", "pygame"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"License :: OSI Approved :: GPLv3 License",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"License :: OSI Approved :: GPLv3 License",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@ -45,29 +45,29 @@ warn_unused_configs = true
|
||||
|
||||
[tool.ruff]
|
||||
extend-select = [
|
||||
"B",
|
||||
"BLE",
|
||||
"C4",
|
||||
"ERA",
|
||||
"I",
|
||||
"ICN",
|
||||
"INP",
|
||||
"ISC",
|
||||
"N",
|
||||
"NPY",
|
||||
"PGH",
|
||||
"PIE",
|
||||
# "PTH",
|
||||
"Q",
|
||||
"RET",
|
||||
"RSE",
|
||||
"RUF",
|
||||
"S",
|
||||
"SIM",
|
||||
"T20",
|
||||
"TCH",
|
||||
"TID",
|
||||
"YTT",
|
||||
"B",
|
||||
"BLE",
|
||||
"C4",
|
||||
"ERA",
|
||||
"I",
|
||||
"ICN",
|
||||
"INP",
|
||||
"ISC",
|
||||
"N",
|
||||
"NPY",
|
||||
"PGH",
|
||||
"PIE",
|
||||
# "PTH",
|
||||
"Q",
|
||||
"RET",
|
||||
"RSE",
|
||||
"RUF",
|
||||
"S",
|
||||
"SIM",
|
||||
"T20",
|
||||
"TCH",
|
||||
"TID",
|
||||
"YTT",
|
||||
]
|
||||
ignore = ["E741"]
|
||||
show-fixes = true
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[General]
|
||||
pause = ["escape", "F1"] # WIP
|
||||
pause = ["escape", "F1"]
|
||||
quit = ["q"]
|
||||
colorscheme = "tokyonight-night" # tokyonight-night / tokyonight-storm / tokyonight-day / tokyonight-moon
|
||||
|
||||
@ -11,16 +11,16 @@ down = ["down", "keypad 2"]
|
||||
[Rotation]
|
||||
cw = ["x", "up", "keypad 1", "keypad 5", "keypad 9"] # clockwise
|
||||
ccw = [
|
||||
"left ctrl",
|
||||
"right ctrl",
|
||||
"z",
|
||||
"keypad 3",
|
||||
"keypad 7",
|
||||
"left ctrl",
|
||||
"right ctrl",
|
||||
"z",
|
||||
"keypad 3",
|
||||
"keypad 7",
|
||||
] # counter-clockwise
|
||||
|
||||
[Action]
|
||||
hold = ["left shift", "right shift", "c", "keypad 0"] # WIP
|
||||
drop = ["space", "keypad 5"]
|
||||
drop = ["space", "keypad 8"]
|
||||
|
||||
[Volume.Music]
|
||||
enabled = true
|
||||
|
||||
@ -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
@ -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
|
||||
@ -30,7 +30,7 @@ class Preview(BaseScreen, SceenElement):
|
||||
Args:
|
||||
next_figures: Next figure.
|
||||
"""
|
||||
self.next_figure = next_figure
|
||||
self.next_figure: Figure = next_figure
|
||||
|
||||
def draw(self) -> None:
|
||||
"""Draw the preview on the preview surface."""
|
||||
@ -51,9 +51,10 @@ class Preview(BaseScreen, SceenElement):
|
||||
|
||||
def _draw_figure(self) -> None:
|
||||
"""Draw a single upcoming figure on the preview surface."""
|
||||
figure_surface = self.next_figure.value.image
|
||||
figure_surface: pygame.Surface = self.next_figure.value.image()
|
||||
x = self.surface.get_width() / 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))
|
||||
self.surface.blit(figure_surface, rect)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from .config import CONFIG
|
||||
from .enum import Direction, GameMode, Rotation
|
||||
from .figure import Figure, FigureConfig
|
||||
from .figure import Figure
|
||||
from .path import BASE_PATH
|
||||
from .settings import read_settings, save_settings
|
||||
from .tuples import Size
|
||||
@ -10,7 +10,6 @@ __all__ = [
|
||||
"CONFIG",
|
||||
"Size",
|
||||
"Figure",
|
||||
"FigureConfig",
|
||||
"Direction",
|
||||
"Rotation",
|
||||
"GameMode",
|
||||
|
||||
@ -1,30 +1,33 @@
|
||||
import random
|
||||
from enum import Enum
|
||||
from typing import NamedTuple
|
||||
|
||||
import pygame
|
||||
from attrs import define
|
||||
from pygame import Vector2 as Vec2
|
||||
|
||||
from .colors import TokyoNightNight
|
||||
from .config import CONFIG
|
||||
from .path import BASE_PATH
|
||||
|
||||
|
||||
class FigureConfig(NamedTuple):
|
||||
@define
|
||||
class FigureParams:
|
||||
"""
|
||||
Attributes:
|
||||
shape: The shape of the figure.
|
||||
color: The color of the figure.
|
||||
image: The image of the figure.
|
||||
filename: The filename of the image of the figure.
|
||||
"""
|
||||
|
||||
shape: list[Vec2]
|
||||
color: str
|
||||
image: pygame.Surface
|
||||
filename: str
|
||||
|
||||
|
||||
def _load_image(filename: str) -> pygame.Surface:
|
||||
return pygame.image.load(BASE_PATH / "assets" / "figures" / filename) # TODO: add `.convert_alpha()``
|
||||
# TODO: change colors of images
|
||||
def image(self) -> pygame.Surface:
|
||||
"""
|
||||
Returns:
|
||||
The image of the figure.
|
||||
"""
|
||||
return pygame.image.load(BASE_PATH / "assets" / "figures" / self.filename).convert_alpha()
|
||||
|
||||
|
||||
class Figure(Enum):
|
||||
@ -39,76 +42,76 @@ class Figure(Enum):
|
||||
L: The L figure.
|
||||
"""
|
||||
|
||||
I = FigureConfig(
|
||||
I = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(0, -2),
|
||||
Vec2(0, 1),
|
||||
],
|
||||
TokyoNightNight().cyan,
|
||||
_load_image("I.png"),
|
||||
CONFIG.colors.cyan,
|
||||
"I.png",
|
||||
)
|
||||
O = FigureConfig(
|
||||
O = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(1, 0),
|
||||
Vec2(1, -1),
|
||||
],
|
||||
TokyoNightNight().yellow,
|
||||
_load_image("O.png"),
|
||||
CONFIG.colors.yellow,
|
||||
"O.png",
|
||||
)
|
||||
T = FigureConfig(
|
||||
T = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(-1, 0),
|
||||
Vec2(1, 0),
|
||||
Vec2(0, -1),
|
||||
],
|
||||
TokyoNightNight().purple,
|
||||
_load_image("T.png"),
|
||||
CONFIG.colors.purple,
|
||||
"T.png",
|
||||
)
|
||||
|
||||
S = FigureConfig(
|
||||
S = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(-1, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(1, -1),
|
||||
],
|
||||
TokyoNightNight().green,
|
||||
_load_image("S.png"),
|
||||
CONFIG.colors.green,
|
||||
"S.png",
|
||||
)
|
||||
Z = FigureConfig(
|
||||
Z = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(1, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(-1, -1),
|
||||
],
|
||||
TokyoNightNight().red,
|
||||
_load_image("Z.png"),
|
||||
CONFIG.colors.red,
|
||||
"Z.png",
|
||||
)
|
||||
J = FigureConfig(
|
||||
J = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(0, 1),
|
||||
Vec2(-1, 1),
|
||||
],
|
||||
TokyoNightNight().blue,
|
||||
_load_image("J.png"),
|
||||
CONFIG.colors.blue,
|
||||
"J.png",
|
||||
)
|
||||
L = FigureConfig(
|
||||
L = FigureParams( # type: ignore
|
||||
[
|
||||
Vec2(0, 0),
|
||||
Vec2(0, -1),
|
||||
Vec2(0, 1),
|
||||
Vec2(1, 1),
|
||||
],
|
||||
TokyoNightNight().orange,
|
||||
_load_image("L.png"),
|
||||
CONFIG.colors.orange,
|
||||
"L.png",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
BIN
typst/main.pdf
@ -99,7 +99,7 @@ cd Tetris
|
||||
|
||||
3. Instalējiet nepieciešamās atkarības:
|
||||
```bash
|
||||
pip install .
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
4. Palaidiet spēli:
|
||||
@ -116,7 +116,7 @@ python -m tetris
|
||||
|
||||
=== Vispārīgi iestatījumi
|
||||
#par(first-line-indent: 0cm, [
|
||||
/ `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>).
|
||||
/ `pause`: definē taustiņu(-s), lai apturētu spēli.
|
||||
/ `quit`: definē taustiņu(-s), lai izietu no spēles.
|
||||
/ `colorscheme`: norāda spēles saskarnes krāsu shēmu. Iespējas ietver:
|
||||
- `tokyonight-day`
|
||||
@ -140,7 +140,7 @@ python -m tetris
|
||||
|
||||
=== Papildus darbību iestatījumi
|
||||
#par(first-line-indent: 0cm, [
|
||||
/ `hold`: definē taustiņu(-s), lai uzglabātu tetromino (WIP @WIP).
|
||||
/ `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>).
|
||||
/ `drop`: definē taustiņ(-s), lai nekavējoties nomestu tetromino.
|
||||
])
|
||||
|
||||
|
||||