Guides·6 min read

How the Computer Decides Its Move in Our Chess Game

Four difficulty levels, one engine. The hardest problem was not making it strong, it was making it lose convincingly.

The Fighting Chess interface with its war log of moves in algebraic notation beside the slain pieces panel.

Our chess game has four opponents: Squire, Knight, Warlord and Grandmaster. They are the same engine with four numbers changed.

The interesting part is not how the strong one plays well. That is a solved problem with sixty years of published technique behind it. The interesting part is how the weak one loses convincingly, because that turns out to be the harder design problem and it is the one that decides whether a game is fun.

Looking ahead

The basic method has not changed since the 1950s and is easy to state.

Play a move in your head. Imagine every reply. Imagine every reply to those. Keep going for a few layers, then score the positions you have reached. Now work back up assuming both sides always pick their best option: the value of a position is the best you can do, given the best your opponent can do.

That is it. Everything else is making it fast enough to look further, because looking further is what strength is.

A crucial refinement, because the naive version is unusably slow. If you are checking a reply and discover it is already worse for you than something you have found elsewhere, you can stop. It cannot change your decision no matter what else it contains. That single observation removes most of the work, and it is why a game running in a browser tab can look six moves ahead at all.

Two more tricks that matter. Try the promising moves first, because a good move found early makes the cut-off above trigger far more often, so the search reorders captures and previous best moves to the front. And remember which quiet move refuted things at this depth last time, because the same defensive resource tends to work again in sibling positions.

Judging a position

At the bottom of the search you have to put a number on a position, and this is where a chess engine reveals what it actually understands.

Counting material is the obvious part and the smallest part. What our evaluation also reads:

  • Where the pieces are, not just what they are. A knight in the centre is worth more than one in a corner.
  • Pawn structure. Doubled pawns on the same file are a weakness.
  • The bishop pair, which is worth a little more than two individual bishops.
  • The phase of the game. It counts remaining material to decide whether this is a middlegame or an endgame, and re-reads the board accordingly. A king that should hide behind pawns in the middlegame should march up the board in an endgame. The same king position is good or bad depending on when it is.

The horizon problem

Here is the flaw that makes a naive engine play strangely, and the fix is a nice piece of thinking.

Say the engine looks exactly four moves ahead. On the fourth move it captures a pawn with its queen and scores the position as a pawn up. Excellent. On the fifth move, which it never looked at, that queen is taken.

It has stopped in the middle of a fight and counted the half it liked. Every fixed-depth search does this, and the resulting play looks greedy and slightly mad.

The fix is to refuse to stop mid-exchange. When the main search runs out of depth, a second smaller search continues, but only following captures, until the position is quiet. You are no longer judging a board with a queen hanging on it; you are judging where the sequence actually ends. Ours follows quiet-down sequences up to six deep at the hardest level.

Time, not depth

A subtlety that matters in a browser. The engine is not told to search four levels. It is told it may think for a certain number of milliseconds, and it searches one level, then two, then three, keeping the best answer it has, until the clock runs out.

That sounds wasteful, since the shallow searches are repeated work. It is not, for two reasons. The shallow ones are almost free compared to the deep ones, and their results are what tell the deeper search which moves to try first, which as above is most of the speed-up. It also means the opponent always has an answer ready, so it never freezes the tab.

Our four levels get 200 milliseconds, 500, 1400 and 3000.

The Fighting Chess interface showing the war log listing moves in algebraic notation alongside the slain pieces panel.
The war log keeps proper notation, so the game is a real record of a real game.

Losing convincingly

Now the design problem.

The obvious way to build an easy mode is to make the engine worse at everything: search less, judge badly. What that produces is a player who is uniformly incompetent, which is unpleasant to play. It hangs pieces for no reason and then, because the search still runs, occasionally finds something brilliant. The behaviour is incoherent, and human beings read incoherence as unfair rather than easy.

Our approach separates the two things. Each level has a blunder rate: a probability, checked before searching at all, that it will simply take a reasonable move at random instead of the best one. Squire blunders 45% of the time, Knight 16%, and the top two never.

The important detail is what "reasonable" means. Even when blundering, it filters out moves that give a piece away for nothing, and only picks at random from what is left. So a weak opponent plays aimlessly rather than suicidally. It misses your plan, it wanders, it lets you build an attack. It does not give you its queen and make the win feel hollow.

That same randomness does a second job: it gives the openings variety. An engine with no random element plays the identical first six moves in every single game, and a game you can predict from move one is not one you play twice.

The last small thing

One more line that changes how it feels. The engine slightly penalises positions it has already visited in this game.

Without that, a satisfied engine shuffles. It has a good position, sees no way to improve, and moves a rook back and forth while you try to make progress. Technically correct and infuriating to sit opposite. A small penalty for repetition pushes it to try something, which is what a human opponent would do.

What this means for a commission

An opponent is a scope decision, not a default. Chess needs one. A platformer starring a four-year-old does not, and adding one would be work spent in the wrong place.

Difficulty is a design problem before it is a technical one. The question is never "how do we make it weaker", it is "what should losing to this feel like". We hit the same question in a completely different genre, and the answer is in designing one game for a four-year-old and an adult.

None of this is where the size goes. The entire engine, four difficulties, evaluation and search included, sits inside a 104KB file with all the artwork and sound. What decides a game's size explains why.

The chess build is playable in the arcade. Squire is genuinely beatable; Grandmaster is not, by me.

◆ Questions

Common questions

How does a computer decide its move in chess?+

It looks ahead. It plays a move in its head, imagines every reply, every reply to those, and scores the positions at the bottom. It then assumes both sides pick their best option and works back up. The strength comes from how deep it looks and how well it judges the positions it finds.

How do difficulty levels work in a chess game?+

In ours, four numbers change: how many moves ahead it looks, how long it may think, how often it deliberately picks a worse move, and how far it follows a sequence of captures. Easy looks one move ahead and blunders almost half the time; the hardest looks six ahead and never does.

Why does an easy computer opponent still feel unfair?+

Because most easy modes just make the engine dumber everywhere, which produces a player that misses the obvious and then finds something brilliant. Ours instead makes a normal choice most of the time and a deliberately weaker one at a set rate, and even the weak choice avoids giving material away for nothing.

Can a custom game include a proper opponent to play against?+

Yes. The chess build in our arcade carries a full engine with four difficulty settings, and it is one hundred and four kilobytes. Whether a commission needs one depends entirely on the brief.

Is a computer opponent expensive to include?+

It is a bigger part of the build than the artwork for a game like chess, and irrelevant for a platformer. It is a scope decision to raise at the brief stage rather than a fixed cost.

  • process
  • behind the scenes
  • craft
  • difficulty
◆ Keep reading

Related from the journal.