TinyGames · how it works
Pipe Mania. The ooze has already started. Lay pipe ahead of it, and every piece you put down is one you cannot take back once it is wet.
Open index.html. No build step, no dependencies, no images — the pipes are drawn.
conduit/
index.html markup
style.css the frame around the canvas
js/plumbing.js the pieces, the flow and the arithmetic
- no pixels, no engine, no DOM
js/conduit.js the playable layer
test-plumbing.js node test-plumbing.js
Which of its sides are open: north, east, south, west. Six pieces have two open sides — two straights and four elbows — and one has all four.
Almost everything here is arithmetic on those bits, and two facts about it look like details and are actually the game.
A cross has all four sides open, and the obvious reading of "which side does the flow leave by" is any of the others. That makes it a junction, and a junction turns Pipe Mania into a maze: the ooze would pick a way and the player could not predict which.
A cross is two pipes that happen to share a square. Enter it going down and you leave going down.
That follows from the above, and it is why "has this pipe got ooze in it" is a set of sides rather than a flag. Store one boolean per square and the second crossing is refused — the cross stops being a cross and becomes an expensive straight.
If the ooze goes in at A and out at B, then in at B must come out at A.
Flow through a piece is reversible. That single property, checked over all sixteen
ways into all seven pieces, fixes the behaviour of every one of them without naming
any of them — and it pays for itself in the drawing code, where the ooze has to be
shown part-way across a square. That needs the side it came in by, and the rules
only record the side it will leave by. It does not need storing: the way in is
exitFrom(piece, out).
The flow always stops. A pipe can be filled only once, a cross once per axis, so the ooze cannot go round a loop twice and a board has only so many sides in it. The test runs 400 boards packed at random with loops and confirms every one of them comes to a halt — then builds a closed square of pipe with the once-only rule removed and watches the same loop followed 200 times.
$ node test-plumbing.js
57 passed, 0 failed
Seven mutants. Six broke something real; the seventh changed nothing on purpose.
| mutation | result |
|---|---|
| the cross is a junction (it turns) | caught (6 assertions) |
| a wet pipe can be re-entered | caught (4) |
| a cross is filled per square, not per axis | caught (2) |
| a wet pipe can be replaced | caught (2) |
| the bag is all crosses | caught (2) |
| flow leaks through closed sides | caught (1) |
exitFrom wrapped, behaviour unchanged |
passes, as it should |
That last real one is worth a note. It survived the first version of the suite, and it was an equivalent mutant for the seven real pieces: every one of them has two open sides, so "the sides that are not the entry" is either one side or two, and two is already refused. The dropped guard only matters for a one-ended stub — which is what a source is. That case is now tested in its own right, rather than the guard being written off as dead code.
Three of the test's own pipe diagrams were wrong on the first run: one had elbows facing the wrong way so the "endless" loop stopped after two steps, and one counted twenty ways into the pieces where there are sixteen. The code was right in all three.
Nothing. After Crossing and Rockfall it is worth saying explicitly: the board starts
bare, the player lays every pipe on it, and any square the ooze points into can be
built on. A board cannot be unwinnable, so boardFor does not pretend to check
that it is not. The source is only kept off the border and clear of blocked squares
so that the first piece is never an impossible one.
Tap a square to lay the next piece from the queue down the left. Lay over a pipe the ooze has not reached yet as often as you like; once it is wet it is permanent.
Space or Enter lets the ooze go early and banks what is left of the grace period
as score — the whole reason to lay quickly rather than carefully.
Fill the target length before the ooze runs out of pipe and the level is cleared. Run it into bare board, off the edge, or into a pipe that is already full, and it is over.
Advertisement