Basic

Rules that read state and change state only

A rule that draws, or that reads the clock, or that picks a random number on its own, is a rule that cannot be tested. The discipline is that rules are functions from state to state, and everything else belongs to the parts around them.

What you will see

true then false. A rule that called Math.random itself could not be tested this way.

About 1 min of reading

Try It

Make one rule testable

1

Write the pure rule

printf 'function collect(w, roll) { if (roll < 0.2) { w.items.push(w.pickup); return true; } return false; } ' | tee game/rule.js

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The roll is an argument. The rule does not call Math.random.
2

Own the seed in the caller

printf 'let seed = 7; function rnd() { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed / 0x7fffffff; } ' | tee -a game/rule.js

Before you run it, say out loud what it will print. Then open the answer.

What it prints
A seeded generator the caller controls. Same seed gives the same sequence.
3

Replay a run

node -e 'const m=require("./game/rule.js"); let s=7; const r=()=>{ s=(s*1103515245+12345)&0x7fffffff; return s/0x7fffffff; }; console.log(r(), r(), r())' 2>/dev/null || echo 'same seed must give the same three numbers twice'

Before you run it, say out loud what it will print. Then open the answer.

What it prints
The same three numbers on every run. That is the replay.
4

Test the rule with a fixed roll

call collect with roll 0.1 and roll 0.9 and print the results

Before you run it, say out loud what it will print. Then open the answer.

What it prints
true then false. A rule that called Math.random itself could not be tested this way.
Do not call the random source inside a rule. The rule then cannot be replayed, and the rare bug becomes unreproducible.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

State to new state through a rule
1 Caller supplies state and inputs
↓
2 Rule computes the new state
↓
3 Rule returns the new state
↓
4 Caller emits effects from the difference
Why this works
What this buys

A replay. Record the seed and the inputs and the state replays exactly, which is the only way to chase a rare game bug.

Where purity is wrong

For output only, like a sound or a particle burst. Those are effects the caller emits from the returned state, not inside the rule.

Examples

One in, one out

The rule takes the state and returns the state. It takes no clock and no canvas, and it reads nothing from the outside.

Randomness is passed in

A rule that needs randomness takes a number the caller produced. The caller owns the seed, so a run can be replayed.

Test the rule alone

Give a state and check the new state. No browser and no timing, which is why the rule is testable at all.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Movement, gravity, and collision that does not tunnel
Previous Lesson
Movement, gravity, and collision that does not tunnel
Next Lesson
Tuning numbers so the game feels right
Tuning numbers so the game feels right →