def ponderhit(self, *, async_callback=None): """ May be sent if the expected ponder move has been played. The engine should continue searching, but should switch from pondering to normal search. :return: Nothing. :raises: :exc:`~chess.uci.EngineStateException` if the engine is not currently searching in ponder mode. """ with self.state_changed: if self.idle: raise EngineStateException("ponderhit but not searching") if not self.pondering: raise EngineStateException("ponderhit but not pondering") self.pondering = False self.state_changed.notify_all() def command(): self.search_started.wait() with self.semaphore: self.send_line("ponderhit") return self._queue_command(command, async_callback)
def on_line_received(self, buf): LOGGER.debug("%s >> %s", self.process, buf) if buf.startswith("feature"): return self._feature(buf[8:]) elif buf.startswith("Illegal"): split_buf = buf.split() illegal_move = split_buf[-1] exception_msg = "Engine received an illegal move: {}".format( illegal_move) if len(split_buf) == 4: reason = split_buf[2][1:-2] exception_msg = " ".join([exception_msg, reason]) raise EngineStateException(exception_msg) elif buf.startswith("Error"): err_msg = buf.split()[1][1:-2] raise EngineStateException( "Engine produced an error: {}".format(err_msg)) elif buf.startswith("#"): return command_and_args = buf.split() if not command_and_args: return if len(command_and_args) == 1: if command_and_args[0] == "resign": return self._resign() elif len(command_and_args) == 2: if command_and_args[0] == "pong": return self._pong(command_and_args[1]) elif command_and_args[0] == "move": return self._move(command_and_args[1]) elif command_and_args[0] == "offer" and command_and_args[ 1] == "draw": return self._offer_draw() elif command_and_args[0] == "Hint:": return self._hint(command_and_args[1]) elif len(command_and_args) >= 5: return self._post(buf)
def send_variant(self, variant, *, async_callback=None): """ Optionally sent to the engine immediately after 'new' for games that use other than FIDE rules. Sets the engine to play mentioned variant. Only used with variants that the engine announced it could play in the 'feature variants="variant,variant,..."' command at startup. :param variant: The variant name to play. :return: Nothing. """ self._assert_not_busy("variant") if variant not in self.supported_variants: raise EngineStateException("Engine does not support %s variant" % variant) command = self.command("variant %s" % variant) return self._queue_command(command, async_callback)
def egtpath(self, egt_type, egt_path, *, async_callback=None): """ Tells the engine to use the *egt_type* endgame tablebases at *egt_path*. The engine must have this type specified in the *feature egt*. For example, the engine may have *feature egt=syzygy*, then it is legal to call *egtpath("syzygy", "<path-to-syzygy>")*. :param egt_type: The type of EGT pointed to (syzygy, gaviota, etc.). :param egt_path: The path to the desired EGT. :return: Nothing. """ if egt_type not in self.features.get("egt"): raise EngineStateException("engine does not support the '{}' egt".format(egt_type)) builder = ["egtpath", egt_type, egt_path] command = self.command(" ".join(builder)) return self._queue_command(command, async_callback)
def _assert_not_busy(self, cmd): with self.state_changed: if not self.idle: raise EngineStateException("{} command while engine is busy".format(cmd))
def _assert_supports_feature(self, feature_name): if not self.features.supports(feature_name): raise EngineStateException("engine does not support the '{}' feature" .format(feature_name))
def usermove(self, move, *, async_callback=None): """ Tells the XBoard engine to make a move on its internal board. If *auto_force* is set to ``True``, the engine will not start thinking about its next move immediately after. :param move: The move to play in XBoard notation. :return: Nothing. """ builder = [] if self.features.supports("usermove"): builder.append("usermove") if self.draw_handler: self.draw_handler.clear_offer() self.engine_offered_draw = False if self.auto_force: self.force() elif not self.in_force: with self.state_changed: if not self.idle: raise EngineStateException("usermove command while engine is already busy") self.idle = False self.search_started.clear() self.move_received.clear() self.state_changed.notify_all() for post_handler in self.post_handlers: post_handler.on_go() try_move(self.board, str(move)) builder.append(str(move)) def command(): move_str = " ".join(builder) self.ponder_move = None if self.in_force: with self.semaphore: self.send_line(move_str) if self.terminated.is_set(): raise EngineTerminatedException() else: with self.semaphore: self.send_line(move_str) self.search_started.set() self.move_received.wait() with self.state_changed: self.idle = True self.state_changed.notify_all() if self.terminated.is_set(): raise EngineTerminatedException() try_move(self.board, str(self.move)) return self.move return self._queue_command(command, async_callback)
def go(self, *, searchmoves=None, ponder=False, wtime=None, btime=None, winc=None, binc=None, movestogo=None, depth=None, nodes=None, mate=None, movetime=None, infinite=False, async_callback=None): """ Start calculating on the current position. All parameters are optional, but there should be at least one of *depth*, *nodes*, *mate*, *infinite* or some time control settings, so that the engine knows how long to calculate. Note that when using *infinite* or *ponder*, the engine will not stop until it is told to. :param searchmoves: Restrict search to moves in this list. :param ponder: Bool to enable pondering mode. The engine will not stop pondering in the background until a *stop* command is received. :param wtime: Integer of milliseconds White has left on the clock. :param btime: Integer of milliseconds Black has left on the clock. :param winc: Integer of white Fisher increment. :param binc: Integer of black Fisher increment. :param movestogo: Number of moves to the next time control. If this is not set, but wtime or btime are, then it is sudden death. :param depth: Search *depth* ply only. :param nodes: Search so many *nodes* only. :param mate: Search for a mate in *mate* moves. :param movetime: Integer. Search exactly *movetime* milliseconds. :param infinite: Search in the background until a *stop* command is received. :return: A tuple of two elements. The first is the best move according to the engine. The second is the ponder move. This is the reply as sent by the engine. Either of the elements may be ``None``. :raises: :exc:`~chess.uci.EngineStateException` if the engine is already calculating. """ with self.state_changed: if not self.idle: raise EngineStateException( "go command while engine is already busy") self.idle = False self.search_started.clear() self.bestmove_received.clear() self.pondering = ponder self.state_changed.notify_all() for info_handler in self.info_handlers: info_handler.on_go() builder = ["go"] if ponder: builder.append("ponder") if wtime is not None: builder.append("wtime") builder.append(str(int(wtime))) if btime is not None: builder.append("btime") builder.append(str(int(btime))) if winc is not None: builder.append("winc") builder.append(str(int(winc))) if binc is not None: builder.append("binc") builder.append(str(int(binc))) if movestogo is not None and movestogo > 0: builder.append("movestogo") builder.append(str(int(movestogo))) if depth is not None: builder.append("depth") builder.append(str(int(depth))) if nodes is not None: builder.append("nodes") builder.append(str(int(nodes))) if mate is not None: builder.append("mate") builder.append(str(int(mate))) if movetime is not None: builder.append("movetime") builder.append(str(int(movetime))) if infinite: builder.append("infinite") if searchmoves: builder.append("searchmoves") for move in searchmoves: builder.append(self.board.uci(move)) def command(): with self.semaphore: self.send_line(" ".join(builder)) self.search_started.set() self.bestmove_received.wait() with self.state_changed: self.idle = True self.state_changed.notify_all() if self.terminated.is_set(): raise EngineTerminatedException() return BestMove(self.bestmove, self.ponder) return self._queue_command(command, async_callback)
def position(self, board, *, async_callback=None): """ Set up a given position. Rather than sending just the final FEN, the initial FEN and all moves leading up to the position will be sent. This will allow the engine to use the move history (for example to detect repetitions). If the position is from a new game, it is recommended to use the *ucinewgame* command before the *position* command. :param board: A *chess.Board*. :return: Nothing :raises: :exc:`~chess.uci.EngineStateException` if the engine is still calculating. """ # Raise if this is called while the engine is still calculating. with self.state_changed: if not self.idle: raise EngineStateException( "position command while engine is busy") # Set UCI_Variant and UCI_Chess960. options = {} uci_variant = type(board).uci_variant if uci_variant != (self.uci_variant or "chess"): if self.uci_variant is None: LOGGER.warning( "engine may not support UCI_Variant or has not been initialized with 'uci' command" ) options["UCI_Variant"] = type(board).uci_variant if bool(self.uci_chess960) != board.chess960: if self.uci_chess960 is None: LOGGER.warning( "engine may not support UCI_Chess960 or has not been initialized with 'uci' command" ) options["UCI_Chess960"] = board.chess960 option_lines = self._setoption(options) # Send starting position. builder = ["position"] root = board.root() fen = root.fen() if uci_variant == "chess" and fen == chess.STARTING_FEN: builder.append("startpos") else: builder.append("fen") builder.append(root.shredder_fen() if self.uci_chess960 else fen) # Send moves. if board.move_stack: builder.append("moves") builder.extend(move.uci() for move in board.move_stack) self.board = board.copy(stack=False) def command(): with self.semaphore: for option_line in option_lines: self.send_line(option_line) self.send_line(" ".join(builder)) if self.terminated.is_set(): raise EngineTerminatedException() return self._queue_command(command, async_callback)