A devlog by Neeraj Kotwani, who builds every game on Elipar.
Mahjong solitaire has one rule that makes it a game: you can only take a tile that is uncovered and has a free left or right edge. Everything else — the matching, the four-of-a-kind suits, the flowers and seasons — is bookkeeping around that single constraint.
It is also the rule that makes dealing hard. The obvious way to start a game is to shuffle the tiles and drop them onto the layout. That takes about four lines and produces, routinely, a board that cannot be finished — a pair buried under each other, or a last four tiles arranged so that two of them are permanently blocked. The player does everything right and runs out of moves.
Take the board apart, then play the film backwards
So the deal is never shuffled. It is generated by solving an imaginary full board and recording the order.
Start with every slot occupied. Work out which slots are currently free under the real rule. Pick two of them, mark them gone, and write the pair down. Repeat until the board is empty. That list of pairs, played in reverse, is the deal: the first pair you removed is the last pair that will be matched, and because each pair was free at the moment it was taken, there is a guaranteed path through the finished board in the exact reverse order.
The faces are assigned afterwards, onto the pairs. Which is why the layout and the tile set are completely independent of each other here — the generator never needs to know what a bamboo is.
The greedy version strands itself
That description is clean and the first implementation of it failed often, for a reason that is obvious in hindsight.
Partway through, the remover can reach a state with exactly one free tile left. Two tiles remaining, one sitting directly on top of the other: the top one is free, the bottom one is covered, and you need two. The generator has painted itself into a corner and the whole deal has to be thrown away.
Two things fix it, and both are small. Before committing to a pair, the generator removes it provisionally and checks that at least two free tiles still exist afterwards — a one-step lookahead. If not, it puts them back and tries a different pair, up to a couple of dozen times. And the candidates are sorted so higher layers go first, which drains stacks from the top down and makes the dead end rare rather than routine.
If a whole deal still fails, it starts over; sixty attempts are allowed before it gives up, which in practice never happens. Measured across the three layouts: 600 deals out of 600 generated successfully, worst case 8ms for the 144-tile turtle. And separately, every single step of 120 sampled deals was checked to be a legal move at play time — proving the recorded order really is playable, not just that the generator believed it was.
A solvable deal is not the same as an unloseable one
This part is worth being precise about, because it would be easy to oversell.
A guaranteed solution exists in every deal. That does not mean every order you play it in reaches that solution. Four identical tiles are on the board and you can match them as the wrong two pairs, splitting them so the remaining two can never meet. That is authentic mahjong — it is the actual skill of the game — and it is what the three shuffles are for.
A shuffle re-runs the same generator restricted to whatever tiles are still on the board, so it always hands back a position that can still be finished. It never just scrambles.
I measured how much that matters rather than asserting it. A reasonably competent bot clears the small Courtyard layout 12 times out of 12 with no help at all. On the turtle it manages 4 out of 12 unaided — and 12 out of 12 when it is allowed to shuffle out of a dead end. The shuffles are not a courtesy, they are load-bearing.
Forty-two faces, all drawn by hand, none of them emoji
A real set is thirty-four faces plus eight bonus tiles. Flowers match any flower and seasons match any season, which is a rule nobody knows unless you tell them, so those tiles carry a small F or S.
They are inline SVG — geometry for the numbered suits, shapes plus a Latin letter for the honours, which is how Western sets have always rendered them. Deliberately not emoji and not CJK text: this catalogue has been caught out by inconsistent glyph coverage before, and a mahjong tile that a player cannot tell apart from another tile is not a cosmetic problem, it is an unplayable game. A smaller layout takes a prefix of the face list and keeps both bonus groups whole, because half a group would silently break the any-flower rule.
The dimming was backwards
Every assertion passed and the game still looked wrong in a screenshot. I had been greying out blocked tiles hard, to make the free ones obvious. On the full turtle that renders about four fifths of the board as mud.
Which is the opposite of what a player needs, because you plan this game by reading the whole board — including the tiles that are about to open up. Blocked tiles are now only about 14% darker, still perfectly readable, and the "you can tap this" signal comes from lifting the free ones instead: a warm rim, full brightness, a deeper shadow. If a game needs to mark out a subset of a board, lift the subset rather than suppressing everything else.
Mahjong Solitaire is free in a browser, three layouts, no account. Every deal it gives you has a path through it, worked out before you see it.