Library / SDK
thomasahle/sunfish avatar
thomasahle/sunfish

Sunfish: a 111-line Python chess engine you can read in one sitting

Sunfish: a Python Chess Engine in 111 lines of code

3,279 stars581 forksPythonNOASSERTION

At a glance

What is it?
Sunfish is a minimalist UCI chess engine written in Python, small enough to study and strong enough to play. It is a teaching and experimentation platform, not a tournament contender.
Who is it for?
Adopt Sunfish if you want to read an entire chess engine, fork it to test search or evaluation ideas, or embed a tiny UCI engine in a teaching project. Do not adopt it as a serious opponent or a production analysis tool; the README admits the evaluation function only uses piece square tables and does not distinguish midgame from endgame, and the engine does not implement the 50-move draw rule.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 22 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 18, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What Sunfish is for, and who should read the source

Sunfish is a chess engine written in Python. The README describes it as simple but strong, and notes that stripping comments and whitespace leaves 126 lines of code; the repository also ships a compressed.py file, which is the file the README links when it cites that line count. The engine speaks UCI, the standard protocol that chess GUIs and match runners use to talk to an engine, so it can be dropped into existing tooling rather than only played in its own interface.

The audience is narrow and specific. This is for people who want to see every part of an engine at once: move generation, search, evaluation. The README says the code is a platform for experimenting, and lists parallel search algorithms, evaluation functions and deep learning chess programs as things people have used it for. If you want a strong opponent, this is the wrong file to open. If you want to understand why a chess engine makes the move it makes, a program whose entire search fits on a screen is a better starting point than a C++ engine with a build system.

Inside the engine: MTD-bi search and piece square tables

The README lists the engine's design in four points. Search is built around MTD-bi, also called NegaC*, a search algorithm that repeatedly narrows a score window instead of running a plain fixed-window alpha-beta. Evaluation is a piece square table lookup, described as efficiently updatable. The code uses standard Python collections rather than hand-rolled data structures, which is a deliberate trade: clarity over speed.

The evaluation function is the part to look at first if you plan to modify anything. The README states it only uses piece square tables and does not distinguish between midgame and endgame. That single sentence explains most of the engine's playing character. There is no material-versus-activity tuning, no king safety term, no pawn structure term. A fork of Sunfish that adds a tapered evaluation, where piece square values interpolate between a midgame table and an endgame table, is one of the smallest changes with the largest effect, and the README points at exactly this gap.

Search side, the README says only null move pruning is done, and no extensions are used. Move ordering is called out as a candidate for MVV/LVA and SEE. These are not hidden limitations; they are the README's own list of what is missing.

Installing Sunfish and playing it in the terminal

The README gives the shortest path first. Install the package from PyPI and run the sunfish command. The default entry point is the play interface, which the pyproject.toml notes needs python-chess, and that dependency is declared in the package metadata, so pip pulls it in.

bash
pip install sunfish
sunfish

What you should see is a terminal board and a prompt, matching the screenshot the README links from docs/screenshot.png. If you would rather run from a checkout, the README gives a second form that launches the engine through the UI script with an explicit command.

bash
sunfish_ui/fancy.py -cmd ./sunfish.py

For a GUI or a match runner, the engine binary is a separate console script. pyproject.toml declares two entry points: sunfish maps to sunfish_ui.fancy:run, and sunfish-uci maps to sunfish:main. So a GUI that wants UCI should be pointed at sunfish-uci after installation, or at ./sunfish.py from a checkout. The README lists Arena, Cute Chess, PyChess and BanksiaGUI as GUIs that work this way, and gives a fastchess command line for engine-versus-engine matches.

bash
fastchess -engine cmd=sunfish-uci name=sunfish \
          -engine cmd=<other> name=other \
          -each proto=uci tc=30+1 -rounds 10 -games 2

One detail worth knowing before you debug anything: the launcher at the top of sunfish.py picks pypy3 automatically when it is installed, and falls back to python3 otherwise. The README recommends PyPy, and reports that on a fixed-depth battery with identical node counts, PyPy 3.11 searches about 2.7 times faster than CPython 3.14 (81 versus 30 kilonodes per second), worth roughly 100 Elo at fast time controls. If the engine will not start, the README suggests running sunfish_ui/fancy.py with -debug to see the underlying error, and checking that python3 is on your PATH.

The 50-move rule is missing, and other places Sunfish breaks

The README's Limitations section opens with a blunt statement: Sunfish supports all chess rules except the 50-move draw rule. That is a real correctness gap, not a cosmetic one. A game that should be drawn by the fifty-move count will not be adjudicated as such by the engine. For casual play and for reading the source, it rarely matters. For perft-style rule testing, for adjudicated engine matches, or for any pipeline that trusts the engine to declare a draw, it does.

Performance is the second boundary. Pure Python search is slow, and the README's own workaround is to run under PyPy, which it says is much stronger. If your environment cannot install PyPy and you are comparing Sunfish against a compiled engine at fixed time controls, you are measuring the interpreter as much as the engine.

The third boundary is strength itself. The piece square evaluation with no midgame and endgame distinction means the engine will mishandle positions where king activity or pawn structure dominates. The README's suggested improvements read as a list of exactly those weaknesses: bitboards, dedicated capture generation, check detection and evasions, midgame and endgame tables, more pruning, extensions, better move ordering. Each of those is a reason the engine is not strong, and each is also a reason it is readable.

