From: Thomas Rimelen Date: Thu, 15 Oct 2026 13:10:04 +0000 (+0200) Subject: loader config and highscores X-Git-Url: https://git.uwuaxy.net/Project%20Management.png?a=commitdiff_plain;h=478aac0c2df819f4f6f4325288fd9fc3a59d8de0;p=axy%2Fft%2Fpacman.git loader config and highscores --- diff --git a/config.json b/config.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/config.json @@ -0,0 +1 @@ +{} diff --git a/highscores.json b/highscores.json new file mode 100644 index 0000000..28f485b --- /dev/null +++ b/highscores.json @@ -0,0 +1,7 @@ +{ + "scores": { + "A": 10, + "B": 30, + "C": 20 + } +} \ No newline at end of file diff --git a/src/pacman/__init__.py b/src/pacman/__init__.py index a0c5722..6c5013b 100644 --- a/src/pacman/__init__.py +++ b/src/pacman/__init__.py @@ -2,4 +2,10 @@ from pacman.game import run_game def main() -> None: - run_game() + try: + run_game() + except Exception as e: + print(f"Error: {e}") + exit(1) + except KeyboardInterrupt: + exit(1) diff --git a/src/pacman/ecs/gui.py b/src/pacman/ecs/gui.py index 6b4f0f8..72f0be5 100644 --- a/src/pacman/ecs/gui.py +++ b/src/pacman/ecs/gui.py @@ -1,7 +1,7 @@ from collections.abc import Callable from dataclasses import dataclass, field from enum import Enum, auto -from typing import override, cast +from typing import cast, override import pygame from pygame import Color, Vector2 diff --git a/src/pacman/game/__init__.py b/src/pacman/game/__init__.py index 419e5ed..87473fb 100644 --- a/src/pacman/game/__init__.py +++ b/src/pacman/game/__init__.py @@ -1,11 +1,12 @@ +import sys from collections.abc import Callable from dataclasses import dataclass from enum import Enum, auto from random import random, shuffle -from typing import override, cast +from typing import cast, override from mazegenerator import MazeGenerator # type: ignore[import-untyped] -from pydantic import BaseModel +from pydantic import BaseModel, Field from pygame import K_ESCAPE, Vector2 from pygame.math import clamp @@ -103,16 +104,21 @@ class InitialPauseTimer(Resource): class Config(Resource, BaseModel): - ghost_chase_speed: float = 2.5 - ghost_flee_speed: float = 1.5 - pacman_speed: float = 3.0 - initial_pos_timer: int = 60 - level_timer: int = 60 * 180 - initial_lives: int = 3 - score_pacgum: int = 1 - score_superpacgum: int = 5 - score_ghost: int = 20 - superpacgum_duration: int = 60 * 8 + ghost_chase_speed: float = Field(default=2.5, ge=0.1, le=100.0) + ghost_flee_speed: float = Field(default=1.5, ge=0.1, le=100.0) + pacman_speed: float = Field(default=3.0, ge=0.1, le=100.0) + initial_pos_timer: int = Field(default=60, ge=0) + level_timer: int = Field(default=60 * 180, ge=60) + initial_lives: int = Field(default=3, ge=1) + score_pacgum: int = Field(default=1, ge=0) + score_superpacgum: int = Field(default=5, ge=0) + score_ghost: int = Field(default=20, ge=0) + superpacgum_duration: int = Field(default=60 * 8, ge=60) + highscores_path: str = "highscores.json" + + +class Highscores(Resource, BaseModel): + scores: dict[str, int] class GameIsOver(Resource): @@ -217,6 +223,36 @@ class Cell(Component): return [(1 & (self.n >> i)) == 1 for i in range(4)] +def remove_comment(s: str) -> str: + return "\n".join(line for line in s.split("\n") if not s.startswith("#")) + + +def load_config(world: World) -> None: + if len(sys.argv) != 2: + raise Exception( + f"Expected exactly 1 argument, got {len(sys.argv) - 1}" + ) + + with open(sys.argv[1]) as f: + config = Config.model_validate_json(remove_comment(f.read())) + world.spawn_resource(config) + + +def load_highscores(world: World, config: Config) -> None: + try: + with open(config.highscores_path) as f: + scores = Highscores.model_validate_json(f.read()) + except FileNotFoundError: + scores = Highscores(scores={}) + world.spawn_resource(scores) + + +def save_highscores(config: Config, scores: Highscores) -> None: + path = config.highscores_path + with open(path, "w+") as f: + f.write(scores.model_dump_json(indent=4)) + + def create_maze( world: World, assets: Assets[Sprite2D] ) -> Callable[[MazeGenerator], Entity]: @@ -728,7 +764,7 @@ def start_pause_timer(timer: "InitialPauseTimer") -> None: def setup_new_maze(world: World) -> None: - for sys in ( + for system in ( setup_maze, spawn_superpacgums, spawn_pacgums, @@ -736,7 +772,7 @@ def setup_new_maze(world: World) -> None: spawn_ghosts, start_pause_timer, ): - world.run_raw_system(sys) + world.run_raw_system(system) def maze_exists(itr: Query[Maze]) -> bool: @@ -804,10 +840,6 @@ def initial_pause_done(timer: InitialPauseTimer, config: Config) -> bool: return True -def setup_resources(world: World) -> None: - world.spawn_resource(Config()) - - def print_score(state: GameState) -> str: return f"Score: {state.score}" @@ -989,6 +1021,10 @@ def setup_game_menu( world.spawn_resource(SelectedButton(selected)) +def set_up_highsores(world: World, scores: Highscores) -> Entity: + return world.spawn_empty() + + def setup_main_menu(world: World) -> None: selected = world.run_system( spawn_button, @@ -1004,9 +1040,11 @@ def setup_main_menu(world: World) -> None: quit_game, ) + scores = world.run_raw_system(set_up_highsores) + world.spawn( tuple[MainMenu, Children, CoordBundle], - (MainMenu(), Children((selected, quit_button)), CoordBundle()), + (MainMenu(), Children((selected, quit_button, scores)), CoordBundle()), ) world.spawn_resource(SelectedButton(selected)) @@ -1015,7 +1053,7 @@ def setup_main_menu(world: World) -> None: def run_game() -> None: World().with_plugins(graphics_plugins, minimal_plugins).with_systems( StartupSchedule, - Systems(setup_resources, setup_main_menu) + Systems(load_config, load_highscores, setup_main_menu) .chain() .after(setup_gui_resources), ).with_systems(