]> Untitled Git - axy/ft/pacman.git/commitdiff
add game menu
authorThomas Rimelen <trimelen@k1r1p12.42mulhouse.fr>
Wed, 14 Oct 2026 13:32:45 +0000 (15:32 +0200)
committerThomas Rimelen <trimelen@k1r1p12.42mulhouse.fr>
Wed, 14 Oct 2026 13:32:45 +0000 (15:32 +0200)
src/pacman/ecs/graphics.py
src/pacman/game/__init__.py

index 8cfb6e3287992505aa8d3b16ea6cad9464e116a2..8bdad796afb2868fd2b4f24cb85fcf8b062bc4cd 100644 (file)
@@ -22,6 +22,7 @@ from pacman.ecs.world import (
     Systems,
     World,
     WorldShouldExit,
+    System,
 )
 
 
@@ -29,6 +30,10 @@ class ShouldRender(Component):
     pass
 
 
+class StopAnimation(Component):
+    pass
+
+
 @dataclass
 class Window(Resource):
     surface: pygame.Surface
@@ -95,6 +100,13 @@ class Sprite2D(Component, Asset):
         return Sprite2D(self.surface)
 
 
+def key_just_pressed(key: int) -> System[bool]:
+    def inner(events: WindowEvents) -> bool:
+        return any(key == k and pressed for k, pressed in events.keys_itr())
+
+    return inner
+
+
 def window_positionned_insert_hook(
     elems: Query[tuple[Coord2D | None, CoordAbs2D | None]],
     window: Maybe[Window],
@@ -199,7 +211,9 @@ def insert_animation_sprites(
 
 
 def update_animations(
-    query: Query[tuple[Sprite2D, AnimationSet], ShouldRender],
+    query: Query[
+        tuple[Sprite2D, AnimationSet], tuple[ShouldRender, Not[StopAnimation]]
+    ],
 ) -> None:
     for sprite, anim in query:
         anim.tick()
index 7a604f7cc6c44f37d03d56611a99b47cd834738d..5b4cedf108546914e43f1045efe1867a5278b7cc 100644 (file)
@@ -1,3 +1,4 @@
+import pacman
 from collections.abc import Callable
 from dataclasses import dataclass
 from enum import Enum, auto
@@ -6,7 +7,7 @@ from typing import override
 
 from mazegenerator import MazeGenerator  # type: ignore[import-untyped]
 from pydantic import BaseModel
-from pygame import Vector2
+from pygame import Vector2, K_ESCAPE
 from pygame.math import clamp
 
 from pacman.ecs import minimal_plugins
@@ -25,6 +26,10 @@ from pacman.ecs.graphics import (
     Sprite2D,
     WindowPositionned,
     graphics_plugins,
+    WindowEvents,
+    key_just_pressed,
+    InputsSystems,
+    StopAnimation,
 )
 from pacman.ecs.gui import (
     Button,
@@ -36,7 +41,7 @@ from pacman.ecs.gui import (
 )
 from pacman.ecs.hierarchy import ChildOf, Children, Hierarchy
 from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
-from pacman.ecs.update import PostUpdate, Update
+from pacman.ecs.update import PostUpdate, Update, PreUpdate
 from pacman.ecs.world import (
     CommandQueue,
     Component,
@@ -736,8 +741,14 @@ def setup_new_maze(world: World) -> None:
         world.run_raw_system(sys)
 
 
-def in_game(itr: Query[Maze]) -> bool:
-    return len(list(itr)) != 0
+def maze_exists(itr: Query[Maze]) -> bool:
+    return next(itr.__iter__(), None) is not None
+
+
+def in_game(world: World) -> bool:
+    return world.run_raw_system(maze_exists) and not world.run_raw_system(
+        in_game_menu
+    )
 
 
 def level_is_finished(
@@ -905,6 +916,81 @@ def spawn_button(
     return inner
 
 
+def back_to_main_menu(
+    world: World,
+    hierarchy: Hierarchy[tuple[()]],
+    mazes: Query[Entity, Maze],
+    gui: Query[Entity, GameGui],
+    queue: CommandQueue,
+) -> None:
+    world.run_raw_system(close_game_menu)
+    world.despawn_resource(GameState)
+    world.despawn_many(list(gui))
+    for maze in mazes:
+        queue.queue(hierarchy.despawn_with_children(maze))
+    world.run_raw_system(setup_main_menu)
+
+
+def in_game_menu(menus: Query[GameMenu]) -> bool:
+    return next(menus.__iter__(), None) is not None
+
+
+def toggle_game_menu(world: World) -> None:
+    if world.run_raw_system(in_game_menu):
+        world.run_raw_system(close_game_menu)
+    else:
+        world.run_raw_system(setup_game_menu)
+
+
+def close_game_menu(
+    world: World,
+    hierarchy: Hierarchy[tuple[()]],
+    menus: Query[Entity, GameMenu],
+    animated: Query[Entity, Pacman | Ghost],
+) -> None:
+    world.remove_many(StopAnimation, list(animated))
+    for menu in menus:
+        hierarchy.despawn_with_children(menu)(world)
+    world.despawn_resource(SelectedButton)
+
+
+def setup_game_menu(
+    world: World, animated: Query[Entity, Pacman | Ghost]
+) -> None:
+    world.insert_many(StopAnimation, [(e, StopAnimation()) for e in animated])
+    selected = world.run_system(
+        spawn_button,
+        WindowPositionned(Vector2(0.5), Vector2(-150, -150)),
+        "Resume",
+        close_game_menu,
+    )
+
+    cheat_button = world.run_system(
+        spawn_button,
+        WindowPositionned(Vector2(0.5), Vector2(-150, -40)),
+        "Cheat mod",
+        quit_game,
+    )
+
+    quit_button = world.run_system(
+        spawn_button,
+        WindowPositionned(Vector2(0.5), Vector2(-150, 70)),
+        "Back to menu",
+        back_to_main_menu,
+    )
+
+    world.spawn(
+        tuple[GameMenu, Children, CoordBundle],
+        (
+            GameMenu(),
+            Children((selected, cheat_button, quit_button)),
+            CoordBundle(),
+        ),
+    )
+
+    world.spawn_resource(SelectedButton(selected))
+
+
 def setup_main_menu(world: World) -> None:
     selected = world.run_system(
         spawn_button,
@@ -949,6 +1035,10 @@ def run_game() -> None:
         .chain()
         .run_if(initial_pause_done, in_game)
         .in_set(Update),
+        Systems(toggle_game_menu)
+        .run_if(key_just_pressed(K_ESCAPE), maze_exists)
+        .in_set(PreUpdate)
+        .after(InputsSystems),
         Systems(update_pacman_anim, update_ghost_anim)
         .before(GraphicsSystems)
         .in_set(PostUpdate),