This project has been created as part of the 42 curriculum by and .
A complete, playable recreation of the classic 1980 arcade game Pac-Man, written in modern, object-oriented Python. Levels are built from an external A-Maze-ing maze generator, the game is fully configurable through a commented JSON file, and it ships with a persistent highscore table and a cheat mode for peer review.
Guide Pac-Man through a maze, eating every pacgum while dodging four ghosts — Blinky, Pinky, Inky and Clyde, each with its own chase behaviour. Grab a super-pacgum in a corner to turn the tables and eat the ghosts for bonus points. Clear all the pacgums to advance; survive 10+ levels to win.
The goal of the project is a modular, reusable architecture: the entire rule set lives in a headless, unit-tested engine, completely decoupled from a thin pygame rendering layer.
- 10+ procedurally generated levels (level 1 uses a fixed seed).
- Four ghosts with distinct chase behaviours, fright mode and respawn.
- Pacgums, super-pacgums (power pellets), scoring and lives.
- Per-level time limit, pause/resume.
- Polished UI: main menu with live highscores, instructions, in-game HUD, pause menu, game-over and victory screens with name entry.
- Persistent top-10 highscore table.
- Cheat mode to make peer review easy.
- Commented-JSON configuration with robust defaults — never crashes.
- flake8- and mypy-clean, fully type-hinted and documented.
- Desktop packaging (PyInstaller) ready for Itch.io / Steam.
- Python 3.10+
pygame(installed bymake install)- The assigned A-Maze-ing package wheel (
mazegenerator-*.whl), placed in the project root. It is installed bymake installand re-installed by your reviewer.
make install # create .venv, install deps + the maze wheel
make run # runs: python pac-man.py config.json
make run CONFIG=my.json # run with a custom config
make debug # run under pdb
make lint # flake8 . + mypy . (mandatory flags)
make lint-strict # flake8 . + mypy . --strict
make test # run the unit test suite
make package # build a standalone desktop app (see packaging/)
make clean # remove caches / build artifactspython -m venv .venv
.venv\Scripts\pip install -r requirements-dev.txt
.venv\Scripts\pip install mazegenerator-2.1.0-py3-none-any.whl
.venv\Scripts\python pac-man.py config.jsonThe program takes exactly one argument: a .json configuration file.
Any error (missing file, bad value, missing key, generator failure) is
reported with a clear message — never a Python traceback.
| Action | Keys |
|---|---|
| Move | Arrow keys or W A S D |
| Pause / resume | P or ESC |
| Menu navigation | Arrows + ENTER / SPACE |
| Confirm name | type (letters/digits/space, ≤10), ENTER |
Cheats exist to let a reviewer test every feature quickly.
| Key | Cheat |
|---|---|
I |
Toggle invincibility (ghosts can't kill you) |
G |
Freeze / unfreeze ghosts |
B |
Toggle speed boost |
N |
Skip to the next level (win the current one) |
L |
Add one life |
C |
Show / hide the cheat help panel |
The config file is JSON with comments: any line content after # or
//, and /* … */ blocks, is ignored. Missing or invalid values fall back
to a safe default (a message is logged) and unknown keys are ignored — so
the game keeps running no matter what. Only a totally unreadable/unparsable
file is fatal (with a clean message). See config.json for a
fully-commented example.
| Key | Type | Default | Range / notes |
|---|---|---|---|
highscore_filename |
string | highscores.json |
where scores are stored |
lives |
int | 3 | 1–99 |
seed |
int | 42 | level 1 seed (0 = random) |
level_max_time |
int | 90 | 5–3600 seconds per level |
width |
int | 19 | 7–41 maze cells (≥14 to show the "42" logo) |
height |
int | 15 | 7–41 maze cells (≥10 to show the "42" logo) |
pacgum |
int | 42 | max pacgums/level; ≤0 = fill every corridor |
points_per_pacgum |
int | 10 | 0–100000 |
points_per_super_pacgum |
int | 50 | 0–100000 |
points_per_ghost |
int | 200 | 0–100000 |
fright_time |
float | 7.0 | 0–60 s ghosts stay edible |
ghost_respawn_delay |
float | 6.0 | 0–60 s before an eaten ghost returns |
player_speed |
float | 5.0 | 1–20 tiles/s |
ghost_speed |
float | 4.5 | 1–20 tiles/s |
frightened_speed_factor |
float | 0.6 | 0.2–1.0 ghost slow-down while edible |
levels |
list | — | optional [{ "width":…, "height":… }, …]; padded to ≥10 |
If levels is omitted, every level uses width×height; the list is always
padded to at least 10 levels (growing slightly for difficulty).
Highscores are stored as a small JSON file on disk (path from
highscore_filename), e.g.:
[ { "name": "Sannaka", "score": 1110 }, { "name": "foliole", "score": 20 } ]- Loaded at game start, saved whenever a score is added.
- Keeps the top 10, best first.
- Player names are validated: letters, digits and spaces only, ≤10
characters (empty →
"Player"); scores are non-negative integers. - Robust to every file error: a missing or corrupted file simply yields an empty table instead of crashing; a failed write is swallowed cleanly.
Why this design: a plain JSON file is human-readable, trivial to inspect during review, dependency-free, and easy to reset (just delete it). The manager fully sanitises input on the way in, so the file can never carry the game into a bad state on the way out.
We do not write our own generator — we adapt the assigned A-Maze-ing
package (mazegenerator) as-is, with PERFECT=False (Pac-Man-friendly
corridors with loops).
The generator returns a thin-wall grid: a list maze[y][x] where each
cell is a 4-bit wall mask (N=1, E=2, S=4, W=8); a set bit means a wall on
that side, and the value 15 marks the isolated cells that draw the hidden
“42” logo. Two adjacent cells are connected when the shared wall bit is
clear.
Pac-Man needs thick walls (walls occupy their own tiles), so our loader
(game/maze.py) doubles the resolution into a (2·w+1) × (2·h+1) grid:
cell (x, y) -> corridor tile (2x+1, 2y+1)
open passage East of (x,y) -> corridor tile (2x+2, 2y+1)
open passage South of (x,y)-> corridor tile (2x+1, 2y+2)
everything else -> wall
Isolated “42” cells stay solid, so the logo appears embedded in the walls.
The loader then decorates the grid: pacgums in the corridors, super-pacgums
and one ghost at each of the four corners, and the player at the corridor
nearest the centre. The loader adapts to their interface, not the
opposite, and every call to the package is wrapped so a generator failure
becomes a clean MazeError, never a crash.
- Language / tooling: Python 3.10+, type-hinted throughout, PEP-257
docstrings, flake8 + mypy clean,
pytestsuite. - Movement model: actors hold continuous float positions in tile units; turns and AI decisions happen only at tile centres, and each frame is split so a sub-step never overshoots a centre. This guarantees perfect grid alignment and makes wall-clipping impossible.
- Ghost AI: at each tile a ghost picks the legal neighbour (no U-turns unless dead-ended) that best fits its target — direct chase (Blinky), four tiles ahead (Pinky), a mirror through Blinky (Inky), or chase/scatter by distance (Clyde). Frightened ghosts maximise distance from the player; eaten ghosts return home and respawn after a delay.
- Engine/render split:
game/engine.pycomputes everything and emits a small list of events per frame;game/app.pyonly reads state and draws. - Robustness: config clamping, generator guarding, and a last-resort
guard around the main loop guarantee the "no crash" requirement. Set
PACMAN_DEBUG=1to re-raise for development.
graph TD
CLI["pac-man.py<br/>(CLI entry, arg parsing)"] --> CFG
LAUNCH["packaging/launcher.py<br/>(frozen entry)"] --> CFG
CLI --> APP
LAUNCH --> APP
subgraph Core["game/ (logic — no rendering)"]
CFG["config.py<br/>GameConfig, JSONC loader"]
HS["highscore.py<br/>HighScoreManager"]
MAZE["maze.py<br/>Maze, build_maze()"]
ENT["entities.py<br/>Player, Ghost"]
ENG["engine.py<br/>GameEngine, Cheats"]
end
APP["app.py<br/>PacmanApp (pygame UI)"]
EXT["mazegenerator<br/>(external A-Maze-ing pkg)"]
APP --> ENG
APP --> HS
ENG --> MAZE
ENG --> ENT
ENG --> CFG
MAZE --> EXT
MAZE --> CONST["constants.py"]
| Module | Responsibility |
|---|---|
pac-man.py |
CLI entry; parse the single argument, load config, launch. |
game/constants.py |
Enums, colours, defaults, tunables. |
game/config.py |
JSONC parsing, validation/clamping → GameConfig. |
game/highscore.py |
Load/validate/persist the top-10 table. |
game/maze.py |
Adapt the external generator into a playable Maze. |
game/entities.py |
Player and Ghost — movement core + ghost AI. |
game/engine.py |
All rules: scoring, lives, fright, levels, timer, cheats. |
game/app.py |
pygame window, screens, HUD, input, render loop. |
packaging/ |
PyInstaller spec + launcher + deployment guide. |
make package (PyInstaller) produces a self-contained desktop build in
dist/pacman/, launchable with a double-click (the bundled config.json is
loaded automatically). Full instructions for publishing an unlisted/free
build on Itch.io or Steam, plus an optional pygbag web build, are
in packaging/README.md.
The project was driven with a lightweight Kanban flow and an engine-first
plan. All planning, analysis, risk tracking, team organisation and the full
acceptance-test plan live in
project_management/.
Classic references used while building the game:
- Pac-Man Dossier — The Pac-Man Dossier by Jamey Pittman (ghost-AI behaviour reference).
- pygame documentation
- MLX (MiniLibX) reference — used to keep the rendering to an MLX-equivalent subset.
- PyInstaller and Itch.io / butler for packaging.
- Python
typing,flake8andmypydocumentation.
AI (an LLM coding assistant) was used as a productivity and review aid, and every suggestion was read, understood, tested and adapted before acceptance:
- Scaffolding & boilerplate: drafting module skeletons, the Makefile,
setup.cfgand docstrings — then hand-tuned. - Maze adaptation: discussing the thin→thick wall doubling and verifying connectivity against the package's own logic.
- Review & debugging: the assistant helped surface the level-timeout life-drain bug, which we then reproduced, fixed and covered with a test.
- Documentation: first drafts of this README and the project-management documents, edited for accuracy.
AI was not used to submit code we could not explain: the engine, the maze adapter and the ghost AI were reviewed line by line by the team.