Ever wondered how the computer on LocalChess decides what to play? It isn't magic, and it isn't connected to a server — the entire opponent runs in your browser tab, on your own processor, in about 43 KB of JavaScript.

Most "how chess engines work" articles describe Stockfish, a program with neural networks and a thousand-line evaluation. This one describes a small engine you can actually reason about end to end, with the real numbers it uses. Everything below is what the shipped code does, not a simplification.

The four stages of a move

Every time it's the computer's turn, the engine runs the same pipeline:

  1. Generate every legal move in the position.
  2. Search ahead through the resulting positions, alternating sides.
  3. Evaluate the quiet positions it reaches, scoring them in centipawns.
  4. Choose the move leading to the best score it can guarantee.

The interesting parts are how it keeps stages 2 and 3 fast enough to run between your moves without freezing the page.

Stage 1: Generating legal moves

Before choosing, the engine needs its options: every pawn push, knight hop, capture, promotion, en passant and castling move the rules permit. This is the same rule logic that stops you making an illegal move, so the engine can never cheat — it is playing by exactly the constraints you are.

A typical middlegame position has 30–40 legal moves. That number is why searching ahead gets expensive so quickly: looking four moves deep at 35 options each is roughly 1.5 million positions if you check every one. The next two sections are about not doing that.

Stage 2: Evaluation — turning a position into a number

The engine scores positions in centipawns, where 100 = one pawn. The base is material:

Pawn   = 100      Rook  = 500
Knight = 320      Queen = 900
Bishop = 330      King  = not counted

Two details worth noticing. The bishop is worth 330 to the knight's 320 — a deliberate 10-centipawn edge reflecting the bishop's longer reach in open positions. It is small enough that the engine will still trade a bishop for a knight when the position calls for it, but with nothing else to separate two options it prefers keeping the bishop. And the king has no material value at all, because it is never captured: losing it is handled separately as checkmate, scored at 1,000,000, a number chosen to dwarf any material total so that no amount of material ever outweighs mate.

Material alone would produce a very stupid engine, though. It would happily leave knights on the rim and its king in the centre, because those cost nothing materially. So on top of material, the engine adds a piece-square table — a per-piece grid of 64 small bonuses and penalties that encodes positional common sense:

  • Knights score −50 in the corners and +20 in the four central squares — a 70-point

swing, most of a pawn. A knight on the rim reaches four squares; one in the centre reaches eight, and the table prices exactly that.

  • Pawns get more valuable as they advance: +25 for occupying d4 or e4, rising to +50

across the whole seventh rank where promotion is one move away. The d2 and e2 squares carry a −20 penalty, which is the engine's way of being told to play its centre pawns rather than leave them home.

  • Rooks get +10 along the seventh rank, encoding the old principle that a rook on the

seventh is worth more than the material on it suggests.

  • The king gets +30 on b1 and g1 — the squares it lands on after

castling — and penalties as deep as −50 for wandering up the board. This is why the engine castles early without being told to.

There is one honest limitation in that last table. The engine uses the same king table in the endgame as in the middlegame, with no phase detection. But in an endgame the advice inverts: with the queens off, the king is a fighting piece and belongs marching toward the centre, which is precisely what a −50 penalty on d5 discourages. So the engine keeps its king tucked away long after it should have activated it. If you want to beat Hard, king activity in the endgame is the most reliable place to find an edge it cannot see.

The result is a single number, positive if the side to move is better. That number is exactly what drives the evaluation bar beside the board.

Checkmate, stalemate, threefold repetition and the drawn positions are special-cased before any of this runs: mate returns the extreme score, and every kind of draw returns exactly 0.

Stage 3: Searching ahead with negamax and alpha-beta

Evaluating only the current position is short-sighted — it would grab a free pawn and walk into mate on the next move. So the engine looks ahead using negamax, a compact form of minimax that exploits a neat symmetry: a position that scores +3 for me scores −3 for you. So instead of writing separate "maximise" and "minimise" logic, you write one function and flip the sign at each level.

