Dijkstra Maps: The Secret Behind Smart Roguelike Movement

One of the most important systems I reimplemented in Swift was the Dijkstra map system.

If you have played a good roguelike, you have already seen the results, even if you did not know the name. Dijkstra maps are one of the core tools that make monsters move intelligently, let allies follow the player, help fleeing creatures escape danger, and allow the game to evaluate space in a fast and flexible way.

A Dijkstra map is essentially a grid of numbers. Starting from one or more target cells, the game spreads outward and assigns each reachable tile a cost or distance. Tiles near the target get low values. Tiles farther away get higher values. Walls and blocked terrain are excluded or given special handling.

Once that map exists, movement becomes simple: a creature that wants to approach a target can step toward a neighboring tile with a lower value. A creature that wants to flee can do the opposite and move toward higher-value space, or use an inverted variant of the same map.

This is one of those ideas that feels almost too simple, but it unlocks a huge amount of useful behavior.

Why I Use Dijkstra Maps

In a roguelike, many systems need more than a direct shortest path. They need a fast way to answer questions like:

A normal pathfinding query is great when one actor wants one path to one destination. But Dijkstra maps are better when the game wants a global view of the dungeon.

That makes them especially useful in Brogue-style gameplay, where many actors and environmental systems interact every turn.

The Basic Idea

Suppose the player stands on one tile. That tile gets value 0.

Then all walkable neighboring tiles get 1.

Their neighbors get 2, and so on, until the reachable space has been filled.

A very small example might look like this:

#######
#4321##
#3210##
#4321##
#######

Here, 0 is the target. Lower numbers mean closer. A monster trying to chase the player can simply move to the adjacent tile with the smallest value.

That sounds basic, but it produces robust behavior with very little code.

Why This Fits a Swift Reimplementation

In the original C-style design, these systems are often tightly coupled to global arrays and procedural update logic. In Swift, I wanted the same behavior but with clearer structure.

So instead of scattering distance logic across multiple systems, I treat Dijkstra maps as a reusable grid-based utility. The same underlying propagation logic can serve multiple purposes:

This makes the code easier to test and easier to evolve.

A simplified Swift-style shape looks like this:

struct DijkstraMap {
    var values: Grid2D<Int>
    let blocked: Int = -1
}

Then a propagation step fills the grid from one or more seed positions.

Multiple Seeds Are Powerful

One reason Dijkstra maps are so useful is that they naturally support multiple starting points.

For example, instead of building a map from only the player, I can build one from:

The algorithm does not care whether there is one source or many. It simply spreads the lowest-cost values outward from all of them.

That makes it easy to represent concepts like “distance to danger” or “distance to safety” without inventing a new system every time.

More Than Just Distance

The real strength of Dijkstra maps is that the numbers do not have to mean only geometric distance.

They can also represent weighted movement cost.

For example:

That means the map can reflect not just where something is, but how attractive or dangerous each path is.

This is where the system becomes much more than “find the shortest route.” It becomes a compact way to encode tactical knowledge into the map itself.

Inverting the Map for Fleeing

One classic roguelike trick is to use a Dijkstra map for fleeing behavior.

If a monster wants to run away from the player, a direct shortest-path map is not enough. It needs to know which reachable spaces lead farther from the threat, preferably without getting cornered.

There are several ways to do this, but the core idea is the same: build or transform a map so that the actor can follow values toward safer territory instead of toward the threat.

This produces much more believable movement than simply “walk in the opposite direction,” which often gets creatures stuck on walls or trapped in dead ends.

Why It Matters in Brogue-Like Gameplay

Brogue is full of situations where movement is not purely local.

A monster may not just want to reach the player. It may want to:

Dijkstra maps provide a flexible foundation for these decisions. They are not the entire AI, but they give the AI a strong spatial model of the dungeon.

That is why they are one of the most important hidden systems in a turn-based roguelike.

Reimplementing It in Swift

In Swift, the main benefit was clarity.

The dungeon is already stored in structured grid types, so Dijkstra propagation fits naturally into that architecture. Instead of raw pointer arithmetic and scattered state, the reimplementation can express the algorithm in terms of:

That makes it easier to preserve the original gameplay behavior while also making the system easier to reason about.

And because roguelikes operate on relatively small grids, a well-implemented Dijkstra map is still very fast. It can be recomputed often enough to support dynamic tactical play without becoming a performance problem.

Final Thought

Dijkstra maps are one of those foundational roguelike techniques that quietly solve many problems at once. They help monsters chase, flee, regroup, avoid hazards, and navigate the dungeon in ways that feel smart without requiring overly complicated AI.

For a Swift reimplementation of Brogue, rebuilding this system was essential. It preserves the spatial intelligence that makes the game feel alive, while fitting neatly into a more modern and maintainable architecture.