How a game from 1980 uses state machines, target selection, local pathfinding, and simple decision rules to create surprisingly intelligent enemies.
Pac-Man looks like a simple game.
You move through a maze, collect dots, avoid ghosts, and try to survive as long as possible.
But have you ever wondered why the ghosts behave differently?
Why does one follow you directly, another try to get ahead of you, and another seem to change its mind when it gets close?
The interesting part is that the original Pac-Man does not need a modern AI model to create these behaviors. Its enemies use a collection of relatively simple rules that work together to make the game challenging.
In this article, we'll break down the main algorithms behind Pac-Man, understand how the four ghosts make decisions, and explore how you could implement a similar system yourself.
1. The Core Idea: AI Without Machine Learning
When people hear "game AI," they often think about neural networks, reinforcement learning, or large language models.
But game AI doesn't necessarily require any of these techniques.
In the original Pac-Man, the ghosts primarily rely on:
- Finite State Machines (FSMs) to manage behavioral modes.
- Target selection to decide where each ghost wants to go.
- Local movement decisions to choose the next direction at intersections.
- Timers and conditions to change behavior during gameplay.
- Rules specific to each ghost to create different personalities.
These techniques are examples of rule-based AI.
The ghosts don't need to understand the maze the way a human does. They only need enough information to choose their next move.
That's an important distinction: a character can appear intelligent without planning every step of its journey in advance.
2. Representing the Maze as a Grid
Before implementing enemy behavior, we need a representation of the game world.
A Pac-Man maze can be modeled as a grid of tiles. Each tile represents a location that may contain a wall, a corridor, a dot, a power pellet, or another game object.
For example:
#############
#P....#.....#
#.#.#.#.###.#
#.#...G.....#
#.....#.....#
#############
Here, P represents Pac-Man and G represents a ghost.
A simple representation in JavaScript could look like this:
const maze = [
"#########",
"#.......#",
"#.###...#",
"#.......#",
"#########",
];
const WALL = "#";
const EMPTY = ".";
We can represent a position using two coordinates:
const pacman = {
x: 1,
y: 1,
direction: "right",
};
This makes it easy to check whether a move is valid.
function canMoveTo(x, y) {
return maze[y]?.[x] !== undefined &&
maze[y][x] !== WALL;
}
This is only a basic example. A complete implementation would also need to handle tunnel transitions, movement timing, collision rules, and the exact geometry of the maze.
Why tiles matter
The original game's ghost logic is based on tile positions and decisions made at intersections, rather than requiring each ghost to continuously calculate a complete route across the maze.
This simplifies movement decisions and makes the behavior predictable enough to learn, while still allowing the ghosts to pressure the player.
3. The Ghost AI State Machine
The ghosts don't use the same behavior all the time.
Their movement changes according to the current game mode.
Three important modes are:
- Chase: Ghosts pursue Pac-Man according to their individual targeting rules.
- Scatter: Ghosts target their assigned corners of the maze.
- Frightened: Ghosts become vulnerable and choose movement directions in a more random way.
A state machine controls which behavior is active.
Conceptually, it looks like this:
┌───────────┐
│ SCATTER │
└─────┬─────┘
│ timer
▼
┌───────────┐
┌─────│ CHASE │
│ └─────┬─────┘
│ │ power pellet
│ ▼
│ ┌───────────┐
└─────│ FRIGHTENED│
└───────────┘
This is a conceptual diagram, not the complete transition graph of the arcade game. The actual implementation also needs timers, mode transitions, and rules for handling frightened ghosts.
A simplified implementation might look like this:
const GhostMode = {
SCATTER: "scatter",
CHASE: "chase",
FRIGHTENED: "frightened",
};
function updateGhost(ghost, game) {
switch (ghost.mode) {
case GhostMode.SCATTER:
return moveTowardTarget(
ghost,
ghost.scatterTarget
);
case GhostMode.CHASE:
return moveTowardTarget(
ghost,
getChaseTarget(ghost, game)
);
case GhostMode.FRIGHTENED:
return moveRandomly(ghost, game);
}
}
The key design decision is that the current mode determines the movement strategy.
Instead of writing one enormous function that handles every possible situation, we separate the behavior into smaller pieces.
This also makes the system easier to extend and test.
Why switch between Chase and Scatter?
If all the ghosts pursued Pac-Man continuously, the game could become much harder and less varied.
Scatter mode gives the player temporary breathing room. Each ghost heads toward its own corner instead of directly targeting the player.
Chase mode then brings the pressure back.
The original game alternates these modes according to a timed schedule, rather than making every transition depend on the player's immediate position.
This creates a rhythm between pursuit and temporary relief.
4. The Four Ghosts Use Different Targeting Algorithms
This is where Pac-Man's design becomes especially interesting.
The ghosts share much of the same movement logic, but they calculate their targets differently.
The four classic ghosts are:
| Ghost | Color | Main behavior |
|---|---|---|
| Blinky | Red | Direct pursuit |
| Pinky | Pink | Ambush |
| Inky | Cyan | Position-dependent targeting |
| Clyde | Orange | Pursuit with a distance-based switch |
These are not four independent, highly sophisticated AI systems. They're different targeting strategies built on top of a shared movement system.
4.1 Blinky: Direct Pursuit
Blinky is the most straightforward ghost to understand.
During Chase mode, his target is Pac-Man's current tile.
If Pac-Man is at position ((p_x, p_y)), Blinky's target is:
[
T_B = (p_x, p_y)
]
In code:
function getBlinkyTarget(pacman) {
return {
x: pacman.x,
y: pacman.y,
};
}
This sounds simple, but direct pursuit is effective because Pac-Man must navigate the same corridors and intersections as Blinky.
The ghost doesn't need to predict the player's future. It continually updates its target as Pac-Man moves.
In later stages of the original game, Blinky can also enter a faster behavior known as Cruise Elroy, which makes him more aggressive.
The lesson: A simple pursuit rule can be dangerous when it operates continuously in a constrained environment.
4.2 Pinky: Ambush Instead of Following
Pinky uses a different strategy.
Instead of targeting Pac-Man's current position, Pinky targets a tile four tiles ahead of Pac-Man's current direction.
Suppose Pac-Man is moving to the right:
P . . . T
P is Pac-Man, and T is Pinky's target.
If Pac-Man is at ((p_x, p_y)) and moving right, the target is approximately:
[
T_P = (p_x + 4, p_y)
]
A simplified implementation:
function getPinkyTarget(pacman) {
const offsets = {
up: { x: 0, y: -4 },
down: { x: 0, y: 4 },
left: { x: -4, y: 0 },
right: { x: 4, y: 0 },
};
const offset = offsets[pacman.direction];
return {
x: pacman.x + offset.x,
y: pacman.y + offset.y,
};
}
This is a simplified version of the targeting rule.
The original arcade implementation contains a well-known quirk: when Pac-Man faces up, Pinky's target is offset four tiles up and four tiles left because of an integer-overflow behavior in the original code.
That bug became part of the game's behavior.
Why is this strategy effective?
Imagine that Pac-Man is moving through a long corridor.
Blinky follows the current position, but Pinky aims farther along the corridor. If Pac-Man continues in the same direction, Pinky may reach an intersection ahead of him.
Instead of simply chasing the player, Pinky can cut off the player's route.
This creates the impression of prediction, even though the algorithm only uses the player's current position and direction.
The lesson: A small change in the target calculation can produce a completely different enemy personality.
4.3 Inky: Targeting Based on Two Characters
Inky uses a more complex rule because his target depends on both Pac-Man and Blinky.
First, calculate a point two tiles ahead of Pac-Man:
[
A = P + 2D
]
Where:
- (P) is Pac-Man's current position.
- (D) is the direction vector.
- (A) is the intermediate target.
Next, calculate the vector from Blinky's position (B) to that point:
[
V = A - B
]
Finally, double that vector from Blinky's position:
[
T_I = B + 2V
]
Combining the equations gives:
[
T_I = 2A - B
]
This target can end up far from Pac-Man's current position.
A simplified implementation:
function getInkyTarget(pacman, blinky) {
const offsets = {
up: { x: 0, y: -2 },
down: { x: 0, y: 2 },
left: { x: -2, y: 0 },
right: { x: 2, y: 0 },
};
const offset = offsets[pacman.direction];
const ax = pacman.x + offset.x;
const ay = pacman.y + offset.y;
return {
x: 2 * ax - blinky.x,
y: 2 * ay - blinky.y,
};
}
This code captures the central vector calculation, but it doesn't reproduce every detail of the original arcade implementation, including the upward-direction quirk and handling of targets outside the maze.
Why does Blinky's position matter?
Imagine Pac-Man moving toward an intersection while Blinky approaches from one side.
Inky's target changes according to Blinky's position. As a result, Inky can approach the same area from a different direction.
This can create situations where Pac-Man is pressured from multiple directions without the ghosts needing a shared plan that explicitly says, "surround the player."
Their individual rules can create that effect.
The lesson: Combining simple algorithms with shared world state can create more complex behavior than any single rule produces on its own.
4.4 Clyde: Chase Until a Distance Threshold
Clyde uses a distance-based rule.
When he's far from Pac-Man, he targets Pac-Man directly. When he's close, he switches to his Scatter target.
In the original arcade game, the important threshold is eight tiles.
A simplified rule is:
[
T_C =
\begin{cases}
P, & d(C,P) \geq 8 \
S_C, & d(C,P) < 8
\end{cases}
]
Where:
- (C) is Clyde's position.
- (P) is Pac-Man's position.
- (S_C) is Clyde's Scatter target.
In code:
function getClydeTarget(clyde, pacman) {
const dx = clyde.x - pacman.x;
const dy = clyde.y - pacman.y;
const distanceSquared = dx * dx + dy * dy;
if (distanceSquared < 8 * 8) {
return clyde.scatterTarget;
}
return {
x: pacman.x,
y: pacman.y,
};
}
This example uses Euclidean distance for simplicity. The exact original behavior should be implemented using the game's actual distance and tile rules rather than assuming this snippet is a bit-for-bit recreation.
Why does this create a distinct personality?
Clyde can approach Pac-Man aggressively, then head toward his corner when he gets too close.
The result looks inconsistent if you assume that every ghost is trying to minimize its distance to Pac-Man at all times.
But the behavior makes sense when you understand that Clyde's target changes according to a rule.
He isn't making an emotional decision. He's following a different policy.
The lesson: Different conditional rules can make agents appear to have distinct personalities without requiring a personality model.
5. How Do Ghosts Choose Their Next Move?
Knowing a target isn't enough.
A ghost also needs to decide which direction to take at the next intersection.
This is where movement selection comes in.
A common misconception is that the original ghosts run a full A* search every time they move.
Their classic movement logic is more local than that.
At a decision point, the ghost considers its legal directions and chooses the one that brings it closest to its current target according to the game's distance calculation.
A simplified version might look like this:
function chooseDirection(ghost, target, legalMoves) {
let bestMove = null;
let bestDistance = Infinity;
for (const move of legalMoves) {
const nextX = ghost.x + move.dx;
const nextY = ghost.y + move.dy;
const dx = target.x - nextX;
const dy = target.y - nextY;
const distance = dx * dx + dy * dy;
if (distance < bestDistance) {
bestDistance = distance;
bestMove = move;
}
}
return bestMove;
}
This example illustrates greedy local decision-making. A faithful implementation needs the original game's movement constraints, direction priority rules, target handling, and intersection timing.
Why use squared distance?
If the goal is only to compare distances, calculating the square root is unnecessary.
Instead of comparing:
[
d = \sqrt{(\Delta x)^2 + (\Delta y)^2}
]
we can compare:
[
d^2 = (\Delta x)^2 + (\Delta y)^2
]
Since square root preserves the ordering of non-negative values, the closest candidate remains the closest candidate.
This avoids an unnecessary calculation.
Why not always use A*?
A* is a pathfinding algorithm that searches for a route through a graph. It can be a great choice for games where characters need a globally planned, obstacle-aware route.
But the original Pac-Man ghosts use a more constrained movement model. Their local decisions, combined with the maze geometry and targeting rules, are sufficient for the intended behavior.
This distinction matters:
- Target selection answers: Where should the ghost try to go?
- Movement selection answers: Which legal direction should it take next?
- Pathfinding usually refers to finding a route through the environment to reach a destination.
These concepts are related, but they aren't interchangeable.
A ghost can have a sophisticated target-selection rule while using relatively simple movement decisions.
6. Movement Restrictions Are Part of the AI
The ghost's decision is not made from every possible direction.
The maze restricts movement, and the game imposes additional rules. For example, ghosts generally cannot reverse direction whenever they want. Direction reversals are associated with specific events, including mode changes.
A more realistic movement function starts by generating legal candidates:
function getLegalMoves(ghost, maze) {
const directions = [
{ name: "up", dx: 0, dy: -1 },
{ name: "left", dx: -1, dy: 0 },
{ name: "down", dx: 0, dy: 1 },
{ name: "right", dx: 1, dy: 0 },
];
return directions.filter((direction) => {
if (isReverseDirection(ghost, direction)) {
return false;
}
return canMoveTo(
ghost.x + direction.dx,
ghost.y + direction.dy
);
});
}
This is illustrative rather than complete arcade logic. In a real implementation, you must also handle dead ends, mode changes, tunnels, and cases where the candidate list becomes empty.
The order in which directions are evaluated can also matter when multiple moves have equal distances. A faithful recreation needs the original priority rules rather than relying on arbitrary array order.
These small implementation details are important because they affect what players observe.
7. Frightened Mode: Changing the Objective
When Pac-Man eats a power pellet, the ghosts enter Frightened mode.
Their behavior changes in several ways:
- They become vulnerable to being eaten.
- Their movement becomes less predictable.
- They generally move more slowly.
- The mode has a limited duration, which varies with the game's level and rules.
This is not simply a different target coordinate.
The objective itself has changed. Instead of pursuing Pac-Man in the usual way, the ghosts use frightened movement behavior.
A simplified implementation could be:
function chooseFrightenedMove(legalMoves) {
if (legalMoves.length === 0) {
return null;
}
const index = Math.floor(
Math.random() * legalMoves.length
);
return legalMoves[index];
}
The original arcade behavior has additional rules and should not be treated as equivalent to uniformly selecting from every legal move. This example only demonstrates how a separate strategy can be plugged into the movement system.
Why randomness matters
A deterministic enemy can become predictable if the player learns its rules.
Randomized movement can make the frightened state less predictable, giving the player a temporary advantage without making every encounter identical.
However, randomness alone is not intelligence. It's simply another tool for creating variation.
8. The Architecture Behind the Behavior
If I were implementing a Pac-Man-style AI today, I would separate the system into several responsibilities.
Game State
|
v
Ghost State Machine
|
v
Target Selection
|
v
Legal Move Generation
|
v
Movement Decision
|
v
Collision and Game Update
Each layer has a different responsibility.
| Component | Responsibility |
|---|---|
| Game state | Tracks positions, directions, pellets, and timers |
| State machine | Determines whether the ghost is chasing, scattering, or frightened |
| Target selection | Calculates the destination for the active mode |
| Move generation | Finds legal directions at the current position |
| Movement decision | Selects the next direction |
| Collision system | Checks contact between Pac-Man and ghosts |
This separation is useful because each component can be changed without rewriting the entire AI.
For example, you could replace the greedy movement strategy with A* while keeping the same target-selection functions.
Or you could add a new ghost personality without duplicating the entire movement engine.
This is one of the most useful software design lessons in the game: separate the decision about where to go from the algorithm used to move there.
9. Could We Make Pac-Man AI Smarter?
Definitely.
The original system is effective because it was designed around the game's constraints, not because it solves every possible decision optimally.
Modern implementations can explore several alternatives.
Option 1: A* Pathfinding
A* searches for a route through a graph using the cost already incurred and an estimate of the remaining cost.
It can be useful when you want ghosts to follow globally optimal routes rather than making only local decisions.
However, if every ghost always takes the shortest route directly to Pac-Man, the game may become too easy to predict or too difficult to survive, depending on speed and coordination.
The algorithm should serve the intended gameplay, not just minimize path length.
Option 2: Predictive Targeting
Instead of aiming at a fixed number of tiles ahead, a ghost could estimate where Pac-Man will be after a short time.
For example:
[
P_{future} = P_{current} + v \Delta t
]
Where (v) is the player's velocity and (\Delta t) is a prediction horizon.
This approximation works in simple movement environments, but a maze introduces walls and turns. A better predictor would account for legal routes and likely player choices.
Option 3: Coordinated Ghosts
The original ghosts use individual targeting rules. A modern system could add explicit coordination.
For example:
- One ghost follows Pac-Man.
- Another targets an upcoming intersection.
- Another attempts to block an escape route.
- Another stays near a strategically important area.
The system could assign these roles dynamically based on the game state.
This would be a different AI design, not a description of how the original arcade game works.
Option 4: Reinforcement Learning
You could train an agent to play Pac-Man using reinforcement learning.
The agent would observe the game state, choose an action, and receive a reward based on the result.
A reward function might consider:
- Pellets collected.
- Survival time.
- Lives lost.
- Power pellets collected.
- Distance to remaining objectives.
But defining the reward function is only part of the problem. You also need to choose the observation representation, action space, training method, and evaluation criteria.
And if the goal is to reproduce the classic ghost behavior, reinforcement learning is probably unnecessary. A rule-based implementation is easier to understand, control, and debug.
10. What Pac-Man Teaches Us About AI Design
There are several broader lessons here.
1. Complex behavior can emerge from simple rules.
The ghosts don't need an elaborate model of Pac-Man's intentions. Their different target calculations and movement constraints can produce the appearance of coordinated pursuit.
2. State is as important as the algorithm.
The same ghost can behave differently depending on whether it is in Chase, Scatter, or Frightened mode. An algorithm cannot be understood independently of the state in which it operates.
3. Local decisions can be enough.
A full search of the maze is not always necessary. A constrained decision rule can work well when the environment and movement rules are designed around it.
4. Imperfections can become part of a system's identity.
Pinky's upward-targeting quirk is a famous example of an implementation bug becoming a recognizable gameplay behavior.
5. Predictability is a design choice.
A game needs rules that players can gradually understand, but it also needs enough variation and pressure to remain interesting.
The goal isn't necessarily to make the enemy as intelligent as possible. It's to make its behavior appropriate for the experience the game is trying to create.
Conclusion
Pac-Man is a useful example of how much can be achieved without machine learning.
Its AI combines state machines, target calculations, distance-based decisions, movement constraints, and carefully timed behavior changes.
The four ghosts share much of the same underlying movement logic, but their different targeting strategies make them feel like distinct opponents.
That is the most interesting part of the design: the personality comes largely from the rules, not from a complex model of intelligence.
If you're building a game AI, it's worth studying systems like this before reaching for a neural network. Sometimes a small number of well-designed rules, combined with the right environment, can produce exactly the behavior you need.
Further Reading
- The Pac-Man Dossier — Game Developer: A detailed breakdown of the original game's ghost behavior and targeting rules.
- The Pac-Man Dossier — Phil Rodgers' archive: An archived version of the detailed technical analysis.
- Pac-Man game mechanics and AI implementation: An implementation-oriented overview of ghost states, targeting, and movement.
What would you use for your own Pac-Man implementation: the original rule-based approach, A* pathfinding, or reinforcement learning? And why?
Top comments (0)