def command(): with self.semaphore: self.send_line("go") 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() if self.auto_force: self.force() if self.move in DUMMY_RESPONSES: return self.move try: self.board.push_uci(str(self.move)) except ValueError: try: self.board.push_san(str(self.move)) except ValueError: LOGGER.exception("exception parsing move") return self.move
def _bestmove(self, arg): tokens = arg.split(None, 2) self.bestmove = None if tokens[0] != "(none)": try: self.bestmove = self.board.parse_uci(tokens[0]) except ValueError: LOGGER.exception("exception parsing bestmove") self.ponder = None if self.bestmove is not None and len(tokens) >= 3 and tokens[1] == "ponder" and tokens[2] != "(none)": # The ponder move must be legal after the bestmove. Generally, we # trust the engine on this. But we still have to convert # non-UCI_Chess960 castling moves. try: self.ponder = chess.Move.from_uci(tokens[2]) if self.ponder.from_square in [chess.E1, chess.E8] and self.ponder.to_square in [chess.C1, chess.C8, chess.G1, chess.G8]: # Make a copy of the board to avoid race conditions. board = self.board.copy(stack=False) board.push(self.bestmove) self.ponder = board.parse_uci(tokens[2]) except ValueError: LOGGER.exception("exception parsing bestmove ponder") self.ponder = None self.bestmove_received.set() for info_handler in self.info_handlers: info_handler.on_bestmove(self.bestmove, self.ponder)
def command(): # Use the join(builder) once we parse usermove=1 feature. move_str = " ".join(builder) 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: self.board.push_uci(str(self.move)) except ValueError: try: self.board.push_san(str(self.move)) except ValueError: LOGGER.exception("exception parsing move") return self.move
def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception("exception parsing integer token") return for info_handler in self.info_handlers: fn(info_handler, intval)
def handle_move_token(token, fn): try: move = chess.Move.from_uci(token) except ValueError: LOGGER.exception("exception parsing move token") return for info_handler in self.info_handlers: fn(info_handler, move)
def _pong(self, pong_arg): try: pong_num = int(pong_arg) except ValueError: LOGGER.exception("exception parsing pong") if self.ping_num == pong_num: self.pong.set() with self.pong_received: self.pong_received.notify_all()
def handle_move_token(token, fn): try: move = chess.Move.from_uci(token) except ValueError: LOGGER.exception("exception parsing move token from info: %r", arg) return for info_handler in self.info_handlers: fn(info_handler, move)
def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception( "exception parsing integer token from info: %r", arg) return for info_handler in self.info_handlers: fn(info_handler, intval)
def try_move(board, move): try: move = board.push_uci(move) except ValueError: try: move = board.push_san(move) except ValueError: LOGGER.exception("exception parsing pv") return None return move
def _move(self, arg): self.move = None try: self.move = self.board.parse_uci(arg) except ValueError: try: self.move = self.board.parse_san(arg) except ValueError: LOGGER.exception("exception parsing move") self.move_received.set() for post_handler in self.post_handlers: post_handler.on_move(self.move)
def set_feature(self, key, value): if key == "egt": for egt_type in value.split(","): self._features["egt"].append(egt_type) else: try: value = int(value) except ValueError: pass try: self._features[key] = value except KeyError: LOGGER.exception("exception looking up feature")
def _post(self, arg): if not self.post_handlers: return # Notify post handlers of start. for post_handler in self.post_handlers: post_handler.pre_info() def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception("exception parsing integer token") return for post_handler in self.post_handlers: fn(post_handler, intval) pv = [] board = self.board.copy(stack=False) tokens = arg.split() # Order: <score> <depth> <time> <nodes> <pv> handle_integer_token(tokens[0], lambda handler, val: handler.depth(val)) handle_integer_token(tokens[1], lambda handler, val: handler.score(val)) handle_integer_token(tokens[2], lambda handler, val: handler.time(val)) handle_integer_token(tokens[3], lambda handler, val: handler.nodes(val)) for token in tokens[4:]: # Ignore move number(for example 1. Nf3 Nf6 -> Nf3 Nf6) if '.' in token or '<' in token: continue try: pv.append(board.push_uci(token)) except ValueError: try: pv.append(board.push_san(token)) except ValueError: LOGGER.exception("exception parsing pv") if pv is not None: for post_handler in self.post_handlers: post_handler.pv(pv) # Notify post handlers of end. for post_handler in self.post_handlers: post_handler.post_info()
def _move(self, arg): self.move = None try: self.move = self.board.parse_uci(arg) except ValueError: try: self.move = self.board.parse_san(arg) except ValueError: LOGGER.exception("exception parsing move") self.move_received.set() if self.draw_handler: self.draw_handler.clear_offer() self.engine_offered_draw = False for post_handler in self.post_handlers: post_handler.on_move(self.move)
def _post(self, arg): if not self.post_handlers: return # Notify post handlers of start. for post_handler in self.post_handlers: post_handler.pre_info() def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception("exception parsing integer token") return for post_handler in self.post_handlers: fn(post_handler, intval) pv = [] board = self.board.copy(stack=False) tokens = arg.split() # Order: <score> <depth> <time> <nodes> <pv>. handle_integer_token(tokens[0], lambda handler, val: handler.depth(val)) handle_integer_token(tokens[1], lambda handler, val: handler.score(val)) handle_integer_token(tokens[2], lambda handler, val: handler.time(val)) handle_integer_token(tokens[3], lambda handler, val: handler.nodes(val)) for token in tokens[4:]: # Ignore move number. For example, 1. Nf3 Nf6 -> Nf3 Nf6. if '.' in token or '<' in token: continue try: pv.append(board.push_uci(token)) except ValueError: try: pv.append(board.push_san(token)) except ValueError: LOGGER.exception("exception parsing pv") if pv is not None: for post_handler in self.post_handlers: post_handler.pv(pv) # Notify post handlers of end. for post_handler in self.post_handlers: post_handler.post_info()
def set_option(self, key, value): try: self._features["option"][key] = value except KeyError: LOGGER.exception("exception looking up option")
def get_option(self, key): try: return self._features["option"][key] except KeyError: LOGGER.exception("exception looking up option")
def _option(self, arg): current_parameter = None name = [] type = [] default = [] min = None max = None current_var = None var = [] for token in arg.split(" "): if token == "name" and not name: current_parameter = "name" elif token == "type" and not type: current_parameter = "type" elif token == "default" and not default: current_parameter = "default" elif token == "min" and min is None: current_parameter = "min" elif token == "max" and max is None: current_parameter = "max" elif token == "var": current_parameter = "var" if current_var is not None: var.append(" ".join(current_var)) current_var = [] elif current_parameter == "name": name.append(token) elif current_parameter == "type": type.append(token) elif current_parameter == "default": default.append(token) elif current_parameter == "var": current_var.append(token) elif current_parameter == "min": try: min = int(token) except ValueError: LOGGER.exception("exception parsing option min") elif current_parameter == "max": try: max = int(token) except ValueError: LOGGER.exception("exception parsing option max") if current_var is not None: var.append(" ".join(current_var)) type = " ".join(type) default = " ".join(default) if type == "check": if default == "true": default = True elif default == "false": default = False else: default = None elif type == "spin": try: default = int(default) except ValueError: LOGGER.exception("exception parsing option spin default") default = None option = Option(" ".join(name), type, default, min, max, var) self.options[option.name] = option
def _info(self, arg): if not self.info_handlers: return # Notify info handlers of start. for info_handler in self.info_handlers: info_handler.pre_info(arg) # Initialize parser state. board = None pv = None score_kind = None score_cp = None score_mate = None score_lowerbound = False score_upperbound = False refutation_move = None refuted_by = [] currline_cpunr = None currline_moves = [] string = [] def end_of_parameter(): # Parameters with variable length can only be handled when the # next parameter starts or at the end of the line. if pv is not None: for info_handler in self.info_handlers: info_handler.pv(pv) if score_cp is not None or score_mate is not None: for info_handler in self.info_handlers: info_handler.score(score_cp, score_mate, score_lowerbound, score_upperbound) if refutation_move is not None: if refuted_by: for info_handler in self.info_handlers: info_handler.refutation(refutation_move, refuted_by) else: for info_handler in self.info_handlers: info_handler.refutation(refutation_move, None) if currline_cpunr is not None: for info_handler in self.info_handlers: info_handler.currline(currline_cpunr, currline_moves) def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception( "exception parsing integer token from info: %r", arg) return for info_handler in self.info_handlers: fn(info_handler, intval) def handle_float_token(token, fn): try: floatval = float(token) except ValueError: LOGGER.exception("exception parsing float token from info: %r", arg) for info_handler in self.info_handlers: fn(info_handler, floatval) def handle_move_token(token, fn): try: move = chess.Move.from_uci(token) except ValueError: LOGGER.exception("exception parsing move token from info: %r", arg) return for info_handler in self.info_handlers: fn(info_handler, move) # Find multipv parameter first. if "multipv" in arg: current_parameter = None for token in arg.split(" "): if token == "string": break if current_parameter == "multipv": handle_integer_token( token, lambda handler, val: handler.multipv(val)) current_parameter = token # Parse all other parameters. current_parameter = None for token in arg.split(" "): if current_parameter == "string": string.append(token) elif not token: # Ignore extra spaces. Those can not be directly discarded, # because they may occur in the string parameter. pass elif token in [ "depth", "seldepth", "time", "nodes", "pv", "multipv", "score", "currmove", "currmovenumber", "hashfull", "nps", "tbhits", "cpuload", "refutation", "currline", "ebf", "string" ]: end_of_parameter() current_parameter = token pv = None score_kind = None score_mate = None score_cp = None score_lowerbound = False score_upperbound = False refutation_move = None refuted_by = [] currline_cpunr = None currline_moves = [] if current_parameter == "pv": pv = [] if current_parameter in ["refutation", "pv", "currline"]: board = self.board.copy(stack=False) elif current_parameter == "depth": handle_integer_token(token, lambda handler, val: handler.depth(val)) elif current_parameter == "seldepth": handle_integer_token( token, lambda handler, val: handler.seldepth(val)) elif current_parameter == "time": handle_integer_token(token, lambda handler, val: handler.time(val)) elif current_parameter == "nodes": handle_integer_token(token, lambda handler, val: handler.nodes(val)) elif current_parameter == "pv": try: pv.append(board.push_uci(token)) except ValueError: LOGGER.exception( "exception parsing pv from info: %r, position at root: %s", arg, self.board.fen()) elif current_parameter == "multipv": # Ignore multipv. It was already parsed before anything else. pass elif current_parameter == "score": if token in ["cp", "mate"]: score_kind = token elif token == "lowerbound": score_lowerbound = True elif token == "upperbound": score_upperbound = True elif score_kind == "cp": try: score_cp = int(token) except ValueError: LOGGER.exception( "exception parsing score cp value from info: %r", arg) elif score_kind == "mate": try: score_mate = int(token) except ValueError: LOGGER.exception( "exception parsing score mate value from info: %r", arg) elif current_parameter == "currmove": handle_move_token(token, lambda handler, val: handler.currmove(val)) elif current_parameter == "currmovenumber": handle_integer_token( token, lambda handler, val: handler.currmovenumber(val)) elif current_parameter == "hashfull": handle_integer_token( token, lambda handler, val: handler.hashfull(val)) elif current_parameter == "nps": handle_integer_token(token, lambda handler, val: handler.nps(val)) elif current_parameter == "tbhits": handle_integer_token(token, lambda handler, val: handler.tbhits(val)) elif current_parameter == "cpuload": handle_integer_token(token, lambda handler, val: handler.cpuload(val)) elif current_parameter == "refutation": try: if refutation_move is None: refutation_move = board.push_uci(token) else: refuted_by.append(board.push_uci(token)) except ValueError: LOGGER.exception( "exception parsing refutation from info: %r, position at root: %s", arg, self.board.fen()) elif current_parameter == "currline": try: if currline_cpunr is None: currline_cpunr = int(token) else: currline_moves.append(board.push_uci(token)) except ValueError: LOGGER.exception( "exception parsing currline from info: %r, position at root: %s", arg, self.board.fen()) elif current_parameter == "ebf": handle_float_token(token, lambda handler, val: handler.ebf(val)) end_of_parameter() if string: for info_handler in self.info_handlers: info_handler.string(" ".join(string)) # Notify info handlers of end. for info_handler in self.info_handlers: info_handler.post_info()
def _info(self, arg): if not self.info_handlers: return # Notify info handlers of start. for info_handler in self.info_handlers: info_handler.pre_info(arg) # Initialize parser state. board = None pv = None score_kind = None score_cp = None score_mate = None score_lowerbound = False score_upperbound = False refutation_move = None refuted_by = [] currline_cpunr = None currline_moves = [] string = [] def end_of_parameter(): # Parameters with variable length can only be handled when the # next parameter starts or at the end of the line. if pv is not None: for info_handler in self.info_handlers: info_handler.pv(pv) if score_cp is not None or score_mate is not None: for info_handler in self.info_handlers: info_handler.score(score_cp, score_mate, score_lowerbound, score_upperbound) if refutation_move is not None: if refuted_by: for info_handler in self.info_handlers: info_handler.refutation(refutation_move, refuted_by) else: for info_handler in self.info_handlers: info_handler.refutation(refutation_move, None) if currline_cpunr is not None: for info_handler in self.info_handlers: info_handler.currline(currline_cpunr, currline_moves) def handle_integer_token(token, fn): try: intval = int(token) except ValueError: LOGGER.exception("exception parsing integer token") return for info_handler in self.info_handlers: fn(info_handler, intval) def handle_move_token(token, fn): try: move = chess.Move.from_uci(token) except ValueError: LOGGER.exception("exception parsing move token") return for info_handler in self.info_handlers: fn(info_handler, move) # Find multipv parameter first. if "multipv" in arg: current_parameter = None for token in arg.split(" "): if token == "string": break if current_parameter == "multipv": handle_integer_token(token, lambda handler, val: handler.multipv(val)) current_parameter = token # Parse all other parameters. current_parameter = None for token in arg.split(" "): if current_parameter == "string": string.append(token) elif not token: # Ignore extra spaces. Those can not be directly discarded, # because they may occur in the string parameter. pass elif token in ["depth", "seldepth", "time", "nodes", "pv", "multipv", "score", "currmove", "currmovenumber", "hashfull", "nps", "tbhits", "cpuload", "refutation", "currline", "string"]: end_of_parameter() current_parameter = token pv = None score_kind = None score_mate = None score_cp = None score_lowerbound = False score_upperbound = False refutation_move = None refuted_by = [] currline_cpunr = None currline_moves = [] if current_parameter == "pv": pv = [] if current_parameter in ["refutation", "pv", "currline"]: board = self.board.copy(stack=False) elif current_parameter == "depth": handle_integer_token(token, lambda handler, val: handler.depth(val)) elif current_parameter == "seldepth": handle_integer_token(token, lambda handler, val: handler.seldepth(val)) elif current_parameter == "time": handle_integer_token(token, lambda handler, val: handler.time(val)) elif current_parameter == "nodes": handle_integer_token(token, lambda handler, val: handler.nodes(val)) elif current_parameter == "pv": try: pv.append(board.push_uci(token)) except ValueError: LOGGER.exception("exception parsing pv") elif current_parameter == "multipv": # Ignore multipv. It was already parsed before anything else. pass elif current_parameter == "score": if token in ["cp", "mate"]: score_kind = token elif token == "lowerbound": score_lowerbound = True elif token == "upperbound": score_upperbound = True elif score_kind == "cp": try: score_cp = int(token) except ValueError: LOGGER.exception("exception parsing score cp value") elif score_kind == "mate": try: score_mate = int(token) except ValueError: LOGGER.exception("exception parsing score mate value") elif current_parameter == "currmove": handle_move_token(token, lambda handler, val: handler.currmove(val)) elif current_parameter == "currmovenumber": handle_integer_token(token, lambda handler, val: handler.currmovenumber(val)) elif current_parameter == "hashfull": handle_integer_token(token, lambda handler, val: handler.hashfull(val)) elif current_parameter == "nps": handle_integer_token(token, lambda handler, val: handler.nps(val)) elif current_parameter == "tbhits": handle_integer_token(token, lambda handler, val: handler.tbhits(val)) elif current_parameter == "cpuload": handle_integer_token(token, lambda handler, val: handler.cpuload(val)) elif current_parameter == "refutation": try: if refutation_move is None: refutation_move = board.push_uci(token) else: refuted_by.append(board.push_uci(token)) except ValueError: LOGGER.exception("exception parsing refutation") elif current_parameter == "currline": try: if currline_cpunr is None: currline_cpunr = int(token) else: currline_moves.append(board.push_uci(token)) except ValueError: LOGGER.exception("exception parsing currline") end_of_parameter() if string: for info_handler in self.info_handlers: info_handler.string(" ".join(string)) # Notify info handlers of end. for info_handler in self.info_handlers: info_handler.post_info()
def usermove(self, move, async_callback=None): """ Tell the XBoard engine to make a move on it's internal board. If auto_force is set to True, the engine will not start thinking about it's 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.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: self.board.push_uci(str(move)) except ValueError: try: self.board.push_san(str(move)) except ValueError: LOGGER.exception("exception parsing move") builder.append(str(move)) def command(): # Use the join(builder) once we parse usermove=1 feature move_str = " ".join(builder) 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: self.board.push_uci(str(self.move)) except ValueError: try: self.board.push_san(str(self.move)) except ValueError: LOGGER.exception("exception parsing move") return self.move return self._queue_command(command, async_callback)
def get(self, key): try: return self._features[key] except KeyError: LOGGER.exception("exception looking up feature")
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: self.board.push_uci(str(move)) except ValueError: try: self.board.push_san(str(move)) except ValueError: LOGGER.exception("exception parsing move") builder.append(str(move)) def command(): # Use the join(builder) once we parse usermove=1 feature. move_str = " ".join(builder) 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: self.board.push_uci(str(self.move)) except ValueError: try: self.board.push_san(str(self.move)) except ValueError: LOGGER.exception("exception parsing move") return self.move return self._queue_command(command, async_callback)