A UCI chess engine in Python. Negamax (fail-soft alpha-beta with a
check-aware quiescence search, null-move pruning, late move reductions, and
principal variation search) is the core; it takes an evaluator, a move orderer,
a transposition table, and a clock as constructor arguments.
lazy_smp.search runs one iterative-deepening Negamax per worker process
against a shared-memory transposition table and returns the deepest completed
line. Evaluation is PeSTO - tapered mid/endgame piece-square tables kept as an
incremental accumulator - plus hand-crafted terms (pawn structure, king safety,
bishop pair, rook files, outposts). See docs/design.md for
the full feature list and backlog.
Self-play at 5 s/move - every move a real search, no opening book.
Around 1950–2150 Elo at blitz, most likely ~2050. This is a feature-based estimate from the search and evaluation, not a measured result - no games against rated opposition have been run yet.
| Time control | Estimate | Why |
|---|---|---|
| Bullet (1+0) | ~1750–1900 | Python per-move overhead dominates. |
| Blitz (3+2 / 5+3) | ~1950–2150 | Reaches depth 8–10. |
| Rapid / Classical | ~2150–2300 | Reaches depth 11–13+; a PST-based eval scales well with depth. |
| Lichess bot pool | ~2050–2300 blitz | Bot ratings there tend to run higher than CCRL. |
See docs/engine-strength.md for how the number is derived, calibration against known engines, and how to turn it into a measured rating; docs/performance.md for search-speed benchmarks.
Requires Python 3.14+.
git clone https://github.com/cjunius/pyChess.git
cd pyChess
pip install -e . # add ".[dev]" for the test / lint tools
pychess # UCI loop on stdin/stdout (also: python -m pychess)Point any UCI GUI (CuteChess, Arena, a Lichess bot harness) at the pychess
command.
Standard UCI: uci, isready, ucinewgame, position, go, stop, quit.
go accepts depth <n>, movetime <ms>, nodes <n>, wtime/btime/winc/binc/movestogo <ms>,
or infinite; a bare go searches for ~4s. Every search runs Lazy SMP - one
worker process per core, sharing a lock-free transposition table.
Extra commands: perft <depth>, selfPlay, printBoard, printLegalMoves,
printMoveStack.
pychess/
├── pyproject.toml packaging, dependencies, ruff / mypy / pytest / coverage config
├── src/pychess/
│ ├── __main__.py UCI protocol loop; `pychess` / `python -m pychess` entry point
│ ├── engine.py Engine - assembles the default search
│ ├── lazy_smp.py Lazy SMP coordinator + worker; returns a SearchResult
│ ├── negamax.py Negamax - fail-soft negamax + quiescence over injected pieces
│ ├── move_ordering.py MoveOrderer - TT move / MVV-LVA / killers / history
│ ├── evaluation.py PeSTO tables + PestoEvaluator
│ ├── eval_terms.py positional terms (pawn structure, king safety, bishop pair, …)
│ ├── eval_board.py EvalBoard - chess.Board with an incremental eval accumulator
│ ├── transposition.py TranspositionTable - in-process dict
│ ├── shared_tt.py SharedTT / SharedFlag - lock-free shared-memory table
│ ├── clock.py Clock + deadline_from_limits (UCI time management)
│ ├── constants.py mate / window / TT-flag constants
│ ├── types.py shared type aliases (GoLimits)
│ └── perft.py move-generation node counter
├── tests/ pytest suite, one test_<module>.py per source module
├── opening_book/ Polyglot opening book (see opening_book/README.md)
└── docs/
├── design.md architecture, feature list, backlog
├── engine-strength.md playing-strength estimate and how to measure it
├── performance.md benchmark tables (search speed, Lazy SMP scaling)
└── tasks.md prioritised roadmap + what's already done
pip install -e ".[dev]"
pre-commit install # optional: runs the checks below on every commit
ruff check . # lint
ruff format --check . # formatting
mypy # type-check src/ (strict) and tests/
pytest # tests + coverage (gate: 85%)CI (.github/workflows/ci.yml) runs lint + format + type-check once and the
test suite on Python 3.14. All tool config lives in pyproject.toml.
Apache-2.0. The bundled opening book has separate provenance - see opening_book/README.md.
