feat(game): movemnt keys from file

This commit is contained in:
Kristofers Solo 2024-01-07 16:47:24 +02:00
parent f1e854a38c
commit 8e3ed493e0
11 changed files with 178 additions and 84 deletions

View File

@ -1,21 +1,20 @@
[keybinds] [General]
rotate_colockwise = "up arrow" pause = ["esc", "F1"]
hard_drop = "space" quit = ["q"]
hold = "shift+c"
rotate_counter_colockwise = "ctrl+z"
pause = "esc+F1"
move_left = "left arrow"
move_right = "right arrow"
move_down = "down arrow"
[numpad] [Movement]
hold = "numpad 0" left = ["left", "kp4"]
hard_drop = "numpad 5" right = ["right", "kp6"]
move_left = "numpad 4" down = ["down", "kp2"]
move_right = "numpad 6"
soft_drop = "numpad 2" [Rotation]
rotate_colockwise_1 = "numpad 1" cw = ["x", "up", "kp1", "kp5", "kp9"] # clockwise
rotate_colockwise_2 = "numpad 5" ccw = ["ctrl", "z", "kp3", "kp7"] # counter-clockwise
rotate_colockwise_3 = "numpad 9"
rotate_counter_colockwise_1 = "numpad 3" [Action]
rotate_counter_colockwise_2 = "numpad 7" hold = ["shift", "c", "kp0"]
drop = ["space", "kp5"]
[Sound]
music = true
volume = 0.5

View File

@ -1,4 +1,4 @@
import sys from typing import Any
import pygame import pygame
from utils import CONFIG, Figure, GameMode from utils import CONFIG, Figure, GameMode
@ -27,8 +27,9 @@ class Game(BaseScreen):
music: Music that plays in the background. music: Music that plays in the background.
""" """
def __init__(self, game_mode: GameMode) -> 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._initialize_game_components() self._initialize_game_components()
self._start_background_music() self._start_background_music()
@ -60,7 +61,9 @@ class Game(BaseScreen):
self.clock = pygame.time.Clock() self.clock = pygame.time.Clock()
self.next_figure: Figure = self._generate_next_figure() self.next_figure: Figure = self._generate_next_figure()
self.tetris = Tetris(self._get_next_figure, self._update_score, self.game_mode) 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()

View File

@ -2,7 +2,7 @@ import sys
from typing import Optional from typing import Optional
import pygame import pygame
from utils import CONFIG, GameMode from utils import CONFIG, PYGAME_EVENT, GameMode, read_settings
from game.log import log from game.log import log
@ -13,13 +13,14 @@ from .game import Game
class Main(BaseScreen, SceenElement, TextScreen): class Main(BaseScreen, SceenElement, TextScreen):
def __init__(self, mode: GameMode) -> None: def __init__(self, mode: GameMode) -> None:
# log.info("Initializing the game") log.info("Initializing the game")
self._initialize_pygame() self._initialize_pygame()
self._initialize_surface() self._initialize_surface()
self._initialize_rect() self._initialize_rect()
self._initialize_font() self._initialize_font()
self._set_buttons() self._set_buttons()
self._initialize_increment_height() self._initialize_increment_height()
self.settings = read_settings()
self.game_mode = mode self.game_mode = mode
self.game: Optional[Game] = None self.game: Optional[Game] = None
@ -36,7 +37,9 @@ 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 == pygame.K_q: if event.key in [
PYGAME_EVENT[key] for key in self.settings["General"]["quit"]
]:
self.exit() self.exit()
if not self.game: if not self.game:
@ -61,13 +64,13 @@ class Main(BaseScreen, SceenElement, TextScreen):
def exit(self) -> None: def exit(self) -> None:
"""Exit the game.""" """Exit the game."""
# log.info("Exiting the game") log.info("Exiting the game")
pygame.quit() pygame.quit()
sys.exit() sys.exit()
def play(self) -> "Main": def play(self) -> "Main":
self._draw_background() self._draw_background()
self.game = Game(self.game_mode) self.game = Game(self.game_mode, self.settings)
return self return self
def _set_buttons(self) -> None: def _set_buttons(self) -> None:

View File

