Skip to main content

pycgt

PyPI Python versions CI License: MIT

Combinatorial game theory in pure Python — exact canonical forms, thermography, and game values.

>>> from pycgt import parse, render, temperature, mean
>>> from pycgt.rulesets import domineering

>>> render(domineering.rectangle(2, 4))
'Miny(2)'
>>> temperature(domineering.rectangle(2, 3))
Fraction(5, 4)
>>> render(parse("{1|-1}") + parse("{1|-1}"))
'0'

pip install pycgt. Python 3.11+, no dependencies.


The one idea everything rests on

Short games are exact objects built from the empty game up. Two values are equal, or one is greater, or they are confused with each other — there is no tolerance to set, no rounding, and no floating point anywhere. Temperatures are Fractions found by bisection on an exact dyadic grid.

The operation that makes this usable is canonical form. Two games of the same value reduce to identical canonical forms, so structural equality becomes value equality and comparison becomes a dictionary lookup. Every Game returned by this library is canonical, which is why == means what you want.

Why it exists

CGSuite is the established tool in this field and is far more complete than this. But it is a Java desktop application: not pip install-able, awkward to drive from a script, and unavailable in a notebook or in CI. Getting it to answer one batch of questions headlessly took a from-source Maven build and a JDK version pin.

pycgt is for when you want game values inside ordinary Python. It has no dependencies, so it also runs unchanged under Pyodide in a browser.

What's here

Core canonical form, the partial order, disjunctive sums, negation, birthdays
Numbers dyadic rationals both ways, Conway's simplicity rule
Named values *, ^, v, nimbers, switches, tiny, miny
Stops left/right stops, confusion intervals, infinitesimality, hot/tepid tests
Reduced form reduced canonical form, and ish — the infinitesimal remainder
Thermography heating, overheating, cooling, temperature, mean value
Structure all-small games, maximal incentives, the census of games born by day n
Notation render to readable text, and parse it back
Rulesets Domineering, Cram, Clobber, Toads-and-Frogs, Col, Snort, Nim, Blue-Red Hackenbush
Reachability was a position arrived at by legal alternating play? with replay certificates
Drawing thermographs as dependency-free SVG, rendered inline in notebooks

The rulesets span four different board shapes on purpose, because each one stresses the core differently: grids of empty cells (Domineering, Cram), grids of coloured stones that move (Clobber), directed strips (Toads-and-Frogs), and arbitrary graphs (Col, Snort). Each needs its own notion of symmetry, and getting that wrong silently corrupts the memo table.

Games are only partially ordered, so <= and >= are defined but < and > deliberately are not — not (G <= H) does not imply G > H. Use compare() for the four-way answer, which includes confused.

>>> from pycgt import compare, STAR, ZERO
>>> compare(STAR, ZERO)
<Relation.CONFUSED: '||'>

A worked example

examples/berlekamp_1988.py reproduces Berlekamp's Blockbusting and Domineering (1988) — his Table III sequences, their period-5/saltus-1 structure, and the overheating bound from Appendix B.1 — and checks the results against the paper.

$ python examples/berlekamp_1988.py
  period 5, saltus 1: holds
  x_1 is the only non-number: 1*
  ok    G_2 = tiny-2
  ok    G_3 = +-1 + 2*tiny-2

Reachability

Some authors count any finite region as a Domineering position; a stricter reading requires the empty cells to be reachable from a rectangle by legal play. A claim about, say, the maximum temperature in Domineering means different things under the two conventions, so reachability is part of the statement — and it is not something CGSuite answers.

>>> from pycgt.rulesets import Position, domineering, reachable_from_rectangle, verify_replay
>>> board = Position.rectangle(2, 3)
>>> target = Position(board.cells - {(0, 0), (1, 0)})
>>> replay = reachable_from_rectangle(target, 2, 3, domineering.DOMINEERING)
>>> print(replay)
1 moves from 6 cells (1 Left, 0 Right)
    1. Left: (0,0) (1,0)
>>> verify_replay(replay, domineering.DOMINEERING)
True

The search returns a certificate, and verify_replay re-checks it from scratch — legality, alternation, and endpoint — without trusting the search. So a replay produced elsewhere, by another program or by hand, can be audited here.

It works for any placement ruleset, not just Domineering. The reduction that makes it tractable: placements never overlap, so any partition of the filled region into legal shapes can be replayed in any order, and alternation constrains only the counts.

Correctness

The interesting values in this field are easy to get subtly wrong, so nothing here is trusted because it looks right. The test suite validates against external sources:

  • CGSuite — canonical forms, temperatures and means of Domineering 2×n boards, generated by driving CGSuite 2.2 headlessly.

  • Berlekamp (1988), Blockbusting and Domineering, Appendix B.1 — his exact values for the 2-wide Domineering rectangles, G₂ = tiny-2 and G₃ = ±1 + 2·tiny-2.

  • Wolfe, via Guy's Problem 4 in Games of No Chance — the 4×5 board is 1.

  • Uiterwijk, arXiv:1305.3257 — the value of the 11×2 board.

  • Closed forms — Nim sums follow exclusive-or; Hackenbush strings give the expected dyadic rationals; Cram values are nimbers.

  • The census — born_by(2) must be exactly 22, the published count. This is the strictest test here: 256 raw expressions have to collapse onto 22 values, so any error in domination or reversibility changes the number.

  • Exhaustive comparison where it's affordable — Toads-and-Frogs was checked against CGSuite on every position of length 1 to 5, all 363.

Plus internal laws that catch real bugs: G + (−G) = 0, transposing a Domineering board negates its value, ish(G) is always infinitesimal, canonical form is idempotent, and reversing a Toads-and-Frogs strip while swapping colours negates it.

One methodological note. Comparing our output to CGSuite's as text produces false disagreements whenever the two name the same value differently. So the tests ask CGSuite to adjudicate equality with our output instead. That has twice exposed a genuine gap in our renderer rather than a phantom bug.

Provenance

Implemented clean-room from the mathematics — Winning Ways, Siegel's Combinatorial Game Theory, and Berlekamp (1988). CGSuite is GPL; its source was consulted only for the API needed to run it as a test oracle, and none of its implementation was used or ported. This library is MIT.

Limitations

Honest about scope:

  • Short games only. No loopy games, stoppers, or on/off.
  • No misère theory and no misère quotients.
  • Slower than CGSuite, which is a JVM program with far more optimisation. Expect exact Domineering values up to about 2×16 comfortably.
  • The number-temperature convention (a number with denominator 2**k has temperature −1/2**k) is checked against CGSuite for 0 and 1/2 only.

Licence

MIT.

Metadata

Release files for pycgt 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pycgt 0.2.0
File Size Uploaded
pycgt-0.2.0.tar.gz 55.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pycgt 0.2.0
File Interpreter ABI Platform
pycgt-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 97.7 kB

Release files / pycgt-0.2.0.tar.gz

Download URL pycgt-0.2.0.tar.gz
Size 55.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e61ed85f309fb0673e2eca6de1cfe7a26a7f3de749f3638b39713dcf871dfc00
BLAKE2b-256 checksum
How to use checksums
6f1d2c9c3d5de0456f36f25435347f8916526de7126f93771550b1a6e729a528
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / pycgt-0.2.0-py3-none-any.whl

Download URL pycgt-0.2.0-py3-none-any.whl
Size 42.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a4db4f0bb2de2a8d7261198972c2e612d6fd8501494c1db4dd16af3146032382
BLAKE2b-256 checksum
How to use checksums
6afe1501922204e9f198f4ba2939d54795920a42e1d25fc0767688cdd5a766f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page