Sunfish versus Micro-Max: same idea, different language

The README names its heritage directly: Sunfish borrows much more from Micro-Max by Geert Muller and from PyChess. Micro-Max is the closest comparison, and the difference is instructive. Micro-Max is a tiny C engine, so it runs at native speed and its compactness comes partly from C idioms. Sunfish makes the opposite bet. It stays in Python, uses standard collections instead of packed representations, and accepts the speed cost in exchange for code a reader can follow without knowing pointer arithmetic.

That bet is why the README's improvement list starts with board representation and bitboards. Moving to a mutable array, adding fast piece enumeration, and eventually bitboards are all steps toward what a C engine already does. If your goal is maximum strength per line of code, Micro-Max's approach is the one to study. If your goal is to understand the algorithm and then modify it, Sunfish's Python is the easier substrate, and the PyPy path recovers part of the speed gap without leaving the language.

The README also mentions a family of derivatives, including a MicroPython port for ESP32-class microcontrollers and Numbfish, which suggests the codebase is treated as a starting point rather than a finished product.

The NNUE variant and the 4-kilobyte packing option

Two extras in the repository change the picture. The first is nnue_4k/, described in the README as a Sunfish whose evaluation is the classic piece square score plus a trained neural residual, with the whole accumulator and evaluation head packed into a single Python integer. The README states it is measured about 200 Elo over classic at tournament time controls, still packs to a few kilobytes, and that every quantized net is certified for lane-exactness, incremental-equals-from-scratch, and exact antisymmetry before it plays a game. Those are claims from the README, not independent results, but the certification step is a concrete design choice worth noting if you plan to train your own net.

The second is packing. The README shows a build script that turns sunfish.py into a self-extracting executable of roughly 3 kilobytes, and gives a sample session where the packed script answers a go command with a depth-1 search and the move d2d4. The packed version uses a simplified UCI protocol under the TCEC 4k rules. If you are building for a constrained environment, this is the path the project documents.

bash
tools/build/pack.sh sunfish.py packed.sh
./packed.sh

Both extras are separate from the main engine. The classic sunfish.py remains the file to read first.

Licence, packaging and what upgrades cost you

The repository licence field reports NOASSERTION, and pyproject.toml points the license key at LICENSE.md rather than an SPDX identifier. That means the licence text is in the repository but the metadata does not classify it. If you plan to redistribute Sunfish, read LICENSE.md yourself; the packaging metadata will not tell you the terms, and nothing here is legal advice.

Upgrade cost is low by design. The package version is 2026.1, the dependency list is short, and the only runtime dependency for the default entry point is chess, pinned to 1.9.4 in pyproject.toml. The engine module itself is declared dependency-free, which is why sunfish-uci can be installed and driven by a GUI without pulling in the play interface's needs. Development dependencies are grouped separately: chess, tqdm, pytest and flake8, with a comment noting that pytest-asyncio was never consumed and no async tests exist.

The practical upgrade risk is Python version. pyproject.toml requires Python 3.10 or newer, and the README notes the code uses the walrus operator, which is why it needs Python 3.8 at minimum. The README also records a historical note that an old version of its performance table referred to PyPy 2.7, and that modern PyPy 3 has closed the gap. If you pin an old interpreter because of that footnote, you are pinning to a stale measurement. Tests run under pytest; the README shows python3.12 -m pytest and the uv sync command that installs the dev group.

Editorial conclusion

Adopt Sunfish if you want to read an entire chess engine, fork it to test search or evaluation ideas, or embed a tiny UCI engine in a teaching project. Do not adopt it as a serious opponent or a production analysis tool; the README admits the evaluation function only uses piece square tables and does not distinguish midgame from endgame, and the engine does not implement the 50-move draw rule. Before relying on it, verify the Python version your environment provides, since the README states modern Sunfish requires Python 3.8 or newer for the walrus operator, and confirm whether pypy3 is installed, because the launcher prefers it and the README reports a large speed difference.

Frequently asked questions

How do I install Sunfish?

The README gives pip install sunfish, followed by running the sunfish command to play in the terminal. From a repository checkout you can instead run sunfish_ui/fancy.py -cmd ./sunfish.py.

How do I use Sunfish with a chess GUI?

The engine speaks UCI. After pip install sunfish, point a UCI GUI at the sunfish-uci command, or at ./sunfish.py from a checkout. The README lists Arena, Cute Chess, PyChess and BanksiaGUI as working this way.

What is so special about Sunfish?

It is small enough to read: the README says removing comments and whitespace leaves 126 lines of code, and the engine uses MTD-bi search with a piece square table evaluation. The README frames it as a platform for experimenting rather than a strength contender.

How do I set up Sunfish for engine matches?

The README gives a fastchess command line that runs two UCI engines against each other, with sunfish-uci as one engine, and points to docs/TESTING.md for the full methodology. Match tools such as cutechess-cli are mentioned alongside it.

Official sources

  1. Issues
  2. Project website
  3. README
  4. Releases
  5. thomasahle/sunfish on GitHub
Community notes

Community notes