]> Untitled Git - axy/ft/pacman.git/commitdiff
loader config and highscores
authorThomas Rimelen <trimelen@k1r1p13.42mulhouse.fr>
Thu, 15 Oct 2026 13:10:04 +0000 (15:10 +0200)
committerThomas Rimelen <trimelen@k1r1p13.42mulhouse.fr>
Thu, 15 Oct 2026 13:10:04 +0000 (15:10 +0200)
config.json [new file with mode: 0644]
highscores.json [new file with mode: 0644]
src/pacman/__init__.py
src/pacman/ecs/gui.py
src/pacman/game/__init__.py

diff --git a/config.json b/config.json
new file mode 100644 (file)
index 0000000..0967ef4
--- /dev/null
@@ -0,0 +1 @@
+{}
diff --git a/highscores.json b/highscores.json
new file mode 100644 (file)
index 0000000..28f485b
--- /dev/null
@@ -0,0 +1,7 @@
+{
+    "scores": {
+        "A": 10,
+        "B": 30,
+        "C": 20
+    }
+}
\ No newline at end of file
index a0c5722b7601558ec4a35ff2aab1a01dae539d9b..6c5013baeb81bc71705398863ab894015dc92308 100644 (file)
@@ -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)
index 6b4f0f83259eaae36c30313c33e3a133102e56a5..72f0be56402cb578676164fcc8d21f6d46516d8e 100644 (file)
@@ -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
index 419e5ed8561e7a6014cfbc1a8dab2e6bc1ef17ca..87473fbf43f810e363bb9cd7134d88c813c20cb7 100644 (file)
@@ -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(