@dataclass
class PlayerName(Resource):
+ """Resource for player name."""
+
name: str = ""
class GameOver(Component):
- """Component for game menu items.
- """
+ """Component for game menu items."""
pass
@dataclass
class Ghost(Component):
- """Component for ghost items storing the ghost variant and the home
- coordinates and entity.
- """
+ """Component for ghost."""
variant: GhostVariant
home: tuple[int, int]
@dataclass
class InMotion(Component):
- """Component for pacman or ghost movement storing the position, the provenance
- and the direction.
- """
+ """Component for pacman or ghost movement."""
src: Entity
pos: Entity
@dataclass
class MoveSpeed(Component):
- """Component for movement storing the movement speed by pixels per frames."""
+ """Component storing the movement speed by pixels per frames."""
pixels_per_frame: float
class Maze(Component):
- """Component for maze storing the x and y dimension and the cells in a
- dictionnary (coordonate entity).
- """
+ """Component for maze storing dimension and cells."""
def __init__(self, x: int, y: int) -> None:
+ """Init the maze with size."""
self._cells: dict[tuple[int, int], Entity] = {}
self._x = x
self._y = y
@dataclass
class Cell(Component):
- """Component for the cells of the maze storing the value and the position."""
+ """Component for the cells of the maze."""
n: int
grid_pos: tuple[int, int]
def load_config(world: World) -> None:
- """Load the configuration json file given in first argument and spawn the
- Config resource.
- """
+ """Load the configuration json."""
if len(sys.argv) != 2:
raise Exception(
f"Expected exactly 1 argument, got {len(sys.argv) - 1}"
def create_maze(
world: World, assets: Assets[Sprite2D]
) -> Callable[[MazeGenerator], Entity]:
- """System creating and adding the maze to the world."""
+ """Add the maze to the world."""
def inner(maze: MazeGenerator) -> Entity:
root_maze = Maze(len(maze.maze[0]), len(maze.maze))
def spawn_superpacgums(
world: World, mazes: Query[Maze], assets: Assets[Sprite2D]
) -> None:
- """System creating and adding super pacgums to the maze."""
+ """Add super pacgums to the maze."""
pacgums = []
for maze in mazes:
for pos in [
superpacgums: Query[ChildOf, SuperPacGum],
assets: Assets[Sprite2D],
) -> None:
- """System creating and adding pacgums to the maze."""
+ """Add pacgums to the maze."""
ignore = {e.parent for e in superpacgums}
pacgums = []
for ent, cell in cells:
def setup_maze(world: World) -> None:
- """System running the 'create_maze' system with a give MazeGenerator."""
+ """Create maze with a given MazeGenerator."""
world.run_system(create_maze, MazeGenerator((14, 14)))
entities: Query[tuple[InMotion, Coord2D, MoveSpeed]],
positions: Query[Coord2D],
) -> None:
- """System enabling movement operation for entity wit InMotion component."""
+ """Enable movement operation for entity wit InMotion component."""
for motion, pos, speed in entities:
if (dst := positions.get(motion.pos)) is None:
continue
entities: Query[tuple[InMotion, Coord2D, TilePos]],
cells: Query[Coord2D],
) -> None:
- """System updating the tile position for entity wit InMotion component."""
+ """Update the tile position for entity with InMotion component."""
for motion, coord, pos in entities:
src = cells[motion.src]
dst = cells[motion.pos]
cells: Query[Cell],
config: Config,
) -> None:
- """System creating and adding pacman to the maze."""
+ """Add pacman to the maze."""
if (maze_t := maze_q.single()) is None:
return
maze, maze_ent = maze_t
maze_q: Query[tuple[Maze, Entity]],
config: Config,
) -> None:
- """System creating and adding ghosts to the maze."""
+ """Add ghosts to the maze."""
if (maze_t := maze_q.single()) is None:
return
maze, maze_ent = maze_t
cells: Query[tuple[Cell, Coord2D]],
maze_q: Query[Maze],
) -> None:
- """System giving target movement to pacman with the input given by the user."""
+ """Give target movement to pacman with the input given by the user."""
if (maze := maze_q.single()) is None:
return
for coord, motion, pos in pacman_q:
score: GameState,
config: Config,
) -> None:
- """System managing the collision and collection of pacgum by pacman."""
+ """Manage the collision and collection of pacgum by pacman."""
collided = [
pacgum
for coord, pacgum in pacgums
score: GameState,
config: Config,
) -> None:
- """System managing the collision and collection of superpacgum by pacman and
- starting the ghost flee state if a superpacgum is collected.
- """
+ """Manage the collision and collection of superpacgum by pacman."""
collided = [
pacgum
for pacgum_pos, pacgum in pacgums
def respawn_pacman(pacmans: Query[tuple[Pacman, Coord2D, InMotion]]) -> None:
- """System reseting pacman position when touched by ghost."""
+ """Reset pacman position when touched by ghost."""
for pacman, pos, motion in pacmans:
pos.xy = Vector2(*pacman.home).elementwise() * 64
motion.pos = pacman.home_entity
def respawn_ghost(ghosts: Query[tuple[Ghost, Coord2D, InMotion]]) -> None:
- """System reseting ghost fleeing position when touched by pacman."""
+ """Reset ghost fleeing position when touched by pacman."""
for ghost, pos, motion in ghosts:
pos.xy = Vector2(*ghost.home).elementwise() * 64
motion.pos = ghost.home_entity
state: GameState,
config: Config,
) -> None:
- """System managing the collision between by pacman and ghost."""
+ """Manage the collision between by pacman and ghost."""
for pacman_pos in pacmans:
for ghost_pos, status, ghost, motion, pos in ghosts:
if (
cells: Query[tuple[Cell, Coord2D]],
maze_q: Query[Maze],
) -> None:
- """System giving target movement to ghost."""
+ """Give target movement to ghost."""
if (maze := maze_q.single()) is None:
return
for target, motion, pos, status in entities:
pacman_q: Query[tuple[InMotion, TilePos], Pacman],
cells: Query[Cell],
) -> None:
- """System updating target to ghost."""
+ """Update target to ghost."""
player = pacman_q.single()
if player is None:
return
def update_chase_status(status_q: Query[GhostStatus], config: Config) -> None:
- """System updating ghost status and cycle."""
+ """Update ghost status and cycle."""
for status in status_q:
target_time: int
next_state: GhostState
def update_ghost_speed(
ghost: Query[tuple[MoveSpeed, GhostStatus]], config: Config
) -> None:
- """System updating ghost speed according to his state."""
+ """Update ghost speed according to his state."""
for speed, status in ghost:
match status.state:
case GhostState.chase | GhostState.wander:
def update_pacman_anim(
pacman_q: Query[tuple[InMotion, AnimationSet], Pacman],
) -> None:
- """System updating pacman animation according to his direction."""
+ """Update pacman animation according to his direction."""
for motion, anim in pacman_q:
anim.idx = motion.direction
ghost_q: Query[tuple[InMotion, AnimationSet, GhostStatus], Ghost],
config: Config,
) -> None:
- """System updating pacman animation according to his direction and state."""
+ """Update ghost animation according to his direction and state."""
for motion, anim, status in ghost_q:
match status.state:
case GhostState.chase | GhostState.wander:
def start_pause_timer(timer: "InitialPauseTimer") -> None:
- """System starting the initial pause timer."""
+ """Start initial pause timer."""
timer.tick = 0
def setup_new_maze(world: World) -> None:
- """System setting up the maze by running 'setup_maze', 'spawn_superpacgums',
- 'spawn_pacgums', 'spawn_pacman', 'spawn_ghosts' and
- 'start_pause_timer' systems.
- """
+ """Set up all system to spawn a new maze."""
for system in (
setup_maze,
spawn_superpacgums,
def maze_exists(itr: Query[Maze]) -> bool:
- """System cheking maze existance."""
+ """Return if maze exist."""
return next(itr.__iter__(), None) is not None
def game_is_over(game_over: Query[GameIsOver]) -> bool:
+ """Return if game is over."""
return next(game_over.__iter__(), None) is not None
def in_game(world: World) -> bool:
- """System cheking that the game is running."""
+ """Return if the game is running."""
return world.run_simple_system(
maze_exists
) and not world.run_simple_system(in_game_menu)
def level_is_finished(
pacgums: Query[SuperPacGum | PacGum], _: Not[GameIsOver]
) -> bool:
- """System cheking the level completion."""
+ """Return if level is completed."""
return next(iter(pacgums), None) is None
queue: CommandQueue,
state: GameState,
) -> None:
- """System called when level is completed, generating a new level or finishing
- the game if all levels are completed.
- """
+ """Despawn maze and spawn new maze or init game over."""
for maze in mazes:
queue.queue(hierarchy.despawn_with_children(maze))
if state.level == 2:
def spawn_game_over(world: World, fonts: Assets[Font]) -> None: # FIXME
+ """Display game over menu."""
+ root = world.spawn(
+ tuple[WindowPositionned, GameOver],
+ (
+ WindowPositionned(Vector2(0.5, 0), Vector2(0, 100)),
+ GameOver(),
+ ),
+ )
+ font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:100")
+ text = "GAME OVER"
world.spawn(
- tuple[WindowPositionned, ShouldRender, TextDisplay, GameOver],
+ tuple[ChildOf, ShouldRender, CoordBundle, TextDisplay],
(
- WindowPositionned(Vector2(0.5, 0), Vector2(-300, 100)),
+ ChildOf(root),
ShouldRender(),
- TextDisplay(
- "GAME OVER",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:100"),
- ),
- GameOver(),
+ font.center_text_for(text, Vector2(0, 0)),
+ TextDisplay(text, font),
),
)
+ font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:40")
world.spawn(
- tuple[
- WindowPositionned,
- ShouldRender,
- ComputedText,
- TextDisplay,
- GameOver,
- ],
+ tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
(
- WindowPositionned(Vector2(0.5, 0), Vector2(-100, 300)),
+ ChildOf(root),
ShouldRender(),
- ComputedText(cast(System[str], print_score)),
- TextDisplay(
- "Score",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:40"),
- ),
- GameOver(),
+ font.center_text_for("", Vector2(0, 300)),
+ ComputedText(cast(System[str], print_name)),
+ TextDisplay("", font),
),
)
+ font = fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30")
world.spawn(
- tuple[
- WindowPositionned,
- ShouldRender,
- ComputedText,
- TextDisplay,
- GameOver,
- ],
+ tuple[ChildOf, ShouldRender, CoordBundle, ComputedText, TextDisplay],
(
- WindowPositionned(Vector2(0.5, 0), Vector2(-300, 400)),
+ ChildOf(root),
ShouldRender(),
- ComputedText(cast(System[str], print_name)),
- TextDisplay(
- "Score",
- fonts.load("./asset/fonts/JetBrainsMonoNL-Regular.ttf:30"),
- ),
- GameOver(),
+ font.center_text_for("Score = XXX", Vector2(0, 200)),
+ ComputedText(cast(System[str], print_score)),
+ TextDisplay("", font),
),
)
mazes: Query[Entity, Maze],
queue: CommandQueue,
) -> None:
- """System called when the player lose the game."""
+ """Despawn mane and run the game over."""
world.despawn_many(list(gui))
for maze in mazes:
queue.queue(hierarchy.despawn_with_children(maze))
def game_over(queue: CommandQueue) -> None:
- """System called when game is finish."""
+ """Spawn resources when game is over."""
queue.queue(lambda world: world.spawn_resource(GameIsOver()))
queue.queue(lambda world: world.spawn_resource(PlayerName()))
def update_game_timer(world: World, state: GameState) -> None:
- """System updating the levels timer."""
+ """Update the level timer."""
if world.run_simple_system(cheat_mode_enabled):
return
state.timer += 1
def initial_pause_done(timer: InitialPauseTimer, config: Config) -> bool:
- """System cheking if the inital timer is done."""
+ """Return if the inital timer is done."""
if timer.tick < config.initial_pos_timer:
timer.tick += 1
return False
def print_name(name: PlayerName) -> str:
- """Return a string with the player score.
- """
+ """Return a string with the player score."""
return f"Enter your name: {name.name}"
def spawn_hud(world: World, fonts: Assets[Font]) -> None:
- """System that print the Heads-Up Display in game with lives, levels and timer
- informations.
- """
+ """Draw in game HUD with lives, levels and timer informations."""
world.spawn_many(
tuple[
WindowPositionned,
hierarchy: Hierarchy[tuple[()]],
menus: Query[Entity, MainMenu],
) -> None:
- """System closing main menu and starting the game and Heads-Up Display."""
+ """Close main menu and starting the game and HUD."""
for menu in menus:
hierarchy.despawn_with_children(menu)(world)
world.despawn_resource(SelectedButton)
def quit_game(world: World) -> None:
- """System closing the game and quiting the program."""
+ """Close the game and quiting the program."""
world.spawn_resource(WorldShouldExit())
def spawn_button(
world: World, fonts: Assets[Font], sprites: Assets[Sprite2D]
) -> Callable[[WindowPositionned, str, System], Entity]:
- """System creating and adding a button to the world."""
+ """Add a button to the world."""
def inner(pos: WindowPositionned, s: str, cb: System) -> Entity:
parent = world.spawn(
def spawn_cheat_info(world: World, fonts: Assets[Font]) -> None:
+ """Display 'CHEAT MODE ENABLED' on screen."""
world.spawn(
tuple[
WindowPositionned,
def cheat_mode_enabled(cheat: Query[CheatMode]) -> bool:
+ """Return if cheat mode is emable."""
return next(cheat.__iter__(), None) is not None
def enable_cheat_mode(world: World) -> None:
+ """Enable cheat mod."""
world.run_simple_system(spawn_cheat_info)
world.spawn(CheatMode, CheatMode())
def disable_cheat_mode(world: World, cheats: Query[Entity, CheatMode]) -> None:
+ """Disable cheat mod."""
items: list[Entity] = [cheat for cheat in cheats]
world.despawn_many(items)
def toggle_cheat_mode(world: World) -> None:
+ """Enable or disable cheat mod."""
if world.run_simple_system(cheat_mode_enabled):
world.run_simple_system(disable_cheat_mode)
else:
gui: Query[Entity, GameGui],
queue: CommandQueue,
) -> None:
- """System removing maze with childrens and game graphical user interface."""
+ """Remove maze with childrens and game GUI."""
world.run_simple_system(close_game_menu)
world.despawn_resource(GameState)
world.despawn_many(list(gui))
def back_to_main_menu(world: World) -> None:
- """System closing the current game and opening the main menu."""
+ """Close the current game and open the main menu."""
world.run_simple_system(close_current_game)
world.run_simple_system(setup_main_menu)
def in_game_menu(menus: Query[GameMenu]) -> bool:
- """System cheking if the main menu is running."""
+ """Check if the main menu is running."""
return next(menus.__iter__(), None) is not None
def toggle_game_menu(world: World) -> None:
- """System opening or closing the game menu."""
+ """Open or close the game menu."""
if world.run_simple_system(in_game_menu):
world.run_simple_system(close_game_menu)
else:
def test(events: WindowEvents) -> None: # FIXME
+ """Test."""
for key, state in events.keys_itr():
if state:
print(key)
def add_score(
- world: World, name: PlayerName, state: GameState, scores: Highscores
+ world: World,
+ name: PlayerName,
+ state: GameState,
+ scores: Highscores,
+ menus: Query[Entity, GameOver],
+ queue: CommandQueue,
+ hierarchy: Hierarchy[tuple[()]],
) -> None:
+ """Add players score to highscores, save it and go to main menu."""
# FIXME
scores.scores[name.name] = state.score
world.run_simple_system(save_highscores)
+ # items = [item for item in menus]
+ # # world.despawn_many(items)
+ for menu in menus:
+ queue.queue(hierarchy.despawn_with_children(menu))
+ world.despawn_resource(GameIsOver)
world.run_simple_system(setup_main_menu)
def get_player_name(
world: World, events: WindowEvents, name: PlayerName
) -> None:
+ """Get the player name."""
for key, state in events.keys_itr():
if state and key == K_BACKSPACE and name.name != "":
name.name = name.name[: len(name.name) - 1]
if state and key in range(K_a, K_z) and len(name.name) < 10:
char = "abcdefghijklmnopqrstuvwxyz"[key - K_a]
name.name += char
- if state and key == K_RETURN:
+ if state and key == K_RETURN and name.name != "":
world.run_simple_system(add_score)
return
def run_game() -> None:
- """Start with all necessary plugins and system to run the game."""
+ """Run the game with all necessary plugins and system."""
World().with_plugins(graphics_plugins, minimal_plugins).with_systems(
StartupSchedule,
Systems(load_config, load_highscores, setup_main_menu)
update_chase_status,
update_ghost_speed,
update_game_timer,
- test,
)
.chain()
.run_if(initial_pause_done, in_game)