Pieces that know only their geometry, a game that decides legality by playing each move and taking it back, and castling, en passant, promotion and undo, proved against published perft counts.
The chess round, as in the lesson, and the one with the most rules per line of code. The key decision: pieces generate pseudo-legal moves from geometry alone; the game decides legality (pins, castling, en passant and check all need more than one piece or some history). Colours, squares, Move, the castling tables, Status, Undo and IllegalMove are given. You build the pieces, Board.attacked and Game.
Pieces. Every piece answers attacks(board, at): the squares it hits from at (a Slider walks each direction until the first piece, which it includes; a Stepper tries fixed offsets; a Pawn attacks its two forward diagonals). moves(board, at, ep) gives Moves: by default the attacked squares not holding a friend. The Pawn overrides it: one step forward if empty, two from its home rank if both are empty, diagonal captures of enemies, en passant onto the ep square, and on the last rank four promotion moves (q, r, b, n). Pieces never store their own square.
Board: squares maps a square to a Piece; king(color) finds the king; attacked(target, by) is true if any piece of colour by attacks target (it reuses attacks()).
Game(fen=START) (the FEN constructor is given): turn, rights (the castling rights such as {"K", "Q", "k", "q"}), ep (the en passant square, valid for one move), history.
| Method | Does |
|---|---|
push(move) |
plays the move on the board, handling the en passant victim, promotion, the castling rook, and records an Undo. Then it updates rights (any move from or onto e1, h1, a1, e8, h8 or a8 loses the rights tied to that square, LOSES_RIGHTS), the en passant square (set by a two-square pawn advance) and the turn |
pop() |
the exact inverse, using the Undo (a promoted piece goes back to being a pawn); returns the move |
legal_moves() |
pseudo-legal moves plus castling, keeping only those after which the mover's own king is not attacked (make-and-test: push, test, pop). Castling needs the right, the squares between empty, and the king not in, through or into check (it crosses the rook's landing square) |
in_check(), status() |
CHECK / ONGOING when there are legal moves; CHECKMATE when none and in check; STALEMATE when none and not |
move(uci, by=None) |
"e2e4", "e7e8q". The matching legal move is played and the new status() returned; no promotion letter on a promotion means a queen; a letter on an ordinary move is illegal. IllegalMove for a move that is not legal or when by is not the side to move |
undo() |
pops one move; IllegalMove("nothing to undo") on an empty history |
JavaScript and TypeScript: squares are algebraic strings ("e4") and rights is a Set; camelCase (legalMoves, inCheck, enPassant); pieces list their squares as arrays (attacks and moves return arrays, not iterators); Slider subclasses declare static dirs, Stepper subclasses static steps, every piece class static symbol; status() returns a Status string. PIECES maps a lower-case letter to a class, and the FEN constructor reads it, so a new piece type can be added to it.
Write your code, then run 13 example tests and 3 hidden tests. Each run is limited to 25 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.