import math
from collections.abc import Callable
+from dataclasses import dataclass
from pathlib import Path
+from random import random
import pygame
from mazegenerator import MazeGenerator
-from pygame import Vector2, Vector3
+from pygame import K_a, K_d, K_s, K_w, Vector2, Vector3
from pacman.ecs import minimal_plugins
from pacman.ecs.assets import Assets
from pacman.ecs.coord import Coord2D, CoordAbs2D
-from pacman.ecs.graphics import ShouldRender, Sprite2D, graphics_plugins
+from pacman.ecs.graphics import (
+ GraphicsSystems,
+ ShouldRender,
+ Sprite2D,
+ WindowEvents,
+ graphics_plugins,
+)
from pacman.ecs.hierarchy import ChildOf
from pacman.ecs.schedule import StartupSchedule, UpdateSchedule
-from pacman.ecs.world import Component, Entity, Query, World
+from pacman.ecs.update import PostUpdate, PreUpdate
+from pacman.ecs.world import Component, Entity, Not, Query, Systems, World
+from pacman.utils.panic import panic
class Maze(Component):
+ def __init__(self, x: int, y: int) -> None:
+ self._cells: dict[tuple[int, int], Entity] = {}
+ self._x = x
+ self._y = y
+
+
+@dataclass
+class Cell(Component):
+ n: int
+ pass
+
+
+class PacGum(Component):
+ pass
+
+
+class SuperPacGum(Component):
pass
world: World, assets: Assets[Sprite2D]
) -> Callable[[MazeGenerator], Entity]:
def inner(maze: MazeGenerator) -> Entity:
+ root_maze = Maze(len(maze.maze[0]), len(maze.maze))
root = world.spawn(
- tuple[Maze, Coord2D, CoordAbs2D], (Maze(), Coord2D(), CoordAbs2D())
+ tuple[Maze, Coord2D, CoordAbs2D],
+ (root_maze, Coord2D(), CoordAbs2D()),
)
- cell_paths = [
- [f"./asset/walls/{cell}.png" for cell in line]
- for line in maze.maze
- ]
cell_dims = Vector3(64, 64, 0)
- cells = [
- (
+ cells = {
+ (x, y): (
ChildOf(root),
Coord2D(cell_dims.elementwise() * Vector3(x, y, 0)),
- CoordAbs2D(),
- assets.load(cell),
+ Cell(cell),
ShouldRender(),
+ CoordAbs2D(),
+ assets.load(f"./asset/walls/{cell}.png"),
)
- for y, line in enumerate(cell_paths)
+ for y, line in enumerate(maze.maze)
for x, cell in enumerate(line)
- ]
- world.spawn_many(
- tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender], cells
+ }
+ spawned_cells = world.spawn_many(
+ tuple[ChildOf, Coord2D, Cell, ShouldRender, CoordAbs2D, Sprite2D],
+ list(cells.values()),
)
+ root_maze._cells = dict(zip(cells.keys(), spawned_cells, strict=True))
return root
return inner
+def spawn_superpacgums(
+ world: World, mazes: Query[Maze], assets: Assets[Sprite2D]
+) -> None:
+ pacgums = []
+ for maze in mazes:
+ for pos in [
+ (0, 0),
+ (0, maze._y - 1),
+ (maze._x - 1, 0),
+ (maze._x - 1, maze._y - 1),
+ ]:
+ cell = maze._cells[pos]
+ pacgums.append(
+ (
+ ChildOf(cell),
+ Coord2D(0, 0, -1),
+ CoordAbs2D(),
+ assets.load("./asset/misc/superpacgum.png"),
+ ShouldRender(),
+ SuperPacGum(),
+ )
+ )
+ world.spawn_many(
+ tuple[
+ ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, SuperPacGum
+ ],
+ pacgums,
+ )
+
+
+def spawn_pacgums(
+ world: World,
+ cells: Query[tuple[Entity, Cell]],
+ superpacgums: Query[ChildOf, SuperPacGum],
+ assets: Assets[Sprite2D],
+) -> None:
+ ignore = {e.parent for e in superpacgums}
+ pacgums = []
+ for ent, cell in cells:
+ if ent in ignore:
+ continue
+ if cell.n == 15:
+ continue
+ if random() > 0.7:
+ continue
+ pacgums.append(
+ (
+ ChildOf(ent),
+ Coord2D(0, 0, -1),
+ CoordAbs2D(),
+ assets.load("./asset/misc/pacgum.png"),
+ ShouldRender(),
+ PacGum(),
+ )
+ )
+ world.spawn_many(
+ tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, PacGum],
+ pacgums,
+ )
+
+
def setup_maze(world: World) -> None:
world.add_system(create_maze)(MazeGenerator())
-def move_maze(mazes: Query[Coord2D, Maze]) -> None:
- tick = pygame.time.get_ticks()
- for coord in mazes:
- n = 400
- coord.y = n + n * math.sin(tick / 100)
+class PlayerControlled(Component):
+ def __init__(self) -> None:
+ self._dir: int = 0
+
+ @property
+ def direction(self) -> Vector2:
+ match self._dir:
+ case 0:
+ return Vector2(0, -1)
+ case 1:
+ return Vector2(1, 0)
+ case 2:
+ return Vector2(0, 1)
+ case 3:
+ return Vector2(-1, 0)
+ case _:
+ raise Exception()
+
+
+def update_player_controlled(
+ entities: Query[PlayerControlled], events: WindowEvents
+) -> None:
+ vec = None
+ for key in events.keys():
+ if key == K_w:
+ vec = 0
+ if key == K_d:
+ vec = 1
+ if key == K_s:
+ vec = 2
+ if key == K_a:
+ vec = 3
+ if vec is not None:
+ for ctl in entities:
+ ctl._dir = vec
+
+
+def move_player_controlled(
+ entities: Query[tuple[Coord2D, PlayerControlled]],
+) -> None:
+ for pos, ctl in entities:
+ direction = ctl.direction * 4
+ pos += Vector3(direction.x, direction.y, 0)
+
+
+@dataclass
+class AnimationSet(Component):
+ anims: list[list[Sprite2D]]
+ ticks_per_frame: int
+ idx: int = 0
+ frame: int = 0
+ curr_tick: int = 0
+
+ @property
+ def curr_frame(self) -> Sprite2D:
+ return self.anims[self.idx][self.frame]
+
+ def tick(self) -> None:
+ self.curr_tick += 1
+ self.frame += self.curr_tick // self.ticks_per_frame
+ self.curr_tick %= self.ticks_per_frame
+ self.frame %= len(self.anims[self.idx])
+
+
+def insert_animation_sprites(
+ world: World, query: Query[tuple[Entity, AnimationSet], Not[Sprite2D]]
+) -> None:
+ inserts = []
+ for ent, anim in query:
+ inserts.append((ent, anim.curr_frame))
+ world.insert_many(Sprite2D, inserts)
+
+
+def update_animations(
+ query: Query[tuple[Sprite2D, AnimationSet], ShouldRender],
+) -> None:
+ for sprite, anim in query:
+ sprite.surface = anim.curr_frame.surface
+ anim.tick()
+
+
+class Pacman(Component):
+ pass
+
+
+def spawn_pacman(world: World, assets: Assets[Sprite2D]) -> None:
+ world.spawn(
+ tuple[
+ Coord2D,
+ CoordAbs2D,
+ AnimationSet,
+ Pacman,
+ PlayerControlled,
+ ShouldRender,
+ ],
+ (
+ Coord2D(0, 0, -10),
+ CoordAbs2D(),
+ AnimationSet(
+ [
+ [
+ assets.load(
+ f"./asset/player/player_{direction}{frame}.png"
+ )
+ for frame in "ABCB"
+ ]
+ for direction in "URDL"
+ ],
+ 4,
+ ),
+ Pacman(),
+ PlayerControlled(),
+ ShouldRender(),
+ ),
+ )
+
+
+def update_pacman_direction(
+ query: Query[tuple[AnimationSet, PlayerControlled], Pacman],
+) -> None:
+ for anims, ctl in query:
+ anims.idx = ctl._dir
def run_game() -> None:
World().with_plugins(graphics_plugins, minimal_plugins).with_systems(
- StartupSchedule, setup_maze
+ StartupSchedule,
+ Systems(
+ setup_maze, spawn_superpacgums, spawn_pacgums, spawn_pacman
+ ).chain(),
+ ).with_systems(
+ UpdateSchedule,
+ Systems(update_player_controlled, move_player_controlled)
+ .chain()
+ .in_set(PreUpdate),
+ Systems(
+ insert_animation_sprites,
+ update_pacman_direction,
+ update_animations,
+ )
+ .chain()
+ .before(GraphicsSystems)
+ .in_set(PostUpdate),
).run_main()