I recently finished building a small suite of puzzle and game solvers that all run entirely in the browser — no backend, no API calls, no machine-learning models. You paste in a Sudoku, a chess position, or a crossword pattern, and the answer comes back instantly, computed on your own device.
The fun part wasn't the UI. It was that each puzzle turned out to be a textbook excuse to reach for a different classic algorithm. Nine solvers, and I got to use constraint propagation, adversarial search, heuristic search, brute-force scanning, and plain old pattern matching — the stuff that shows up in an algorithms course and then, in most day jobs, never again.
This is a tour of which algorithm fits which puzzle, and a few of the potholes I hit along the way. Everything here is vanilla JavaScript running in a Web Worker.
Before the algorithms, the rule that shaped all of them: it has to run client-side. That's a privacy choice (your puzzle never leaves the tab) and a cost choice (no compute bill), but it's also a fun forcing function. You can't lean on a beefy backend or a hosted model — you get one browser thread (well, a Worker thread) and whatever you can compute in a few hundred milliseconds.
That budget is exactly why classic algorithms shine here. They're fast, deterministic, and small enough to ship as a script. Let's group the solvers by the technique each one leans on.
Sudoku is the poster child for constraint propagation. A cell that can only be one value forces that value; that in turn shrinks its neighbours' options, which forces more cells, and so on. Most "easy" and "medium" boards fall over from propagation alone (naked singles + hidden singles), and only the hard ones need a backtracking search on top.
The nice property: the same engine that solves the board also powers the hint feature (find the next forced cell and explain why it's forced) and a uniqueness check — count solutions, stop at two, and you know instantly whether a puzzle is well-formed.
Minesweeper is the same idea wearing different clothes. Each revealed number is a constraint: "exactly N of my hidden neighbours are mines." Treat the frontier as a system of those constraints and you can deduce two things exactly: Cells that are provably safe in every valid arrangement. Cells that are certainly mines in every valid arrangement.
And for everything the logic can't settle, you enumerate the valid mine placements over the frontier and report an exact probability per cell. So instead of "looks risky," you get "this cell is a mine 25% of the time." That exact-probability step is the difference between a toy and something you'd actually trust mid-game.
Three of the solvers are two-player games, and they all run on the same engine idea: look ahead, assume the opponent plays their best reply, and pick the move that's best for you after they do. That's minimax, and its tidier sibling negamax, with alpha–beta pruning to skip branches that can't change the outcome.
The game tree is tiny (at most 9! leaves, far fewer in practice), so you can search it exhaustively. That means the solver is perfect: from an empty board it correctly reports that every move leads to a draw with best play, and it never misses a win or a block. Tic-Tac-Toe is where you go to convince yourself your minimax is actually correct before you trust it anywhere harder.
Same algorithm, much bigger tree — so representation matters. I store each position as a pair of bitboards (using BigInt), which makes "is this a win?" a couple of bit shifts and masks instead of a loop over cells. With a 7-bit column stride, win detection is four shifts:
With that speed, an iterative-deepening negamax with a time budget gets you strong play from any legal position.
Same family again, one honesty note. The chess solver uses negamax + alpha–beta with a hand-written evaluation (material, position, king safety, mate detection) and shows an eval bar, the best move, the likely line, and mate-in-N. It plays at a solid club level.
It is emphatically not Stockfish. I think it's worth saying that out loud in the tool itself, because "chess engine" carries Stockfish-sized expectations, and a few-hundred-line negamax is a different (and, honestly, more readable) thing. It's a great way to actually see how positional search works, not a bench-topping engine.
Draw a maze, mark start and end, and you want the shortest path. That's breadth-first search — the textbook shortest-path algorithm for an unweighted grid. BFS explores in rings outward from the start, so the first time it reaches the goal, it's reached it by a shortest route. No heuristics needed; the grid is small and BFS is exact.
This is the one that fought back. The state space of the 15-puzzle is enormous, so plain BFS is hopeless. The right tool is IDA\ (iterative-deepening A\) with an admissible heuristic — I use Manhattan distance plus linear-conflict.
Two things I learned the hard way: Optimal 15-puzzle solving is genuinely slow on hard scrambles. For the 4×4 I switched to a weighted IDA\ (inflate the heuristic slightly): it returns a near-optimal solution in milliseconds instead of a perfectly optimal one in minutes. For the 3×3 it stays fully optimal and instant. My backtracking was subtly wrong and it cost me an evening. When you make a move you swap the blank with a tile; to undo it you have to restore both squares. My restore reused a variable I'd already reassigned to the new blank position, so it zeroed the wrong square and corrupted the search — which happily returned invalid "solutions." The tell: 1-move scrambles worked (the winning branch never backtracks), harder ones didn't. Lesson: always replay-verify a search solver on hard instances, not just the easy ones that pass by luck.
