A fully playable Pac-Man clone built from scratch with Python, Pygame, and a clean MVC architecture.
A full recreation of the classic Pac-Man arcade game built from scratch in Python with Pygame — from game logic and ghost AI to the graphical interface. Features procedurally generated mazes, a persistent highscore system, and a cheat mode. Built following a strict MVC architecture designed collaboratively before any code was written.
The entire application follows a strict Model-View-Controller pattern designed as the foundation before implementation began.
Models own all game state and business rules. They have zero knowledge of rendering or input handling.
| Class | Responsibility |
|---|---|
PacMan |
Position, direction, buffered input (anticipatory turning), movement physics, cell snapping |
Maze |
Stores the bitmask matrix, manages the pellet grid, validates traversability per direction |
Ghost (abstract) |
Base movement, direction selection via shortest-distance heuristic, vulnerable state, spawn memory |
GameStats |
Lives, score, level counter, point values per pellet/ghost type |
ConfigParser |
Pydantic-validated config loader with automatic fallback to defaults |
HighscoresValidator |
Persistent top-10 leaderboard with sorted insertion and file I/O |
Controllers process input, update models, and tell views what to draw. They never touch pixels directly.
| Class | Responsibility |
|---|---|
MainController |
Master loop and screen state machine (TITLE → GAME → END → TITLE) |
GameController |
Per-frame game tick: move entities, check collisions, eat pellets, manage power-up timer, handle pause |
TitleController |
Menu navigation across main menu, highscores view, and instructions view |
EndController |
Win/lose display, highscore name entry (3-char input), score persistence |
ScreenController |
Abstract base — defines the run(dt, events) → Optional[ScreenType] contract |
Views receive data and draw it. They hold no game state and make no decisions.
| Class | Responsibility |
|---|---|
GameRenderer |
Draws the maze (bitmask walls), pellets, Pac-Man (animated mouth), ghosts, HUD, and pause overlay |
TitleRenderer |
Renders main menu options, highscore table with aligned columns, and instructions screen |
EndRenderer |
Draws win/lose screens, score display, and the highscore name input interface |
SpriteManager |
Loads all PNG assets at startup into a dictionary keyed by AssetType, handles scaling and rotation |
Each ghost extends the abstract Ghost base class and implements a single method — get_target_cell — that defines its personality. The base class handles all shared logic: grid movement, direction selection via shortest-distance heuristic, vulnerable state retreat, and sprite animation.
When Pac-Man eats a super pellet, all ghosts enter vulnerable mode — they reverse course and flee toward their spawn corners. Eating a vulnerable ghost awards bonus points and respawns it.
- Delta Time: Game loop uses delta time for framerate-independent movement and animations.
- Bitmasking & Surface Caching: Maze geometry uses a 4-bit masking system (N, E, S, W) to determine wall borders. Walls are drawn once to a static Pygame
Surfaceper level instead of redrawing every frame. - Procedural Generation: Mazes are generated at runtime via a seed-based algorithm (
mazegenerator), ensuring replayability with deterministic layouts. - Input Buffering: Pac-Man's direction input is buffered ahead of intersections, mirroring the original arcade feel.
- Config Validation: All game parameters are externalized in
config.jsonand validated at startup with Pydantic, with automatic fallback to defaults.
The game is configured via a config.json file that controls maze dimensions (width, height), number of lives, levels_quantity, scoring values, maze generation seed, and level_max_time. All fields are validated with Pydantic at startup — if the file is missing or malformed, the game falls back to default_files/default_config.json automatically.
Scores are persisted in a JSON file (path defined in config). The leaderboard is sorted in descending order on load and on every insertion. A new score is only added if it matches or exceeds the lowest score on the board.
| Key | Action |
|---|---|
↑ ↓ ← → / W A S D |
Move Pac-Man (input is buffered) |
Esc |
Pause / Resume |
Enter |
Confirm selection |
Backspace |
Go back in menus |
Requirements: Python 3.12+, uv
Option 1: Using Make (Recommended)
makeOption 2: Using uv manually
uv sync
uv run python3 -m pac-man.py [config.json]Other targets:
make test # Run unit & integration tests
make lint # flake8 + mypy
make lint-strict # mypy --strict
make clean # Remove caches and venv| Component | Technology |
|---|---|
| Language | Python 3.12+ |
| Game framework | Pygame |
| Data validation | Pydantic |
| Maze generation | mazegenerator (bundled wheel) |
| Testing | Pytest |
| Linting | flake8 + mypy |
| Package manager | uv |
| Role | Focus | |
|---|---|---|
| mpadronrz | Engine & Gameplay | PacMan, Maze, GameController, GameRenderer — movement physics, collision system, input buffering, maze rendering |
| Edugs94 | AI & Metagame | Ghost definitions and behaviors, GameStats, parsers, MainController, ScreenController, TitleController, EndController, SpriteManager, menu renderers |
The MVC architecture was designed collaboratively before any implementation began.




