From: Thomas Rimelen Date: Thu, 22 Oct 2026 10:44:33 +0000 (+0200) Subject: fix config, add level count + maze dimension, game over added, cheat options added... X-Git-Url: https://git.uwuaxy.net/Project%20Management.png?a=commitdiff_plain;h=db6fc73bf9bdb98613de97258a311cd6af1da4cd;p=axy%2Fft%2Fpacman.git fix config, add level count + maze dimension, game over added, cheat options added and gui, Makefile added --- diff --git a/Makefile b/Makefile index e69de29..5eccacb 100644 --- a/Makefile +++ b/Makefile @@ -0,0 +1,32 @@ +MAIN=pacman +PYTHON=python3 +CONFIG=config.json + + +.PHONY: install run venv debug clean lint lint--strict + +install: + @uv sync + +run: + @uv run $(MAIN) $(CONFIG) + + +debug: + @$(PYTHON) -m pdb $(MAIN) $(CONFIG) + +clean: + @find . -type d -name "__pycache__" -exec rm -r {} + + @find . -type d -name ".mypy_cache" -exec rm -r {} + + @find . -type d -name "*.pyc" -delete +# @rm -rf .venv + +lint: + @uv run flake8 src + @uv run mypy src --warn-return-any \ + --warn-unused-ignores --ignore-missing-imports \ + --disallow-untyped-defs + +lint-strict: + @uv run flake8 src + @uv run mypy src --strict \ No newline at end of file diff --git a/config.json b/config.json index 2314f00..4f6ed91 100644 --- a/config.json +++ b/config.json @@ -3,11 +3,14 @@ "ghost_flee_speed": 1.5, "pacman_speed": 3.0, "initial_pos_timer": 60, - "level_timer": 180, + "level_timer": 10800, + "level_count": 10, "initial_lives": 3, "score_pacgum": 1, "score_superpacgum": 5, "score_ghost": 20, "superpacgum_duration": 480, + "maze_width": 15, + "maze_heigth": 13, "highscores_path": "highscores.json" } diff --git a/highscores.json b/highscores.json index 416a837..adc46f9 100644 --- a/highscores.json +++ b/highscores.json @@ -1,22 +1,5 @@ { "scores": { - "Axel": 30, - "Thomas": 20, - "Garobert": 1, - "agilliar": 5, - "agilliaras": 5, - "": 7, - "sdasd": 7, - "sdasdswwsw": 7, - "asads": 6, - "sswswss": 6, - "dds": 7, - "s": 8, - "sssd": 7, - "w": 8, - "ssswsws": 7, - "sssssaaa": 7, - "ssss": 8, - "sws": 8 + "test": 3 } } \ No newline at end of file diff --git a/src/pacman/__init__.py b/src/pacman/__init__.py index a9f9fdd..4f0ad42 100644 --- a/src/pacman/__init__.py +++ b/src/pacman/__init__.py @@ -2,6 +2,7 @@ from pacman.game import run_game def main() -> None: + """Run the game.""" run_game() return None try: diff --git a/src/pacman/game/__init__.py b/src/pacman/game/__init__.py index 9040306..271a16c 100644 --- a/src/pacman/game/__init__.py +++ b/src/pacman/game/__init__.py @@ -7,7 +7,7 @@ from typing import cast, override from mazegenerator import MazeGenerator # type: ignore[import-untyped] from pydantic import BaseModel, Field -from pygame import K_BACKSPACE, K_ESCAPE, K_RETURN, K_a, K_z, Vector2 +from pygame import K_BACKSPACE, K_ESCAPE, K_RETURN, K_a, K_c, K_n, K_z, Vector2 from pygame.math import clamp from pacman.ecs import minimal_plugins @@ -127,11 +127,14 @@ class Config(Resource, BaseModel): 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) + level_count: int = Field(default=10, ge=1) 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) + maze_width: int = Field(default=12, ge=5, le=15) + maze_heigth: int = Field(default=12, ge=5, le=15) highscores_path: str = "highscores.json" @@ -162,6 +165,7 @@ class GameState(Resource): level: int = 0 timer: int = 0 lives: int = 3 + state: str = "" class CheatMode(Component): @@ -425,9 +429,11 @@ def spawn_pacgums( ) -def setup_maze(world: World) -> None: +def setup_maze(world: World, config: Config) -> None: """Create maze with a given MazeGenerator.""" - world.run_system(create_maze, MazeGenerator((14, 14))) + world.run_system( + create_maze, MazeGenerator((config.maze_width, config.maze_heigth)) + ) def move_in_motion( @@ -604,6 +610,20 @@ def move_pacman( motion.direction = direction +def chase( + ghosts: Query[tuple[InMotion, GhostStatus], Ghost], +) -> None: + """Change ghost status to 'flee'.""" + for ghost, status in ghosts: + if status.state in (GhostState.wander, GhostState.chase): + tmp = ghost.src + ghost.src = ghost.pos + ghost.pos = tmp + status.state = GhostState.flee + if status.state is GhostState.flee: + status.tick = 0 + + def collect_pacgum( world: World, pacgums: Query[tuple[CoordAbs2D, Entity], PacGum], @@ -626,7 +646,6 @@ def collect_superpacgum( world: World, pacgums: Query[tuple[CoordAbs2D, Entity], SuperPacGum], pacmans: Query[CoordAbs2D, Pacman], - ghosts: Query[tuple[InMotion, GhostStatus], Ghost], score: GameState, config: Config, ) -> None: @@ -640,14 +659,15 @@ def collect_superpacgum( score.score += len(collided * config.score_superpacgum) if len(collided) == 0: return - for ghost, status in ghosts: - if status.state in (GhostState.wander, GhostState.chase): - tmp = ghost.src - ghost.src = ghost.pos - ghost.pos = tmp - status.state = GhostState.flee - if status.state is GhostState.flee: - status.tick = 0 + world.run_simple_system(chase) + # for ghost, status in ghosts: + # if status.state in (GhostState.wander, GhostState.chase): + # tmp = ghost.src + # ghost.src = ghost.pos + # ghost.pos = tmp + # status.state = GhostState.flee + # if status.state is GhostState.flee: + # status.tick = 0 world.despawn_many(collided) @@ -898,12 +918,15 @@ def victory( mazes: Query[Entity, Maze], queue: CommandQueue, state: GameState, + config: Config, ) -> None: """Despawn maze and spawn new maze or init game over.""" for maze in mazes: queue.queue(hierarchy.despawn_with_children(maze)) - if state.level == 2: + if state.level == config.level_count - 1: + state.state = "Victory" queue.queue(lambda world: world.run_simple_system(game_over)) + queue.queue(lambda world: world.run_simple_system(spawn_game_over)) return state.level += 1 state.lives += 1 @@ -918,17 +941,21 @@ def level_is_lost( return config.level_timer < state.timer or state.lives <= 0 -def spawn_game_over(world: World, fonts: Assets[Font]) -> None: # FIXME +def spawn_game_over( + world: World, + fonts: Assets[Font], + state: GameState, +) -> None: """Display game over menu.""" root = world.spawn( tuple[WindowPositionned, GameOver], ( - WindowPositionned(Vector2(0.5, 0), Vector2(0, 100)), + WindowPositionned(Vector2(0.5, 0), Vector2(0, 200)), GameOver(), ), ) - font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:100") - text = "GAME OVER" + font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:200") + text = state.state world.spawn( tuple[ChildOf, ShouldRender, CoordBundle, TextDisplay], ( @@ -945,20 +972,31 @@ def spawn_game_over(world: World, fonts: Assets[Font]) -> None: # FIXME ( ChildOf(root), ShouldRender(), - font.center_text_for("", Vector2(0, 300)), - ComputedText(cast(System[str], print_name)), + font.center_text_for("Score = XXX", Vector2(0, 1200)), + ComputedText(cast(System[str], print_score)), TextDisplay("", font), ), ) - font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30") + text = "Enter your name:" + world.spawn( + tuple[ChildOf, ShouldRender, CoordBundle, TextDisplay], + ( + ChildOf(root), + ShouldRender(), + font.center_text_for(text, Vector2(0, 450)), + TextDisplay(text, font), + ), + ) + + font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:50") world.spawn( tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay], ( ChildOf(root), ShouldRender(), - font.center_text_for("Score = XXX", Vector2(0, 200)), - ComputedText(cast(System[str], print_score)), + font.center_text_for(" ", Vector2(0, 700)), + ComputedText(cast(System[str], print_name)), TextDisplay("", font), ), ) @@ -969,18 +1007,22 @@ def lose( gui: Query[Entity, GameGui], hierarchy: Hierarchy[tuple[()]], mazes: Query[Entity, Maze], + state: GameState, queue: CommandQueue, ) -> None: """Despawn mane and run the game over.""" world.despawn_many(list(gui)) for maze in mazes: queue.queue(hierarchy.despawn_with_children(maze)) + state.state = "Game Over" queue.queue(lambda world: world.add_simple_system(game_over)()) - world.run_simple_system(spawn_game_over) + queue.queue(lambda world: world.run_simple_system(spawn_game_over)) -def game_over(queue: CommandQueue) -> None: +def game_over(world: World, queue: CommandQueue) -> None: """Spawn resources when game is over.""" + if world.run_simple_system(cheat_mode_enabled): + world.run_simple_system(disable_cheat_mode) queue.queue(lambda world: world.spawn_resource(GameIsOver())) queue.queue(lambda world: world.spawn_resource(PlayerName())) @@ -1002,7 +1044,7 @@ def initial_pause_done(timer: InitialPauseTimer, config: Config) -> bool: def print_name(name: PlayerName) -> str: """Return a string with the player score.""" - return f"Enter your name: {name.name}" + return name.name def print_score(state: GameState) -> str: @@ -1021,6 +1063,11 @@ def print_timer(state: GameState, config: Config) -> str: return f"Time left: {time // 60}" +def print_level(state: GameState, config: Config) -> str: + """Return a string with the level timer.""" + return f"Level {state.level + 1} / {config.level_count}" + + def spawn_hud(world: World, fonts: Assets[Font]) -> None: """Draw in game HUD with lives, levels and timer informations.""" world.spawn_many( @@ -1034,7 +1081,7 @@ def spawn_hud(world: World, fonts: Assets[Font]) -> None: ], [ ( - WindowPositionned(Vector2(), Vector2(20, y * 100 + 20)), + WindowPositionned(Vector2(), Vector2(20, y * 60 + 20)), CoordBundle(depth=-20), ShouldRender(), ComputedText(cast(System[str], fn)), @@ -1046,6 +1093,7 @@ def spawn_hud(world: World, fonts: Assets[Font]) -> None: ) for y, fn in enumerate( [ + print_level, print_score, print_lives, print_timer, @@ -1059,13 +1107,14 @@ def start_game( world: World, hierarchy: Hierarchy[tuple[()]], menus: Query[Entity, MainMenu], + config: Config, ) -> None: """Close main menu and starting the game and HUD.""" for menu in menus: hierarchy.despawn_with_children(menu)(world) world.despawn_resource(SelectedButton) - world.spawn_resource(GameState()) + world.spawn_resource(GameState(lives=config.initial_lives)) world.run_simple_system(setup_new_maze) world.run_simple_system(spawn_hud) @@ -1140,6 +1189,33 @@ def spawn_cheat_info(world: World, fonts: Assets[Font]) -> None: CheatMode(), ), ) + text = """ + Cheat commands: +Press 'n to go to next level +Press 'c' to start chase +""" + world.spawn_many( + tuple[ + WindowPositionned, + ShouldRender, + TextDisplay, + CoordBundle, + CheatMode, + ], + [ + ( + WindowPositionned(Vector2(1, 1), Vector2(-400, -220 + i * 50)), + ShouldRender(), + TextDisplay( + s, + fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:22"), + ), + CoordBundle(depth=-20), + CheatMode(), + ) + for i, s in enumerate(text.split("\n")) + ], + ) def cheat_mode_enabled(cheat: Query[CheatMode]) -> bool: @@ -1399,6 +1475,15 @@ def test(events: WindowEvents) -> None: # FIXME print(key) +def cheat_input(world: World, events: WindowEvents) -> None: + """Get cheat inputs.""" + for key, state in events.keys_itr(): + if key == K_n and state: + world.run_simple_system(victory) + if key == K_c and state: + world.run_simple_system(chase) + + def add_score( world: World, name: PlayerName, @@ -1412,11 +1497,10 @@ def add_score( # FIXME scores.scores[name.name] = state.score world.run_simple_system(save_highscores) - # items = [item for item in menus] - # # world.despawn_many(items) for menu in menus: queue.queue(hierarchy.despawn_with_children(menu)) world.despawn_resource(GameIsOver) + world.despawn_resource(GameState) world.run_simple_system(setup_main_menu) @@ -1475,4 +1559,7 @@ def run_game() -> None: Systems(victory).run_if(level_is_finished, in_game), Systems(lose).run_if(level_is_lost), Systems(get_player_name).run_if(game_is_over), + Systems(cheat_input).run_if( + cheat_mode_enabled, initial_pause_done, in_game + ), ).run_main()