# Gambit · Malte Therkildsen

> A board, every legal move and a search, in one file with no dependencies. Play it in the page, and count its moves against the published perft tables in your browser.

Source: https://therkildsen.dev/writing/gambit

[Writing](https://therkildsen.dev/writing)/Gambit
W01NotesEngines from nothing

# Gambit, a chess engine written from nothing

Gambit is a chess engine written from an empty file: the board, every legal move, and a search that picks one. It has no dependencies and thinks in a worker in your browser, so the page stays smooth while it does. Play it below. Its move generator is held against the published perft counts for six standard positions, and it gives the same count in every one.

PostedKind**Notes**Series**Engines from nothing**

        It is your move.

          Depth·

          Score·

          Positions·

          Per second·

        The line it expects·

          Time to think
          A tenth
          One second
          Three

          New game
          Play black
          Take back
          Turn the board

    Drag a piece, or click it and then a square. The panel shows what Gambit is weighing while it thinks: how many moves ahead it has looked, the score in pawns from white’s side, and the line it expects both sides to play. The game is kept in this browser when you leave.

## Held against the published counts

      A move generator is easy to get almost right. Castling through an attacked square, an en passant capture that leaves the king open, a pawn that takes and promotes in one move: each is a rule that a few positions in a million depend on, and a game can go on for hours without meeting one.

      Perft is the standard test. From a position it counts every sequence of legal moves to a given depth, and the totals for a handful of well-known positions are published. One missing or extra move anywhere in the tree changes a total. Press Count to run it here, with the same code the board above plays with.

        CountSix positions, from the opening to a crowded middlegame with every special move on the board.

      The page stops where a count would take more than a few seconds. The command runs every depth that is published up to a few hundred million, and this is its output:

      $ node web/notes/gambit/perft.mjs --deep
same  Start       depth 1           20  published          20  1 ms
same  Start       depth 2          400  published         400  1 ms
same  Start       depth 3         8902  published        8902  26 ms
same  Start       depth 4       197281  published      197281  63 ms
same  Start       depth 5      4865609  published     4865609  849 ms
same  Start       depth 6    119060324  published   119060324  19736 ms
same  Kiwipete    depth 1           48  published          48  0 ms
same  Kiwipete    depth 2         2039  published        2039  3 ms
same  Kiwipete    depth 3        97862  published       97862  28 ms
same  Kiwipete    depth 4      4085603  published     4085603  720 ms
same  Kiwipete    depth 5    193690690  published   193690690  33022 ms
same  Position 3  depth 1           14  published          14  0 ms
same  Position 3  depth 2          191  published         191  0 ms
same  Position 3  depth 3         2812  published        2812  1 ms
same  Position 3  depth 4        43238  published       43238  10 ms
same  Position 3  depth 5       674624  published      674624  136 ms
same  Position 3  depth 6     11030083  published    11030083  2401 ms
same  Position 4  depth 1            6  published           6  0 ms
same  Position 4  depth 2          264  published         264  0 ms
same  Position 4  depth 3         9467  published        9467  1 ms
same  Position 4  depth 4       422333  published      422333  77 ms
same  Position 4  depth 5     15833292  published    15833292  2610 ms
same  Position 5  depth 1           44  published          44  0 ms
same  Position 5  depth 2         1486  published        1486  0 ms
same  Position 5  depth 3        62379  published       62379  11 ms
same  Position 5  depth 4      2103487  published     2103487  352 ms
same  Position 5  depth 5     89941194  published    89941194  16449 ms
same  Position 6  depth 1           46  published          46  0 ms
same  Position 6  depth 2         2079  published        2079  0 ms
same  Position 6  depth 3        89890  published       89890  14 ms
same  Position 6  depth 4      3894594  published     3894594  1021 ms
same  Position 6  depth 5    164075551  published   164075551  24439 ms
every count is the published one; 610,195,852 positions in 102.0 s

## The board has twice as many squares as it shows

      The board is an array of sixteen squares per rank, where only the left eight of each rank are real. A square’s index is its rank times sixteen plus its file. Stepping off the edge of the board gives an index where `index & 0x88` is not zero, so the edge test is a single AND, the same for every piece and every direction. The format is called `0x88` after that mask.

        Point at a square to see its index and where a knight on it can go. A move that would leave the board is caught by the mask and drawn in red.

      Here is the knight’s part of that as a script you can run. It is the same test the engine makes, written out in a few lines. Step walks through it the way the engine reads it, one part of an expression at a time, with every variable beside the code. Change the square or the steps and run it again.

          Run
          Step
          Next line
          Start over

```
const KNIGHT = [33, 31, 18, 14, -33, -31, -18, -14];
const name = (s) => 'abcdefgh'[s & 7] + '87654321'[s >> 4];

function knightMoves(from) {
  const moves = [];
  for (const step of KNIGHT) {
    const to = from + step;
    if (to
            It printed

        The script runs in [Spark](https://therkildsen.dev/lab/p39-spark/), a JavaScript engine written from nothing like Gambit. It loads when you press a button.

## Every legal move, by trying it

      The generator first lists the moves the pieces can make without asking whether their own king is left in check. Each one is then played on the board, the king’s square is tested for attack, and the move is taken back. A move is a single integer that holds the two squares, the piece it promotes to and whether it is a capture, an en passant, a castle or a pawn’s double step.

      Taking a move back restores the board, the castling rights, the en passant square and the position’s hash exactly. The tests check the hash against one computed from scratch after every move in every perft position, two moves deep.

## Looking ahead, and skipping what cannot matter

      Gambit searches with alpha-beta: once it has found one good line, any branch that the opponent can make worse than that line is cut without being read to the end. It searches one move deep, then two, then three, until its time is up, and keeps every position it has scored in a table keyed by the position’s hash. Each deeper pass starts with the move the last pass found best, which is what makes the cuts come early.

      At the end of a line it keeps playing captures until no capture is left, so it never scores a position halfway through a trade. It also checks whether passing would still be good enough, and searches the later, quieter moves less deep unless they surprise it. The panel beside the board shows how deep that gets on your device.

## What a position is worth

      The score is the material on the board plus where each piece stands. Every piece has two tables of bonuses, one for the middlegame and one for the endgame, and Gambit blends them by how much material is left. A knight is worth most in the center. A king scores best behind its pawns while the queens are on, and in the middle once they are gone. Choose a piece to see its table.

          Middlegame

          Endgame

        The table is drawn from white’s side. Violet adds to the score, gray takes from it, in hundredths of a pawn.

## What it does not have

      Gambit has no opening book and no endgame tables, and it thinks on one thread. Its strength has not been measured against a rated engine, so this page gives it no rating. What is measured is the move generator, above, and the tests: mates in one and two, a hanging queen taken, notation, the ends of a game, and two copies playing a full game where every move is checked for legality.

      The source is in the portfolio’s repository under `web/notes/gambit/`: `engine.js` for the board and the search, `perft.mjs` for the counts, `engine.test.mjs` for the tests.

[Newer · W02Weft, a text that stays one text when nobody is online](https://therkildsen.dev/writing/weft)[OlderEvery post](https://therkildsen.dev/writing)
