A devlog by Neeraj Kotwani, who builds every game on Elipar.
Checkers looks like the easy one. It is chess with a quarter of the rules and no special cases. In practice two things in it are genuinely easy to get wrong, and one of them I got wrong by picking a number that felt sensible rather than measuring anything.
You do not get to choose your move
The rule most casual implementations drop is mandatory capture. In proper draughts, if any of your pieces can take, you must take — a quiet developing move is not legal while a capture is on the board. And a capture is not one hop: if the landing square offers another jump you keep going, and the whole chain counts as a single move.
That rule is the game. Without it checkers is a gentle shuffling exercise. With it, the entire tactical content appears: you win by offering a piece, because your opponent is forced to take it, and the taking is what opens the line you actually wanted.
So move generation does not collect moves and filter afterwards. It builds captures and simple moves into separate lists, and if there is a single capture anywhere on the board for that colour it returns only the captures and discards every simple move outright. Chains are found by recursing from each landing square, so one "move" in the returned list may be a four-piece sweep across the board.
One convention worth stating because it surprises people: a piece that reaches the back rank and is crowned stops there, even if another jump is available from the new square. That is the standard rule and it is deliberate, not a missing case.
The number I guessed, and what timing it said
The opponent is minimax with alpha-beta pruning — look ahead N moves, assume the other side plays well, discard branches that cannot beat what you have already found.
N was 5. I chose 5 because it felt like a reasonable compromise between strength and not hanging a phone, which is to say I chose it for no reason at all. Then I timed it on a representative midgame position: 44 milliseconds.
44ms. I had been protecting a budget I was not within two orders of magnitude of spending.
The thing to time is not a comfortable midgame position, though — it is the worst one, which here is the opening board, where every piece is on and the branching factor is at its highest. At depth 8 that comes in at 639ms, and drops to between 28ms and 424ms in later rounds as pieces come off and the tree narrows.
639ms sounds slow until you notice the game already waits 450ms before the AI moves, on purpose, so that it reads as thinking rather than snapping back instantly. The worst case overshoots a delay that was already there, once, on the first move of the game. So the setting is 8, and the opponent is meaningfully stronger than the one I nearly shipped for no reason.
The general version of this: if you have a constant in your code that trades quality against speed, and you picked it by feel, you have probably picked it wrong in one direction or the other. Timing it takes ten minutes.
Ordering captures first, for free
Alpha-beta only prunes well if good moves are examined early — find a strong line first and everything worse can be cut off quickly. A real engine spends serious effort on move ordering.
Here it is one line: sort the candidate moves so the longest capture chains go first. It costs a sort and it is a decent proxy for "this move is probably good", which is all the ordering has to be.
The position scoring is similarly plain. A piece is worth 1, a king 1.6, and an uncrowned piece gets a very small bonus for how far it has advanced toward the back rank. That advancement term is tiny on purpose — large enough to break ties between otherwise equal moves and push pieces forward, small enough that it never talks the engine into trading a piece for territory.
Testing rules that look fine when they are broken
Checkers bugs are not crashes. A capture that was skipped, a chain that stopped one jump early, a king that forgot it can move backwards — all of those produce a game that plays perfectly smoothly and is simply not checkers. You will not catch them by having a game against it.
So they are tested against constructed positions, directly. A board where one piece has a capture available and a different piece has a tempting simple move, asserting the simple move does not appear in the legal list at all. A two-jump chain, asserting it comes back as a single move carrying two captures and the correct landing square. Crowning on the back rank. Win detection in both directions, with the right score reported each time.
One more that is easy to skip and worth having: a semi-realistic midgame position where the only assertion is that whatever the AI chose was present in the pre-computed list of legal moves. It does not check the move was good. It checks the opponent cannot cheat, which is a different and more important property.
On top of that, a real browser test that clicks actual squares through the real handlers — select, move, AI thinks, AI answers — because engine tests that call functions directly will happily pass while the thing between a finger and the engine is broken.
Scoring that pays for a good loss
Sixty points per piece you capture, plus 400 for winning. Captures pay whether or not you win, so a long fight you narrowly lose is worth more than a short one — which matches how the game actually feels, and means a beginner sees their score climb while they are still losing.
Checkers is free in a browser, no account, no download. Mandatory captures, real multi-jumps, and an opponent whose search depth I can tell you the timings for.