A C++ (Qt5) implementation of Pacman powered by graph theory, advanced AI pathfinding, and computational geometry
- 6 Distinct AI Ghosts — A* Pathfinding, Manhattan Heuristic, APSP Ambush, Predictive Pursuit, Cooperative Pinch, and Hybrid Random
- Power Pellets & Frightened Mode — Eat power pellets to frighten ghosts; frightened ghosts backtrack and can be eaten for bonus points
- Chain Lightning Ability — Spend score to slow ghosts using a closest-pair divide & conquer algorithm
- Ghost Territory System — QuickHull convex hull detects encirclement and dynamically adjusts speeds
- Dynamic Role Assignment — Ghosts switch between Chase, Normal, Ambush, and Scatter modes every tick
- Ghost Eating — Catch frightened ghosts for 200 bonus points; eaten ghosts respawn at their start position
- Toroidal Map Wrapping — Opposite maze edges are connected, allowing seamless wrap-around movement
- Detailed Ghost & Wall Rendering — Ghosts rendered with classic shapes; walls use rounded corners for a polished look
- Multi-Round Progression — Clear all pellets and power pellets to advance rounds with cumulative scoring
Eat every pellet and power pellet on the maze while avoiding six ghosts, each driven by a unique AI algorithm. Grab power pellets to frighten the ghosts and eat them for bonus points. Use the Chain Lightning ability strategically to slow down pursuing ghosts and survive long enough to clear the board.
- Launch the game — you'll see the maze with pellets on every walkable tile and power pellets near corners
- Press any arrow key to start — Pacman begins moving and ghosts activate
- Eat pellets (+10 points each) to build your score
- Eat power pellets (+50 points) to activate Frightened Mode — all ghosts turn blue and backtrack
- Eat frightened ghosts (+200 points each) — they respawn at their starting positions
- Avoid normal ghosts — contact with any non-frightened ghost ends the game
- Use Chain Lightning (press
Z) when your score ≥ 100 to slow the two closest ghosts - Clear all pellets and power pellets to win the round and advance to the next
| Key | Action |
|---|---|
↑ ↓ ← → |
Move Pacman (also starts the game on first press) |
Z |
Chain Lightning — costs 100 score, slows 2 closest ghosts |
R |
Restart — full reset after Game Over, next round after Win |
| State | Trigger | Display |
|---|---|---|
| Playing | First arrow key press | Active maze with HUD |
| You Win! | All pellets & power pellets eaten | Green overlay (48pt), auto-advances in 2s |
| Game Over | Non-frightened ghost catches Pacman | Red overlay (48pt), press R to restart |
The game features six ghosts, each employing a unique pathfinding strategy, creating diverse and challenging pursuit patterns. Ghost behaviour is further modified by a dynamic role assignment system and a frightened mode triggered by power pellets.
| Ghost | Colour | Start Position | Strategy | Personality |
|---|---|---|---|---|
| M1 | Red | (2, 2) | A* Pathfinding | Optimal pursuer — always finds the shortest path |
| M2 | Pink | (52, 2) | Heuristic Greedy | Manhattan-guided — efficient axis-aligned chaser |
| M3 | Cyan | (2, 58) | APSP Ambush | Predictive Hunter — uses precomputed DP shortest paths |
| M4 | Orange | (52, 58) | Aggressive Greedy | Predictive — targets where Pacman will be |
| M5 | Teal | (28, 58) | Pinch Greedy | Cooperative — flanks Pacman from the opposite side of the closest ghost |
| M6 | Purple | (28, 2) | Random (Hybrid) | Unpredictable — randomly mixes aggressive, distance, APSP, and scatter behaviours |
The gold standard of pathfinding. Guarantees the shortest path on every move.
// Min-heap priority queue with F = G + H cost function
// Manhattan distance heuristic (admissible for 4-connected grid)
Location AStarStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Uses
std::priority_queuewithstd::greater(min-heap) - Accounts for toroidal wrapping in heuristic calculation
- Maintains
gCostmap,parentmap, andclosedSet - Returns the first step of the optimal path via backtracking
Evaluates all neighbours and picks the one with minimum Manhattan distance to Pacman.
// Manhattan distance: d = |dx| + |dy| (with toroidal wrapping)
Location HeuristicGreedyStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Simple yet effective for corridor-heavy mazes
- Handles toroidal wrapping for accurate distance calculation
Predicts Pacman's future position and uses a precomputed All-Pairs Shortest Path (APSP) table to intercept him optimally.
// Target 4 tiles ahead, O(1) optimal lookup in DP table
Location AllPairsShortestPath::findNextMove(Graph *graph, Entity *monster, Entity *target);- Calculates APSP table via BFS from every tile (once at startup)
- Predicts intersection points 4 tiles ahead of Pacman
- Guarantees the absolute shortest path to the predicted ambush point
The most dangerous ghost — it targets where Pacman will be, not where he is.
// Predicts Pacman's position 2 steps ahead
// predicted = ((pos + dir*2) % SIZE + SIZE) % SIZE
Location AggressiveGreedyStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Projects Pacman's position 2 ticks into the future based on current direction
- Uses modular arithmetic for wrapping:
((x + dir*2) % WIDTH + WIDTH) % WIDTH - Selects neighbour closest (Euclidean) to the predicted position
A cooperative flanking strategy that coordinates with other ghosts to surround Pacman.
// Finds the opposite side of Pacman relative to the closest other ghost
Location PinchGreedyStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Identifies the closest other ghost to Pacman
- Computes the point diametrically opposite that ghost relative to Pacman
- Moves greedily toward the flanking point using Euclidean distance (toroidal)
- Falls back to direct chase if no other ghost is available
- Creates natural pincer / encirclement behaviour with minimal computation — O(N) per frame
A non-deterministic hybrid strategy that randomly selects one of four behaviours each tick.
// 25% chance each: Aggressive greedy, Distance greedy, APSP, or Scatter/Flee
Location RandomStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- 25% Aggressive greedy — direct chase toward Pacman's current position
- 25% Distance greedy — minimise Euclidean distance to Pacman
- 25% APSP optimal — precomputed shortest-path intercept
- 25% Avoid ghosts — maximise total distance from all other ghosts (spread-out)
- Creates emergent, unpredictable movement patterns — feels "alive" vs. deterministic chasers
When a power pellet is eaten, all ghosts enter FRIGHTENED mode and switch to this strategy.
// Retrace recorded path history in reverse (LIFO), then flee
Location BacktrackStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Phase 1 — Backtrack: Pops positions from the ghost's path history deque (LIFO), retracing its steps backward
- Phase 2 — Flee: Once history is exhausted, chooses the neighbour that maximises Euclidean distance from Pacman
- Path history is capped at 15 recorded positions
- Creates the characteristic "panicked wobbling" motion before purposeful fleeing
- O(1) per frame — extremely lightweight
An additional pursuit strategy available in the codebase for future use or custom configurations.
Location DirectionalGreedyStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Evaluates neighbours based on directional distance heuristics
- Provides alternative chasing behaviour
While the default game uses the 6 ghosts above, the codebase includes additional pursuit strategies.
// Evaluates raw straight-line distance to Pacman
Location DistanceGreedyStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Uses straight-line Euclidean distance for evaluation
- More susceptible to getting stuck behind walls than A* or Heuristic
During chain lightning, all ghosts retreat toward corner (0, 0).
Location ScatterStrategy::findNextMove(Graph *graph, Entity *monster, Entity *target);- Uses Euclidean distance to scatter target
- Overrides normal strategy during lightning events
| Strategy | Optimality | Complexity | Predictability |
|---|---|---|---|
| A* Pathfinding | Optimal | O(E log V) | Low |
| Heuristic Greedy | Local | O(k) | Medium |
| APSP Ambush | Optimal | O(1) lookup | Low |
| Aggressive Greedy | Local | O(k) | Low |
| Pinch Greedy | Local | O(N) | Low |
| Random (Hybrid) | Varies | O(G) | Very Low |
| Backtrack (Frightened) | N/A | O(1) | Medium |
| Distance Greedy | Local | O(k) | Medium |
| Scatter | N/A | O(k) | High |
k = number of neighbours (≤ 4 in a grid maze), N = number of ghosts, G = number of other ghosts
Power pellets are special collectibles placed near maze corners and key locations. Eating one triggers Frightened Mode.
| Property | Value |
|---|---|
| Pellet Locations | 8 positions near corners and mid-maze intersections |
| Score Bonus | +50 points per power pellet |
| Effect | All ghosts enter FRIGHTENED mode for 60 ticks |
| Ghost Behaviour | Ghosts switch to BacktrackStrategy — retrace path then flee |
| Ghost Eating | Contact with a frightened ghost earns +200 points |
| Ghost Respawn | Eaten ghosts teleport back to their spawn position |
| Visual Indicator | Power pellets blink red and are rendered larger than normal pellets |
| Win Condition | Both regular pellets and power pellets must be cleared to win |
A powerful offensive ability that punishes clustered ghosts.
| Property | Value |
|---|---|
| Activation | Press Z |
| Cost | 100 score points |
| Target Selection | Closest pair of ghosts (divide & conquer algorithm) |
| Effect | Slows 2 targeted ghosts for 20 ticks (skip every other move) |
| Mode Override | Forces all ghosts into SCATTER mode |
| Visual | Blue-white lightning bolt connecting affected ghosts |
| Duration | Lightning visual lasts 8 ticks (~2 seconds) |
The game computes a convex hull around all ghost positions every frame using the QuickHull algorithm.
When Pacman is inside the ghost hull:
| Entity | Speed Modifier |
|---|---|
| Ghosts | Slowed to 0.9× |
| Pacman | Boosted to 1.1× |
Algorithm Details:
- QuickHull divide & conquer: find leftmost/rightmost → partition upper/lower → recurse
- Cross-product test for point sidedness:
(A→B) × (A→P) - Point-in-convex-polygon via consistent cross-product winding
- Requires ≥ 3 unique, non-collinear ghost positions for a valid hull
- Complexity: O(N log N) average
Ghost roles are re-evaluated every game tick based on proximity to Pacman. M5 (Pinch) and M6 (Random) are excluded from dynamic role assignment and always operate in NORMAL mode using their default strategies.
Sort eligible ghosts (M1–M4) by squared distance to Pacman (toroidal)
├── Closest → CHASE mode (speed: 1.5×)
├── Middle ghosts → NORMAL mode (speed: 1.0×, uses default strategy)
└── Farthest → AMBUSH mode (speed: 1.25×)
Role assignment is suspended during Frightened Mode and overridden during Chain Lightning.
| Mode | Speed | Behaviour |
|---|---|---|
| CHASE | 1.5× | Uses original AI strategy, aggressively pursues |
| NORMAL | 1.0× | Uses original AI strategy, standard pursuit |
| AMBUSH | 1.25× | Uses original AI strategy, moderate pursuit |
| SCATTER | 1.0× | Overrides to ScatterStrategy, flees to corner |
| FRIGHTENED | 1.0× | Overrides to BacktrackStrategy, then flees Pacman |
Speed Cap: Effective speed is hard-capped at 1.5× —
std::min(speed * territoryMultiplier, 1.5)
The maze implements toroidal wrapping — opposite edges of the map are connected, allowing entities to seamlessly wrap around from one side to the other. This affects:
- All pathfinding and distance calculations (A*, Manhattan, Euclidean)
- Ghost territory convex hull computation
- Entity movement at map boundaries
| Algorithm | Used For | Time Complexity | Space Complexity |
|---|---|---|---|
| A* Pathfinding | Red ghost pathfinding | O(E log V) | O(V) |
| Manhattan Distance | Heuristic greedy evaluation | O(1) | O(1) |
| Euclidean Distance | Distance/Aggressive greedy, territory | O(1) | O(1) |
| All-Pairs Shortest Path | APSP ambush & random strategy | O(V²) precomp / O(1) lookup | O(V²) |
| Cooperative Pinch | Flanking target calculation | O(N) | O(1) |
| Path Backtracking | Frightened mode ghost movement | O(1) | O(H) |
| QuickHull | Ghost territory convex hull | O(N log N) avg | O(N) |
| Point-in-Polygon | Testing if Pacman is inside hull | O(N) | O(1) |
| Closest Pair of Points | Chain lightning target selection | O(N log N) | O(N) |
| Graph BFS/Adjacency | Maze representation & traversal | O(V + E) | O(V + E) |
V = walkable tiles, E = edges (~4V), N = number of ghosts (6), H = path history length (≤ 15)
| Structure | Type | Purpose |
|---|---|---|
| Maze Graph | unordered_map<Location, Node*> |
O(1) node lookup by coordinate |
| Node Adjacency | vector<Node*> |
Neighbour list per walkable tile |
| Pellet Set | unordered_set<Location> |
Fast membership check for pellet collection |
| Power Pellet Set | unordered_set<Location> |
Tracks power pellet positions separately |
| Path History | deque<Location> |
Per-ghost LIFO history for backtracking (cap: 15) |
| APSP Table | vector<vector<int>> |
Precomputed all-pairs shortest distances |
| A* Open Set | priority_queue (min-heap) |
Frontier management for A* search |
| A* Cost Maps | unordered_map<Location, double> |
g-cost and parent tracking |
| Convex Hull | vector<Location> |
Ghost territory polygon vertices |
| Location Hash | std::hash<Location> specialisation |
XOR-shift hash for unordered containers |
This project requires C++17, CMake (v3.10+), and Qt5 (Widgets module).
mkdir build && cd build
cmake ..
make -j$(nproc)
./PacmanThis project is licensed under the AGPL-3.0 License — see the LICENSE file for details.