Skip to main content

A clean, extensible Python library for communicating with Gomoku engines via the Gomocup protocol.

Project description

PyGomo Developer Cookbook 📖

PyGomo is designed to be the backbone of your Gomoku application. Whether you are building a GUI, a tournament manager, or an analysis bot, this cookbook provides the recipes you need to get things done.

🏗️ Architecture Overview

Understanding the layers will help you know where to look:

  1. pygomo (Top Level): Exports common objects like EngineClient, Move, SearchInfo. Start here.
  2. pygomo.board: The "Physics Engine". Handles board state, move generation (BitBoard), and Renju rules validation.
  3. pygomo.client: The High-Level API. EngineClient lives here.
  4. pygomo.protocol: The Translator. Converts Python objects to/from Gomocup protocol strings.
  5. pygomo.transport: The Plumber. Handles subprocess creation and non-blocking I/O.

🍳 Recipe 1: The "Hello World" Connection

The simplest way to connect to an engine. Always use the with statement to ensure the subprocess is killed clean.

from pygomo import EngineClient

# Engine path can be absolute or relative
with EngineClient("./engines/piskvork") as engine:
    # 1. Start a game (default 15x15)
    engine.start(board_size=15)
    
    # 2. Make a move (Human plays h8)
    result = engine.turn("h8")
    
    # 3. Print result
    print(f"Engine replied: {result.move}")

🍳 Recipe 2: Configuring The Engine

Before starting the game, you often need to set limits. Note that start() resets some engine internals, so configure after start() usually (depending on engine quirk, but safely: connect -> start -> configure).

with EngineClient("./engines/rapfi") as engine:
    engine.start(15)
    
    # Setup Time Control
    engine.set_time(
        turn_time_ms=5000,    # 5 seconds per move
        match_time_ms=300000, # 5 minutes total
        time_left_ms=300000   # Current remaining time
    )
    
    # Setup Rules (1 = Standard, 4 = Renju)
    engine.set_rule(1)
    
    # Advanced Config (Thread, Hash, etc.)
    engine.set_threads(4)
    engine.set_memory(1024 * 1024 * 1024) # 1GB

🍳 Recipe 3: Dealing with Realtime Search Info

Modern engines (like Rapfi, Yixin) provide a stream of search data while thinking. You can tap into this via the on_info callback.

from pygomo import SearchInfo

def my_info_handler(info: SearchInfo):
    # This is called repeatedly while engine thinks!
    if info.depth > 0:
        pv_string = " ".join(str(m) for m in info.pv[:5])
        print(f"\rDepth: {info.depth} | Eval: {info.eval.raw_value} | PV: {pv_string}", end="")

# Pass the handler to any thinking command (turn, begin, board, nbest)
result = engine.turn("h8", on_info=my_info_handler)

# The final 'result' also contains the LAST search info received
print(f"\nFinal Winrate: {result.search_info.winrate_percent:.2f}%")

Understanding SearchInfo fields:

  • depth/sel_depth: Search depth.
  • eval: Evaluate object. Use .raw_value for engine score, .score() for int, .winrate_percent() for 0-100%.
  • pv: List of Move objects (Principal Variation / Best Line).
  • nodes/nps: Performance metrics.

🍳 Recipe 4: Managing Board State with BitBoard

Don't track 2D arrays yourself. Use pygomo.board for high-performance state management.

from pygomo.board import BitBoard, RenjuBitBoard

# Use RenjuBitBoard for complex Renju rules (double-3, double-4, overline)
board = RenjuBitBoard(size=15)

# 1. Place moves
board.place("h8") # Black
board.place("h9") # White

# 2. Check for Win/Forbidden
last_move = board.get_last_move()
win_info = board.check_win(last_move)

if win_info:
    print(f"Winner: {win_info.winner}") # 1=Black, 2=White
    print(f"Line: {win_info.direction}") # e.g. [(7,7), (8,8)...]

# 3. Check Forbidden (Renju Only)
if board.is_forbidden(move="j9", color=1): # Is j9 forbidden for Black?
    print("Foul!")

# 4. Undo
board.undo() # Takes back h9
board.undo() # Takes back h8

🍳 Recipe 5: Synchronizing Engine with Custom Positions

If you implement Undo, Redo, or Load Game, you must sync the engine. The engine has its own internal board.

# ... user clicks undo ...
my_local_board.undo()

# SYNC RECIPE:
# 1. Convert local board to protocol objects
protocol_position = my_local_board.to_position() 

# 2. Send BOARD command
# start_thinking=False means "just set the board, don't move yet"
engine.board(protocol_position, start_thinking=False)

🍳 Recipe 6: Parsing User Input Handling

Users type garbage. Move handles it.

from pygomo import Move

try:
    m1 = Move("h8")       # Standard algebraic
    m2 = Move("H8")       # Case insensitive
    m3 = Move("7,7")      # Numeric (0-indexed)
    m4 = Move((7, 7))     # Tuple
    
    print(m1.col, m1.row) # 7, 7
except ValueError:
    print("Invalid move format")

🍳 Recipe 7: Building a Non-Blocking GUI Loop

If you use asyncio or a GUI event loop (PyQt/Tkinter), you don't want engine.turn() to freeze the UI. PyGomo is synchronous by default (for simplicity), but you can wrap it easily.

Pattern for Threading (Simplest):

import threading

def worker():
    # This blocks, but it's in a thread
    result = engine.turn("h8", on_info=update_gui_progress)
    # Post result to GUI thread
    gui.post_event(GameMoveEvent(result.move))

thread = threading.Thread(target=worker)
thread.start()

🍳 Recipe 8: Custom Commands

Need to send a command PyGomo doesn't support explicitly?

# Send raw string (fire and forget)
engine.send_raw("MY_CUSTOM_COMMAND args")

# Execute and want response (handles timeout/locking)
# This returns a CommandResult object
result = engine.execute("YXSHOWINFO") 
print(result.data) 

🛠️ Developer Checklist

When implementing a full game:

  1. Always handle specific Exceptions: Engines crash. Catch RuntimeError or timeouts on turn execution.
  2. Hash Synchronization: If your board supports Zobrist hashing (board.hash), check it against the engine occasionally (Rapfi supports checksum in some debug modes) to detect desyncs.
  3. Clean Shutdown: Ensure engine.quit() is called. Orphaned engine processes consume CPU.
  4. Protocol Logging: For debugging, you can hook into the transport or use run_game.sh which prints what it's doing.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pygomo_lib-0.1.0.tar.gz (48.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pygomo_lib-0.1.0-py3-none-any.whl (51.9 kB view details)

Uploaded Python 3

File details

Details for the file pygomo_lib-0.1.0.tar.gz.

File metadata

  • Download URL: pygomo_lib-0.1.0.tar.gz
  • Upload date:
  • Size: 48.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pygomo_lib-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2ba23b236065f216cd0f97d1f0ebd67ffa2b61786de304dcef130e90e4aceab5
MD5 728448a61ee9f0dcf63b3f0e1cac96a6
BLAKE2b-256 e2b7ba10a5e30811a4880d027d9b1d4f75656f49de623c41e4fffcfa71650030

See more details on using hashes here.

File details

Details for the file pygomo_lib-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pygomo_lib-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 51.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.11

File hashes

Hashes for pygomo_lib-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 de986fad60d6846176e5e9b28820efd1fbc72e0bbc89c9fe81056dd9dee1c0f5
MD5 7d7fcb11120e16b7a0d1730844761bd6
BLAKE2b-256 672797d1d135dddb56ac963dc68ebb405d6b34ca0aa371fb0559b09d92795e97

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page