Chris Collins

Tools · retro

A chess program for the Z80

A small chess program I wrote in Z80 assembly, playing on an emulated ZX Spectrum 48K at 3.5 MHz — 0x88 board, legal moves with castling, en passant and promotion, alpha-beta search with quiescence. Watch the Z80 think (nodes, emulated time, the line it's searching, a routine profile, a debugger), check its move generator with perft, and download a .TAP for a real Spectrum.

Added

Works with

How to use it

A chess engine in about 2.5 KB of Z80 code (5 KB with its tables, piece graphics and screen code), written for this page. It runs on the same ZX Spectrum emulator as the Spectrum tool, at the real machine’s speed, so you can see what a 3.5 MHz CPU can do: about 200 positions a second, a few seconds for a 3-ply search from the opening, up to a minute in a busy middlegame.

How to use it

Press Power on. You play White. Click one of your pieces on the board beside the screen and then the square to move it to — the page types the move on the Spectrum’s keyboard for you — or click the Spectrum’s screen and type it yourself: e2e4 and Enter. While the Z80 thinks, the panel shows the positions it has searched (Nodes), the emulated time at 3.5 MHz, the root move it is looking at and the whole line below it.

  • Z80, move now (S on the Spectrum) makes the Z80 play the side to move — press it at the start to play Black.

  • Depth sets how many plies (half-moves) the search looks ahead, 1 to 4. Depth 3 is the default; 4 takes a minute or several on a real Spectrum. Warp runs the emulator as fast as your computer can — the emulated time it reports stays the same.

  • Position and moves shows the game so far and the position as FEN, and sets up any position from a FEN. Share link (top right) gives a link to the position on the board.

  • Download .TAP gives a tape with a BASIC loader: LOAD "" on a real 48K or 128K Spectrum (or open the file in Fuse, ZEsarUX or any Spectrum emulator) and it plays the same game, with the same keys. Source .asm is the program’s source; the site’s Spectrum tool can assemble it too.

Watching the Z80 think

The Debugger tab shows the CPU as it runs: registers and flags, the disassembly at the program counter with the routine it is in, the search stack (the move tried at each ply with its alpha-beta window), and memory (type an address or a label such as BOARD, PLYREC or MOVES). Where the time goes samples the program counter while the Z80 searches and shows the share of time in each routine — most of it goes to move generation (GEN, ADDMOVE, SLIDER, LEAPER) and to the check test (SLIDEATT and LEAPATT, called by ATTACKED). Set a breakpoint on a routine, or click a line in the Source tab; when the Z80 stops, Step and Step over walk through the code with the source line highlighted.

Perft: checking the move generator

The Perft and search tab runs the same program on a bare Z80 with 64 KB of RAM, as fast as your browser allows, and counts T-states. Perft makes every legal move to a given depth and counts the positions at the end; the totals for standard test positions are published, and any bug in castling, en passant, promotion or check detection changes them. The Z80 gets them all right: 20, 400 and 8,902 from the opening; 48, 2,039 and 97,862 for "Kiwipete"; 14, 191, 2,812 and 43,238 for position 3; 6, 264 and 9,467 for position 4; 44, 1,486 and 62,379 for position 5. Perft 3 from the opening takes 21 emulated seconds.

How the engine works

The board is the 0x88 layout: 128 bytes, square = rank × 16 + file, so a1 is $00 and h8 $77. The extra eight columns make the edge test one instruction — a square is off the board when AND $88 isn’t zero — and because the board sits on a 256-byte page, the square number is the low byte of its address. Pieces are 1–6 (pawn to king), plus 8 for Black.

Move generation scans the board for the side to move’s pieces and steps through direction tables: knights and kings one step in each of 8 directions, bishops, rooks and queens along rays until they hit something. Pawns push one or two squares, capture diagonally, capture en passant onto the square left by a double push, and become four promotions on the last rank. Castling checks the rights, the empty squares between, and that the king isn’t in check or crossing an attacked square. Each move takes 4 bytes on a move stack — from, to, flags, and a sort key.

Legality is checked the simple way: make the move, ask whether the mover’s king is attacked, take it back if it is. ATTACKED looks outwards from the king square for pawns, knights, a king, and sliding pieces along the eight rays.

Make and unmake change the board in place and save what they need to undo (the captured piece, castling rights, en passant square and evaluation) in a 32-byte record per ply, which also holds the search’s alpha, beta, depth and move-list pointers. The IX register points at the current ply’s record.

The evaluation is material (pawn 100, knight 310, bishop 325, rook 500, queen 900) plus a piece-square table for each piece: pawns gain as they advance, knights and bishops in the centre, rooks on the seventh rank, and the king prefers to stay behind its pawns. It is kept up to date as moves are made — a move subtracts its piece’s value on the old square and adds it on the new one — so evaluating a position costs nothing.

The search is negamax alpha-beta to a fixed depth, recursive on the Z80’s stack. Captures are tried first, the most valuable victim with the cheapest attacker (picked from the list as it goes, a selection sort that stops once the captures are used up). At depth 0 a quiescence search keeps playing captures and promotions until the position is quiet, so the engine doesn’t stop counting in the middle of an exchange. Checkmate scores 30,000 less the ply it happens at, so the engine prefers the quickest mate.

What it doesn’t do, to stay small: no opening book, no transposition table, no iterative deepening, no check extensions, no repetition or fifty-move draws, and the king’s table is a middlegame one all game. It plays a sound tactical game at depth 3 and loses to strategy.

The front end clears the screen, draws each square as a 2 × 2 character block with 16 × 16 piece graphics (outlines for White, solid for Black) and attributes for the square colours, prints text with the ROM’s character set, and reads the keyboard by scanning the eight half-rows of port $FE itself, with interrupts off.

Memory map

$8000

Entry points: JP MAIN (the game), SETUP, PERFT_ROOT, THINK, COUNTLEGAL — RANDOMIZE USR 32768 starts the game

$8003–$93xx

The program, its tables and piece graphics

$B400

The legal moves of the position on the board (for the page’s board)

$B800

Ply records, 32 bytes each

$BC00

The 0x88 board

$C000

The move stack

$FFFF down

The Z80 stack

Browser support

Any current browser. The emulator is plain JavaScript; nothing needs installing.

Privacy

Everything runs in your browser. The page loads the program’s source and the Spectrum ROM from this site; nothing you play is sent anywhere.

The engine, its graphics and its piece-square tables are my own. The Spectrum ROM is © Amstrad, who allow it to be redistributed (see the Spectrum tool); the program uses it only for its character set. Perft counts are from the Chess Programming Wiki.

Runs entirely in your browser; nothing you type leaves this page.