BE AWESOME
Settings
Games
📅 May 11, 2026 · ⏱️ 5 min read

Chess: Where 'Close Enough' Isn't an Option

Castling, en passant, and promotion all depend on history, not just the current board — and check detection means simulating every candidate move.

This post is about Chess: Where 'Close Enough' Isn't an Option
Play Chess →

Chess is the first game on the site where "getting the rules right" is the entire bar, not just a starting point. Nobody's going to forgive a buggy castling implementation the way they might shrug off a slightly janky jump arc in a platformer. The rules of chess are centuries old, extremely well specified, and every person who plays this has almost certainly played real chess before and will notice immediately if something behaves wrong. That's a different kind of pressure than anything else I've built so far — there's no room for "close enough."

The three rules everyone gets wrong on a first pass

Basic piece movement is genuinely easy — a rook moves in straight lines, a knight jumps in an L, done. The three rules that actually separate a real chess implementation from a toy one are castling, en passant, and promotion, and all three have the same shape of problem: they're not just "can this piece move here," they're conditional on history. Castling requires knowing that neither the king nor that specific rook has ever moved before, not just that the squares between them are currently clear. En passant only exists for exactly one move immediately after an opponent's pawn advances two squares past yours — miss that window by even one move and the capture is no longer legal. Neither of those can be answered by looking at the current board state alone; both need memory of what happened earlier in the game, which meant the board representation had to track more than just "what piece is on what square" from the start.

Check detection has to run on every hypothetical move, not just the real one

The rule that a move is illegal if it would leave your own king in check sounds like a simple validation step, and it's actually the most computationally involved part of the whole engine. To confirm a candidate move doesn't expose your king, you effectively have to simulate making that move, then check whether any opposing piece could capture the king from the resulting position, then undo the simulation and move on to checking the next candidate. For a piece with several legal-looking moves, that's several full hypothetical board states evaluated before the player even sees which moves are actually legal to click. Checkmate detection builds directly on top of this same machinery — a king is in checkmate when it's in check and there's no legal move, for any piece, in the entire position that gets it out of check, which means the check simulation has to run across every piece's every candidate move before the game can confidently declare the game over.

A game doesn't only end in checkmate

Checkmate is the ending everyone thinks of first, but a correct chess implementation has to handle several other ways a game legitimately ends, and each one is its own small piece of logic layered on top of the same check-detection machinery. Stalemate — no legal move available, but the king isn't currently in check — draws the game rather than losing it, which is an easy rule to state and an easy one to get backwards if checkmate and stalemate share too much code without a careful boundary between them. The fifty-move rule and threefold repetition are the two I spent the most unglamorous time on: tracking how many moves have passed without a capture or pawn move, and tracking whether the exact same board position has now occurred three times, both require the game to remember its own history in a structured way rather than just knowing the current position. Neither rule comes up often in a casual game, which is exactly why they're easy to skip and exactly why skipping them would have been noticed eventually by someone playing a long, grindy endgame.

Clicking is the whole interface, so it has to feel forgiving

Moving a piece is click the piece, then click the destination square — no drag-and-drop required, which matters a lot on touch devices where a precise drag gesture is much less reliable than two separate taps. Once a piece is selected, its legal destination squares highlight immediately, which isn't just a nice-to-have — it's doing real communication work, silently teaching the underlying rules to anyone who doesn't already have them memorized. A player who's fuzzy on exactly how a bishop moves doesn't need to be told; clicking it and seeing exactly where it's allowed to go answers the question without a single line of instructional text.

Why there's a computer opponent instead of just local two-player

Local two-player, same device, was the easy version to ship — it needs none of the rule engine to make decisions on its own, just to validate whatever a human tells it. A computer opponent means that same rule engine has to be reused to generate and evaluate candidate moves for itself, which was the real test of whether the underlying move-generation code was actually clean and correct, rather than just correct enough to validate a human's intent. If the legal-move generator has a subtle bug, a human player might never trigger it in casual play. A computer opponent evaluating every legal move in a position, every single turn, finds that bug far faster than a person ever would.

I'll say plainly that the computer opponent isn't strong — it plays a fully legal, reasonable game, not one built to punish anyone who isn't already good at chess. A genuinely strong engine, with real search depth and position evaluation, is its own deep project, and that wasn't the goal here. The goal was a completely correct rules implementation with an opponent that's actually there when a second player isn't around, and those are two different bars to clear. I cleared the first one carefully. The second one is good enough for now, and it's the part I'd most like to come back and actually strengthen once the rules foundation has proven itself solid under real play.

Hello!