TinyGames · how it works

Conduit

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

A piece is four bits

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.

The cross goes straight

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.

A cross is filled twice, once per axis

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.


One line that pins down every piece

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

What the tests are worth

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.


What is not promised

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.


Playing it

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.