from pacman.ecs.update import PostUpdate, PreUpdate, Update
from pacman.ecs.world import Component, Entity, Query, Resource, Systems, World
+_DIR_OFFSETS = [(0, -1), (1, 0), (0, 1), (-1, 0)]
+
+
+class GhostVariant(Enum):
+ A = auto()
+ B = auto()
+ C = auto()
+ D = auto()
+
+ def compute_target_tile(
+ self,
+ player: tuple[int, int],
+ player_dir: int,
+ pos: tuple[int, int],
+ slf: "Ghost",
+ blinky_pos: tuple[int, int],
+ ) -> tuple[int, int]:
+ match self:
+ case GhostVariant.A:
+ return player
+ case GhostVariant.B:
+ if Vector2(player).distance_squared_to(pos) >= 16:
+ return player
+ return slf.home
+ case GhostVariant.C:
+ dx, dy = _DIR_OFFSETS[player_dir]
+ x, y = player
+ return (x + 2 * dx, y + 2 * dy)
+ case GhostVariant.D:
+ dx, dy = _DIR_OFFSETS[player_dir]
+ x, y = player
+ x_target, y_target = (x + 2 * dx, y + 2 * dy)
+ x_blinky, y_blinky = blinky_pos
+ return (x_target * 2 - x_blinky, y_target * 2 - y_blinky)
+
@dataclass
class Score(Resource):
flee: bool = False
+@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
+
+
+class SuperPacGum(Component):
+ pass
+
+
+@dataclass
+class Pacman(Component):
+ pass
+
+
+@dataclass
+class Ghost(Component):
+ variant: GhostVariant
+ home: tuple[int, int]
+
+
+@dataclass
+class TargetTile(Component):
+ pos: tuple[int, int]
+
+
+@dataclass
+class TilePos(Component):
+ pos: Entity
+
+
+@dataclass
+class InMotion(Component):
+ src: Entity
+ pos: Entity
+ direction: int
+
+
+@dataclass
+class MoveSpeed(Component):
+ pixels_per_frame: float
+
+
class Maze(Component):
def __init__(self, x: int, y: int) -> None:
self._cells: dict[tuple[int, int], Entity] = {}
self._y = y
-_DIR_OFFSETS = [(0, -1), (1, 0), (0, 1), (-1, 0)]
-
-
@dataclass
class Cell(Component):
n: int
return [(1 & (self.n >> i)) == 1 for i in range(4)]
-class PacGum(Component):
- pass
-
-
-class SuperPacGum(Component):
- pass
-
-
def create_maze(
world: World, assets: Assets[Sprite2D]
) -> Callable[[MazeGenerator], Entity]:
world.add_system(create_maze)(MazeGenerator((12, 12)))
-@dataclass
-class TargetTile(Component):
- pos: tuple[int, int]
-
-
-@dataclass
-class TilePos(Component):
- pos: Entity
-
-
-@dataclass
-class InMotion(Component):
- src: Entity
- pos: Entity
- direction: int
-
-
-@dataclass
-class MoveSpeed(Component):
- pixels_per_frame: float
-
-
def move_in_motion(
entities: Query[tuple[InMotion, Coord2D, MoveSpeed]],
positions: Query[Coord2D],
entities: Query[tuple[TargetTile, InMotion, Coord2D]],
cells: Query[tuple[Cell, Coord2D]],
maze_q: Query[Maze],
- status: GameStatus
+ status: GameStatus,
) -> None:
if (maze := maze_q.single()) is None:
return
motion.direction = nxt_dir
-@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]]
-
-
-@dataclass
-class Pacman(Component):
- pass
-
-
def spawn_pacman(
world: World, assets: Assets[Sprite2D], maze_q: Query[Maze]
) -> None:
TilePos,
],
(
- Coord2D(0, 0, -10),
+ Coord2D(3 * 64, 3 * 64, -10),
CoordAbs2D(),
AnimationSet(
[
),
Pacman(),
ShouldRender(),
- InMotion(maze._cells[(0, 0)], maze._cells[(0, 0)], 1),
+ InMotion(maze._cells[(3, 3)], maze._cells[(3, 3)], 1),
MoveSpeed(3.0),
- TilePos(maze._cells[(0, 0)]),
+ TilePos(maze._cells[(3, 3)]),
),
)
-class GhostVariant(Enum):
- A = auto()
- B = auto()
- C = auto()
- D = auto()
-
- def compute_target_tile(
- self,
- player: tuple[int, int],
- player_dir: int,
- pos: tuple[int, int],
- slf: "Ghost",
- blinky_pos: tuple[int, int],
- ) -> tuple[int, int]:
- match self:
- case GhostVariant.A:
- return player
- case GhostVariant.B:
- if Vector2(player).distance_squared_to(pos) >= 16:
- return player
- return slf.home
- case GhostVariant.C:
- dx, dy = _DIR_OFFSETS[player_dir]
- x, y = player
- return (x + 2 * dx, y + 2 * dy)
- case GhostVariant.D:
- dx, dy = _DIR_OFFSETS[player_dir]
- x, y = player
- x_target, y_target = (x + 2 * dx, y + 2 * dy)
- x_blinky, y_blinky = blinky_pos
- return (x_target * 2 - x_blinky, y_target * 2 - y_blinky)
-
-
-@dataclass
-class Ghost(Component):
- variant: GhostVariant
- home: tuple[int, int]
-
-
def spawn_ghosts(
world: World, assets: Assets[Sprite2D], maze_q: Query[Maze]
) -> None:
) -> None:
collided = [
pacgum
- for coord, pacgum in pacgums
- for pos in pacmans
- if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100
+ for pacgum_pos, pacgum in pacgums
+ for pacman_pos in pacmans
+ if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) < 100
]
score.score += len(collided * 10)
if len(collided) > 0:
world.despawn_many(collided)
+def collide_ghost(
+ pacmans: Query[CoordAbs2D, Pacman], ghosts: Query[CoordAbs2D, Ghost]
+) -> None:
+ for pacman_pos in pacmans:
+ for ghost_pos in ghosts:
+ if (
+ pacman_pos._vec.xy.distance_squared_to(ghost_pos._vec.xy)
+ < 3600
+ ):
+ pass
+
+
def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool:
return next(iter(pacgums), None) is None
def update_ghost_anim(
- pacman_q: Query[tuple[InMotion, AnimationSet], Ghost],
+ ghost_q: Query[tuple[InMotion, AnimationSet], Ghost],
) -> None:
- for motion, anim in pacman_q:
+ for motion, anim in ghost_q:
anim.idx = motion.direction
Systems(update_pacman_anim, update_ghost_anim)
.before(GraphicsSystems)
.in_set(PostUpdate),
- Systems(collect_pacgum, collect_superpacgum).after(PropagateTransform),
+ Systems(collect_pacgum, collect_superpacgum, collide_ghost).after(
+ PropagateTransform
+ ),
Systems(victory).run_if(level_is_finished),
).run_main()