This project has been created as part of the 42 curriculum by smikhail, evieito-.
Recreating the classic 1980 arcade game in Python — with modern architecture, AI-powered ghosts, and a polished UI.
This project is a faithful recreation of Namco's Pac-Man (1980), implemented entirely in Python 3.10+ using pygame for graphics. The game features:
- 10+ procedurally generated levels via the external
mazegeneratorpackage - 4 ghost AI variants (Blinky, Pinky, Inky, Clyde) with chase/frightened/eaten states
- Persistent highscore system (top 10, stored as JSON)
- JSON configuration file with
#comment support - Cheat mode for peer review testing
- Full UI: main menu, in-game HUD, pause, game over, and victory screens
- Deployment: Play the standalone build on Itch.io
- Python 3.10 or later
- pip
# Clone the repository and enter the project
cd pac-man/
# Install all dependencies (pygame, flake8, mypy, pytest)
# and the mazegenerator package
make installmake run
# Equivalent to: python3 pac-man.py config.json| Key | Action |
|---|---|
| Arrow keys / WASD | Move Pac-Man |
| P / Escape | Pause / Resume |
| C | Toggle cheat overlay |
make debug # launches python3 -m pdb pac-man.py config.jsonmake lint # flake8 + mypy (standard flags)
make lint-strict # flake8 + mypy --strictmake test # runs pytest tests/The game is configured via a JSON file passed as a command-line argument:
python3 pac-man.py config.jsonLines starting with # are treated as comments and ignored.
| Key | Type | Default | Description |
|---|---|---|---|
highscore_filename |
str |
"highscores.json" |
Path to highscore file |
lives |
int |
3 |
Starting lives (1–99) |
points_per_pacgum |
int |
10 |
Score per pacgum eaten |
points_per_super_pacgum |
int |
50 |
Score per super-pacgum |
points_per_ghost |
int |
200 |
Base score per ghost eaten |
frightened_duration |
float |
7.0 |
Seconds ghosts stay frightened |
ghost_respawn_delay |
float |
7.0 |
Seconds before eaten ghost respawns |
level_max_time |
int |
90 |
Time limit per level (seconds) |
timeout_behavior |
str |
"game_over" |
What happens on timeout: "game_over" or "restart" |
levels |
list |
10 default levels | Array of level configs (see below) |
{
"levels": [
{"width": 21, "height": 21, "seed": 42},
{"width": 23, "height": 23, "seed": 0}
]
}| Key | Default | Description |
|---|---|---|
width |
21 |
Maze width in cells (odd recommended) |
height |
21 |
Maze height in cells (odd recommended) |
seed |
0 |
RNG seed. 42 = fixed (level 1). 0 = random |
Error handling: Missing keys use defaults. Invalid values are clamped to safe ranges with a warning message. Unknown keys are silently ignored. No tracebacks are ever shown to the user.
Highscores are stored in a JSON file (default: highscores.json) in the project directory. The file contains an array of up to 10 entries, each with a name and score field.
[
{"name": "SOFIA", "score": 9420},
{"name": "PLAYER1", "score": 3100}
]- Top 10 only: lower scores are discarded
- Player names: max 10 characters, uppercase alphanumeric + spaces only
- Scores: non-negative integers only
- Persistence: loaded at game start, saved atomically on game end (temp file + rename to prevent corruption)
- Corruption recovery: if the file is missing or corrupt, the game starts with an empty list and overwrites on save
We chose a plain JSON file for simplicity, portability, and debuggability. It requires no database, no network, and is human-readable. The atomic save (write to .tmp + os.replace()) prevents partial writes from corrupting the file.
The game uses the external mazegenerator package (provided as a .whl file) to generate mazes. We do not write our own maze generator.
Our adapter (src/maze/maze_adapter.py) is the only file that imports MazeGenerator directly. All game code uses MazeAdapter, which exposes a Pac-Man-friendly interface.
from mazegenerator import MazeGenerator
mg = MazeGenerator(size=(width, height), perfect=False, seed=seed)perfect=False— generates imperfect mazes with loops, required for Pac-Man gameplayseed=42— fixed seed for level 1 (reproducible)seed=0— random seed for levels 2+ (different maze each playthrough)
Each cell in mg.maze encodes its walls as a 4-bit integer:
| Bit | Value | Meaning |
|---|---|---|
| 0 | 1 |
North wall (blocks UP) |
| 1 | 2 |
East wall (blocks RIGHT) |
| 2 | 4 |
South wall (blocks DOWN) |
| 3 | 8 |
West wall (blocks LEFT) |
| all | 15 |
Solid block — includes the "42" logo at center |
The 42 pattern embedded by the generator (cells = 15) is treated as a decorative wall cluster. The player and ghosts spawn at the nearest walkable cells to the center and corners respectively.
The game runs at a fixed 60 FPS target using pygame.time.Clock. Delta time (dt) is capped at 50ms to prevent physics tunneling during lag spikes.
Player and ghosts use sub-cell interpolation: they store both their current grid cell and a progress value (0.0–1.0) toward the next cell. The renderer uses the interpolated position for smooth movement. Player direction input is buffered — the next requested direction is applied automatically as soon as the current cell is reached.
| Ghost | Strategy |
|---|---|
| Blinky (Red) | BFS shortest path to player |
| Pinky (Pink) | Targets 4 cells ahead of player's facing direction |
| Inky (Cyan) | Fully random (no reversals) |
| Clyde (Orange) | BFS if distance > 8 cells, random if closer |
Ghost combo scoring: eating multiple ghosts in one frightened phase awards base × 2^n points (capped at 8×).
MAIN_MENU → PLAYING ⇄ PAUSED → LEVEL_TRANSITION → PLAYING
↘ GAME_OVER → ENTER_NAME → MAIN_MENU
↘ VICTORY → ENTER_NAME → MAIN_MENU
pac-man.py # Entry point — arg parsing + error handling
├── src/config/ # JSON config loading + validation
│ └── config_loader.py # ConfigLoader → GameConfig dataclass
├── src/maze/ # MazeGenerator adapter (single isolation layer)
│ └── maze_adapter.py # MazeAdapter + Direction enum
├── src/game/ # Core game logic
│ ├── game.py # Game state machine + main loop
│ ├── level.py # Level setup + update + collision detection
│ ├── player.py # Pac-Man entity + movement
│ ├── ghost.py # Ghost base + Blinky/Pinky/Inky/Clyde AI
│ ├── pacgum.py # Pacgum + SuperPacgum entities
│ └── scoring.py # Score tracker + ghost combo
├── src/highscore/ # Persistent highscore system
│ └── highscore.py # HighscoreManager — load/save/validate
├── src/ui/ # All rendering and screen logic
│ ├── renderer.py # Renderer — draws everything to pygame surface
│ └── screens/
│ └── main_menu.py # Main menu screen
└── src/cheat/ # Cheat mode for peer review
└── cheat_mode.py # CheatMode — 6 cheats
Key design decisions:
maze_adapter.pyis the only file touchingMazeGenerator— one place to update if the API changesrenderer.pyis the only file touchingpygamedisplay surfaces — keeps game logic pure- All game config flows through the immutable
GameConfigdataclass - No global state — every component receives what it needs via constructor injection
Activate by pressing C during gameplay. A reference panel appears in the top-right corner.
| Key | Cheat | Effect |
|---|---|---|
I |
Invincibility | Ghosts cannot kill the player (toggle) |
N |
Level skip | Win the current level immediately |
F |
Ghost freeze | All ghosts stop moving (toggle) |
L |
Extra life | Add +1 life |
S |
Speed boost | Player moves at 2× speed (toggle) |
G |
Scare ghosts | Trigger frightened mode on all ghosts |
See the docs/ directory for:
gantt.md— Project timelinekanban.md— Task tracking boardrisk_analysis.md— Risk registeracceptance_tests.md— Feature acceptance test plan
- Pac-Man Dossier — Definitive analysis of original ghost AI
- pygame documentation
- Python
typingmodule docs - flake8 docs
- mypy docs
- PEP 257 — Docstring conventions
AI (Antigravity / Claude) was used for:
- Development plan creation: translating the subject PDF requirements into a structured 11-phase implementation plan
- Boilerplate generation: initial file structures,
__init__.pyfiles,Makefiletargets - Test scaffolding: generating pytest test cases for config, highscore, maze, and scoring modules
All AI-generated code was reviewed, understood, and validated by the team before inclusion. The core game logic (ghost AI strategies, collision detection, state machine transitions) was designed and written by smikhail and evieito-.