On its own, that still means visiting every branch. The engine avoids most of them with alpha-beta pruning, which tracks the best score each side can already force. The moment a branch proves worse than something already guaranteed, the rest of that branch is irrelevant — the opponent would never allow it — so it is abandoned unsearched. In practice this cuts the tree enormously without changing the move chosen: the answer is identical to searching everything, just far faster.

Quiescence: not stopping mid-trade

There's a classic trap here called the horizon effect. If the search stops at exactly four moves and move four happens to be your queen captures a defended pawn, the engine sees "+1 pawn" and calls the position good — never noticing that on move five the queen is recaptured.

The fix is quiescence search. When the main search runs out of depth, it doesn't stop dead; it keeps going but looks at captures only, until it reaches a quiet position where nothing is hanging. This is why the engine doesn't hang pieces at the edge of its search window, and it's the single feature that most separates a bot that feels competent from one that feels random.

Iterative deepening and the time budget

The engine doesn't jump straight to its full depth. It searches depth 1, then depth 2, then 3, keeping the best move found so far. This sounds wasteful — you redo work each pass — but shallow passes are so much cheaper than deep ones that the overhead is small, and it buys something valuable: the engine always has a legal, sensible move ready.

That matters because each move also carries a wall-clock deadline. If a search explodes — a tactical position with long capture chains is the usual cause — the deadline fires, the partial search is discarded, and the engine plays the best move from the last depth that finished. The page never freezes waiting for the bot, which is the whole reason the deadline exists.

What actually changes between Easy, Medium and Hard

This is where most sites are vague. The exact settings:

| Level | Search depth | Time budget per move | Random move chance | | --- | --- | --- | --- | | Easy | 2 ply | 250 ms | 35% | | Medium | 3 ply | 500 ms | none | | Hard | 4 ply | 1100 ms | none |

A "ply" is one side's move, so Hard's depth of 4 means it sees two of its own moves and two of yours — plus the capture chains that quiescence follows past that point.

The Easy column is worth dwelling on. 35% of the time, Easy ignores its search entirely and plays a random legal move. It is not a weakened evaluation or a shallower plan; it is a genuinely good move roughly two thirds of the time and a coin flip the rest. That is a deliberate design choice for beginners: it means Easy will hang pieces in a way a merely shallow engine never would, giving a new player real chances to punish a mistake.

It also means one thing worth knowing: if Easy plays something baffling, there may be no idea behind it at all. Don't spend five minutes looking for the plan. Against Medium and Hard, every move is the output of a real search, so an odd-looking move usually does have a point.

Why it plays the way it does

Knowing the internals tells you how to play it, which is really the point:

  • It plays no book moves. There is a small openings table in the code, but it exists

only to name the opening you played and to label early moves as "book" in the analysis — the engine never consults it when choosing. Every move from move one is searched from scratch, so it plays reasonable-but-unbookish openings and leaves theory almost immediately.

  • It's a better tactician than strategist. Four ply plus quiescence catches most short

tactics, but a plan that pays off in ten moves is entirely invisible to it. Closed, slow positions are where it is weakest.

  • It won't fall for deep traps, but it will walk into long-term binds. If your idea

needs three moves to work, it will see it. If it needs twelve, it won't.

There's a fuller strategic treatment of this in how to play chess against the computer.

The same engine reviews your game

When you hit Analyze game after a match, it's this same code running again — not a different, stronger analyser. For each position it searches twice: once for the move you actually played, once for the best move it can find, both to the same depth and both using quiescence at the leaves. The difference between those two scores is your centipawn loss for that move.

Because both searches run to the same real depth, the numbers are internally consistent — you're comparing like with like. They're not Stockfish-grade absolute judgements, but for spotting where a game turned, they're reliable. If the search is cut short by its deadline, the partial result is thrown away rather than reported, so the analysis degrades to a shallower but still exact depth instead of quietly giving you noise.

Try it yourself

Play the same opening against Easy and then Hard and watch how the replies sharpen. Then run Analyze game on both and compare the evaluation curves — the Easy game will show sudden cliffs where the random-move die came up short, while the Hard game shows a smoother line with losses concentrated at genuine tactical moments.

All of it runs on your device, which is also why it keeps working with no internet connection.