+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
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):
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]:
def setup_new_maze(world: World) -> None:
- for sys in (
+ for system in (
setup_maze,
spawn_superpacgums,
spawn_pacgums,
spawn_ghosts,
start_pause_timer,
):
- world.run_raw_system(sys)
+ world.run_raw_system(system)
def maze_exists(itr: Query[Maze]) -> bool:
return True
-def setup_resources(world: World) -> None:
- world.spawn_resource(Config())
-
-
def print_score(state: GameState) -> str:
return f"Score: {state.score}"
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,
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))
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(