Skip to content

Commit dc2e284

Browse files
committed
Send the final result of a game to engines
UCI engines receive the final board position. Xboard engines receive the final move of the game and a message describing the winner and reason for the game ending.
1 parent bd371f6 commit dc2e284

1 file changed

Lines changed: 68 additions & 0 deletions

File tree

chess/engine.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,27 @@ async def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, m
12461246
and stopping the analysis at any time.
12471247
"""
12481248

1249+
@abc.abstractmethod
1250+
async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None:
1251+
"""
1252+
Sends the engine the result of the game.
1253+
1254+
UCI engines only recieve the final position of the board. XBoard engines
1255+
receive the final moves and a line of the form "result <winner> {<ending>}",
1256+
where <winner> is one of "1-0", "0-1", "1/2-1/2", or "*" to indicate white
1257+
won, black won, draw, or adjournment, respectively.
1258+
1259+
:param board: The final state of the board.
1260+
:param winner: Optional. Specify the winner of the game. This is useful
1261+
if the result of the game is not evident from the board--e.g., time
1262+
forfeiture or draw by agreement.
1263+
:param game_ending: Optional. Text describing the reason for the game
1264+
ending. Similarly to the winner paramter, this overrides any game
1265+
result derivable from the board.
1266+
:param game_complete: Optional. Whether the game is complete or may resume
1267+
at a later time.
1268+
"""
1269+
12491270
@abc.abstractmethod
12501271
async def quit(self) -> None:
12511272
"""Asks the engine to shut down."""
@@ -1797,6 +1818,15 @@ def engine_terminated(self, engine: UciProtocol, exc: Exception) -> None:
17971818

17981819
return await self.communicate(UciAnalysisCommand)
17991820

1821+
async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None:
1822+
class UciGameResultCommand(BaseCommand[UciProtocol, None]):
1823+
def start(self, engine: UciProtocol) -> None:
1824+
engine._position(board) # Send final board position
1825+
self.result.set_result(None)
1826+
self.set_finished()
1827+
1828+
return await self.communicate(UciGameResultCommand)
1829+
18001830
async def quit(self) -> None:
18011831
self.send_line("quit")
18021832
await asyncio.shield(self.returncode)
@@ -2477,6 +2507,38 @@ def _opponent_configuration(self, *, opponent: Optional[Opponent] = None, engine
24772507
async def send_opponent_information(self, *, opponent: Optional[Opponent] = None, engine_rating: Optional[int] = None) -> None:
24782508
return await self.configure(self._opponent_configuration(opponent=opponent, engine_rating=engine_rating))
24792509

2510+
async def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None:
2511+
class XBoardGameResultCommand(BaseCommand[XBoardProtocol, None]):
2512+
def start(self, engine: XBoardProtocol) -> None:
2513+
engine._new(board, engine.game, {}) # Send final moves to engine.
2514+
2515+
outcome = board.outcome(claim_draw=True)
2516+
2517+
if not game_complete:
2518+
result = "*"
2519+
ending = game_ending or "Game adjourned"
2520+
elif winner is not None or game_ending:
2521+
result = "1-0" if winner == chess.WHITE else ("0-1" if winner == chess.BLACK else "1/2-1/2")
2522+
ending = game_ending or ""
2523+
elif outcome is not None and outcome.winner is not None:
2524+
result = outcome.result()
2525+
winning_color = "White" if outcome.winner == chess.WHITE else "Black"
2526+
is_checkmate = outcome.termination == chess.Termination.CHECKMATE
2527+
ending = f"{winning_color} {'mates' if is_checkmate else 'variant win'}"
2528+
elif outcome is not None:
2529+
result = outcome.result()
2530+
ending = outcome.termination.name.capitalize().replace("_", " ")
2531+
else:
2532+
result = "*"
2533+
ending = ""
2534+
2535+
ending_text = f"{{{ending}}}" if ending else ""
2536+
engine.send_line(f"result {result} {ending_text}".strip())
2537+
self.result.set_result(None)
2538+
self.set_finished()
2539+
2540+
return await self.communicate(XBoardGameResultCommand)
2541+
24802542
async def quit(self) -> None:
24812543
self.send_line("quit")
24822544
await asyncio.shield(self.returncode)
@@ -2904,6 +2966,12 @@ def analysis(self, board: chess.Board, limit: Optional[Limit] = None, *, multipv
29042966
future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop)
29052967
return SimpleAnalysisResult(self, future.result())
29062968

2969+
def send_game_result(self, board: chess.Board, winner: Optional[Color] = None, game_ending: Optional[str] = None, game_complete: bool = True) -> None:
2970+
with self._not_shut_down():
2971+
coro = asyncio.wait_for(self.protocol.send_game_result(board, winner, game_ending, game_complete), self.timeout)
2972+
future = asyncio.run_coroutine_threadsafe(coro, self.protocol.loop)
2973+
return future.result()
2974+
29072975
def quit(self) -> None:
29082976
with self._not_shut_down():
29092977
coro = asyncio.wait_for(self.protocol.quit(), self.timeout)

0 commit comments

Comments
 (0)