from collections.abc import Iterator
from dataclasses import dataclass
+from time import time
from typing import cast, override
import pygame
+from pygame import K_RETURN, K_a, K_d, K_s, K_w
from pacman.ecs.assets import Asset
from pacman.ecs.coord import CoordAbs2D, PropagateTransform
clock: pygame.time.Clock
+dir_lookup = {K_w: 0, K_d: 1, K_s: 2, K_a: 3}
+
+
@dataclass
class WindowEvents(Resource):
events: list[pygame.event.Event]
)
-def init_window(world: World) -> None:
- pygame.init()
- world.spawn_resource(
- Window(pygame.display.set_mode((1600, 1200)), pygame.time.Clock()),
- )
- world.spawn_resource(WindowEvents([]))
+@dataclass
+class Inputs(Resource):
+ inputs: dict[int, float]
+
+ def get_dir(self) -> int | None:
+ res = sorted(
+ ((k, v) for k, v in self.inputs.items() if k in dir_lookup),
+ key=lambda e: e[1],
+ reverse=True,
+ )
+ if len(res) == 0:
+ return None
+ return dir_lookup[res[0][0]]
+
+ def activate(self) -> bool:
+ return K_RETURN in self.inputs
@dataclass
return Sprite2D(self.surface)
+def init_graphics(world: World) -> None:
+ pygame.init()
+ world.spawn_resource(
+ Window(pygame.display.set_mode((1600, 1200)), pygame.time.Clock()),
+ )
+ world.spawn_resource(WindowEvents([]))
+ world.spawn_resource(Inputs({}))
+
+
def poll_events(events: WindowEvents) -> None:
events.events = pygame.event.get()
sprite.surface = anim.curr_frame.surface
+def update_inputs(inputs: Inputs, events: WindowEvents) -> None:
+ for key, state in events.keys_itr():
+ if state:
+ inputs.inputs[key] = time()
+ else:
+ del inputs.inputs[key]
+
+
GraphicsSystems = SystemSet("graphics-systems")
InputsSystems = SystemSet("inputs-systems")
def graphics_plugins(world: World) -> None:
from pacman.ecs.gui import gui_plugins
- world.with_systems(StartupSchedule, init_window).with_systems(
+ world.with_systems(StartupSchedule, init_graphics).with_systems(
UpdateSchedule,
Systems(
insert_animation_sprites,
)
.chain()
.in_set(PostUpdate, GraphicsSystems),
- Systems(poll_events, close_on_close)
+ Systems(poll_events, close_on_close, update_inputs)
.chain()
.in_set(PreUpdate, InputsSystems),
).with_plugins(gui_plugins)
+from collections.abc import Callable
from dataclasses import dataclass, field
+from enum import Enum, auto
from typing import override
import pygame
from pygame import Color
from pacman.ecs.assets import Asset
-from pacman.ecs.graphics import GraphicsSystems, Sprite2D
+from pacman.ecs.coord import CoordAbs2D, PropagateTransform
+from pacman.ecs.graphics import (
+ AnimationSet,
+ GraphicsSystems,
+ Inputs,
+ Sprite2D,
+ WindowEvents,
+ dir_lookup,
+)
from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
from pacman.ecs.update import PostUpdate
from pacman.ecs.world import (
Component,
Entity,
Query,
+ Resource,
System,
Systems,
World,
cb: System[str]
+class ButtonState(Enum):
+ unselected = auto()
+ selected = auto()
+ pressed = auto()
+
+
+@dataclass
+class Button(Component):
+ cb: System[None]
+ state: ButtonState = ButtonState.unselected
+
+
+@dataclass
+class SelectedButton(Resource):
+ button: Entity
+
+
+def update_selected_button(
+ buttons: Query[tuple[Entity, CoordAbs2D], Button],
+ selected: SelectedButton,
+ events: WindowEvents,
+) -> None:
+ for key, active in events.keys_itr():
+ if not active:
+ continue
+ if (direction := dir_lookup.get(key)) is None:
+ continue
+ extract: Callable[[CoordAbs2D], float] = (
+ (lambda e: e._vec.x)
+ if direction % 2 == 1
+ else (lambda e: e._vec.y)
+ )
+ curr_select = buttons[selected.button][1]
+ candidates = sorted(
+ (
+ (ent, coord)
+ for ent, coord in buttons
+ if ent is selected.button
+ or extract(curr_select) != extract(coord)
+ ),
+ key=lambda e: extract(e[1]),
+ reverse=direction % 3 != 0,
+ )
+ i = candidates.index((selected.button, curr_select))
+ selected.button = candidates[i - 1][0]
+
+
+def update_button_state(
+ buttons: Query[tuple[Entity, Button]],
+ selected: SelectedButton,
+ inputs: Inputs,
+ world: World,
+) -> None:
+ cbs = []
+ for entity, button in buttons:
+ if entity is not selected.button:
+ button.state = ButtonState.unselected
+ continue
+ if button.state == ButtonState.selected and inputs.activate():
+ button.state = ButtonState.pressed
+ cbs.append(button.cb)
+ elif not inputs.activate():
+ button.state = ButtonState.selected
+ for cb in cbs:
+ world.run_raw_system(cb)
+
+
+def update_button_texture(buttons: Query[tuple[Button, AnimationSet]]) -> None:
+ for button, anim in buttons:
+ anim.idx = button.state.value - 1
+
+
def update_computed_text(
elems: Query[tuple[TextDisplay, ComputedText]],
world: World,
def gui_plugins(world: World) -> None:
world.with_systems(StartupSchedule, setup_gui_resources).with_systems(
UpdateSchedule,
- Systems(update_computed_text, update_text_sprites)
+ Systems(
+ update_selected_button,
+ update_button_state,
+ update_button_texture,
+ update_computed_text,
+ update_text_sprites,
+ )
.chain()
.in_set(PostUpdate)
- .before(GraphicsSystems),
+ .before(GraphicsSystems)
+ .after(PropagateTransform),
)
from dataclasses import dataclass
from enum import Enum, auto
from random import random, shuffle
-from time import time
from typing import override
from mazegenerator import MazeGenerator # type: ignore[import-untyped]
from pydantic import BaseModel
-from pygame import K_a, K_d, K_s, K_w, Vector2
+from pygame import Vector2
from pygame.math import clamp
from pacman.ecs import minimal_plugins
from pacman.ecs.graphics import (
AnimationSet,
GraphicsSystems,
- InputsSystems,
+ Inputs,
ShouldRender,
Sprite2D,
- WindowEvents,
graphics_plugins,
)
-from pacman.ecs.gui import ComputedText, Font, TextDisplay
+from pacman.ecs.gui import (
+ Button,
+ ComputedText,
+ Font,
+ SelectedButton,
+ TextDisplay,
+)
from pacman.ecs.hierarchy import ChildOf, Hierarchy
from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
-from pacman.ecs.update import PostUpdate, PreUpdate, Update
+from pacman.ecs.update import PostUpdate, Update
from pacman.ecs.world import (
CommandQueue,
Component,
lives: int = 3
-@dataclass
-class Inputs(Resource):
- inputs: dict[int, float]
-
- def get_dir(self) -> int | None:
- lookup = {K_w: 0, K_d: 1, K_s: 2, K_a: 3}
- res = sorted(
- ((k, v) for k, v in self.inputs.items() if k in lookup),
- key=lambda e: e[1],
- reverse=True,
- )
- if len(res) == 0:
- return None
- return lookup[res[0][0]]
-
-
class PacGum(Component):
pass
return inner
-
-
-
def spawn_superpacgums(
world: World, mazes: Query[Maze], assets: Assets[Sprite2D]
) -> None:
world.run_raw_system(sys)
-def update_inputs(inputs: Inputs, events: WindowEvents) -> None:
- for key, state in events.keys_itr():
- if state:
- inputs.inputs[key] = time()
- else:
- del inputs.inputs[key]
-
-
def level_is_finished(
pacgums: Query[SuperPacGum | PacGum], _: Not[GameIsOver]
) -> bool:
def setup_resources(world: World) -> None:
- world.spawn_resource(Inputs({}))
world.spawn_resource(GameState())
world.spawn_resource(Config())
def print_timer(state: GameState, config: Config) -> str:
- time = config.level_timer - state.timer
+ time = config.level_timer - state.timer
return f"{time // 60}"
+def print_a() -> None:
+ print("a")
+
+
+def print_b() -> None:
+ print("b")
+
+
+def print_c() -> None:
+ print("c")
+
+
def setup_text(world: World, fonts: Assets[Font]) -> None:
- world.spawn_many(
- tuple[CoordBundle, ShouldRender, ComputedText, TextDisplay],
+ spawned = world.spawn_many(
+ tuple[CoordBundle, ShouldRender, ComputedText, TextDisplay, Button],
[
(
CoordBundle(depth=-20),
"",
fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
),
+ Button(print_a),
),
(
CoordBundle(y=100, depth=-20),
"",
fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
),
+ Button(print_b),
),
(
CoordBundle(y=200, depth=-20),
"",
fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
),
+ Button(print_c),
),
],
)
+ world.spawn_resource(SelectedButton(spawned[0]))
def run_game() -> None:
.chain()
.run_if(initial_pause_done)
.in_set(Update),
- Systems(update_inputs).in_set(PreUpdate).after(InputsSystems),
Systems(update_pacman_anim, update_ghost_anim)
.before(GraphicsSystems)
.in_set(PostUpdate),