Lab Puzzle #1 · 2026-05-28
Frog Tongue
Pull, slide, land on the lily pad
Play
Controls: arrow keys to move, Z to undo, R to restart. Mobile: open in a new tab — /labs/frog-tongue/play.html ↗
Controls
| Key | Action |
|---|---|
| ↑ ↓ ← → | Move the frog. The frog faces the direction you just walked. |
| X | Fire the tongue in the facing direction. The first fly it hits is dragged toward you. |
| Z | Undo one move. |
| R | Restart the stage. |
Note: Move at least once before pressing X. The tongue cannot fire without a facing.
Stages
- L1 Pull and stop — A basic introduction.
- L2 Become the stopper — Feel how your own body anchors the slide.
- L3 The heron arrives — A predator appears, mirroring your every input.
- L4 Skirt the spikes — Two spikes destroy any fly that slides over them.
- L5 Heron + spikes — Both hazards combined.
Every stage is BFS-verified solvable. Minimum move counts are roughly 4 / 5 / 9 / 12 / 14, and on L4 and L5 removing the hazards shortens the optimal path by at least two moves — the generator only keeps layouts where the hazard actually matters.
Design notes
The verb “pull” and its novelty
Major puzzle games (Sokoban, Lights Out, Snake, Tetris, Match-3, 2048) rarely treat pulling at distance as the primary verb. It might sound like Sokoban’s “push” in reverse, but adding the pulled object slides until it hits something turns it into a different game entirely. Ice-style sliding isn’t rare, but the constraint of using the player’s own position to govern where the slide stops is, as far as I can tell, almost unique to this puzzle.
Solver-driven design
All five of my first hand-built stages were unsolvable. The design fell apart because “the frog just turns on the target” was enough to win. Once I added the constraints “the player cannot stand on a target” and “the tongue only fires in the last facing,” every level suddenly required pre-turning and pushing.
With the rule encoded as a pure TypeScript function and a BFS that finishes in under a second, I could generate 1500 random layouts, keep only those that the solver could clear and where the hazards actually lengthened the path. Shipping an unsolvable puzzle is no longer possible.
How hazards earn their place
My first try at adding spikes and the heron made them decorative — the BFS routed around both. Only after adding a filter (“removing the hazard must shorten the optimal path by two or more moves”) did I get layouts where dodging the hazard is mandatory.
Converging TypeScript and PuzzleScript
I built two implementations in parallel — TypeScript for testing and a PuzzleScript port for the playable version. Every behaviour mismatch became a unit test on the TS side; I went through that loop three times before the two converged. The trickiest bug was “does a fly die when it slides onto a spike, or merely when it stops next to one?” — the two engines initially disagreed. I rewrote the PuzzleScript rule so that flies only die when they actually land on the spike cell.
A small L5 hint (no spoilers)
The first X lands the fly at (5,2); everything afterward is about how to disengage from the heron. The heron also advances on every X, so a wasted action ends the run.
Reactions (no login)
Anonymous • one of each per visitor per day