"""Simple Go engine wrapper using sgfmill instead of sente.This module provides a compatibility layer to replace sente with sgfmill, which iscompatible with Python 3.12."""importloggingimportsgfmill.boardsimportsgfmill.commonimportsgfmill.sgf# Handle optional sgfmill dependencytry:SGFMILL_AVAILABLE=TrueexceptImportError:SGFMILL_AVAILABLE=False# Create dummy classes for when sgfmill is not availableclassDummySgfmill:def__getattr__(self,name):""" Getattr .Args: name: [TODO: Add description]"""raiseImportError("sgfmill is required for Go game functionality. Please install it with: pip install sgfmill")sgfmill=DummySgfmill()logger=logging.getLogger(__name__)
[docs]classGoGame:"""Simple Go game wrapper using sgfmill."""def__init__(self,board_size:int=19):""" Init .Args: board_size: [TODO: Add description]"""ifnotSGFMILL_AVAILABLE:raiseImportError("sgfmill is required for Go game functionality. Please install it with: pip install sgfmill")self.board_size=board_sizeself.board=sgfmill.boards.Board(board_size)self.move_history=[]self.passes=0self.current_player="b"# 'b' for black, 'w' for whiteself.captured={"b":0,"w":0}
[docs]defplay_move(self,color:str,move:tuple[int,int]|None):"""Play a move on the board. Args: color: 'b' for black, 'w' for white move: (row, col) tuple or None for pass """ifmoveisNone:# Passself.passes+=1self.move_history.append((color,None))else:row,col=move# sgfmill uses different coordinate systempoint=(self.board_size-1-row,col)# Check for captures before playing# Play the movetry:self.board.play(point[0],point[1],color)self.passes=0self.move_history.append((color,move))# Simple capture detection (this is simplified)# In a real implementation, you'd check for captured groupsexceptValueError:raiseValueError(f"Invalid move at {move}")# Switch playerself.current_player="w"ifcolor=="b"else"b"
[docs]defto_sgf(self)->str:"""Convert game to SGF format."""game_tree=sgfmill.sgf.Sgf_game(size=self.board_size)root=game_tree.get_root()root.set("GM",1)# Go gameroot.set("FF",4)# File formatroot.set("SZ",self.board_size)root.set("KM",6.5)# Kominode=rootforcolor,moveinself.move_history:node=game_tree.extend_main_sequence()ifmoveisNone:node.set_move(color,None)else:row,col=move# Convert to sgfmill coordinatespoint=(self.board_size-1-row,col)node.set_move(color,point)returngame_tree.serialise()
[docs]defturn(self)->str:"""Get current player to move."""returnself.current_player
[docs]defloads_sgf(sgf_string:str)->GoGame:"""Load a game from SGF string."""try:sgf_game=sgfmill.sgf.Sgf_game.from_string(sgf_string)except(ValueError,AttributeError)ase:# If parsing fails, return empty gamelogger.warning(f"Failed to parse SGF string: {e}")returnGoGame(19)size=sgf_game.get_size()game=GoGame(size)# Replay moves from SGFfornodeinsgf_game.get_main_sequence()[1:]:# Skip rootcolor,move=node.get_move()ifcolorisnotNone:ifmoveisNone:game.play_move(color,None)else:# Convert from sgfmill to our coordinatesrow=size-1-move[0]col=move[1]game.play_move(color,(row,col))returngame
[docs]defdumps_sgf(game:GoGame)->str:"""Convert game to SGF string."""returngame.to_sgf()
# Constants for compatibilityBLACK="b"WHITE="w"# Wrapper classes for compatibility
[docs]@staticmethoddefloads(sgf_string:str):"""Load game from SGF."""returnloads_sgf(sgf_string)
[docs]@staticmethoddefdumps(game):"""Save game to SGF."""ifisinstance(game,GoGame):returndumps_sgf(game)elifhasattr(game,"to_sgf"):returngame.to_sgf()else:# Assume it's already an SGF stringreturnstr(game)
[docs]defGame(board_size:int=19)->GoGame:"""Create a new Go game."""returnGoGame(board_size)