@ -2,7 +2,7 @@ from typing import Any, Callable, Optional
import numpy as np import numpy as np
import pygame import pygame
from utils import CONFIG, Direction, Figure, GameMode, Rotation from utils import CONFIG, PYGAME_EVENT, Direction, Figure, GameMode, Rotation
from game.log import log from game.log import log
from game.sprites import Block, Tetromino from game.sprites import Block, Tetromino
@ -45,10 +45,12 @@ class Tetris(BaseScreen):
get_next_figure: Callable[[], Figure], get_next_figure: Callable[[], Figure],
update_score: Callable[[int, int, int], None], update_score: Callable[[int, int, int], None],
game_mode: GameMode, game_mode: GameMode,
settings: dict[str, Any],
) -> None: ) -> None:
self._initialize_surface() self._initialize_surface()
self._initialize_rect() self._initialize_rect()
self._initialize_sprites() self._initialize_sprites()
self.settings = settings
self.get_next_figure = get_next_figure self.get_next_figure = get_next_figure
self.update_score = update_score self.update_score = update_score
@ -150,8 +152,8 @@ class Tetris(BaseScreen):
self._check_finished_rows() self._check_finished_rows()
self.game_over: bool = self._check_game_over() self.game_over: bool = self._check_game_over()
# if self.game_over: if self.game_over:
# self.restart() self.restart()
self.tetromino = Tetromino( self.tetromino = Tetromino(
self.sprites, self.sprites,
@ -171,13 +173,13 @@ class Tetris(BaseScreen):
""" """
for block in self.tetromino.blocks: for block in self.tetromino.blocks:
if block.pos.y <= 0: if block.pos.y <= 0:
# log.info("Game over!") log.info("Game over!")
return True return True
return False return False
def restart(self) -> None: def restart(self) -> None:
"""Restart the game.""" """Restart the game."""
# log.info("Restarting the game") log.info("Restarting the game")
self._reset_game_state() self._reset_game_state()
self._initialize_field_and_tetromino() self._initialize_field_and_tetromino()
self.game_over = False self.game_over = False
@ -356,17 +358,19 @@ class Tetris(BaseScreen):
""" """
Handle movement keys. Handle movement keys.
Move right [K_d, K_l]. See `settings.toml` for the default key bindings.
Move left [K_a, K_h].
""" """
right_keys = keys[pygame.K_d] or keys[pygame.K_l] right_keys = [PYGAME_EVENT[key] for key in self.settings["Movement"]["right"]]
left_keys = keys[pygame.K_a] or keys[pygame.K_h] right_key_pressed = any(keys[key] for key in right_keys)
left_keys = [PYGAME_EVENT[key] for key in self.settings["Movement"]["left"]]
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_keys: if left_key_pressed:
self.move_left() self.move_left()
self.timers.horizontal.activate() self.timers.horizontal.activate()
elif right_keys: elif right_key_pressed:
self.move_right() self.move_right()
self.timers.horizontal.activate() self.timers.horizontal.activate()
@ -374,46 +378,49 @@ class Tetris(BaseScreen):
""" """
Handle rotation keys. Handle rotation keys.
Rotation clockwise [K_RIGHT, K_UP, K_r, K_w, K_k]. See `settings.toml` for the default key bindings.
Rotation counter-clockwise [K_LEFT, K_e, K_i].
""" """
clockwise_keys = ( cw_keys = [PYGAME_EVENT[key] for key in self.settings["Rotation"]["cw"]]
keys[pygame.K_r] cw_key_pressed = any(keys[key] for key in cw_keys)
or keys[pygame.K_UP]
or keys[pygame.K_w]
or keys[pygame.K_k]
or keys[pygame.K_RIGHT]
)
counter_clockwise_keys = ( ccw_keys = [PYGAME_EVENT[key] for key in self.settings["Rotation"]["ccw"]]
keys[pygame.K_e] or keys[pygame.K_i] or keys[pygame.K_LEFT] ccw_key_pressed = any(keys[key] for key in ccw_keys)
)
if not self.timers.rotation.active: if not self.timers.rotation.active:
if clockwise_keys: if cw_key_pressed:
self.rotate() self.rotate()
self.timers.rotation.activate() self.timers.rotation.activate()
if counter_clockwise_keys: if ccw_key_pressed:
self.rotate_reverse() self.rotate_reverse()
self.timers.rotation.activate() self.timers.rotation.activate()
def _handle_down_key(self, keys: pygame.key.ScancodeWrapper) -> 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] Handle the down key.
if not self.down_pressed and down_keys:
See `settings.toml` for the default key bindings.
"""
down_keys = [PYGAME_EVENT[key] for key in self.settings["Movement"]["down"]]
down_key_pressed = any(keys[key] for key in down_keys)
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
if self.down_pressed and not down_keys: if self.down_pressed and not down_key_pressed:
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: pygame.key.ScancodeWrapper) -> None: def _handle_drop_key(self, keys: pygame.key.ScancodeWrapper) -> None:
"""Handle the drop key [K_SPACE].""" """
drop_keys = keys[pygame.K_SPACE] Handle the drop key.
if not self.timers.drop.active and drop_keys: See `settings.toml` for the default key bindings.
"""
drop_keys = [PYGAME_EVENT[key] for key in self.settings["Action"]["drop"]]
drop_key_pressed = any(keys[key] for key in drop_keys)
if not self.timers.drop.active and drop_key_pressed:
self.drop() self.drop()
self.timers.drop.activate() self.timers.drop.activate()

View File

@ -203,7 +203,7 @@ class Tetromino:
""" """
return all( return all(
0 <= pos.x < CONFIG.game.columns 0 <= pos.x < CONFIG.game.columns
and 0 <= pos.y < CONFIG.game.rows and -2 <= pos.y < CONFIG.game.rows
and not self.field[int(pos.y), int(pos.x)] and not self.field[int(pos.y), int(pos.x)]
for pos in new_positions for pos in new_positions
) )

View File

@ -1,11 +1,11 @@
from .config import CONFIG from .config import CONFIG
from .enum import Direction, GameMode, Rotation from .enum import Direction, GameMode, Rotation
from .events import PYGAME_EVENT
from .figure import Figure, FigureConfig from .figure import Figure, FigureConfig
from .log import log from .log import log
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 BestMove, Size from .tuples import Size
from .weights import Weights
__all__ = [ __all__ = [
"BASE_PATH", "BASE_PATH",
@ -17,8 +17,7 @@ __all__ = [
"Direction", "Direction",
"Rotation", "Rotation",
"GameMode", "GameMode",
"Weights",
"BestMove",
"read_settings", "read_settings",
"save_settings", "save_settings",
"PYGAME_EVENT",
] ]

View File

@ -21,7 +21,7 @@ class Game:
size: Size = Size(columns * cell.width, rows * cell.width) size: Size = Size(columns * cell.width, rows * cell.width)
pos: Vec2 = Vec2(padding, padding) pos: Vec2 = Vec2(padding, padding)
offset: Vec2 = Vec2(columns // 2, -1) offset: Vec2 = Vec2(columns // 2, -1)
initial_speed: float | int = 100 initial_speed: float | int = 300
movment_delay: int = 150 movment_delay: int = 150
rotation_delay: int = 200 rotation_delay: int = 200
drop_delay: int = 200 drop_delay: int = 200

94
src/utils/events.py Normal file
View File

@ -0,0 +1,94 @@
import pygame
PYGAME_EVENT: dict[str, int] = {
"up": pygame.K_UP,
"down": pygame.K_DOWN,
"left": pygame.K_LEFT,
"right": pygame.K_RIGHT,
"space": pygame.K_SPACE,
"escape": pygame.K_ESCAPE,
"q": pygame.K_q,
"w": pygame.K_w,
"e": pygame.K_e,
"r": pygame.K_r,
"t": pygame.K_t,
"y": pygame.K_y,
"u": pygame.K_u,
"i": pygame.K_i,
"o": pygame.K_o,
"p": pygame.K_p,
"a": pygame.K_a,
"s": pygame.K_s,
"d": pygame.K_d,
"f": pygame.K_f,
"g": pygame.K_g,
"h": pygame.K_h,
"j": pygame.K_j,
"k": pygame.K_k,
"l": pygame.K_l,
"z": pygame.K_z,
"x": pygame.K_x,
"c": pygame.K_c,
"v": pygame.K_v,
"b": pygame.K_b,
"n": pygame.K_n,
"m": pygame.K_m,
"1": pygame.K_1,
"2": pygame.K_2,
"3": pygame.K_3,
"4": pygame.K_4,
"5": pygame.K_5,
"6": pygame.K_6,
"7": pygame.K_7,
"8": pygame.K_8,
"9": pygame.K_9,
"0": pygame.K_0,
"shift": pygame.K_LSHIFT,
"ctrl": pygame.K_LCTRL,
"alt": pygame.K_LALT,
"tab": pygame.K_TAB,
"capslock": pygame.K_CAPSLOCK,
"return": pygame.K_RETURN,
"backspace": pygame.K_BACKSPACE,
"insert": pygame.K_INSERT,
"delete": pygame.K_DELETE,
"home": pygame.K_HOME,
"end": pygame.K_END,
"pageup": pygame.K_PAGEUP,
"pagedown": pygame.K_PAGEDOWN,
"numlock": pygame.K_NUMLOCK,
"printscreen": pygame.K_PRINTSCREEN,
"scrolllock": pygame.K_SCROLLLOCK,
"pause": pygame.K_PAUSE,
"F1": pygame.K_F1,
"F2": pygame.K_F2,
"F3": pygame.K_F3,
"F4": pygame.K_F4,
"F5": pygame.K_F5,
"F6": pygame.K_F6,
"F7": pygame.K_F7,
"F8": pygame.K_F8,
"F9": pygame.K_F9,
"F10": pygame.K_F10,
"F11": pygame.K_F11,
"F12": pygame.K_F12,
"F13": pygame.K_F13,
"F14": pygame.K_F14,
"F15": pygame.K_F15,
"kp0": pygame.K_KP0,
"kp1": pygame.K_KP1,
"kp2": pygame.K_KP2,
"kp3": pygame.K_KP3,
"kp4": pygame.K_KP4,
"kp5": pygame.K_KP5,
"kp6": pygame.K_KP6,
"kp7": pygame.K_KP7,
"kp8": pygame.K_KP8,
"kp9": pygame.K_KP9,
"kp_divide": pygame.K_KP_DIVIDE,
"kp_multiply": pygame.K_KP_MULTIPLY,
"kp_minus": pygame.K_KP_MINUS,
"kp_plus": pygame.K_KP_PLUS,
"kp_enter": pygame.K_KP_ENTER,
"kp_equals": pygame.K_KP_EQUALS,
}

View File

@ -1,10 +1,11 @@
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Any, Optional
import toml import toml
from .config import CONFIG, Config from .config import CONFIG, Config
from .log import log from .log import log
from .path import BASE_PATH
def save_settings(settings: Config, file_path: Path) -> None: def save_settings(settings: Config, file_path: Path) -> None:
@ -12,7 +13,9 @@ def save_settings(settings: Config, file_path: Path) -> None:
toml.dump(settings, file) toml.dump(settings, file)
def read_settings(file_path: Path) -> Optional[dict[str, str]]: def read_settings(
file_path: Path = BASE_PATH / "settings.toml",
) -> dict[str, Any]:
""" """
Read and parse a TOML file and return the content as a dictionary. Read and parse a TOML file and return the content as a dictionary.
@ -27,7 +30,7 @@ def read_settings(file_path: Path) -> Optional[dict[str, str]]:
return toml.load(file) return toml.load(file)
except FileNotFoundError: except FileNotFoundError:
log.error(f"Error: The file '{file_path}' does not exist.") log.error(f"Error: The file '{file_path}' does not exist.")
return None return {}
except toml.TomlDecodeError as e: except toml.TomlDecodeError as e:
log.error(f"rror decoding TOML file: {e}") log.error(f"rror decoding TOML file: {e}")
return None return {}

View File

@ -11,8 +11,3 @@ class Size(NamedTuple):
if isinstance(other, Size): if isinstance(other, Size):
return Size(self.width - other.width, self.height - other.height) return Size(self.width - other.width, self.height - other.height)
return Size(self.width - other, self.height - other) return Size(self.width - other, self.height - other)
class BestMove(NamedTuple):
rotation: int
x: int

View File

@ -1,9 +0,0 @@
from attrs import define
@define
class Weights:
height: float
lines: float
holes: float
bumpiness: float