♟ Infographic · Chess Programming

Explain Chess Engine
Move Generation

Search asks “what moves exist?” millions of times per second. Move generation is the factory floor — fast, correct, and tested with perft.

📅 June 29, 2026 ⏱ 12 min read 🏷️ Move Gen · Perft
Human players see legal moves intuitively. Engines must construct them from raw board state — every push, capture, castle, en passant capture, and promotion — then discard moves that leave the king in check. Get this wrong and every search score above it is garbage.

Core idea

Two-Phase Move Generation

📋
1. Pseudo-legal
All moves that follow piece rules (ignoring check)
2. Filter illegal
Make move → king in check? → discard
3. Legal list
Feed search / perft / GUI
20Max legal moves in one position (record)
~35Typical moves in midgame
486KPerft nodes at startpos depth 5
make/unmakePreferred over copy-make in engines

Piece by piece

How Each Piece Generates Moves

PawnsMost special rules

Single push, double push from starting rank, diagonal captures, en passant, and promotion (queen/rook/bishop/knight — usually four moves per promotion square).

Push blocked by piece ahead Double only if square behind clear EP only after opponent double-push
// Pawn attack mask AND enemy occupancy attacks = pawnAttacks[sq][color] & enemyPieces; if (empty(sq + dir)) addPush(); if (onStartRank && empty(sq+dir+dir)) addDouble();
KnightsLeaper

Jump to up to 8 squares from a precomputed attack table. Remove squares occupied by friendly pieces. No blocking — simplest slider-free piece.

O(1) lookup from knightAttacks[sq] Bitwise targets = attacks & ~friendly
targets = knightAttacks[sq] & ~friendlyOccupancy; while (targets) { to = popLSB(targets); addMove(sq, to); }
♗♖♕Sliders (Bishop, Rook, Queen)Magic bitboards

Ray attacks stop at first blocker. Engines use magic bitboards: index a prebuilt attack table from occupancy in O(1). Queen = bishop attacks | rook attacks.

Rook rank + file rays Bishop diagonal rays Capture include enemy, stop at blocker
KingCastling

One-step moves from king attack table plus castling if rights remain, squares empty, king not in check, and squares king crosses not attacked.

Kingside O-O: f1/g1 clear, not attacked Queenside O-O-O: b1/c1/d1 rules

Special moves

Castling, En Passant & Promotion

🏰 Castling

King moves two squares; rook jumps to other side. Engine checks: castling right bit set, path empty, king not in check, transit squares not attacked. Common bug: forgetting attacked-square test on f1/c1.

⚡ En passant

Only on the move immediately after an adjacent pawn double-push. Capturing pawn moves diagonally to the “passed-through” square; captured pawn removed from its actual square — not the destination.

White pawn on e5 can capture en passant on d6 or f6 after black …d7–d5

👑 Promotion

Pawn reaching last rank becomes Q/R/B/N. Engines emit four moves per promotion capture or push (unless you default to queen-only). Underpromotion matters in endgame tablebase positions.

Search integration

Make / Unmake Cycle

Generate moves
Make move
Search / recurse
Unmake move

Make updates bitboards, castling rights, en passant square, halfmove clock, side to move, and XORs the Zobrist hash. Unmake restores saved state from a move struct — no board copying. This runs billions of times during search.

Validation

Perft: The Move-Gen Unit Test

PositionDepthNodesWhat it catches
Start position54,865,609Basic push/capture/castle bugs
Start position6119,060,324Deep interaction bugs
Kiwipete FEN44,079,606Castling, en passant, checks
Position 34797,742En passant edge cases

Perft algorithm (sketch)

Count leaf nodes by recursive make/unmake — no search pruning, no evaluation. If your count ≠ published number, fix move gen before writing eval or alpha-beta.

function perft(depth): if depth == 0: return 1 nodes = 0 for move in generateLegalMoves(): make(move); nodes += perft(depth-1); unmake(move) return nodes

Debugging

Common Move-Gen Bugs

Move encoding

Engines pack a move into 16 bits: from square (6) + to square (6) + flags (promotion piece, castling, en passant, capture). Same list format for search, perft, and UCI output (e2e4, e7e8q).

Next in the engine series

Move generation is layer 3 of the stack — see the full architecture or train tactics on FujiBit.

Related: Engine Anatomy · All Blogs · Chess Engines