pass
+class GhostState(Enum):
+ chase = auto()
+ wander = auto()
+ flee = auto()
+ dead = auto()
+ respawning = auto()
+
+
@dataclass
class GhostStatus(Component):
tick: int = 0
- cycle_chase: int = 60 * 10
cycle_wander: int = 60 * 10
- chase: bool = False
- flee: bool = False
+ state: GhostState = GhostState.wander
@dataclass
class Ghost(Component):
variant: GhostVariant
home: tuple[int, int]
+ home_entity: Entity
@dataclass
if (maze := maze_q.single()) is None:
return
for target, motion, pos, status in entities:
+ if status.state in (GhostState.dead, GhostState.respawning):
+ continue
cell, cell_pos = cells[motion.pos]
if cell_pos.xy.distance_squared_to(pos.xy) > 0.01:
continue
)
),
)
- if status.flee:
+ if status.state is GhostState.flee:
lst = candidates[:-1]
shuffle(lst)
candidates = lst + [candidates[-1]]
)
]
for direction in "URDL"
- ] + [[assets.load("./asset/enemy/ghost_blue.png")]],
+ ]
+ + [[assets.load("./asset/enemy/ghost_blue.png")]],
6,
),
ShouldRender(),
InMotion(maze._cells[pos], maze._cells[pos], 1),
MoveSpeed(2.5),
TilePos(maze._cells[pos]),
- Ghost(variant, pos),
+ Ghost(variant, pos, maze._cells[pos]),
TargetTile(pos),
GhostStatus(),
)
pacgum
for coord, pacgum in pacgums
for pos in pacmans
- if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100
+ if pos._vec.xy.distance_squared_to(coord._vec.xy) <= 256
]
score.score += len(collided)
world.despawn_many(collided)
pacgum
for pacgum_pos, pacgum in pacgums
for pacman_pos in pacmans
- if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) < 100
+ if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) <= 256
]
score.score += len(collided * 10)
- if len(collided) > 0:
- for ghost, status in ghosts:
+ if len(collided) == 0:
+ return
+ for ghost, status in ghosts:
+ if status.state in (GhostState.wander, GhostState.chase):
tmp = ghost.src
ghost.src = ghost.pos
ghost.pos = tmp
- status.flee = True
- status.tick = 60 * 8
+ status.state = GhostState.flee
+ if status.state is GhostState.flee:
+ status.tick = 0
world.despawn_many(collided)
def collide_ghost(
- pacmans: Query[CoordAbs2D, Pacman], ghosts: Query[CoordAbs2D, Ghost]
+ pacmans: Query[CoordAbs2D, Pacman],
+ ghosts: Query[
+ tuple[CoordAbs2D, GhostStatus, Ghost, InMotion, Coord2D], Ghost
+ ],
) -> None:
for pacman_pos in pacmans:
- for ghost_pos in ghosts:
+ for ghost_pos, status, ghost, motion, pos in ghosts:
if (
pacman_pos._vec.xy.distance_squared_to(ghost_pos._vec.xy)
- < 3600
+ > 3600
):
- pass
+ continue
+ match status.state:
+ case GhostState.wander | GhostState.chase:
+ # TODO: kill pacman
+ print("Pacman hit")
+ case GhostState.flee:
+ status.state = GhostState.dead
+ status.tick = 0
+ motion.src = ghost.home_entity
+ motion.pos = ghost.home_entity
+ pos.xy = Vector2(*ghost.home) * 64
+ case _:
+ pass
def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool:
if ghost.variant == GhostVariant.A
)
for ghost, pos, target, status in ghosts:
- if not status.chase:
- target.pos = ghost.home
- continue
- target.pos = ghost.variant.compute_target_tile(
- cells[player_pos.pos].grid_pos,
- player_motion.direction,
- cells[pos.pos].grid_pos,
- ghost,
- cells[blinky_pos].grid_pos,
- )
+ match status.state:
+ case GhostState.wander:
+ target.pos = ghost.home
+ case GhostState.chase:
+ target.pos = ghost.variant.compute_target_tile(
+ cells[player_pos.pos].grid_pos,
+ player_motion.direction,
+ cells[pos.pos].grid_pos,
+ ghost,
+ cells[blinky_pos].grid_pos,
+ )
+ case _:
+ pass
def update_chase_status(status_q: Query[GhostStatus]) -> None:
for status in status_q:
- if status.flee:
- if status.tick == 0:
- status.flee = False
- continue
- status.tick -= 1
- else:
- status.tick += 1
- status.chase = status.tick > status.cycle_wander
- if status.tick < (status.cycle_wander + status.cycle_chase):
- continue
-
+ target_time: int
+ next_state: GhostState
+ cycle_wander_next: int = status.cycle_wander
+ match status.state:
+ case GhostState.flee:
+ target_time, next_state = (8 * 60, GhostState.wander)
+ case GhostState.chase:
+ if cycle_wander_next > 60 * 4:
+ cycle_wander_next -= 120
+ target_time, next_state = (
+ 60 * 10,
+ GhostState.wander,
+ )
+ case GhostState.wander:
+ target_time, next_state = (
+ status.cycle_wander,
+ GhostState.chase,
+ )
+ case GhostState.dead:
+ target_time, next_state = (5 * 60, GhostState.respawning)
+ case GhostState.respawning:
+ target_time, next_state = (3 * 60, GhostState.wander)
+ status.tick += 1
+ if target_time <= status.tick:
+ status.state = next_state
status.tick = 0
- if status.cycle_wander > 60 * 4:
- status.cycle_wander -= 60
- if status.cycle_chase > 60 * 7:
- status.cycle_chase -= 60
+ status.cycle_wander = cycle_wander_next
def update_ghost_speed(ghost: Query[tuple[MoveSpeed, GhostStatus]]) -> None:
for speed, status in ghost:
- speed.pixels_per_frame = 2.5
- if status.flee:
- speed.pixels_per_frame = 1.5
-
+ match status.state:
+ case GhostState.chase | GhostState.wander:
+ speed.pixels_per_frame = 2.5
+ case GhostState.flee:
+ speed.pixels_per_frame = 1.5
def update_pacman_anim(
ghost_q: Query[tuple[InMotion, AnimationSet, GhostStatus], Ghost],
) -> None:
for motion, anim, status in ghost_q:
- anim.idx = motion.direction if not status.flee else 4
+ match status.state:
+ case GhostState.chase | GhostState.wander:
+ anim.idx = motion.direction
+ case GhostState.flee:
+ anim.idx = 4
+ case _:
+ pass
def setup_resources(world: World) -> None:
Systems(update_pacman_anim, update_ghost_anim)
.before(GraphicsSystems)
.in_set(PostUpdate),
- Systems(collect_pacgum, collect_superpacgum, collide_ghost).after(
- PropagateTransform
- ),
+ Systems(
+ collect_pacgum,
+ collect_superpacgum,
+ Systems(collide_ghost).before(update_ghost_anim),
+ )
+ .after(PropagateTransform)
+ .before(GraphicsSystems),
Systems(victory).run_if(level_is_finished),
).run_main()