Python port of the gungi.js library with verified cross-language parity
Project description
gungi.py
Original JS project ported to Python. It implements the official Gungi rules shown in HUNTER×HUNTER: move generation/validation, piece placement/movement, drafting, captures, betrayal, arata, and endgame detection.
Installation
pip install gungi
Or from this repo:
pip install -e .
Development
pip install -e .[dev]
ruff check .
ruff format .
pytest
python -m build
Parity Verification
gungi.js is the source of truth for engine behavior. The verified upstream commit is recorded in
gungi_js_commit_hash.txt.
Run the local cross-language parity suite against an adjacent ../gungi.js checkout or by setting
GUNGI_JS_DIR:
python scripts/check_gungi_js_parity.py
For a heavier randomized differential run:
python scripts/check_gungi_js_parity.py --parity-cases-per-mode 5 --parity-plies 40
Example Code
Random game:
from gungi import ADVANCED_POSITION, Gungi
def clear_terminal() -> None:
print("\033[2J\033[H", end="")
def print_text(text: str) -> None:
clear_terminal()
print(text)
gungi = Gungi(ADVANCED_POSITION)
while not gungi.is_game_over():
moves = gungi.moves()
move = moves[len(moves) // 2]
gungi.move(move)
print_text(gungi.ascii())
print(gungi.fen() + "\n")
print(gungi.pgn())
API
Constants
from gungi import (
WHITE, BLACK,
TSUKE, TAKE, BETRAY, ARATA,
MARSHAL, GENERAL, LIEUTENANT_GENERAL, MAJOR_GENERAL, WARRIOR, LANCER,
RIDER, SPY, FORTRESS, SOLDIER, CANNON, ARCHER, MUSKETEER, TACTICIAN,
INTRO_POSITION, BEGINNER_POSITION, INTERMEDIATE_POSITION, ADVANCED_POSITION,
SQUARES, CANONICAL_NAMES, ENGLISH_NAMES, FEN_CODES,
)
Constructor: Gungi(fen: str | None = None)
from gungi import Gungi
# default (INTRO_POSITION)
gungi = Gungi()
# load a FEN
gungi = Gungi(
"d1f1|r:d||r:j|j|k:a:c||g:s|/3|w:s|2|n:w|2/f8/3n2d1m/2i4W1/3dN1tR1/4D2R1/ASJ2|C:a|1A1/|I:F:W|F1SGMT1|K:J| -/- w 3 - 57"
)
.ascii(english: bool = False)
from gungi import BEGINNER_POSITION, Gungi
gungi = Gungi(BEGINNER_POSITION)
gungi.move("槍(8-5-1)(7-5-2)付")
gungi.move("新馬(1-7-1)")
gungi.move("新忍(8-7-2)付")
print(gungi.ascii())
print(gungi.ascii(english=True))
.board()
gungi = Gungi()
print(gungi.board())
.captured(color)
from gungi import BEGINNER_POSITION, Gungi
gungi = Gungi(BEGINNER_POSITION)
gungi.move("新少(7-5-2)付")
gungi.move("兵(3-5-1)(4-5-1)")
gungi.move("新槍(7-4-2)付")
gungi.move("兵(4-5-1)(5-5-1)")
gungi.move("少(7-5-2)取-(5-5-1)")
print(gungi.captured("b"))
.clear()
gungi.clear()
print(gungi.fen())
.fen()
gungi = Gungi(BEGINNER_POSITION)
gungi.move("槍(8-5-1)(7-5-2)付")
gungi.move("新馬(1-7-1)")
gungi.move("新忍(8-7-2)付")
print(gungi.fen())
.get(square)
gungi = Gungi(BEGINNER_POSITION)
gungi.move("砦(7-3-1)(7-4-2)付")
print(gungi.get("7-4"))
print(gungi.get("7-3"))
.get_drafting_rights(color)
from gungi import ADVANCED_POSITION, Gungi
gungi = Gungi(ADVANCED_POSITION)
gungi.move("新帥(7-2-1)終")
print(gungi.get_drafting_rights())
print(gungi.get_drafting_rights("b"))
.get_top(square)
gungi = Gungi(BEGINNER_POSITION)
print(gungi.get_top("7-4"))
print(gungi.get_top("7-3"))
.hand(color)
gungi = Gungi(BEGINNER_POSITION)
gungi.move("槍(8-5-1)(7-5-2)付")
gungi.move("新馬(1-7-1)")
gungi.move("新忍(8-7-2)付")
print(gungi.hand())
print(gungi.hand("w"))
.history(verbose: bool = False)
gungi = Gungi(BEGINNER_POSITION)
gungi.move("新少(7-5-2)付")
gungi.move("兵(3-5-1)(4-5-1)")
gungi.move("新槍(7-4-2)付")
gungi.move("兵(4-5-1)(5-5-1)")
gungi.move("少(7-5-2)取-(5-5-1)")
print(gungi.history())
print(gungi.history(verbose=True))
.in_draft()
gungi = Gungi(ADVANCED_POSITION)
gungi.move("新帥(7-2-1)終")
print(gungi.in_draft())
gungi.move("新帥(2-4-1)終")
print(gungi.in_draft())
.is_fourfold_repetition()
from gungi import INTRO_POSITION, Gungi
gungi = Gungi(INTRO_POSITION)
print(gungi.is_fourfold_repetition())
gungi.move("兵(7-1-1)(6-1-1)")
gungi.move("侍(3-4-1)(4-4-1)")
gungi.move("兵(6-1-1)(7-1-1)")
gungi.move("侍(4-4-1)(3-4-1)")
gungi.move("兵(7-1-1)(6-1-1)")
gungi.move("侍(3-4-1)(4-4-1)")
gungi.move("兵(6-1-1)(7-1-1)")
gungi.move("侍(4-4-1)(3-4-1)")
gungi.move("兵(7-1-1)(6-1-1)")
gungi.move("侍(3-4-1)(4-4-1)")
gungi.move("兵(6-1-1)(7-1-1)")
gungi.move("侍(4-4-1)(3-4-1)")
print(gungi.is_fourfold_repetition())
.is_game_over()
from gungi import ADVANCED_POSITION, Gungi
gungi = Gungi(ADVANCED_POSITION)
print(gungi.is_game_over())
gungi.load("1|g:N|2|W:N|Ad1f/7r1/1nd2Adfr/2|c:G|j2K2/6s1D/1w|W:T|6/2F4J|F:D|/i8/2|S:w||R:M|3C1 -/- b 3 - 164")
print(gungi.is_game_over())
.load(fen)
gungi = Gungi()
gungi.load("d1f1|r:d||r:j|j|k:a:c||g:s|/3|w:s|2|n:w|2/f8/3n2d1m/2i4W1/3dN1tR1/4D2R1/ASJ2|C:a|1A1/|I:F:W|F1SGMT1|K:J| -/- w 3 - 57")
try:
gungi.load("3img3/1ra1n1xas1/d1fwdwf1d/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1")
except Exception as e:
print(e)
.load_pgn(pgn, fen=ADVANCED_POSITION, opts=None)
from gungi import ADVANCED_POSITION, Gungi, PGNOptions
gungi = Gungi(ADVANCED_POSITION)
pgn = [
"1.新帥(7-9-1)終 新帥(1-5-1) 新少(2-8-1) 新謀(1-2-1) 新馬(2-8-2)付 新将(3-4-1)終",
"2.帥(7-9-1)(8-8-1) 新砦(3-2-1) 3.新謀(8-2-1) 新弓(1-8-1) 4.新弓(9-9-1) 新馬(1-3-1)",
"5.新筒(9-5-1) 新弓(1-8-2)付 6.新兵(8-5-1) 新砦(3-6-1) 7.新将(8-9-1) 砦(3-6-1)(2-5-1)",
"8.新少(8-9-2)付 新少(1-7-1) 9.新槍(8-2-2)付 新侍(1-2-2)付 10.新兵(9-8-1) 少(1-7-1)(2-7-1)",
"11.新槍(8-6-1) 新侍(2-4-1) 12.新砦(9-9-2)付 侍(2-4-1)(3-3-1) 13.少(8-9-2)(7-8-1) 新兵(1-4-1)",
]
gungi.load_pgn("\n".join(pgn), opts=PGNOptions(newline="\n"))
gungi.print()
.move(move)
SAN:
from gungi import INTRO_POSITION, Gungi
gungi = Gungi(INTRO_POSITION)
print(gungi.move("兵(7-1-1)(6-1-1)"))
gungi.move("将(1-4-1)(2-10-1)") # raises
Object:
from gungi import Gungi, SOLDIER
gungi = Gungi(INTRO_POSITION)
print(gungi.move({"piece": SOLDIER, "from_": "7-1-1", "to": "6-1-1", "type": "route"}))
.move_number()
from gungi import Gungi
gungi = Gungi()
gungi.load("d1f1|r:d||r:j|j|k:a:c||g:s|/3|w:s|2|n:w|2/f8/3n2d1m/2i4W1/3dN1tR1/4D2R1/ASJ2|C:a|1A1/|I:F:W|F1SGMT1|K:J| -/- w 3 - 57")
print(gungi.move_number())
.moves(...)
from gungi import INTRO_POSITION, Gungi
gungi = Gungi(INTRO_POSITION)
print(gungi.moves())
print(gungi.moves(square="8-5"))
print(gungi.moves(arata=gungi.hand("w")[0]))
print(gungi.moves(verbose=True))
.pgn(opts=None)
from gungi import BEGINNER_POSITION, Gungi, PGNOptions
gungi = Gungi(BEGINNER_POSITION)
gungi.move("槍(8-5-1)(7-5-2)付")
gungi.move("新馬(1-7-1)")
gungi.move("新忍(8-7-2)付")
print(gungi.pgn(PGNOptions(max_width=2, newline="<br />")))
.print()
gungi.print()
.reset()
gungi.reset()
.turn()
gungi.load("3img3/1s2n2s1/d1fwdwf1d/9/9/9/D1FW|D:J|WF1D/1S2N2S1/3GMI3 J1N2R2D1/j2n2r2d1 b 0 - 1")
print(gungi.turn())
.undo()
from gungi import BEGINNER_POSITION, Gungi
gungi = Gungi(BEGINNER_POSITION)
print(gungi.fen())
gungi.move("兵(7-1-1)(6-1-1)")
print(gungi.fen())
print(gungi.undo())
print(gungi.fen())
print(gungi.undo())
validate_fen(fen)
from gungi import validate_fen
print(validate_fen("3img3/1s2n2s1/d1fw1wf1d/9/4J4/9/D1FWD|W:N|F1D/1S2N2S1/3GMI3 J1N1R2D1/j2n2r2d1 b 0 - 3"))
print(validate_fen("3img3/1ra1n1xas1/d1fwdwf1d/9/9/9/9/9/9 J2N2R1D1/j2n2r2d1 w 1 - 1"))
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gungi-0.1.0.tar.gz.
File metadata
- Download URL: gungi-0.1.0.tar.gz
- Upload date:
- Size: 34.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1a98e1d251216a5e493fd9613d8501a41bf5fc97b8e0740248522794b670a54
|
|
| MD5 |
3b9b698ffd5edb95eb75b056234005de
|
|
| BLAKE2b-256 |
dbb138a4276dec3b0f1d76d73485865062051ecdf0e61f5dd42c18a9bf95d70f
|
File details
Details for the file gungi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: gungi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13cd7765165abc7ba69ff2d1ec7befc3b428d0cb32182cc88bd5b13a376b41af
|
|
| MD5 |
3958e36633f4b7cea1f4285acde55463
|
|
| BLAKE2b-256 |
3ba40243d4f7d1964c99d2fb9a6b72c13696763b4e0f7093845a814e78ecb75b
|