Strategos — AI Chess Engine
A C++ chess engine that plays full games through a CLI. It pulls from a Polyglot opening book for the first ~25 moves, then hands off to an alpha-beta search with a transposition table and phase-aware evaluation for the middlegame and endgame.
- Timeline
- Jun – Jul 2025 · Summer of Code (Queen's Gambit track)
- Type
- Self project
- Language
- C++17
- Search
- Iterative deepening · alpha-beta · transposition table · MVV-LVA
- Built on
disservin/chess-libraryv0.6.51 — bitboards, magic-bitboard sliders, FEN, movegen- Repo
- github.com/Y-Harsha-Vardhan/AI-Chess-Bot
What I built, what I built on
Move generation, magic-bitboard sliders, FEN parsing, and the board representation
come from the disservin/chess-library (v0.6.51, ~4.4k LOC of header-only
C++). That left the part I actually wanted to write: the search, the evaluation, the
opening-book reader, and the move-ordering heuristics — the parts that determine
how the engine plays, not how it represents a board.
The project also opens with a week of game-theory groundwork — small C++ and Python solvers for Nim (Sprague–Grundy / Nim-sum), Chomp, Pawnscape, and a "greedy or not" puzzle. None of that code ships with the engine, but the framing — minimax, zero-sum, value of a position — carries directly into what comes next.
How it picks a move
The engine runs iterative deepening with alpha-beta pruning, escalating from depth 1 up to a hard cap of 32. Time budget is 15 seconds for the first move out of the opening book and 8 seconds thereafter — so the engine always returns a legal move even if the deeper search gets cut off.
Move ordering is the single biggest determinant of how much pruning the alpha-beta actually does. The engine orders candidate moves in tiers:
- Hash move — the best move from the transposition table for this
position, if it exists (score
10000). - Captures, ranked by MVV-LVA — most-valuable-victim, least-valuable-attacker — so a pawn taking a queen comes before a queen taking a pawn.
- Killer moves — two slots per ply, holding moves that recently caused a beta-cutoff at a sibling node.
- Everything else, in generation order.
Evaluation
The evaluator combines weighted material (Pawn 100, Knight 300, Bishop 320, Rook 500, Queen 900) with piece-square tables that reward good squares per piece type. The interesting part is the king: two separate tables, one for the middlegame (defensive, tucked away from the centre) and one for the endgame (aggressive, marching to the centre). The endgame detection switches between them based on remaining material — a small detail that changes how the engine plays late in a game.
Transposition table
Positions are hashed with Zobrist keys. Each TT entry stores the best move, score, search depth, and a flag — exact, lower-bound, or upper-bound — that records what kind of cutoff produced it. Subsequent searches that reach the same position re-use the entry to prune entire subtrees and to seed move ordering with the hash move.
Opening book
For the first few dozen moves the engine reads from Titans.bin, a
Polyglot binary book — 12 bytes per entry: Zobrist key, encoded move, weight, learn
data. The engine decodes the key, binary-searches the book, and plays the move if a
match exists. This avoids spending search time on positions that already have a
well-known theoretical answer.
Draws and dead positions
Repetition (board.isRepetition(2)), the 50-move rule
(board.isHalfMoveDraw()), and insufficient-material detection all return
a score of 0, nudging the engine away from positions that are technically drawn —
which matters because evaluating a "drawn but ahead in material" position naively
would otherwise look like a win.
What I'd carry forward
The biggest takeaway was how much engineering effort goes into not searching — opening books, transposition reuse, move ordering, draw detection. The actual alpha-beta core is short; everything around it is what makes it playable in real time.