Skip to content

Commit

Permalink
Merge pull request #1003 from MarkZH/xboard-clock-update
Browse files Browse the repository at this point in the history
Add optional identifier to Limit instances
  • Loading branch information
niklasf committed Jul 8, 2023
2 parents 58a54f1 + 40e633b commit 714f672
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 8 deletions.
25 changes: 19 additions & 6 deletions chess/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,14 @@ class Limit:
*white_clock* and *black_clock* are, then it is sudden death.
"""

clock_id: object = None
"""
An identifier to use with XBoard engines to signal that the time
control has changed. When this field changes, Xboard engines are
sent level or st commands as appropriate. Otherwise, only time
and otim commands are sent to update the engine's clock.
"""

def __repr__(self) -> str:
# Like default __repr__, but without None values.
return "{}({})".format(
Expand Down Expand Up @@ -2013,6 +2021,7 @@ def __init__(self) -> None:
self.target_config: Dict[str, ConfigValue] = {}
self.board = chess.Board()
self.game: object = None
self.clock_id: object = None
self.first_game = True

async def initialize(self) -> None:
Expand Down Expand Up @@ -2214,10 +2223,9 @@ def start(self, engine: XBoardProtocol) -> None:
# Limit or time control.
clock = limit.white_clock if board.turn else limit.black_clock
increment = limit.white_inc if board.turn else limit.black_inc
if limit.remaining_moves or clock is not None or increment is not None:
base_mins, base_secs = divmod(int(clock or 0), 60)
engine.send_line(f"level {limit.remaining_moves or 0} {base_mins}:{base_secs:02d} {increment or 0}")

if limit.clock_id is None or limit.clock_id != engine.clock_id:
self._send_time_control(engine, clock, increment)
engine.clock_id = limit.clock_id
if limit.nodes is not None:
if limit.time is not None or limit.white_clock is not None or limit.black_clock is not None or increment is not None:
raise EngineError("xboard does not support mixing node limits with time limits")
Expand All @@ -2229,8 +2237,6 @@ def start(self, engine: XBoardProtocol) -> None:

engine.send_line("nps 1")
engine.send_line(f"st {max(1, int(limit.nodes))}")
if limit.time is not None:
engine.send_line(f"st {max(0.01, limit.time)}")
if limit.depth is not None:
engine.send_line(f"sd {max(1, int(limit.depth))}")
if limit.white_clock is not None:
Expand Down Expand Up @@ -2282,6 +2288,13 @@ def line_received(self, engine: XBoardProtocol, line: str) -> None:
else:
LOGGER.warning("%s: Unexpected engine output: %r", engine, line)

def _send_time_control(self, engine: XBoardProtocol, clock: Optional[float], increment: Optional[float]) -> None:
if limit.remaining_moves or clock is not None or increment is not None:
base_mins, base_secs = divmod(int(clock or 0), 60)
engine.send_line(f"level {limit.remaining_moves or 0} {base_mins}:{base_secs:02d} {increment or 0}")
if limit.time is not None:
engine.send_line(f"st {max(0.01, limit.time)}")

def _post(self, engine: XBoardProtocol, line: str) -> None:
if not self.result.done():
self.play_result.info = _parse_xboard_post(line, engine.board, info)
Expand Down
21 changes: 19 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3714,7 +3714,9 @@ async def main():
mock.assert_done()

limit = chess.engine.Limit(black_clock=65, white_clock=100,
black_inc=4, white_inc=8)
black_inc=4, white_inc=8,
clock_id="xboard level")
board = chess.Board()
mock.expect("new")
mock.expect("force")
mock.expect("level 0 1:40 8")
Expand All @@ -3724,10 +3726,25 @@ async def main():
mock.expect("easy")
mock.expect("go", ["move e2e4"])
mock.expect_ping()
result = await protocol.play(chess.Board(), limit)
result = await protocol.play(board, limit)
self.assertEqual(result.move, chess.Move.from_uci("e2e4"))
mock.assert_done()

board.push(result.move)
board.push_uci("e7e5")

mock.expect("force")
mock.expect("e7e5")
mock.expect("time 10000")
mock.expect("otim 6500")
mock.expect("nopost")
mock.expect("easy")
mock.expect("go", ["move d2d4"])
mock.expect_ping()
result = await protocol.play(board, limit)
self.assertEqual(result.move, chess.Move.from_uci("d2d4"))
mock.assert_done()

asyncio.set_event_loop_policy(chess.engine.EventLoopPolicy())
asyncio.run(main())

Expand Down

0 comments on commit 714f672

Please sign in to comment.