Worm Man Has Fingers
A free, no login required, web-based game. Part escape room. Part maze.
A free browser game with no accounts and nothing to install: part escape room, part maze. Every room is a photograph and a riddle, and the only way forward is to work out what it wants you to type. The whole thing is framed as a place that has not yet been fully explored, mapped, or approved for visitors, so the game plays more like wandering somewhere you probably should not be than working through a quiz. There are dozens of rooms across branching paths, plus an ending for anyone who makes it through.
Built on Flask with Jinja templates, with well over a hundred routes. Each room is a pair of routes: one that serves the page and one that checks the answer. The game tracks each visitor's progress through the rooms. The entrance is a text prompt that responds to whatever you type. Known phrases trigger specific behavior, and everything else gets a greeting that depends on the time of day, followed by a sentence generated with Markovify. The result is a place that feels like it is answering back. The site also has basic abuse protection: the entrance prompt is filtered and rate limited, and suspicious traffic is logged.
The biggest job was structural. The game had grown into a single module of more than 2,000 lines, with every room route repeating the same three-way guard block for pre-loading, banned visitors, and fallback. It was split into a proper package: an app instance, a core module for access control and logging, and route modules grouped by branch of the game. A single decorator replaced the repeated guard. The risk in a refactor like that is silently dropping or renaming a route in a game where every route matters. To prove nothing changed, the route map, endpoint names, and allowed methods were diffed before and after and came out identical, and a scripted playthrough with Flask's test client walked the whole flow from the entrance through answers, wrong answers, help, a 404, and the ending. The split also exposed two latent bugs. One route had been reading a variable that another function set as a side effect in the same file, which stops working when the code moves to a separate module. Another checked a resubmission guard that nothing ever wrote to. Both were fixed, and the codebase was updated for the current Flask release while at it.