Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
 def _assert_not_busy(self, cmd):
     with self.state_changed:
         if not self.idle:
             raise EngineStateException("{} command while engine is busy".format(cmd))
Ejemplo n.º 6
0
 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))
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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)