Tutorial 9 · Rules — Times, Targets & Scores
This page explains the game's bookkeeper — the part that keeps time, score and goals. It answers the two questions every game asks: "how am I doing?" and "have I won yet?". The Manor below runs all of it — 90 seconds on the clock, 100 points a trinket, 25 × every second left:
The whole configuration
const rules = new Rules({
timer: {
seconds: 90,
perRoom: false, // true = the Manic Miner air supply, reset every room;
// false = one Bruce Lee global clock
onExpire: 'die', // 'die' costs a life and the clock re-arms;
// 'end' is game over, rules.gameOver goes true
},
scoring: { // the points table, paid on rules.event(name)
collect: 100, // an item's own `points` overrides (the golden trinket)
kill: 250,
roomVisit: 50, // first entry into each room (the start room is free)
targetComplete: 500,
secondLeft: 25, // × seconds remaining, paid once on completion
},
targets: [ // the win conditions — ALL must be met
{ type: 'collect-all' }, // every item in the world
{ type: 'collect', count: 3, tag: 'key' }, // n items, optionally by tag
{ type: 'reach', room: 'the-vault' }, // set foot in a room
{ type: 'visit', rooms: ['attic', 'cellar'] }, // set foot in all of them
{ type: 'survive', seconds: 60 }, // stay alive this long
],
});
Everything is optional. Omit targets and you get the classic default — collect everything. Omit timer and time is still counted (for the HUD and time bonuses); it just never runs out.
Wiring: one bind, one tick
rules.bind(world, player); // after world.attach(player, spawn)
// then, once per update:
rules.tick();
bind subscribes to the World's events, so room visits and collects score themselves — each item's points and tag flow through. With the player bound, a non-fatal countdown expiry hurts them as { type: 'time' } — which routes through the normal contact event, so yes, a shield can cancel time itself. Anything the World doesn't know about, you report yourself:
ninjaFelled && rules.event('kill'); // scores from the table
rules.event('kill', { points: 400 }); // or override per-event
Targets and gates: the lock and the key
Give a target an id, and a World gate can demand it:
targets: [{ id: 'six-trinkets', type: 'collect', count: 6 }]
// in a room definition:
{ type: 'gate', x, y, w, h, goal: 'six-trinkets' }
bind hands the World a goalCheck hook, and the gate stays shut — solid tiles, remember — until targetDone('six-trinkets') says otherwise. That's the Manor's vault: the portcullis over the loft ladder is a rule, rendered. A gate naming an unknown id never opens, loudly visible in play — a typo fails on the first playtest, not in production.
Reading the state
For the HUD: rules.score, rules.timeLeft (seconds, or null with no timer), rules.elapsed, rules.complete, rules.gameOver, and each target's progress/done. For the moments, events (with matching single-listener onX props): 'score', 'event', 'target-complete', 'complete' (all targets met — you won), 'expire', 'game-over'.
What you've learned
Times, targets and scores are declared, not programmed — a Rules object is the game's win contract in JSON shape, and gates make objectives physical. This is the last module: you've now read the whole engine.