Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,010 changes: 2,010 additions & 0 deletions chess/chesstb.py

Large diffs are not rendered by default.

Binary file added data/chesstb/dtc/KBK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KBNK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KNK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KPK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KQK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KRK.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtc/KRKR.lzdtc
Binary file not shown.
Binary file added data/chesstb/dtm50/KBK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KBNK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KNK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KPK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KQK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KRK.lzdtm50
Binary file not shown.
Binary file added data/chesstb/dtm50/KRKR.lzdtm50
Binary file not shown.
Binary file added data/chesstb/wdl/KBK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KBNK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KNK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KPK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KQK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KRK.lzw
Binary file not shown.
Binary file added data/chesstb/wdl/KRKR.lzw
Binary file not shown.
37 changes: 37 additions & 0 deletions docs/chesstb.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
chesstb endgame tablebase probing
==================================

`chesstb <https://github.com/noobpwnftw/chesstb>`_ tablebases provide
50-move-rule-aware **WDL** (win/draw/loss, with cursed/blessed classes),
**DTC** (distance to conversion -- plies to the next zeroing move), and a
**DTM50** pack giving both the unbounded **DTM** (depth to mate) and the exact
50-move-rule DTM at any halfmove clock. Positions with castling rights are not
included.

This is a pure-Python prober (it depends only on the standard library); no
native extension is required.

.. code-block:: python

import chess
import chess.chesstb

with chess.chesstb.open_tablebase("data/chesstb") as tablebase:
board = chess.Board("8/8/8/5k2/8/8/1Q6/K7 w - - 0 1")
print(tablebase.probe_wdl(board)) # 2 (+2 win .. -2 loss)
print(tablebase.probe_dtz(board)) # 19 (signed distance to conversion)
print(tablebase.probe_dtm(board)) # 19 (signed distance to mate)
print(tablebase.probe_dtm50(board)) # (2, 19): rule-true (wdl, plies)

.. warning::
Maliciously crafted tablebase files may cause denial of service.

.. autofunction:: chess.chesstb.open_tablebase

.. autoclass:: chess.chesstb.Tablebase
:members: probe_wdl, get_wdl, probe_dtz, get_dtz, probe_dtm, get_dtm, probe_dtm50, probe, add_directory, close

.. autoclass:: chess.chesstb.ProbeResult
:members:

.. autoexception:: chess.chesstb.MissingTableError
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Contents
polyglot
gaviota
syzygy
chesstb
engine
svg
variant
Expand Down
59 changes: 59 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io

import chess
import chess.chesstb
import chess.gaviota
import chess.engine
import chess.pgn
Expand Down Expand Up @@ -4955,6 +4956,64 @@ def test_antichess_pgn(self):
self.assertEqual(game.end().board().fen(), "8/6k1/3K4/8/8/3k4/8/8 w - - 4 33")


class ChesstbTestCase(unittest.TestCase):

def test_probe(self):
with chess.chesstb.open_tablebase("data/chesstb") as tables:
# KQK: mate distances and the signed WDL convention.
board = chess.Board("8/8/8/5k2/8/8/1Q6/K7 w - - 0 1")
self.assertEqual(tables.probe_wdl(board), 2)
self.assertEqual(tables.probe_dtz(board), 19)
self.assertEqual(tables.probe_dtm(board), 19)
self.assertEqual(tables.probe_dtm50(board), (2, 19))

board = chess.Board("8/8/8/5k2/8/8/1Q6/K7 b - - 0 1")
self.assertEqual(tables.probe_wdl(board), -2)
self.assertEqual(tables.probe_dtz(board), -20)
self.assertEqual(tables.probe_dtm(board), -20) # signed: losing side
self.assertEqual(tables.probe_dtm50(board), (-2, 20))

def test_mirrored_material(self):
# Stronger side is Black: internally mirrored to the canonical KQK table.
with chess.chesstb.open_tablebase("data/chesstb") as tables:
self.assertEqual(tables.probe_wdl(chess.Board("k7/1q6/8/5K2/8/8/8/8 b - - 0 1")), 2)

def test_dropped_frame_symmetric(self):
# KRKR ships one frame dropped; the missing side is reconstructed by the
# symmetric color mirror.
with chess.chesstb.open_tablebase("data/chesstb") as tables:
board = chess.Board("8/2r5/8/8/8/1k6/8/K1R5 b - - 0 1")
self.assertEqual(tables.probe_wdl(board), 2)
self.assertEqual(tables.probe_dtz(board), 1)
self.assertEqual(tables.probe_dtm(board), 1)

def test_dropped_frame_minimax(self):
# KBNK ships an asymmetric dropped frame, reconstructed by one-ply minimax.
with chess.chesstb.open_tablebase("data/chesstb") as tables:
board = chess.Board("8/8/8/8/8/2k5/2N5/KB6 w - - 0 1")
self.assertEqual(tables.probe_wdl(board), 2)
self.assertEqual(tables.probe_dtm(board), 61)
board = chess.Board("8/8/8/8/8/2k5/2N5/KB6 b - - 0 1")
self.assertEqual(tables.probe_wdl(board), -2)
self.assertEqual(tables.probe_dtm(board), -60) # signed: losing side

def test_layered_dtm50(self):
with chess.chesstb.open_tablebase("data/chesstb") as tables:
board = chess.Board("8/8/8/4k3/8/8/Q7/K7 w - - 10 30")
# Halfmove clock selects the DTM50 layer.
self.assertEqual(tables.probe_dtm50(board, 10), (2, 17))
# Past the 50-move window the win still has a flat DTM but draws under 50MR.
board = chess.Board("8/8/8/5k2/8/8/1Q6/K7 b - - 0 1")
self.assertEqual(tables.probe_dtm(board), -20)
self.assertEqual(tables.probe_dtm50(board, 100), (0, 0))

def test_missing_table(self):
with chess.chesstb.open_tablebase("data/chesstb") as tables:
self.assertIsNone(tables.get_wdl(chess.Board("8/8/8/8/8/5k2/3QQQQ1/K7 w - - 0 1")))
with self.assertRaises(chess.chesstb.MissingTableError):
tables.probe_wdl(chess.Board("8/8/8/8/8/5k2/3QQQQ1/K7 w - - 0 1"))


if __name__ == "__main__":
verbosity = sum(arg.count("v") for arg in sys.argv if all(c == "v" for c in arg.lstrip("-")))
verbosity += sys.argv.count("--verbose")
Expand Down
Loading