Moonlit Garden: designing a calm pipe puzzle with spanning trees and coloured light
Moonlit Garden is a calm, untimed pipe puzzle. Rotate pipe tiles so light from the lanterns reaches every flower and every pipe glows. No timer, no lives, just a garden lighting up at night.
Why a pipe puzzle
"Pipe puzzle" on Google Play has a giant leader (a top app with over 200 million installs) and then a weak tail: a top-ten median around 25,000 installs, with six of the ten under 100,000 and four less than a year old. Big demand, beatable competition. The audience is older players who like calm logic games, which suits this portfolio.
The repo itself had a history. It began months earlier as a template for a different game, with translations and a server but no game code, and sat abandoned. The pipe puzzle took it over in one session.
A solved board is a tree
The design rests on one idea: a solved board is a spanning tree. Every tile connects to the lantern by exactly one path, with no loops and no loose ends.
That makes generation clean. Grow a random spanning tree over the grid from the lantern, turn each cell's connections into a pipe piece (end, straight, bend, T or cross), then rotate every piece randomly. The puzzle is guaranteed solvable, because you started from a solution.
The win check is just as clean, and it's important: any tree wins, not just the one I generated. If a player finds a different valid arrangement, it counts. Checking "is this a spanning tree from the lanterns, with every pipe lit" is easy. Checking "is this my solution" would punish players for being clever.
Colours, prisms and crystals
Each mechanic was written up as a short design decision before any code:
- Coloured lanterns (from level 20): two to four lanterns whose light must not mix. The board becomes a forest, one tree per lantern.
- One-way crystals (from level 30): turn one colour into another as light passes through.
- Moon lanterns and prisms (from level 41): split white light into two colours.
The first prism placement split flowers nine to one between colours. Now each colour gets at least a third.
Levels without these devices stay byte-identical, so levels 1 to 29 never changed when the mechanics arrived. Nobody's early progress moved under them.
Stars, not par
Each level has three-, two- and one-star goals by number of moves. A run of taps on the same pipe counts as one move, because spinning a tile three times to get where you meant is one decision. The target is called a "goal", never "par". Calm games shouldn't sound like golf.
The Journey runs from 4×4 up to 8×11 at level 131, with a vine that grows a node per level and six painted garden themes, one per 50 levels, crossfading as you go. There's a Daily Garden with a streak.
The counter that taught the wrong goal
In my own testing I kept "finishing" boards that weren't finished. All the flowers were lit, and the game wasn't over.
The status line counted flowers. So the game was teaching me that the goal was to light the flowers, while the real rule was that every pipe must glow too. A dark pipe in a corner held the puzzle open, and the counter said nothing about it.
Now the counter counts every tile, a persistent tip on the first levels says that every pipe must glow, and if all the flowers are lit but the garden isn't solved, a nudge says so. A flow test checks all three.
That's a lesson that applies far beyond this game: whatever your UI counts is what players think the goal is.
Two bugs from looking at pictures
- An opened flower hid itself. The bloom's own
.openclass matched the flower group's "open" state rule. Only a screenshot of a solved board showed it. The state is now calledlit. - A background image with a relative
url(art/...)showed in development and vanished in the built app. The build now refuses relative URLs in CSS.
The name, by the way, was "Night Garden" until I realised that collides with a well-known BBC children's programme. The package id was already fixed by then, so the store and the game say Moonlit Garden while the internals keep the old name.


