All nine parts
A city builder lives or dies on whether the town feels alive. Under the cartoon streets there is a small, honest simulation. Here are its main ideas, with the real algorithms simplified.
Keeping the lights on: a graph problem
Power poles and generators are the nodes of a graph. The rule is simple: every pole links to its three nearest neighbours within a maximum distance, and then electricity flows outwards from every generator. Whatever the flow reaches is powered; whatever it cannot reach is dark.
This is a flood fill, the same algorithm a paint bucket uses. It is cheap, and it explains itself visually, which matters: a rule the player can see on the map is a rule the player trusts.
function linkGrid(nodes, maxLink) {
// 1. link each node to its three nearest neighbours in range
nodes.forEach((a, i) => {
nodes
.map((b, j) => ({ j, d: Math.hypot(a.x - b.x, a.z - b.z) }))
.filter(o => o.j !== i && o.d <= maxLink)
.sort((p, q) => p.d - q.d)
.slice(0, 3)
.forEach(o => connect(nodes, i, o.j)); // links both ways
});
// 2. flood outwards from every generator
const queue = [];
nodes.forEach((n, i) => { if (n.gen > 0) { n.live = true; queue.push(i) } });
while (queue.length) {
const n = nodes[queue.pop()];
n.adj.forEach(j => { if (!nodes[j].live) { nodes[j].live = true; queue.push(j) } });
}
return nodes;
}
Having power is not all-or-nothing, though. Supply is compared with demand across the city, and batteries add a share of their stored charge. The result is a ratio between 0 and 1, so a town that is slightly short of power is slightly unhappy, not plunged into darkness.
Services fade with distance
Clinics, schools, police posts and water points each have a reach and a strength. For any home we add up the strength of every building that reaches it, but the strength fades the further the home is from the service:
function coverAt(providers, x, z, need) {
let v = 0;
for (const p of providers) {
const d = Math.hypot(p.x - x, p.z - z);
if (d <= p.reach) v += p.strength[need] * p.efficiency * (1 - FALLOFF * d / p.reach);
}
return Math.min(1, v); // two clinics do not make you more than fully covered
}
Why linear? Because it is easy to explain ("the further away, the weaker") and easy to draw as a ring around the building. Every number lives in a tuning file that can be edited and reloaded, so balance is a conversation, not a code change.
From coverage to mood to score
For each home the game looks at six needs: power, water, safety, health, education and roads. Each becomes a number between 0 and 1 and they are blended into the home’s mood. The city’s score and its one-to-five star rating come from the homes’ moods, jobs and the way the town is run. There is no hidden machinery: when a family is unhappy, the card says which need is short.
The monthly tick
Everything runs on the month. Systems subscribe to the month event rather than running their own timers, which keeps them in step with each other and makes pausing or fast-forwarding trivial. The simulation does not draw anything: it reads the world, computes numbers, writes numbers back, and lets the renderer notice.
Floods: risk as a landscape
Rain is risky only where water would gather. The flat island has a few low basins, each a point and a radius; a spot’s flood risk is highest at a basin’s centre and fades to nothing at its rim. The shoreline adds a second source of risk close to the sea. Drainage and sea walls lower the risk locally.
function floodRisk(x, z, { basins, limit, factor = 1 }) {
let r = 0;
for (const [bx, bz, radius] of basins) {
const d = Math.hypot(x - bx, z - bz);
if (d < radius) r = Math.max(r, 1.15 * (1 - d / radius)); // deepest in the middle
}
const shore = Math.min(limit - Math.abs(x), limit - Math.abs(z));
if (shore < 9) r = Math.max(r, 0.9 * (9 - shore) / 9); // and near the sea
return Math.min(1, r * factor);
}
On cities built on shaped ground (Part 4), the same question is answered from the height map instead: low ground next to the water floods first.
The yearly report card
Once a year the council grades the town A to E. Stars count most, then whether the books balance, whether floods hit, and how happy people are. The grade pays a bonus, so doing well makes the next year easier, while doing badly makes it harder but never hopeless. A kind game is allowed to be gentle about failure.
What we learned
- Prefer rules a player can see. A flood fill on a map explains itself; a hidden multiplier does not.
- Put every tunable number in one place, with a plain-language description. Designers will use it; you will thank them for it.
- Let time be one event, not many timers.
The next part is about drawing this world, and about making it run on a phone that is also doing three other things.