@@ -1266,6 +1266,31 @@ async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, m
12661266 and stopping the analysis at any time.
12671267 """
12681268
1269+ @abc .abstractmethod
1270+ async def send_game_result (self , board : chess .Board , winner : Optional [Color ] = None , game_ending : Optional [str ] = None , game_complete : bool = True ) -> None :
1271+ """
1272+ Sends the engine the result of the game.
1273+
1274+ XBoard engines receive the final moves and a line of the form
1275+ "result <winner> {<ending>}". The <winner> field is one of "1-0",
1276+ "0-1", "1/2-1/2", or "*" to indicate white won, black won, draw,
1277+ or adjournment, respectively. The <ending> field is a description
1278+ of the specific reason for the end of the game: "White mates",
1279+ "Time forfeiture", "Stalemate", etc.
1280+
1281+ UCI engines do not expect end-of-game information and so are not
1282+ sent anything.
1283+
1284+ :param board: The final state of the board.
1285+ :param winner: Optional. Specify the winner of the game. This is useful
1286+ if the result of the game is not evident from the board--e.g., time
1287+ forfeiture or draw by agreement.
1288+ :param game_ending: Optional. Text describing the reason for the game
1289+ ending. Similarly to the winner paramter, this overrides any game
1290+ result derivable from the board.
1291+ :param game_complete: Optional. Whether the game reached completion.
1292+ """
1293+
12691294 @abc .abstractmethod
12701295 async def quit (self ) -> None :
12711296 """Asks the engine to shut down."""
@@ -1817,6 +1842,9 @@ def engine_terminated(self, engine: UciProtocol, exc: Exception) -> None:
18171842
18181843 return await self .communicate (UciAnalysisCommand )
18191844
1845+ async def send_game_result (self , board : chess .Board , winner : Optional [Color ] = None , game_ending : Optional [str ] = None , game_complete : bool = True ) -> None :
1846+ pass
1847+
18201848 async def quit (self ) -> None :
18211849 self .send_line ("quit" )
18221850 await asyncio .shield (self .returncode )
@@ -2502,6 +2530,41 @@ def _opponent_configuration(self, *, opponent: Optional[Opponent] = None, engine
25022530 async def send_opponent_information (self , * , opponent : Optional [Opponent ] = None , engine_rating : Optional [int ] = None ) -> None :
25032531 return await self .configure (self ._opponent_configuration (opponent = opponent , engine_rating = engine_rating ))
25042532
2533+ async def send_game_result (self , board : chess .Board , winner : Optional [Color ] = None , game_ending : Optional [str ] = None , game_complete : bool = True ) -> None :
2534+ class XBoardGameResultCommand (BaseCommand [XBoardProtocol , None ]):
2535+ def start (self , engine : XBoardProtocol ) -> None :
2536+ if game_ending and any (c in game_ending for c in "{}\n \r " ):
2537+ raise EngineError (f"invalid line break or curly braces in game ending message: { game_ending !r} " )
2538+
2539+ engine ._new (board , engine .game , {}) # Send final moves to engine.
2540+
2541+ outcome = board .outcome (claim_draw = True )
2542+
2543+ if not game_complete :
2544+ result = "*"
2545+ ending = game_ending or ""
2546+ elif winner is not None or game_ending :
2547+ result = "1-0" if winner == chess .WHITE else "0-1" if winner == chess .BLACK else "1/2-1/2"
2548+ ending = game_ending or ""
2549+ elif outcome is not None and outcome .winner is not None :
2550+ result = outcome .result ()
2551+ winning_color = "White" if outcome .winner == chess .WHITE else "Black"
2552+ is_checkmate = outcome .termination == chess .Termination .CHECKMATE
2553+ ending = f"{ winning_color } { 'mates' if is_checkmate else 'variant win' } "
2554+ elif outcome is not None :
2555+ result = outcome .result ()
2556+ ending = outcome .termination .name .capitalize ().replace ("_" , " " )
2557+ else :
2558+ result = "*"
2559+ ending = ""
2560+
2561+ ending_text = f"{{{ ending } }}" if ending else ""
2562+ engine .send_line (f"result { result } { ending_text } " .strip ())
2563+ self .result .set_result (None )
2564+ self .set_finished ()
2565+
2566+ return await self .communicate (XBoardGameResultCommand )
2567+
25052568 async def quit (self ) -> None :
25062569 self .send_line ("quit" )
25072570 await asyncio .shield (self .returncode )
@@ -2929,6 +2992,12 @@ def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, multipv
29292992 future = asyncio .run_coroutine_threadsafe (coro , self .protocol .loop )
29302993 return SimpleAnalysisResult (self , future .result ())
29312994
2995+ def send_game_result (self , board : chess .Board , winner : Optional [Color ] = None , game_ending : Optional [str ] = None , game_complete : bool = True ) -> None :
2996+ with self ._not_shut_down ():
2997+ coro = asyncio .wait_for (self .protocol .send_game_result (board , winner , game_ending , game_complete ), self .timeout )
2998+ future = asyncio .run_coroutine_threadsafe (coro , self .protocol .loop )
2999+ return future .result ()
3000+
29323001 def quit (self ) -> None :
29333002 with self ._not_shut_down ():
29343003 coro = asyncio .wait_for (self .protocol .quit (), self .timeout )
0 commit comments