from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum, auto
-from random import random
+from random import random, shuffle
from time import time
from mazegenerator import MazeGenerator # type: ignore[import-untyped]
from pacman.ecs import minimal_plugins
from pacman.ecs.assets import Assets
-from pacman.ecs.coord import Coord2D, CoordAbs2D
+from pacman.ecs.coord import Coord2D, CoordAbs2D, PropagateTransform
from pacman.ecs.graphics import (
AnimationSet,
GraphicsSystems,
from pacman.ecs.world import Component, Entity, Query, Resource, Systems, World
+@dataclass
+class Score(Resource):
+ score: int = 0
+
+
+@dataclass
+class GameStatus(Resource):
+ tick: int = 0
+ cycle_chase: int = 60 * 10
+ cycle_wander: int = 60 * 10
+ chase: bool = False
+ flee: bool = False
+
+
class Maze(Component):
def __init__(self, x: int, y: int) -> None:
self._cells: dict[tuple[int, int], Entity] = {}
def setup_maze(world: World) -> None:
- world.add_system(create_maze)(MazeGenerator())
+ world.add_system(create_maze)(MazeGenerator((12, 12)))
@dataclass
entities: Query[tuple[TargetTile, InMotion, Coord2D]],
cells: Query[tuple[Cell, Coord2D]],
maze_q: Query[Maze],
+ status: GameStatus
) -> None:
if (maze := maze_q.single()) is None:
return
)
),
)
+ if status.flee:
+ lst = candidates[:-1]
+ shuffle(lst)
+ candidates = lst + [candidates[-1]]
nxt, nxt_dir = candidates[0]
motion.src = motion.pos
motion.pos = nxt
case GhostVariant.A:
return player
case GhostVariant.B:
- if Vector2(player).distance_squared_to(pos) >= 64:
+ if Vector2(player).distance_squared_to(pos) >= 16:
return player
return slf.home
case GhostVariant.C:
x, y = player
x_target, y_target = (x + 2 * dx, y + 2 * dy)
x_blinky, y_blinky = blinky_pos
- return ((x_target - x_blinky) * 2, (y_target - y_blinky) * 2)
+ return (x_target * 2 - x_blinky, y_target * 2 - y_blinky)
@dataclass
motion.direction = direction
-@dataclass
-class ChaseStatus(Resource):
- tick: int = 0
- cycle_chase: int = 60 * 10
- cycle_wander: int = 60 * 10
- chase: bool = False
+def collect_pacgum(
+ world: World,
+ pacgums: Query[tuple[CoordAbs2D, Entity], PacGum],
+ pacmans: Query[CoordAbs2D, Pacman],
+ score: Score,
+) -> None:
+ collided = [
+ pacgum
+ for coord, pacgum in pacgums
+ for pos in pacmans
+ if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100
+ ]
+ score.score += len(collided)
+ world.despawn_many(collided)
+
+
+def collect_superpacgum(
+ world: World,
+ pacgums: Query[tuple[CoordAbs2D, Entity], SuperPacGum],
+ pacmans: Query[CoordAbs2D, Pacman],
+ ghosts: Query[InMotion, Ghost],
+ score: Score,
+ status: GameStatus,
+) -> None:
+ collided = [
+ pacgum
+ for coord, pacgum in pacgums
+ for pos in pacmans
+ if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100
+ ]
+ score.score += len(collided * 10)
+ if len(collided) > 0:
+ status.flee = True
+ status.tick = 60 * 8
+ for ghost in ghosts:
+ tmp = ghost.src
+ ghost.src = ghost.pos
+ ghost.pos = tmp
+ world.despawn_many(collided)
+
+
+def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool:
+ return next(iter(pacgums), None) is None
def update_ghost_targets(
ghosts: Query[tuple[Ghost, TilePos, TargetTile]],
pacman_q: Query[tuple[InMotion, TilePos], Pacman],
cells: Query[Cell],
- chase: ChaseStatus,
+ chase: GameStatus,
) -> None:
player = pacman_q.single()
if player is None:
)
-def update_chase_status(status: ChaseStatus) -> None:
- status.tick += 1
- status.chase = status.tick > status.cycle_wander
- if status.tick < (status.cycle_wander + status.cycle_chase):
- return
- status.tick = 0
- if status.cycle_wander > 60 * 4:
- status.cycle_wander -= 60
- if status.cycle_chase > 60 * 7:
- status.cycle_chase -= 60
+def update_chase_status(status: GameStatus) -> None:
+ if status.flee:
+ if status.tick == 0:
+ status.flee = False
+ return
+ status.tick -= 1
+ else:
+ status.tick += 1
+ status.chase = status.tick > status.cycle_wander
+ if status.tick < (status.cycle_wander + status.cycle_chase):
+ return
+
+ status.tick = 0
+ if status.cycle_wander > 60 * 4:
+ status.cycle_wander -= 60
+ if status.cycle_chase > 60 * 7:
+ status.cycle_chase -= 60
def update_pacman_anim(
def setup_resources(world: World) -> None:
world.spawn_resource(Inputs({}))
- world.spawn_resource(ChaseStatus())
+ world.spawn_resource(GameStatus())
+ world.spawn_resource(Score())
def update_inputs(inputs: Inputs, events: WindowEvents) -> None:
del inputs.inputs[key]
+def victory() -> None:
+ return
+
+
def run_game() -> None:
World().with_plugins(graphics_plugins, minimal_plugins).with_systems(
StartupSchedule,
Systems(update_pacman_anim, update_ghost_anim)
.before(GraphicsSystems)
.in_set(PostUpdate),
+ Systems(collect_pacgum, collect_superpacgum).after(PropagateTransform),
+ Systems(victory).run_if(level_is_finished),
).run_main()