1. Work
  2. AI Chess Engine & Analytics
  3. Docs

AI Chess Engine & Analytics

UCI chess engine in C++ with bitboards, alpha-beta search and a NEON assembly popcount.

4 min read

Source on GitHub

A small but real chess engine: bitboard move generation, alpha-beta search, a UCI loop, a hand-written assembly primitive, and a Python analytics pipeline that tunes the engine’s evaluation weights.

Architecture note: the original spec called for hand-written x86 SIMD. This machine is Apple Silicon (arm64), so the low-level primitive is real AArch64/NEON assembly instead: same idea (hand-optimized bitboard math), different host ISA.

What’s here

  1. C++ engine core (src/): bitboard board representation, fully legal move generation (castling, en passant, promotion), negamax + alpha-beta search with captures-first move ordering, and a UCI protocol loop.
  2. AArch64 NEON assembly (asm/popcount_neon.s): a hand-written popcount64_asm routine (CNT + ADDV over a NEON register), called from the C++ engine via extern "C" and used for real in eval.cpp’s material counting. Benchmarked against __builtin_popcountll over 10M calls.
  3. Python analytics (python/analytics.py): generates ~300 random self-play games with python-chess, computes aggregate stats with pandas (opening move frequency, average game length), fits a Ridge regression over piece-square-occupancy + material + mobility features against game outcome, and writes the learned piece-square weights to src/eval_weights.hpp, which the C++ evaluation function #includes.

Build

cd "01-ai-chess-engine"
./build.sh

Produces build/engine (UCI binary), build/perft_test (correctness self-check), and build/bench_popcount (asm vs. builtin benchmark). No CMake: build.sh just runs clang/clang++ directly.

Run

Perft correctness self-check (asserts perft(4) == 197281 from startpos):

./build/perft_test

Popcount benchmark (NEON asm vs. __builtin_popcountll, 10M calls each):

./build/bench_popcount

UCI engine, interactive or piped:

printf 'uci\nisready\nposition startpos\ngo depth 4\nquit\n' | ./build/engine

Supported UCI commands: uci, isready, ucinewgame, position startpos [moves ...], position fen <fen> [moves ...], go depth N, go perft N (bonus, not required by spec), quit.

Python analytics (regenerates src/eval_weights.hpp: rerun ./build.sh afterward to rebuild the engine with the new weights):

../.venv10projects/bin/python python/analytics.py

Captured output

Perft self-check (./build/perft_test):

Running perft from startpos...
perft(1) = 20  (0.0 ms)
perft(2) = 400  (0.0 ms)
perft(3) = 8902  (0.2 ms)
perft(4) = 197281  (3.6 ms)
perft(5) = 4865609  (95.6 ms)
perft(4) == 197281: PASS
All perft self-checks PASS.

(Move generator additionally verified against the standard “Kiwipete” stress position: r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1, matching known perft(1..4) = 48 / 2039 / 97862 / 4085603 exactly, which specifically exercises castling, en passant, and promotion legality.)

UCI search (go depth 4 from the start position):

id name AI-Powered-Chess-Engine-MVP
id author adamanwar
uciok
readyok
info depth 4 score cp -1 nodes 23170 time 11
bestmove e2e3

Popcount benchmark:

popcount benchmark, 10000000 calls each
  NEON asm popcount64_asm:         11.48 ms  (checksum=320008108)
  __builtin_popcountll:            11.57 ms  (checksum=320008108)
  checksums match: asm popcount is correct.

Python analytics (300 self-play games): average game length ~40 full moves, most games end in a claimed draw (random play rarely mates), Ridge regression train R^2 ≈ 0.17 over piece-square/material/mobility features: weak but real signal, honestly reported rather than inflated.

Simplified vs. full spec

  • No opening book. Moves are searched from scratch every time. Add when you want faster/stronger play in known openings: load a small PGN/EPD book and probe it before calling search.
  • No transposition table. Every node is re-evaluated, no position caching. Add when search depth needs to go beyond ~5-6 ply: a Zobrist-hash keyed hash table is the standard next step.
  • No NNUE-style learned eval. Evaluation is material + a linear-model-tuned piece-square table, not a neural net. Add when you want to invest in a proper NNUE pipeline (requires a large labeled position dataset).
  • No ELO measurement. The engine’s playing strength isn’t benchmarked against reference engines. Add when you have time to run it through a rated gauntlet (e.g. cutechess-cli against known-strength opponents).
  • No iterative deepening / time management. go depth N runs a fixed depth. There’s no movetime/clock handling. Add when hooking this up to a real GUI that needs to respect a time budget.
  • Self-play training data, not grandmaster games. No bundled dataset of real games exists here, so the Python analytics use random-legal-move self-play instead of “millions of grandmaster games.” Add when you have access to a real PGN corpus (e.g. Lichess database dumps) to tune against.
  • Ridge regression tuning is noisy by design (R^2 ≈ 0.17). Random self-play games are a weak training signal since neither side plays well. Add when you want stronger tuning: replace random self-play with the engine playing itself (using its own search), which produces less noisy outcome labels.

Support

Submit issues, questions, or bug reports to the GitHub issue tracker: https://github.com/ADM1SH/ai-chess-engine/issues

Roadmap

  • Done: Core architecture and baseline implementation.
  • Done: Functional verification and test coverage.
  • To do: Add opening book support (Polyglot .bin)
  • To do: Implement Syzygy endgame tablebase probing

Contributing

Contributions are welcome.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/improvement.
  3. Commit your changes: git commit -m "feat: enhance functionality".
  4. Push to the branch: git push origin feature/improvement.
  5. Open a Pull Request.

Authors and Acknowledgment

  • Adam Anwar (ADM1SH) - Lead architect and developer.
  • Built by Adam Anwar. Bitboard architectures referenced from the Chess Programming Wiki.

License

Licensed under the MIT License. See LICENSE for details.

Project Status

Portfolio MVP. Functional UCI chess engine with NEON SIMD optimizations.

Back to top