def transfer_time(time_list): def num(ts): try: return int(ts) except ValueError: return 1 if len(time_list) == 1: secs = num(time_list[0]) time_control = TimeControl(TimeMode.FIXED, seconds_per_move=secs) text = dgttranslate.text('B00_tc_fixed', '{:2d}'.format(secs)) elif len(time_list) == 2: mins = num(time_list[0]) finc = num(time_list[1]) if finc == 0: time_control = TimeControl(TimeMode.BLITZ, minutes_per_game=mins) text = dgttranslate.text('B00_tc_blitz', '{:2d}'.format(mins)) else: time_control = TimeControl(TimeMode.FISCHER, minutes_per_game=mins, fischer_increment=finc) text = dgttranslate.text('B00_tc_fisch', '{:2d} {:2d}'.format(mins, finc)) else: time_control = TimeControl(TimeMode.BLITZ, minutes_per_game=5) text = dgttranslate.text('B00_tc_blitz', ' 5') return time_control, text
def _process_startup_info(self, message): self.play_mode = message.info['play_mode'] self.dgtmenu.set_mode(message.info['interaction_mode']) self.dgtmenu.set_book(message.info['book_index']) self.dgtmenu.all_books = message.info['books'] tc_init = message.info['tc_init'] timectrl = self.time_control = TimeControl(**tc_init) self.dgtmenu.set_time_mode(timectrl.mode) # try to find the index from the given time_control (timectrl) # if user gave a non-existent timectrl value update map & list index = 0 isnew = True if timectrl.mode == TimeMode.FIXED: for val in self.dgtmenu.tc_fixed_map.values(): if val == timectrl: self.dgtmenu.set_time_fixed(index) isnew = False break index += 1 if isnew: self.dgtmenu.tc_fixed_map.update({('', timectrl)}) self.dgtmenu.tc_fixed_list.append(timectrl.get_list_text()) self.dgtmenu.set_time_fixed(index) elif timectrl.mode == TimeMode.BLITZ: for val in self.dgtmenu.tc_blitz_map.values(): if val == timectrl: self.dgtmenu.set_time_blitz(index) isnew = False break index += 1 if isnew: self.dgtmenu.tc_blitz_map.update({('', timectrl)}) self.dgtmenu.tc_blitz_list.append(timectrl.get_list_text()) self.dgtmenu.set_time_blitz(index) elif timectrl.mode == TimeMode.FISCHER: for val in self.dgtmenu.tc_fisch_map.values(): if val == timectrl: self.dgtmenu.set_time_fisch(index) isnew = False break index += 1 if isnew: self.dgtmenu.tc_fisch_map.update({('', timectrl)}) self.dgtmenu.tc_fisch_list.append(timectrl.get_list_text()) self.dgtmenu.set_time_fisch(index)
def process_file(filepath, database): with open(filepath, "r") as f: player1 = None player2 = None variant = None tc = None for line in f: line = line.strip() if len(line) == 0: continue if line.startswith("[White "): player1 = line.split("\"")[1].lower() elif line.startswith("[Black "): player2 = line.split("\"")[1].lower() elif line.startswith("[Variant "): variant = line.split("\"")[1] elif line.startswith("[TimeControl "): tc = TimeControl(line.split("\"")[1]) elif not line.startswith("["): if player1 is None or player2 is None or variant is None or tc is None: raise Exception("bug") update_player(player1, tc.score, database, variant) update_player(player2, tc.score, database, variant)
def main(): #Command line argument parsing parser = configargparse.ArgParser(default_config_files=[os.path.join(os.path.dirname(__file__), "picochess.ini")]) parser.add_argument("-e", "--engine", type=str, help="UCI engine executable path", required=True) parser.add_argument("-d", "--dgt-port", type=str, help="enable dgt board on the given serial port such as /dev/ttyUSB0") parser.add_argument("-leds", "--enable-dgt-board-leds", action='store_true', help="enable dgt board leds") parser.add_argument("-hs", "--hash-size", type=int, help="hashtable size in MB (default:64)", default=64) parser.add_argument("-t", "--threads", type=int, help="number of engine threads (default:1)", default=1) parser.add_argument("-l", "--log-level", choices=['notset', 'debug', 'info', 'warning', 'error', 'critical'], default='warning', help="logging level") parser.add_argument("-lf", "--log-file", type=str, help="log to the given file") parser.add_argument("-r", "--remote", type=str, help="remote server running the engine") parser.add_argument("-u", "--user", type=str, help="remote user on server running the engine") parser.add_argument("-p", "--password", type=str, help="password for the remote user") parser.add_argument("-sk", "--server-key", type=str, help="key file used to connect to the remote server") parser.add_argument("-pgn", "--pgn-file", type=str, help="pgn file used to store the games", default='games.pgn') parser.add_argument("-ar", "--auto-reboot", action='store_true', help="reboot system after update") parser.add_argument("-web", "--web-server", dest="web_server_port", nargs="?", const=80, type=int, metavar="PORT", help="launch web server") parser.add_argument("-mail", "--email", type=str, help="email used to send pgn files", default=None) parser.add_argument("-mail_s", "--smtp_server", type=str, help="Adress of email server", default=None) parser.add_argument("-mail_u", "--smtp_user", type=str, help="Username for email server", default=None) parser.add_argument("-mail_p", "--smtp_pass", type=str, help="Password for email server", default=None) parser.add_argument("-mail_enc", "--smtp_encryption", action='store_true', help="use ssl encryption connection to smtp-Server") parser.add_argument("-mk", "--mailgun-key", type=str, help="key used to send emails via Mailgun Webservice", default=None) parser.add_argument("-uci", "--uci-option", type=str, help="pass an UCI option to the engine (name;value)", default=None) parser.add_argument("-dgt3000", "--dgt-3000-clock", action='store_true', help="use dgt 3000 clock") parser.add_argument("-nobeep", "--disable-dgt-clock-beep", action='store_true', help="disable beeps on the dgt clock") parser.add_argument("-uvoice", "--user-voice", type=str, help="voice for user", default=None) parser.add_argument("-cvoice", "--computer-voice", type=str, help="voice for computer", default=None) args = parser.parse_args() # Enable logging logging.basicConfig(filename=args.log_file, level=getattr(logging, args.log_level.upper()), format='%(asctime)s.%(msecs)d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") # Update update_picochess(args.auto_reboot) # Load UCI engine engine = uci.Engine(args.engine, hostname=args.remote, username=args.user, key_file=args.server_key, password=args.password) logging.debug('Loaded engine [%s]', engine.get().name) logging.debug('Supported options [%s]', engine.get().options) if 'Hash' in engine.get().options: engine.set_option("Hash", args.hash_size) if 'Threads' in engine.get().options: # Stockfish engine.set_option("Threads", args.threads) if 'Core Threads' in engine.get().options: # Hiarcs engine.set_option("Core Threads", args.threads) if args.uci_option: for uci_option in args.uci_option.strip('"').split(";"): uci_parameter = uci_option.strip().split('=') engine.set_option(uci_parameter[0], uci_parameter[1]) # Connect to DGT board if args.dgt_port: logging.debug("Starting picochess with DGT board on [%s]", args.dgt_port) dgt.DGTBoard(args.dgt_port, args.enable_dgt_board_leds, args.dgt_3000_clock, not args.disable_dgt_clock_beep).start() else: logging.warning("No DGT board port provided") # Enable keyboard input and terminal display KeyboardInput().start() TerminalDisplay().start() # Save to PGN PgnDisplay(args.pgn_file, email=args.email, fromINIMailGun_Key=args.mailgun_key, fromIniSmtp_Server=args.smtp_server, fromINISmtp_User=args.smtp_user, fromINISmtp_Pass=args.smtp_pass, fromINISmtp_Enc=args.smtp_encryption).start() # Create ChessTalker for speech output talker = None if(args.user_voice or args.computer_voice): logging.debug("Initializing ChessTalker [%s, %s]", str(args.user_voice), str(args.computer_voice)) talker = chesstalker.chesstalker.ChessTalker(args.user_voice, args.computer_voice) talker.start() else: logging.debug("ChessTalker disabled") # Launch web server if(args.web_server_port): WebServer(args.web_server_port).start() def compute_legal_fens(g): """ Computes a list of legal FENs for the given game. Also stores the initial position in the 'root' attribute. :param g: The game :return: A list of legal FENs, and the root FEN """ class FenList(list): def __init__(self, *args): list.__init__(self, *args) self.root = '' fens = FenList() for move in g.legal_moves: g.push(move) fens.append(g.fen().split(' ')[0]) g.pop() fens.root = g.fen().split(' ')[0] return fens def think(time): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ def send_book_move(move): g=copy.deepcopy(game) g.push(move) book_ponder = weighted_choice(book, g) Observable.fire(Event.BEST_MOVE, move=move, ponder=book_ponder) Observable.fire(Event.SCORE, score='book', mate=None) # global book_thread book_move = weighted_choice(book, game) Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time) time.run(game.turn) if book_move: Display.show(Message.BOOK_MOVE, move=book_move.uci()) send_book_move(book_move) # No need for one more thread at this point given slightly slower picochess, can bring back if needed # book_thread = threading.Timer(2, send_book_move, [book_move]) # book_thread.start() else: # book_thread = None engine.set_position(game) engine.go(time.uci()) Display.show(Message.SEARCH_STARTED) def analyse(): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ engine.set_position(game) engine.ponder() Display.show(Message.SEARCH_STARTED) def observe(time): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time) # logging.debug("Starting clock") time.run(game.turn) engine.set_position(game) engine.ponder() Display.show(Message.SEARCH_STARTED) def stop_thinking(): """ Stop current search or book thread. :return: """ #if book_thread: # book_thread.cancel() #else: engine.stop() def check_game_state(game, play_mode): """ Check if the game has ended or not ; it also sends Message to Displays if the game has ended. :param game: :return: True is the game continues, False if it has ended """ custom_fen = game.custom_fen if hasattr(game, 'custom_fen') else None if game.is_stalemate(): Display.show(Message.GAME_ENDS, result=GameResult.STALEMATE, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) return False if game.is_insufficient_material(): Display.show(Message.GAME_ENDS, result=GameResult.INSUFFICIENT_MATERIAL, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) return False if game.is_seventyfive_moves(): Display.show(Message.GAME_ENDS, result=GameResult.SEVENTYFIVE_MOVES, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) return False if game.is_fivefold_repetition(): Display.show(Message.GAME_ENDS, result=GameResult.FIVEFOLD_REPETITION, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) return False if game.is_game_over(): Display.show(Message.GAME_ENDS, result=GameResult.MATE, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) return False return True game = chess.Board() # Create the current game legal_fens = compute_legal_fens(game) # Compute the legal FENs book = chess.polyglot.open_reader(get_opening_books()[8][1]) # Default opening book interaction_mode = Mode.GAME # Interaction mode play_mode = GameMode.PLAY_WHITE # book_thread = None # The thread that will fire book moves time_control = TimeControl(ClockMode.BLITZ, minutes_per_game=5) #Send the engine's UCI options to all Displays Display.show(Message.UCI_OPTION_LIST, options=engine.get().options) Display.show(Message.SYSTEM_INFO, info={"version": version, "location": get_location(), "books": get_opening_books(), "ip": get_ip()}) #Event loop while True: event = event_queue.get() logging.debug('Received event in event loop : %s', event) for case in switch(event): if case(Event.FEN): # User sets a new position, convert it to a move if it is legal if event.fen in legal_fens: # Check if we have to undo a previous move (sliding) if interaction_mode == Mode.GAME: if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.BLACK) or (play_mode == GameMode.PLAY_BLACK and game.turn == chess.WHITE): stop_thinking() if game.move_stack: game.pop() legal_moves = list(game.legal_moves) Observable.fire(Event.USER_MOVE, move=legal_moves[legal_fens.index(event.fen)]) elif event.fen == game.fen().split(' ')[0]: # Player had done the computer move on the board if check_game_state(game, play_mode): Display.show(Message.COMPUTER_MOVE_DONE_ON_BOARD) if time_control.mode != ClockMode.FIXED_TIME: Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time_control) # logging.debug("Starting player clock") time_control.run(game.turn) elif event.fen == legal_fens.root: # Allow user to take his move back while the engine is searching stop_thinking() game.pop() if interaction_mode == Mode.ANALYSIS: analyse() if interaction_mode == Mode.OBSERVE: observe(time_control) Display.show(Message.USER_TAKE_BACK) else: # Check if this a a previous legal position and allow user to restart from this position game_history = copy.deepcopy(game) while game_history.move_stack: game_history.pop() if (play_mode == GameMode.PLAY_WHITE and game_history.turn == chess.WHITE) \ or (play_mode == GameMode.PLAY_BLACK and game_history.turn == chess.BLACK) \ or (interaction_mode == Mode.OBSERVE) or (interaction_mode == Mode.ANALYSIS): if game_history.fen().split(' ')[0] == event.fen: logging.debug("Undoing game until FEN :" + event.fen) stop_thinking() while len(game_history.move_stack) < len(game.move_stack): game.pop() if interaction_mode == Mode.ANALYSIS: analyse() if interaction_mode == Mode.OBSERVE: observe(time_control) Display.show(Message.USER_TAKE_BACK) legal_fens = compute_legal_fens(game) break break if case(Event.USER_MOVE): # User sends a new move move = event.move logging.debug('User move [%s]', move) if move not in game.legal_moves: logging.warning('Illegal move [%s]', move) # Check if we are in play mode and it is player's turn elif interaction_mode == Mode.GAME: if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.WHITE) or \ (play_mode == GameMode.PLAY_BLACK and game.turn == chess.BLACK): time_control.stop() # logging.debug("Stopping player clock") game.push(move) if check_game_state(game, play_mode): think(time_control) Display.show(Message.USER_MOVE, move=move, game=copy.deepcopy(game)) elif interaction_mode == Mode.OBSERVE: stop_thinking() time_control.stop() fen = game.fen() game.push(move) if check_game_state(game, play_mode): observe(time_control) Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=copy.deepcopy(game), mode=interaction_mode) legal_fens = compute_legal_fens(game) elif (interaction_mode == Mode.ANALYSIS) or (interaction_mode == Mode.KIBITZ): stop_thinking() # time_control.stop() fen = game.fen() game.push(move) if check_game_state(game, play_mode): analyse() Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=copy.deepcopy(game), mode=interaction_mode) legal_fens = compute_legal_fens(game) break if case(Event.LEVEL): # User sets a new level level = event.level logging.debug("Setting engine to level %i", level) engine.set_level(level) break if case(Event.SETUP_POSITION): # User sets up a position logging.debug("Setting up custom fen: {0}".format(event.fen)) game = chess.Board(event.fen) game.custom_fen = event.fen legal_fens = compute_legal_fens(game) time_control.stop() time_control.reset() Display.show(Message.START_NEW_GAME) if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.BLACK) or (play_mode == GameMode.PLAY_BLACK and game.turn == chess.WHITE): think(time_control) break if case(Event.NEW_GAME): # User starts a new game if game.move_stack: logging.debug("Starting a new game") if not game.is_game_over(): custom_fen = game.custom_fen if hasattr(game, 'custom_fen') else None Display.show(Message.GAME_ENDS, result=GameResult.ABORT, moves=list(game.move_stack), color=game.turn, mode=play_mode, custom_fen=custom_fen) game = chess.Board() legal_fens = compute_legal_fens(game) time_control.stop() time_control.reset() Display.show(Message.START_NEW_GAME) if interaction_mode == Mode.ANALYSIS: play_mode = GameMode.PLAY_WHITE if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.BLACK) or (play_mode == GameMode.PLAY_BLACK and game.turn == chess.WHITE): think(time_control) break if case(Event.OPENING_BOOK): logging.debug("Changing opening book [%s]", get_opening_books()[event.book_index][1]) book = chess.polyglot.open_reader(get_opening_books()[event.book_index][1]) break if case(Event.BEST_MOVE): move = event.move ponder = event.ponder # Check if we are in play mode and it is computer's turn if interaction_mode == Mode.GAME: if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.BLACK) or (play_mode == GameMode.PLAY_BLACK and game.turn == chess.WHITE): time_control.stop() fen = game.fen() game.push(move) Display.show(Message.COMPUTER_MOVE, move=move, ponder=ponder, fen=fen, game=copy.deepcopy(game), time_control=time_control) # if check_game_state(game, interaction_mode): legal_fens = compute_legal_fens(game) break if case(Event.NEW_PV): if (interaction_mode == Mode.ANALYSIS) or (interaction_mode == Mode.OBSERVE) or (interaction_mode == Mode.KIBITZ): Display.show(Message.NEW_PV, pv=event.pv, interaction_mode=interaction_mode, fen=game.fen()) break if case(Event.SCORE): Display.show(Message.SCORE, score=event.score, mate=event.mate, interaction_mode=interaction_mode) break if case(Event.SET_MODE): Display.show(Message.INTERACTION_MODE, mode=event.mode) # Useful for pgn display device interaction_mode = event.mode break if case(Event.SET_PLAYMODE): Display.show(Message.PLAY_MODE, mode=event.mode) # Useful for pgn display device play_mode = event.mode break if case(Event.CHANGE_PLAYMODE): if play_mode == GameMode.PLAY_WHITE: play_mode = GameMode.PLAY_BLACK else: play_mode = GameMode.PLAY_WHITE Display.show(Message.INTERACTION_MODE, mode=play_mode) if (play_mode == GameMode.PLAY_WHITE and game.turn == chess.BLACK) or (play_mode == GameMode.PLAY_BLACK and game.turn == chess.WHITE): if check_game_state(game, play_mode): think(time_control) break if case(Event.SET_TIME_CONTROL): time_control = event.time_control break if case(Event.OUT_OF_TIME): stop_thinking() custom_fen = game.custom_fen if hasattr(game, 'custom_fen') else None Display.show(Message.GAME_ENDS, result=GameResult.TIME_CONTROL, moves=list(game.move_stack), color=event.color, mode=play_mode, custom_fen=custom_fen) break if case(Event.UCI_OPTION_SET): engine.set_option(event.name, event.value) break if case(Event.SHUTDOWN): if talker: talker.say_event(event) shutdown() break if case(): # Default logging.warning("Event not handled : [%s]", event)
def main(): def engine_startup(): if 'Hash' in engine.get().options: engine.option("Hash", args.hash_size) if 'Threads' in engine.get().options: # Stockfish engine.option("Threads", args.threads) if 'Core Threads' in engine.get().options: # Hiarcs engine.option("Core Threads", args.threads) if args.uci_option: for uci_option in args.uci_option.strip('"').split(";"): uci_parameter = uci_option.strip().split('=') engine.option(uci_parameter[0], uci_parameter[1]) # send the options to the engine engine.send() # Log the engine info logging.debug('Loaded engine [%s]', engine_name) logging.debug('Supported options [%s]', engine.get().options) def display_system_info(): if args.enable_internet: place = get_location() addr = get_ip() else: place = "?" addr = "?" DisplayMsg.show(Message.SYSTEM_INFO(info={"version": version, "location": place, "books": get_opening_books(), "ip": addr, "engine_name": engine_name, "user_name": user_name })) def compute_legal_fens(g): """ Computes a list of legal FENs for the given game. Also stores the initial position in the 'root' attribute. :param g: The game :return: A list of legal FENs, and the root FEN """ class FenList(list): def __init__(self, *args): list.__init__(self, *args) self.root = '' fens = FenList() for move in g.legal_moves: g.push(move) fens.append(g.board_fen()) g.pop() fens.root = g.board_fen() return fens def probe_tablebase(game): if not gaviota: return None score = gaviota.probe_dtm(game) if score is not None: Observable.fire(Event.NEW_SCORE(score='tb', mate=score)) return score def think_callback(): tc = time_control tc.start(game.turn) book_move = searchmoves.book(bookreader, game) if book_move: Observable.fire(Event.NEW_SCORE(score='book', mate=None)) Observable.fire(Event.BEST_MOVE(result=book_move, inbook=True)) else: probe_tablebase(game) engine.position(copy.deepcopy(game)) uci_dict = tc.uci() uci_dict['searchmoves'] = searchmoves.all(game) engine.go(uci_dict) def think(game, tc): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ DisplayMsg.show(Message.RUN_CLOCK(turn=game.turn, time_control=tc, callback=think_callback)) def analyse(game): """ Starts a new ponder search on the current game. :return: """ probe_tablebase(game) engine.position(copy.deepcopy(game)) engine.ponder() def observe_callback(): tc = time_control tc.start(game.turn) analyse(game) def observe(game, tc): """ Starts a new ponder search on the current game. :return: """ DisplayMsg.show(Message.RUN_CLOCK(turn=game.turn, time_control=tc, callback=observe_callback)) def stop_search(): """ Stop current search. :return: """ engine.stop() def stop_clock(): nonlocal time_control time_control.stop() DisplayMsg.show(Message.STOP_CLOCK()) def stop_search_and_clock(): stop_clock() stop_search() def check_game_state(game, play_mode): """ Check if the game has ended or not ; it also sends Message to Displays if the game has ended. :param game: :return: True is the game continues, False if it has ended """ result = None if game.is_stalemate(): result = GameResult.STALEMATE if game.is_insufficient_material(): result = GameResult.INSUFFICIENT_MATERIAL if game.is_seventyfive_moves(): result = GameResult.SEVENTYFIVE_MOVES if game.is_fivefold_repetition(): result = GameResult.FIVEFOLD_REPETITION if game.is_checkmate(): result = GameResult.MATE if result is None: return True else: custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=result, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) return False def timecontrol_callback(): time_control.start(game.turn) def process_fen(fen, legal_fens): nonlocal last_computer_fen if fen in legal_fens: # Check if we have to undo a previous move (sliding) if interaction_mode == Mode.NORMAL: if (play_mode == PlayMode.USER_WHITE and game.turn == chess.BLACK) or \ (play_mode == PlayMode.USER_BLACK and game.turn == chess.WHITE): stop_search() if game.move_stack: game.pop() legal_moves = list(game.legal_moves) time_control.add_inc(game.turn) Observable.fire(Event.USER_MOVE(move=legal_moves[legal_fens.index(fen)])) elif fen == last_computer_fen: # Player had done the computer move on the board last_computer_fen = None if check_game_state(game, play_mode) and ((interaction_mode == Mode.NORMAL) or (interaction_mode == Mode.REMOTE)): # finally reset all alternative moves see: handle_move() nonlocal searchmoves searchmoves.reset() time_control.add_inc(not game.turn) DisplayMsg.show(Message.COMPUTER_MOVE_DONE_ON_BOARD()) if time_control.mode != TimeMode.FIXED: DisplayMsg.show(Message.RUN_CLOCK(turn=game.turn, time_control=time_control, callback=timecontrol_callback)) # time_control.start(game.turn) else: # Check if this a a previous legal position and allow user to restart from this position game_history = copy.deepcopy(game) while game_history.move_stack: game_history.pop() if (play_mode == PlayMode.USER_WHITE and game_history.turn == chess.WHITE) \ or (play_mode == PlayMode.USER_BLACK and game_history.turn == chess.BLACK) \ or (interaction_mode == Mode.OBSERVE) or (interaction_mode == Mode.KIBITZ) \ or (interaction_mode == Mode.REMOTE) or (interaction_mode == Mode.ANALYSIS): if game_history.board_fen() == fen: logging.debug("Legal Fens root : " + str(legal_fens.root)) logging.debug("Current game FEN : " + str(game.fen())) logging.debug("Undoing game until FEN: " + fen) stop_search() while len(game_history.move_stack) < len(game.move_stack): game.pop() if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) DisplayMsg.show(Message.USER_TAKE_BACK()) legal_fens = compute_legal_fens(game) break return legal_fens def set_wait_state(): if interaction_mode == Mode.NORMAL: nonlocal play_mode play_mode = PlayMode.USER_WHITE if game.turn == chess.WHITE else PlayMode.USER_BLACK def handle_move(result, game): move = result.bestmove fen = game.fen() game.push(move) nonlocal last_computer_fen nonlocal searchmoves last_computer_fen = None if interaction_mode == Mode.NORMAL: stop_clock() # If UserMove: reset all alternative moves # If ComputerMove: disallow this move, and finally reset all if DONE_ON_BOARD event @see: process_fen() if (play_mode == PlayMode.USER_WHITE and game.turn == chess.WHITE)\ or (play_mode == PlayMode.USER_BLACK and game.turn == chess.BLACK): last_computer_fen = game.board_fen() searchmoves.add(move) text = Message.COMPUTER_MOVE(result=result, fen=fen, game=game.copy(), time_control=time_control) DisplayMsg.show(text) else: searchmoves.reset() DisplayMsg.show(Message.USER_MOVE(move=move, game=game.copy())) if check_game_state(game, play_mode): think(game, time_control) elif interaction_mode == Mode.REMOTE: stop_search_and_clock() # If UserMove: reset all alternative moves # If Remote Move: same process as for computer move above if (play_mode == PlayMode.USER_WHITE and game.turn == chess.WHITE)\ or (play_mode == PlayMode.USER_BLACK and game.turn == chess.BLACK): last_computer_fen = game.board_fen() searchmoves.add(move) text = Message.COMPUTER_MOVE(result=result, fen=fen, game=game.copy(), time_control=time_control) DisplayMsg.show(text) else: searchmoves.reset() DisplayMsg.show(Message.USER_MOVE(move=move, game=game.copy())) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.OBSERVE: stop_search_and_clock() DisplayMsg.show(Message.REVIEW_MOVE(move=move, fen=fen, game=game.copy(), mode=interaction_mode)) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: stop_search() DisplayMsg.show(Message.REVIEW_MOVE(move=move, fen=fen, game=game.copy(), mode=interaction_mode)) if check_game_state(game, play_mode): analyse(game) return game # Enable garbage collection - needed for engine swapping as objects orphaned gc.enable() # Command line argument parsing parser = configargparse.ArgParser(default_config_files=[os.path.join(os.path.dirname(__file__), "picochess.ini")]) parser.add_argument("-e", "--engine", type=str, help="UCI engine executable path", default=None) parser.add_argument("-d", "--dgt-port", type=str, help="enable dgt board on the given serial port such as /dev/ttyUSB0") parser.add_argument("-b", "--book", type=str, help="Opening book - full name of book in 'books' folder", default='h-varied.bin') parser.add_argument("-g", "--enable-gaviota", action='store_true', help="enable gavoita tablebase probing") parser.add_argument("-leds", "--enable-revelation-leds", action='store_true', help="enable Revelation leds") parser.add_argument("-hs", "--hash-size", type=int, help="hashtable size in MB (default:64)", default=64) parser.add_argument("-t", "--threads", type=int, help="number of engine threads (default:1)", default=1) parser.add_argument("-l", "--log-level", choices=['notset', 'debug', 'info', 'warning', 'error', 'critical'], default='warning', help="logging level") parser.add_argument("-lf", "--log-file", type=str, help="log to the given file") parser.add_argument("-r", "--remote", type=str, help="remote server running the engine") parser.add_argument("-u", "--user", type=str, help="remote user on server running the engine") parser.add_argument("-p", "--password", type=str, help="password for the remote user") parser.add_argument("-sk", "--server-key", type=str, help="key file used to connect to the remote server") parser.add_argument("-pgn", "--pgn-file", type=str, help="pgn file used to store the games", default='games.pgn') parser.add_argument("-pgn_u", "--pgn-user", type=str, help="user name for the pgn file", default=None) parser.add_argument("-ar", "--auto-reboot", action='store_true', help="reboot system after update") parser.add_argument("-web", "--web-server", dest="web_server_port", nargs="?", const=80, type=int, metavar="PORT", help="launch web server") parser.add_argument("-mail", "--email", type=str, help="email used to send pgn files", default=None) parser.add_argument("-mail_s", "--smtp_server", type=str, help="Adress of email server", default=None) parser.add_argument("-mail_u", "--smtp_user", type=str, help="Username for email server", default=None) parser.add_argument("-mail_p", "--smtp_pass", type=str, help="Password for email server", default=None) parser.add_argument("-mail_enc", "--smtp_encryption", action='store_true', help="use ssl encryption connection to smtp-Server") parser.add_argument("-mk", "--mailgun-key", type=str, help="key used to send emails via Mailgun Webservice", default=None) parser.add_argument("-uci", "--uci-option", type=str, help="pass an UCI option to the engine (name;value)", default=None) parser.add_argument("-beep", "--beep-level", type=int, help="sets a beep level from 0(=no beeps) to 15(=all beeps)", default=0x0f) parser.add_argument("-uvoice", "--user-voice", type=str, help="voice for user", default=None) parser.add_argument("-cvoice", "--computer-voice", type=str, help="voice for computer", default=None) parser.add_argument("-inet", "--enable-internet", action='store_true', help="enable internet lookups") parser.add_argument("-nookmove", "--disable-ok-move", action='store_true', help="disable ok move messages") parser.add_argument("-v", "--version", action='version', version='%(prog)s version {}'.format(version), help="show current version", default=None) parser.add_argument("-pi", "--dgtpi", action='store_true', help="use the dgtpi hardware") parser.add_argument("-lang", "--language", choices=['en', 'de', 'nl', 'fr', 'es'], default='en', help="picochess language") parser.add_argument("-c", "--console", action='store_true', help="use console interface") args = parser.parse_args() if args.engine is None: el = read_engine_ini() args.engine = el[0][0] # read the first engine path and use it as standard else: args.engine = which(args.engine) # Enable logging if args.log_file: handler = RotatingFileHandler('logs' + os.sep + args.log_file, maxBytes=1024*1024, backupCount=9) logging.basicConfig(level=getattr(logging, args.log_level.upper()), format='%(asctime)s.%(msecs)03d %(levelname)5s %(module)10s - %(funcName)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S", handlers=[handler]) logging.getLogger("chess.uci").setLevel(logging.INFO) # don't want to get so many python-chess uci messages logging.debug('#'*20 + ' PicoChess v' + version + ' ' + '#'*20) # log the startup parameters but hide the password fields p = copy.copy(vars(args)) p['mailgun_key'] = p['server_key'] = p['password'] = p['smtp_pass'] = '******' logging.debug('startup parameters: {}'.format(p)) # Update if args.enable_internet: update_picochess(args.auto_reboot) gaviota = None if args.enable_gaviota: try: gaviota = chess.gaviota.open_tablebases('tablebases/gaviota') logging.debug('Tablebases gaviota loaded') except OSError: logging.error('Tablebases gaviota doesnt exist') gaviota = None # This class talks to DgtHw/DgtPi or DgtVr dgttranslate = DgtTranslate(args.beep_level, args.language) DgtDisplay(args.disable_ok_move, dgttranslate).start() # Launch web server if args.web_server_port: WebServer(args.web_server_port).start() if args.console: # Enable keyboard input and terminal display logging.debug("starting picochess with virtual DGT board") KeyboardInput().start() TerminalDisplay().start() dgthardware = DgtVr(args.enable_revelation_leds) else: # Connect to DGT board logging.debug("starting picochess with DGT board on [%s]", args.dgt_port) dgtserial = DgtSerial(args.dgt_port) if args.dgtpi: dgthardware = DgtPi(dgtserial, dgttranslate, args.enable_revelation_leds) else: dgthardware = DgtHw(dgtserial, dgttranslate, args.enable_revelation_leds) # Start the show dgthardware.start() dgthardware.startup() # Save to PGN PgnDisplay( args.pgn_file, net=args.enable_internet, email=args.email, mailgun_key=args.mailgun_key, smtp_server=args.smtp_server, smtp_user=args.smtp_user, smtp_pass=args.smtp_pass, smtp_encryption=args.smtp_encryption).start() if args.pgn_user: user_name = args.pgn_user else: if args.email: user_name = args.email.split('@')[0] else: user_name = "Player" # Create ChessTalker for speech output talker = None if args.user_voice or args.computer_voice: logging.debug("initializing ChessTalker [%s, %s]", str(args.user_voice), str(args.computer_voice)) talker = chesstalker.chesstalker.ChessTalker(args.user_voice, args.computer_voice) talker.start() else: logging.debug("ChessTalker disabled") # Gentlemen, start your engines... engine = uci.Engine(args.engine, hostname=args.remote, username=args.user, key_file=args.server_key, password=args.password) try: engine_name = engine.get().name except AttributeError: logging.error("no engines started") sys.exit(-1) # Startup - internal game = chess.Board() # Create the current game legal_fens = compute_legal_fens(game) # Compute the legal FENs all_books = get_opening_books() try: book_index = [book[1] for book in all_books].index('books/' + args.book) except ValueError: logging.warning("selected book not present, defaulting to %s", all_books[7][1]) book_index = 7 bookreader = chess.polyglot.open_reader(all_books[book_index][1]) searchmoves = AlternativeMover() interaction_mode = Mode.NORMAL play_mode = PlayMode.USER_WHITE time_control = TimeControl(TimeMode.BLITZ, minutes_per_game=5) last_computer_fen = None game_declared = False # User declared resignation or draw system_info_thread = threading.Timer(0, display_system_info) system_info_thread.start() engine_startup() # send the args options to the engine # Startup - external text = Dgt.DISPLAY_TEXT(l=None, m='bl 5', s=None, beep=False, duration=0) DisplayMsg.show(Message.STARTUP_INFO(info={"interaction_mode": interaction_mode, "play_mode": play_mode, "book": all_books[book_index][1], "book_index": book_index, "time_text": text})) DisplayMsg.show(Message.UCI_OPTION_LIST(options=engine.options)) DisplayMsg.show(Message.ENGINE_STARTUP(shell=engine.get_shell(), path=engine.get_path(), has_levels=engine.has_levels(), has_960=engine.has_chess960())) # Event loop while True: try: event = evt_queue.get() except queue.Empty: pass else: logging.debug('received event from evt_queue: %s', event) for case in switch(event): if case(EventApi.FEN): legal_fens = process_fen(event.fen, legal_fens) break if case(EventApi.KEYBOARD_MOVE): move = event.move logging.debug('keyboard move [%s]', move) if move not in game.legal_moves: logging.warning('illegal move [%s]', move) else: g = copy.deepcopy(game) g.push(move) legal_fens = process_fen(g.board_fen(), legal_fens) break if case(EventApi.USER_MOVE): move = event.move logging.debug('user move [%s]', move) if move not in game.legal_moves: logging.warning('Illegal move [%s]', move) else: result = chess.uci.BestMove(bestmove=move, ponder=None) game = handle_move(result, game) # if check_game_state(game, interaction_mode): legal_fens = compute_legal_fens(game) break if case(EventApi.LEVEL): logging.debug("setting engine to level %i", event.level) if engine.level(event.level): engine.send() DisplayMsg.show(Message.LEVEL(level=event.level, level_text=event.level_text)) break if case(EventApi.NEW_ENGINE): old_path = engine.path engine_shutdown = True # Stop the old engine cleanly engine.stop() # Closeout the engine process and threads # The all return non-zero error codes, 0=success if engine.quit(): # Ask nicely if engine.terminate(): # If you won't go nicely.... if engine.kill(): # Right that does it! logging.error('engine shutdown failure') DisplayMsg.show(Message.ENGINE_FAIL()) engine_shutdown = False if engine_shutdown: # Load the new one and send args. # Local engines only engine_fallback = False engine = uci.Engine(event.eng[0]) try: engine_name = engine.get().name except AttributeError: # New engine failed to start, restart old engine logging.error("new engine failed to start, reverting to %s", old_path) engine_fallback = True engine = uci.Engine(old_path) try: engine_name = engine.get().name except AttributeError: # Help - old engine failed to restart. There is no engine logging.error("no engines started") sys.exit(-1) # Schedule cleanup of old objects gc.collect() # Restore options - this doesn't deal with any # supplementary uci options sent 'in game', see event.UCI_OPTION_SET engine_startup() # Send user selected engine level to new engine if event.level and engine.level(event.level): engine.send() DisplayMsg.show(Message.LEVEL(level=event.level, level_text=event.level_text)) # All done - rock'n'roll if not engine_fallback: DisplayMsg.show(Message.ENGINE_READY(eng=event.eng, engine_name=engine_name, eng_text=event.eng_text, has_levels=engine.has_levels(), has_960=engine.has_chess960())) else: DisplayMsg.show(Message.ENGINE_FAIL()) set_wait_state() DisplayMsg.show(Message.WAIT_STATE()) # Go back to analysing or observing if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) break if case(EventApi.SETUP_POSITION): logging.debug("setting up custom fen: {0}".format(event.fen)) if engine.has_chess960(): engine.option('UCI_Chess960', event.uci960) engine.send() else: # start normal new game if engine can't handle the user wish event.uci960 = False logging.warning('engine doesnt support 960 mode') if game.move_stack: if game.is_game_over() or game_declared: custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) game = chess.Board(event.fen, event.uci960) game.custom_fen = event.fen legal_fens = compute_legal_fens(game) stop_search_and_clock() time_control.reset() interaction_mode = Mode.NORMAL last_computer_fen = None searchmoves.reset() DisplayMsg.show(Message.START_NEW_GAME(time_control=time_control)) game_declared = False set_wait_state() DisplayMsg.show(Message.WAIT_STATE()) break if case(EventApi.STARTSTOP_THINK): if engine.is_thinking() and (interaction_mode != Mode.REMOTE): stop_clock() engine.stop(show_best=True) else: play_mode = PlayMode.USER_WHITE if play_mode == PlayMode.USER_BLACK else PlayMode.USER_BLACK DisplayMsg.show(Message.PLAY_MODE(play_mode=play_mode)) if check_game_state(game, play_mode) and (interaction_mode != Mode.REMOTE): time_control.reset_start_time() think(game, time_control) break if case(EventApi.ALTERNATIVE_MOVE): game.pop() DisplayMsg.show(Message.ALTERNATIVE_MOVE()) think(game, time_control) break if case(EventApi.STARTSTOP_CLOCK): if time_control.is_ticking(): stop_clock() else: time_control.add_inc(game.turn) DisplayMsg.show(Message.RUN_CLOCK(turn=game.turn, time_control=time_control, callback=timecontrol_callback)) # time_control.start(game.turn) break if case(EventApi.NEW_GAME): if game.move_stack: logging.debug("starting a new game") if not (game.is_game_over() or game_declared): custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) game = chess.Board() legal_fens = compute_legal_fens(game) # interaction_mode = Mode.NORMAL @todo last_computer_fen = None stop_search_and_clock() time_control.reset() searchmoves.reset() DisplayMsg.show(Message.START_NEW_GAME(time_control=time_control)) game_declared = False set_wait_state() DisplayMsg.show(Message.WAIT_STATE()) break if case(EventApi.DRAWRESIGN): if not game_declared: # in case user leaves kings in place while moving other pieces stop_search_and_clock() custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=event.result, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) game_declared = True break if case(EventApi.REMOTE_MOVE): if interaction_mode == Mode.REMOTE: bm = chess.uci.BestMove(bestmove=chess.Move.from_uci(event.move), ponder=None) game = handle_move(bm, game) legal_fens = compute_legal_fens(game) break if case(EventApi.BEST_MOVE): if event.inbook: DisplayMsg.show(Message.BOOK_MOVE(result=event.result)) game = handle_move(event.result, game) legal_fens = compute_legal_fens(game) break if case(EventApi.NEW_PV): if interaction_mode == Mode.NORMAL: pass else: DisplayMsg.show(Message.NEW_PV(pv=event.pv, mode=interaction_mode, fen=game.fen())) break if case(EventApi.NEW_SCORE): if event.score == 'book': score = 'book' elif event.score == 'tb': score = 'tb {0}'.format(event.mate) else: try: score = int(event.score) if game.turn == chess.BLACK: score *= -1 except ValueError: score = event.score logging.debug('could not convert score ' + score) except TypeError: score = 'm {0}'.format(event.mate) DisplayMsg.show(Message.NEW_SCORE(score=score, mate=event.mate, mode=interaction_mode)) break if case(EventApi.SET_INTERACTION_MODE): if interaction_mode == Mode.NORMAL or interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: stop_clock() # only stop, if the clock is really running interaction_mode = event.mode if engine.is_thinking(): stop_search() # dont need to stop, if pondering if engine.is_pondering() and interaction_mode == Mode.NORMAL: stop_search() # if change from ponder modes to game, also stops the pondering set_wait_state() DisplayMsg.show(Message.INTERACTION_MODE(mode=event.mode, mode_text=event.mode_text)) break if case(EventApi.SET_OPENING_BOOK): logging.debug("changing opening book [%s]", event.book[1]) bookreader = chess.polyglot.open_reader(event.book[1]) DisplayMsg.show(Message.OPENING_BOOK(book_name=event.book[0], book_text=event.book_text)) break if case(EventApi.SET_TIME_CONTROL): time_control = event.time_control DisplayMsg.show(Message.TIME_CONTROL(time_text=event.time_text)) break if case(EventApi.OUT_OF_TIME): stop_search_and_clock() custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=GameResult.OUT_OF_TIME, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) break if case(EventApi.UCI_OPTION_SET): # Nowhere calls this yet, but they will need to be saved for engine restart engine.option(event.name, event.value) break if case(EventApi.SHUTDOWN): if talker: talker.say_event(event) custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) shutdown(args.dgtpi) break if case(EventApi.REBOOT): if talker: talker.say_event(event) custom_fen = getattr(game, 'custom_fen', None) DisplayMsg.show(Message.GAME_ENDS(result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), custom_fen=custom_fen)) reboot() break if case(EventApi.DGT_BUTTON): DisplayMsg.show(Message.DGT_BUTTON(button=event.button)) break if case(EventApi.DGT_FEN): DisplayMsg.show(Message.DGT_FEN(fen=event.fen)) break if case(EventApi.DGT_CLOCK_STARTED): if event.callback: logging.debug('callback started {}'.format(event.callback)) event.callback() logging.debug('callback ended {}'.format(event.callback)) else: logging.debug('callback is not set') break if case(): # Default logging.warning("event not handled : [%s]", event) evt_queue.task_done()
def _process_clock_start(self, message): self.time_control = TimeControl(**message.tc_init) side = ClockSide.LEFT if ( message.turn == chess.WHITE ) != self.dgtmenu.get_flip_board() else ClockSide.RIGHT self._set_clock(side=side, devs=message.devs)
def _process_time_control(self, message): wait = not self.dgtmenu.get_confirm() or not message.show_ok if wait: DgtObserver.fire(message.time_text) self.time_control = TimeControl(**message.tc_init) self._set_clock()
def __init__(self,whiteEng, blackEng, **kwargs): """ The basic GameSession class. whiteEng and blackEng are the two engines, with respective colors Note: All times are in milliseconds as per UCI protocol, unless time_modifier (see the TimeControl class) is used. start_time is the time that the engines are given on the clock, not including this parameter will result in an untimed game. A tuple can be fed to allow different starting times like so: start_time = (<white start time>,<black start time>) inc is the incremented time value after each turn. Like start_time, it can be fed a tuple for different incrementations to each engine. time is a tuple that combines start_time and inc in the following format: time=(start_time,inc). If time is a single number (int, float, etc.), the value it is passed will be considered as start_time, and inc as 0. The position keyword argument is used to pass a FEN value as the starting position, when not used, the default starting position (startpos) is used. """ self.white = whiteEng self.black = blackEng self.gamehistory = "" self.timed = False self.tied = False self.tiereason = '' self.winner = None #Process TimeControl Arguments tc_dict = {} #Pass any argument in tc_dict to time control. if 'tc_dict' in kwargs: tc_dict.update(kwargs['tc_dict']) if 'time_unit' in kwargs: tc_dict['time_unit'] = kwargs['time_unit'] if 'time' in kwargs: self.timeControl = TimeControl(*kwargs['time'], **tc_dict) self.timed = True elif 'start_time' in kwargs: inc = 0 if 'inc' in kwargs: inc = kwargs['inc'] self.timeControl = TimeControl(kwargs['start_time'],inc, **tc_dict) self.timed = True starting_position = 'startpos' if 'position' in kwargs: starting_position = kwargs['position'] #Tie testing (repeated moves) self.played_positions = {} self.FENOp = FENOperator(starting_position) self.played_positions[self.FENOp.getPositionOnly()] = 1 self.write_all_engines('position fen {0}\n'.format(self.FENOp))
class GameSession: def __init__(self,whiteEng, blackEng, **kwargs): """ The basic GameSession class. whiteEng and blackEng are the two engines, with respective colors Note: All times are in milliseconds as per UCI protocol, unless time_modifier (see the TimeControl class) is used. start_time is the time that the engines are given on the clock, not including this parameter will result in an untimed game. A tuple can be fed to allow different starting times like so: start_time = (<white start time>,<black start time>) inc is the incremented time value after each turn. Like start_time, it can be fed a tuple for different incrementations to each engine. time is a tuple that combines start_time and inc in the following format: time=(start_time,inc). If time is a single number (int, float, etc.), the value it is passed will be considered as start_time, and inc as 0. The position keyword argument is used to pass a FEN value as the starting position, when not used, the default starting position (startpos) is used. """ self.white = whiteEng self.black = blackEng self.gamehistory = "" self.timed = False self.tied = False self.tiereason = '' self.winner = None #Process TimeControl Arguments tc_dict = {} #Pass any argument in tc_dict to time control. if 'tc_dict' in kwargs: tc_dict.update(kwargs['tc_dict']) if 'time_unit' in kwargs: tc_dict['time_unit'] = kwargs['time_unit'] if 'time' in kwargs: self.timeControl = TimeControl(*kwargs['time'], **tc_dict) self.timed = True elif 'start_time' in kwargs: inc = 0 if 'inc' in kwargs: inc = kwargs['inc'] self.timeControl = TimeControl(kwargs['start_time'],inc, **tc_dict) self.timed = True starting_position = 'startpos' if 'position' in kwargs: starting_position = kwargs['position'] #Tie testing (repeated moves) self.played_positions = {} self.FENOp = FENOperator(starting_position) self.played_positions[self.FENOp.getPositionOnly()] = 1 self.write_all_engines('position fen {0}\n'.format(self.FENOp)) def get_move(self, engine): command = 'go' if self.timed: wtime, btime = self.timeControl.clocks winc, binc = self.timeControl.increment command += ' wtime {0} btime {1} winc {2} binc {3}' command = command.format(wtime, btime, winc, binc) engine.write('{0}\n'.format(command)) while True: for line in engine.readAll(): if "bestmove" in line: return line[9:14] def update_board(self,position): if "none" in position: return False self.tied = self.check_tie(position) #Also updates FENOperator self.gamehistory += " {0}".format(position) self.write_all_engines('position fen {0}\n'.format(self.FENOp)) return True def write_all_engines(self,message): self.white.write(message) self.black.write(message) def play(self): engines = [self.white,self.black] turn = 0 while True: if self.timed: self.timeControl.start(turn) newmove = self.get_move(engines[turn]) if not self.update_board(newmove): print self.gamehistory if turn == 1: print "White checkmates" else: print "Black checkmates" break if self.tied: print self.gamehistory print "Draw by {0}".format(self.tiereason) break # Check for time if self.timed: if not self.timeControl.stop(): print self.gamehistory #Print "loses on time" message. if turn == 0: print "White loses on time" else: print "Black loses on time" break turn = -(turn -1) # 0 -> 1; 1 -> 0 def check_tie(self, move): """ Checks for tie and updates FENOperator """ if self.white.score == 0 and self.black.score == 0: return True self.FENOp.do_move(move) newstr = self.FENOp.getPositionOnly() if newstr in self.played_positions: self.played_positions[newstr] += 1 if self.played_positions[newstr] >= 3: self.tiereason = '3 move repeat' return True else: self.played_positions[newstr] = 1 if int(self.FENOp.cp_clock) >= 100: self.tiereason = '50 moves without capture or pawn moving' return True return False
def __init__(self, whiteEng, blackEng, **kwargs): """ The basic GameSession class. whiteEng and blackEng are the two engines, with respective colors Note: All times are in milliseconds as per UCI protocol, unless time_modifier (see the TimeControl class) is used. start_time is the time that the engines are given on the clock, not including this parameter will result in an untimed game. A tuple can be fed to allow different starting times like so: start_time = (<white start time>,<black start time>) inc is the incremented time value after each turn. Like start_time, it can be fed a tuple for different incrementations to each engine. time is a tuple that combines start_time and inc in the following format: time=(start_time,inc). If time is a single number (int, float, etc.), the value it is passed will be considered as start_time, and inc as 0. The position keyword argument is used to pass a FEN value as the starting position, when not used, the default starting position (startpos) is used. """ self.white = whiteEng self.black = blackEng self.gamehistory = "" self.timed = False self.tied = False self.tiereason = '' self.winner = None #Process TimeControl Arguments tc_dict = {} #Pass any argument in tc_dict to time control. if 'tc_dict' in kwargs: tc_dict.update(kwargs['tc_dict']) if 'time_unit' in kwargs: tc_dict['time_unit'] = kwargs['time_unit'] if 'time' in kwargs: self.timeControl = TimeControl(*kwargs['time'], **tc_dict) self.timed = True elif 'start_time' in kwargs: inc = 0 if 'inc' in kwargs: inc = kwargs['inc'] self.timeControl = TimeControl(kwargs['start_time'], inc, **tc_dict) self.timed = True starting_position = 'startpos' if 'position' in kwargs: starting_position = kwargs['position'] #Tie testing (repeated moves) self.played_positions = {} self.FENOp = FENOperator(starting_position) self.played_positions[self.FENOp.getPositionOnly()] = 1 self.write_all_engines('position fen {0}\n'.format(self.FENOp))
class GameSession: def __init__(self, whiteEng, blackEng, **kwargs): """ The basic GameSession class. whiteEng and blackEng are the two engines, with respective colors Note: All times are in milliseconds as per UCI protocol, unless time_modifier (see the TimeControl class) is used. start_time is the time that the engines are given on the clock, not including this parameter will result in an untimed game. A tuple can be fed to allow different starting times like so: start_time = (<white start time>,<black start time>) inc is the incremented time value after each turn. Like start_time, it can be fed a tuple for different incrementations to each engine. time is a tuple that combines start_time and inc in the following format: time=(start_time,inc). If time is a single number (int, float, etc.), the value it is passed will be considered as start_time, and inc as 0. The position keyword argument is used to pass a FEN value as the starting position, when not used, the default starting position (startpos) is used. """ self.white = whiteEng self.black = blackEng self.gamehistory = "" self.timed = False self.tied = False self.tiereason = '' self.winner = None #Process TimeControl Arguments tc_dict = {} #Pass any argument in tc_dict to time control. if 'tc_dict' in kwargs: tc_dict.update(kwargs['tc_dict']) if 'time_unit' in kwargs: tc_dict['time_unit'] = kwargs['time_unit'] if 'time' in kwargs: self.timeControl = TimeControl(*kwargs['time'], **tc_dict) self.timed = True elif 'start_time' in kwargs: inc = 0 if 'inc' in kwargs: inc = kwargs['inc'] self.timeControl = TimeControl(kwargs['start_time'], inc, **tc_dict) self.timed = True starting_position = 'startpos' if 'position' in kwargs: starting_position = kwargs['position'] #Tie testing (repeated moves) self.played_positions = {} self.FENOp = FENOperator(starting_position) self.played_positions[self.FENOp.getPositionOnly()] = 1 self.write_all_engines('position fen {0}\n'.format(self.FENOp)) def get_move(self, engine): command = 'go' if self.timed: wtime, btime = self.timeControl.clocks winc, binc = self.timeControl.increment command += ' wtime {0} btime {1} winc {2} binc {3}' command = command.format(wtime, btime, winc, binc) engine.write('{0}\n'.format(command)) while True: for line in engine.readAll(): if "bestmove" in line: return line[9:14] def update_board(self, position): if "none" in position: return False self.tied = self.check_tie(position) #Also updates FENOperator self.gamehistory += " {0}".format(position) self.write_all_engines('position fen {0}\n'.format(self.FENOp)) return True def write_all_engines(self, message): self.white.write(message) self.black.write(message) def play(self): engines = [self.white, self.black] turn = 0 while True: if self.timed: self.timeControl.start(turn) newmove = self.get_move(engines[turn]) if not self.update_board(newmove): print self.gamehistory if turn == 1: print "White checkmates" else: print "Black checkmates" break if self.tied: print self.gamehistory print "Draw by {0}".format(self.tiereason) break # Check for time if self.timed: if not self.timeControl.stop(): print self.gamehistory #Print "loses on time" message. if turn == 0: print "White loses on time" else: print "Black loses on time" break turn = -(turn - 1) # 0 -> 1; 1 -> 0 def check_tie(self, move): """ Checks for tie and updates FENOperator """ if self.white.score == 0 and self.black.score == 0: return True self.FENOp.do_move(move) newstr = self.FENOp.getPositionOnly() if newstr in self.played_positions: self.played_positions[newstr] += 1 if self.played_positions[newstr] >= 3: self.tiereason = '3 move repeat' return True else: self.played_positions[newstr] = 1 if int(self.FENOp.cp_clock) >= 100: self.tiereason = '50 moves without capture or pawn moving' return True return False
def main(): def engine_startup(): if "Hash" in engine.get().options: engine.option("Hash", args.hash_size) if "Threads" in engine.get().options: # Stockfish engine.option("Threads", args.threads) if "Core Threads" in engine.get().options: # Hiarcs engine.option("Core Threads", args.threads) if args.uci_option: for uci_option in args.uci_option.strip('"').split(";"): uci_parameter = uci_option.strip().split("=") engine.option(uci_parameter[0], uci_parameter[1]) # send the options to the engine engine.send() # Notify other display processes Display.show(Message.UCI_OPTION_LIST, options=engine.get().options) def display_system_info(): if args.enable_internet: place = get_location() addr = get_ip() else: place = "?" addr = "?" Display.show( Message.SYSTEM_INFO, info={ "version": version, "location": place, "books": get_opening_books(), "ip": addr, "engine_name": engine_name, "user_name": user_name, }, ) def compute_legal_fens(g): """ Computes a list of legal FENs for the given game. Also stores the initial position in the 'root' attribute. :param g: The game :return: A list of legal FENs, and the root FEN """ class FenList(list): def __init__(self, *args): list.__init__(self, *args) self.root = "" fens = FenList() for move in g.legal_moves: g.push(move) fens.append(g.board_fen()) g.pop() fens.root = g.board_fen() return fens def probe_tablebase(game): if not gaviota: return None score = gaviota.probe_dtm(game) if score: Observable.fire(Event.SCORE, score="tb", mate=score) return score def think(game, time): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time) time.run(game.turn) book_move = searchmoves.book(bookreader, game) if book_move: Observable.fire(Event.BEST_MOVE, result=book_move, inbook=True) Observable.fire(Event.SCORE, score="book", mate=None) else: probe_tablebase(game) engine.position(copy.deepcopy(game)) uci_dict = time.uci() uci_dict["searchmoves"] = searchmoves.all(game) engine.go(uci_dict) def analyse(game): """ Starts a new ponder search on the current game. :return: """ probe_tablebase(game) engine.position(copy.deepcopy(game)) engine.ponder() def observe(game, time): """ Starts a new ponder search on the current game. :return: """ Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time) time.run(game.turn) analyse(game) def stop_search(): """ Stop current search. :return: """ engine.stop() def stop_clock(): nonlocal time_control time_control.stop() Display.show(Message.STOP_CLOCK) def stop_search_and_clock(): stop_clock() stop_search() def check_game_state(game, play_mode): """ Check if the game has ended or not ; it also sends Message to Displays if the game has ended. :param game: :return: True is the game continues, False if it has ended """ result = None if game.is_stalemate(): result = GameResult.STALEMATE if game.is_insufficient_material(): result = GameResult.INSUFFICIENT_MATERIAL if game.is_seventyfive_moves(): result = GameResult.SEVENTYFIVE_MOVES if game.is_fivefold_repetition(): result = GameResult.FIVEFOLD_REPETITION if game.is_checkmate(): result = GameResult.MATE if result is None: return True else: Display.show(Message.GAME_ENDS, result=result, play_mode=play_mode, game=copy.deepcopy(game)) return False def process_fen(fen, legal_fens): if fen in legal_fens: # Check if we have to undo a previous move (sliding) if interaction_mode == Mode.GAME: if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.BLACK) or ( play_mode == PlayMode.PLAY_BLACK and game.turn == chess.WHITE ): stop_search() if game.move_stack: game.pop() legal_moves = list(game.legal_moves) Observable.fire(Event.USER_MOVE, move=legal_moves[legal_fens.index(fen)]) elif fen == last_computer_fen: # Player had done the computer move on the board if check_game_state(game, play_mode) and ( (interaction_mode == Mode.GAME) or (interaction_mode == Mode.REMOTE) ): # finally reset all alternative moves see: handle_move() nonlocal searchmoves searchmoves.reset() Display.show(Message.COMPUTER_MOVE_DONE_ON_BOARD) if time_control.mode != ClockMode.FIXED_TIME: Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time_control) time_control.run(game.turn) else: # Check if this a a previous legal position and allow user to restart from this position game_history = copy.deepcopy(game) while game_history.move_stack: game_history.pop() if ( (play_mode == PlayMode.PLAY_WHITE and game_history.turn == chess.WHITE) or (play_mode == PlayMode.PLAY_BLACK and game_history.turn == chess.BLACK) or (interaction_mode == Mode.OBSERVE) or (interaction_mode == Mode.KIBITZ) or (interaction_mode == Mode.REMOTE) or (interaction_mode == Mode.ANALYSIS) ): if game_history.board_fen() == fen: logging.debug("Legal Fens root : " + str(legal_fens.root)) logging.debug("Current game FEN : " + str(game.fen())) logging.debug("Undoing game until FEN: " + fen) stop_search() while len(game_history.move_stack) < len(game.move_stack): game.pop() if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) Display.show(Message.USER_TAKE_BACK) legal_fens = compute_legal_fens(game) break return legal_fens def set_wait_state(): if interaction_mode == Mode.GAME: nonlocal play_mode play_mode = PlayMode.PLAY_WHITE if game.turn == chess.WHITE else PlayMode.PLAY_BLACK def handle_move(result, game): move = result.bestmove fen = game.fen() game.push(move) nonlocal last_computer_fen nonlocal searchmoves last_computer_fen = None if interaction_mode == Mode.GAME: stop_clock() # If UserMove: reset all alternative moves # If ComputerMove: disallow this move, and finally reset all if DONE_ON_BOARD event @see: process_fen() if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.WHITE) or ( play_mode == PlayMode.PLAY_BLACK and game.turn == chess.BLACK ): last_computer_fen = game.board_fen() searchmoves.add(move) Display.show(Message.COMPUTER_MOVE, result=result, fen=fen, game=game.copy(), time_control=time_control) else: searchmoves.reset() Display.show(Message.USER_MOVE, move=move, game=game.copy()) if check_game_state(game, play_mode): think(game, time_control) elif interaction_mode == Mode.REMOTE: stop_search_and_clock() # If UserMove: reset all alternative moves # If Remote Move: same process as for computer move above if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.WHITE) or ( play_mode == PlayMode.PLAY_BLACK and game.turn == chess.BLACK ): last_computer_fen = game.board_fen() searchmoves.add(move) Display.show(Message.COMPUTER_MOVE, result=result, fen=fen, game=game.copy(), time_control=time_control) else: searchmoves.reset() Display.show(Message.USER_MOVE, move=move, game=game.copy()) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.OBSERVE: stop_search_and_clock() Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=game.copy(), mode=interaction_mode) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: stop_search() Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=game.copy(), mode=interaction_mode) if check_game_state(game, play_mode): analyse(game) return game # Enable garbage collection - needed for engine swapping as objects orphaned gc.enable() # Command line argument parsing parser = configargparse.ArgParser(default_config_files=[os.path.join(os.path.dirname(__file__), "picochess.ini")]) parser.add_argument("-e", "--engine", type=str, help="UCI engine executable path", default="engines/stockfish") parser.add_argument( "-d", "--dgt-port", type=str, help="enable dgt board on the given serial port such as /dev/ttyUSB0" ) parser.add_argument( "-b", "--book", type=str, help="Opening book - full name of book in 'books' folder", default="h-varied.bin" ) parser.add_argument("-g", "--enable-gaviota", action="store_true", help="enable gavoita tablebase probing") parser.add_argument("-leds", "--enable-dgt-board-leds", action="store_true", help="enable dgt board leds") parser.add_argument("-hs", "--hash-size", type=int, help="hashtable size in MB (default:64)", default=64) parser.add_argument("-t", "--threads", type=int, help="number of engine threads (default:1)", default=1) parser.add_argument( "-l", "--log-level", choices=["notset", "debug", "info", "warning", "error", "critical"], default="warning", help="logging level", ) parser.add_argument("-lf", "--log-file", type=str, help="log to the given file") parser.add_argument("-r", "--remote", type=str, help="remote server running the engine") parser.add_argument("-u", "--user", type=str, help="remote user on server running the engine") parser.add_argument("-p", "--password", type=str, help="password for the remote user") parser.add_argument("-sk", "--server-key", type=str, help="key file used to connect to the remote server") parser.add_argument("-pgn", "--pgn-file", type=str, help="pgn file used to store the games", default="games.pgn") parser.add_argument("-pgn_u", "--pgn-user", type=str, help="user name for the pgn file", default=None) parser.add_argument("-ar", "--auto-reboot", action="store_true", help="reboot system after update") parser.add_argument( "-web", "--web-server", dest="web_server_port", nargs="?", const=80, type=int, metavar="PORT", help="launch web server", ) parser.add_argument("-mail", "--email", type=str, help="email used to send pgn files", default=None) parser.add_argument("-mail_s", "--smtp_server", type=str, help="Adress of email server", default=None) parser.add_argument("-mail_u", "--smtp_user", type=str, help="Username for email server", default=None) parser.add_argument("-mail_p", "--smtp_pass", type=str, help="Password for email server", default=None) parser.add_argument( "-mail_enc", "--smtp_encryption", action="store_true", help="use ssl encryption connection to smtp-Server" ) parser.add_argument( "-mk", "--mailgun-key", type=str, help="key used to send emails via Mailgun Webservice", default=None ) parser.add_argument( "-uci", "--uci-option", type=str, help="pass an UCI option to the engine (name;value)", default=None ) parser.add_argument("-dgt3000", "--dgt-3000-clock", action="store_true", help="do NOT use it anymore (DEPRECATED!)") parser.add_argument( "-nobeep", "--disable-dgt-clock-beep", action="store_true", help="disable beeps on the dgt clock" ) parser.add_argument("-uvoice", "--user-voice", type=str, help="voice for user", default=None) parser.add_argument("-cvoice", "--computer-voice", type=str, help="voice for computer", default=None) parser.add_argument("-inet", "--enable-internet", action="store_true", help="enable internet lookups") parser.add_argument("-nookmove", "--disable-ok-move", action="store_false", help="disable ok move messages") parser.add_argument( "-v", "--version", action="version", version="%(prog)s version {}".format(version), help="show current version", default=None, ) args = parser.parse_args() # Enable logging logging.basicConfig( filename=args.log_file, level=getattr(logging, args.log_level.upper()), format="%(asctime)s.%(msecs)d %(levelname)s %(module)s - %(funcName)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) logging.getLogger("chess.uci").setLevel(logging.INFO) # don't want to get so many python-chess uci messages # Update if args.enable_internet: update_picochess(args.auto_reboot) gaviota = None if args.enable_gaviota: try: gaviota = chess.gaviota.open_tablebases("tablebases/gaviota") logging.debug("Tablebases gaviota loaded") except OSError: logging.error("Tablebases gaviota doesnt exist") gaviota = None # This class talks to DGTHardware or DGTVirtual DGTDisplay(args.disable_ok_move).start() if args.dgt_port: # Connect to DGT board logging.debug("Starting picochess with DGT board on [%s]", args.dgt_port) DGTHardware(args.dgt_port, args.enable_dgt_board_leds, args.disable_dgt_clock_beep).start() else: # Enable keyboard input and terminal display logging.warning("No DGT board port provided") KeyboardInput().start() TerminalDisplay().start() DGTVirtual(args.enable_dgt_board_leds, args.disable_dgt_clock_beep).start() # Save to PGN PgnDisplay( args.pgn_file, net=args.enable_internet, email=args.email, fromINIMailGun_Key=args.mailgun_key, fromIniSmtp_Server=args.smtp_server, fromINISmtp_User=args.smtp_user, fromINISmtp_Pass=args.smtp_pass, fromINISmtp_Enc=args.smtp_encryption, ).start() if args.pgn_user: user_name = args.pgn_user else: if args.email: user_name = args.email.split("@")[0] else: user_name = "Player" # Create ChessTalker for speech output talker = None if args.user_voice or args.computer_voice: logging.debug("Initializing ChessTalker [%s, %s]", str(args.user_voice), str(args.computer_voice)) talker = chesstalker.chesstalker.ChessTalker(args.user_voice, args.computer_voice) talker.start() else: logging.debug("ChessTalker disabled") # Launch web server if args.web_server_port: WebServer(args.web_server_port).start() # Gentlemen, start your engines... engine = uci.Engine( args.engine, hostname=args.remote, username=args.user, key_file=args.server_key, password=args.password ) try: engine_name = engine.get().name except AttributeError: logging.debug("FATAL: no engines started") sys.exit(-1) logging.debug("Loaded engine [%s]", engine_name) logging.debug("Supported options [%s]", engine.get().options) # Startup - internal game = chess.Board() # Create the current game legal_fens = compute_legal_fens(game) # Compute the legal FENs all_books = get_opening_books() try: book_index = [book[1] for book in all_books].index("books/" + args.book) except ValueError: logging.debug("ERROR: Selected book not present, defaulting to %s", all_books[7][1]) book_index = 7 bookreader = chess.polyglot.open_reader(all_books[book_index][1]) searchmoves = AlternativeMover() interaction_mode = Mode.GAME play_mode = PlayMode.PLAY_WHITE time_control = TimeControl(ClockMode.BLITZ, minutes_per_game=5) last_computer_fen = None game_declared = False # User declared resignation or draw engine_level = None system_info_thread = threading.Timer(0, display_system_info) system_info_thread.start() # send the args options to the engine engine_startup() # Startup - external Display.show( Message.STARTUP_INFO, info={ "interaction_mode": interaction_mode, "play_mode": play_mode, "book": all_books[book_index][1], "book_index": book_index, "time_control_string": "mov 5", }, ) Display.show(Message.ENGINE_START, path=engine.get_path(), has_levels=engine.has_levels()) # Event loop while True: try: event = event_queue.get() except queue.Empty: pass else: logging.debug("Received event in event loop : %s", event) for case in switch(event): if case(Event.FEN): # User sets a new position, convert it to a move if it is legal legal_fens = process_fen(event.fen, legal_fens) break if case(Event.KEYBOARD_MOVE): move = event.move logging.debug("Keyboard move [%s]", move) if move not in game.legal_moves: logging.warning("Illegal move [%s]", move) else: g = copy.deepcopy(game) g.push(move) legal_fens = process_fen(g.board_fen(), legal_fens) break if case(Event.USER_MOVE): # User sends a new move move = event.move logging.debug("User move [%s]", move) if move not in game.legal_moves: logging.warning("Illegal move [%s]", move) else: result = chess.uci.BestMove(bestmove=move, ponder=None) game = handle_move(result, game) # if check_game_state(game, interaction_mode): legal_fens = compute_legal_fens(game) break if case(Event.LEVEL): # User sets a new level engine_level = event.level logging.debug("Setting engine to level %i", engine_level) if engine.level(engine_level): engine.send() Display.show(Message.LEVEL, level=engine_level) break if case(Event.NEW_ENGINE): old_path = engine.path engine_shutdown = True # Stop the old engine cleanly engine.stop() # Closeout the engine process and threads # The all return non-zero error codes, 0=success if engine.quit(): # Ask nicely if engine.terminate(): # If you won't go nicely.... if engine.kill(): # Right that does it! logging.error("Serious: Engine shutdown failure") Display.show(Message.ENGINE_FAIL) engine_shutdown = False if engine_shutdown: # Load the new one and send args. # Local engines only engine_fallback = False engine = uci.Engine(event.eng[0]) try: engine_name = engine.get().name except AttributeError: # New engine failed to start, restart old engine logging.error("New engine failed to start, reverting to %s", old_path) engine_fallback = True engine = uci.Engine(old_path) try: engine_name = engine.get().name except AttributeError: # Help - old engine failed to restart. There is no engine logging.error("FATAL: no engines started") sys.exit(-1) # Schedule cleanup of old objects gc.collect() # Restore options - this doesn't deal with any # supplementary uci options sent 'in game', see event.UCI_OPTION_SET engine_startup() # Send user selected engine level to new engine if engine_level and engine.level(engine_level): engine.send() Display.show(Message.LEVEL, level=engine_level) # Go back to analysing or observing if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) # All done - rock'n'roll if not engine_fallback: Display.show(Message.ENGINE_NAME, ename=engine_name) Display.show(Message.ENGINE_READY, eng=event.eng, has_levels=engine.has_levels()) else: Display.show(Message.ENGINE_FAIL) break if case(Event.SETUP_POSITION): # User sets up a position logging.debug("Setting up custom fen: {0}".format(event.fen)) if engine.has_chess960(): engine.option("UCI_Chess960", event.uci960) engine.send() else: # start normal new game if engine can't handle the user wish event.uci960 = False Display.show(Message.ENGINE_FAIL) # @todo not really true but inform the user about the result if game.move_stack: if game.is_game_over() or game_declared: Display.show( Message.GAME_ENDS, result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), ) game = chess.Board(event.fen, event.uci960) game.custom_fen = event.fen legal_fens = compute_legal_fens(game) stop_search_and_clock() time_control.reset() interaction_mode = Mode.GAME last_computer_fen = None set_wait_state() searchmoves.reset() Display.show(Message.START_NEW_GAME) game_declared = False break if case(Event.STARTSTOP_THINK): if engine.is_thinking() and (interaction_mode != Mode.REMOTE): stop_search_and_clock() else: play_mode = PlayMode.PLAY_WHITE if play_mode == PlayMode.PLAY_BLACK else PlayMode.PLAY_BLACK Display.show(Message.PLAY_MODE, play_mode=play_mode) if check_game_state(game, play_mode) and (interaction_mode != Mode.REMOTE): think(game, time_control) break if case(Event.ALTERNATIVE_MOVE): game.pop() Display.show(Message.ALTERNATIVE_MOVE) think(game, time_control) break if case(Event.STARTSTOP_CLOCK): if time_control.is_ticking(): stop_clock() else: Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time_control) time_control.run(game.turn) break if case(Event.NEW_GAME): # User starts a new game if game.move_stack: logging.debug("Starting a new game") if not (game.is_game_over() or game_declared): Display.show( Message.GAME_ENDS, result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game), ) game = chess.Board() legal_fens = compute_legal_fens(game) last_computer_fen = None stop_search_and_clock() time_control.reset() set_wait_state() searchmoves.reset() Display.show(Message.START_NEW_GAME) game_declared = False break if case(Event.DRAWRESIGN): if not game_declared: # in case user leaves kings in place while moving other pieces Display.show( Message.GAME_ENDS, result=event.result, play_mode=play_mode, game=copy.deepcopy(game) ) game_declared = True break if case(Event.OPENING_BOOK): logging.debug("Changing opening book [%s]", event.book[1]) bookreader = chess.polyglot.open_reader(event.book[1]) Display.show(Message.OPENING_BOOK, book=event.book) break if case(Event.REMOTE_MOVE): if interaction_mode == Mode.REMOTE: bm = chess.uci.BestMove(bestmove=chess.Move.from_uci(event.move), ponder=None) game = handle_move(bm, game) legal_fens = compute_legal_fens(game) break if case(Event.BEST_MOVE): if event.inbook: Display.show(Message.BOOK_MOVE, result=event.result) game = handle_move(event.result, game) legal_fens = compute_legal_fens(game) break if case(Event.NEW_PV): if interaction_mode == Mode.GAME: pass else: Display.show(Message.NEW_PV, pv=event.pv, mode=interaction_mode, fen=game.fen()) break if case(Event.SCORE): if event.score == "book": score = "book" elif event.score == "tb": score = "tb {0}".format(event.mate) else: try: score = int(event.score) if game.turn == chess.BLACK: score *= -1 except ValueError: score = event.score logging.debug("Could not convert score " + score) except TypeError: score = "m {0}".format(event.mate) Display.show(Message.SCORE, score=score, mate=event.mate, mode=interaction_mode) break if case(Event.SET_MODE): if ( interaction_mode == Mode.GAME or interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE ): stop_clock() # only stop, if the clock is really running interaction_mode = event.mode if engine.is_thinking(): stop_search() # dont need to stop, if pondering if engine.is_pondering() and interaction_mode == Mode.GAME: stop_search() # if change from ponder modes to game, also stops the pondering set_wait_state() Display.show(Message.INTERACTION_MODE, mode=event.mode) break if case(Event.SET_TIME_CONTROL): time_control = event.time_control Display.show(Message.TIME_CONTROL, time_control_string=event.time_control_string) break if case(Event.OUT_OF_TIME): stop_search_and_clock() Display.show( Message.GAME_ENDS, result=GameResult.TIME_CONTROL, play_mode=play_mode, game=copy.deepcopy(game) ) break if case(Event.UCI_OPTION_SET): # Nowhere calls this yet, but they will need to be saved for engine restart engine.option(event.name, event.value) break if case(Event.SHUTDOWN): if talker: talker.say_event(event) shutdown() break if case(Event.DGT_BUTTON): Display.show(Message.DGT_BUTTON, button=event.button) break if case(Event.DGT_FEN): Display.show(Message.DGT_FEN, fen=event.fen) break if case(): # Default logging.warning("Event not handled : [%s]", event) event_queue.task_done()
def main(): def engine_startup(): if 'Hash' in engine.get().options: engine.option("Hash", args.hash_size) if 'Threads' in engine.get().options: # Stockfish engine.option("Threads", args.threads) if 'Core Threads' in engine.get().options: # Hiarcs engine.option("Core Threads", args.threads) if args.uci_option: for uci_option in args.uci_option.strip('"').split(";"): uci_parameter = uci_option.strip().split('=') engine.option(uci_parameter[0], uci_parameter[1]) # send the options to the engine engine.send() # Notify other display processes Display.show(Message.UCI_OPTION_LIST, options=engine.get().options) def display_system_info(): if args.enable_internet: place = get_location() addr = get_ip() else: place = "?" addr = "?" Display.show(Message.SYSTEM_INFO, info={ "version": version, "location": place, "books": get_opening_books(), "ip": addr, "engine_name": engine_name, "user_name": user_name }) def compute_legal_fens(g): """ Computes a list of legal FENs for the given game. Also stores the initial position in the 'root' attribute. :param g: The game :return: A list of legal FENs, and the root FEN """ class FenList(list): def __init__(self, *args): list.__init__(self, *args) self.root = '' fens = FenList() for move in g.legal_moves: g.push(move) fens.append(g.board_fen()) g.pop() fens.root = g.board_fen() return fens def probe_tablebase(game): if not gaviota: return None score = gaviota.probe_dtm(game) if score: Observable.fire(Event.SCORE, score='tb', mate=score) return score def think(game, tc): """ Starts a new search on the current game. If a move is found in the opening book, fire an event in a few seconds. :return: """ Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=tc) tc.run(game.turn) book_move = searchmoves.book(bookreader, game) if book_move: Observable.fire(Event.SCORE, score='book', mate=None) time.sleep(0.5) Observable.fire(Event.BEST_MOVE, result=book_move, inbook=True) else: probe_tablebase(game) engine.position(copy.deepcopy(game)) uci_dict = tc.uci() uci_dict['searchmoves'] = searchmoves.all(game) engine.go(uci_dict) def analyse(game): """ Starts a new ponder search on the current game. :return: """ probe_tablebase(game) engine.position(copy.deepcopy(game)) engine.ponder() def observe(game, tc): """ Starts a new ponder search on the current game. :return: """ Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=tc) tc.run(game.turn) analyse(game) def stop_search(): """ Stop current search. :return: """ engine.stop() def stop_clock(): nonlocal time_control time_control.stop() Display.show(Message.STOP_CLOCK) def stop_search_and_clock(): stop_clock() stop_search() def check_game_state(game, play_mode): """ Check if the game has ended or not ; it also sends Message to Displays if the game has ended. :param game: :return: True is the game continues, False if it has ended """ result = None if game.is_stalemate(): result = GameResult.STALEMATE if game.is_insufficient_material(): result = GameResult.INSUFFICIENT_MATERIAL if game.is_seventyfive_moves(): result = GameResult.SEVENTYFIVE_MOVES if game.is_fivefold_repetition(): result = GameResult.FIVEFOLD_REPETITION if game.is_checkmate(): result = GameResult.MATE if result is None: return True else: Display.show(Message.GAME_ENDS, result=result, play_mode=play_mode, game=copy.deepcopy(game)) return False def process_fen(fen, legal_fens): if fen in legal_fens: # Check if we have to undo a previous move (sliding) if interaction_mode == Mode.GAME: if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.BLACK) or \ (play_mode == PlayMode.PLAY_BLACK and game.turn == chess.WHITE): stop_search() if game.move_stack: game.pop() legal_moves = list(game.legal_moves) Observable.fire(Event.USER_MOVE, move=legal_moves[legal_fens.index(fen)]) elif fen == last_computer_fen: # Player had done the computer move on the board if check_game_state( game, play_mode) and ((interaction_mode == Mode.GAME) or (interaction_mode == Mode.REMOTE)): # finally reset all alternative moves see: handle_move() nonlocal searchmoves searchmoves.reset() Display.show(Message.COMPUTER_MOVE_DONE_ON_BOARD) if time_control.mode != ClockMode.FIXED_TIME: Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time_control) time_control.run(game.turn) else: # Check if this a a previous legal position and allow user to restart from this position game_history = copy.deepcopy(game) while game_history.move_stack: game_history.pop() if (play_mode == PlayMode.PLAY_WHITE and game_history.turn == chess.WHITE) \ or (play_mode == PlayMode.PLAY_BLACK and game_history.turn == chess.BLACK) \ or (interaction_mode == Mode.OBSERVE) or (interaction_mode == Mode.KIBITZ) \ or (interaction_mode == Mode.REMOTE) or (interaction_mode == Mode.ANALYSIS): if game_history.board_fen() == fen: logging.debug("Legal Fens root : " + str(legal_fens.root)) logging.debug("Current game FEN : " + str(game.fen())) logging.debug("Undoing game until FEN: " + fen) stop_search() while len(game_history.move_stack) < len( game.move_stack): game.pop() if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) Display.show(Message.USER_TAKE_BACK) legal_fens = compute_legal_fens(game) break return legal_fens def set_wait_state(): if interaction_mode == Mode.GAME: nonlocal play_mode play_mode = PlayMode.PLAY_WHITE if game.turn == chess.WHITE else PlayMode.PLAY_BLACK def handle_move(result, game): move = result.bestmove fen = game.fen() game.push(move) nonlocal last_computer_fen nonlocal searchmoves last_computer_fen = None if interaction_mode == Mode.GAME: stop_clock() # If UserMove: reset all alternative moves # If ComputerMove: disallow this move, and finally reset all if DONE_ON_BOARD event @see: process_fen() if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.WHITE)\ or (play_mode == PlayMode.PLAY_BLACK and game.turn == chess.BLACK): last_computer_fen = game.board_fen() searchmoves.add(move) Display.show(Message.COMPUTER_MOVE, result=result, fen=fen, game=game.copy(), time_control=time_control) else: searchmoves.reset() Display.show(Message.USER_MOVE, move=move, game=game.copy()) if check_game_state(game, play_mode): think(game, time_control) elif interaction_mode == Mode.REMOTE: stop_search_and_clock() # If UserMove: reset all alternative moves # If Remote Move: same process as for computer move above if (play_mode == PlayMode.PLAY_WHITE and game.turn == chess.WHITE)\ or (play_mode == PlayMode.PLAY_BLACK and game.turn == chess.BLACK): last_computer_fen = game.board_fen() searchmoves.add(move) Display.show(Message.COMPUTER_MOVE, result=result, fen=fen, game=game.copy(), time_control=time_control) else: searchmoves.reset() Display.show(Message.USER_MOVE, move=move, game=game.copy()) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.OBSERVE: stop_search_and_clock() Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=game.copy(), mode=interaction_mode) if check_game_state(game, play_mode): observe(game, time_control) elif interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: stop_search() Display.show(Message.REVIEW_MODE_MOVE, move=move, fen=fen, game=game.copy(), mode=interaction_mode) if check_game_state(game, play_mode): analyse(game) return game # Enable garbage collection - needed for engine swapping as objects orphaned gc.enable() # Command line argument parsing parser = configargparse.ArgParser(default_config_files=[ os.path.join(os.path.dirname(__file__), "picochess.ini") ]) parser.add_argument("-e", "--engine", type=str, help="UCI engine executable path", default='engines/stockfish') parser.add_argument( "-d", "--dgt-port", type=str, help="enable dgt board on the given serial port such as /dev/ttyUSB0") parser.add_argument( "-b", "--book", type=str, help="Opening book - full name of book in 'books' folder", default='h-varied.bin') parser.add_argument("-g", "--enable-gaviota", action='store_true', help="enable gavoita tablebase probing") parser.add_argument("-leds", "--enable-dgt-board-leds", action='store_true', help="enable dgt board leds") parser.add_argument("-hs", "--hash-size", type=int, help="hashtable size in MB (default:64)", default=64) parser.add_argument("-t", "--threads", type=int, help="number of engine threads (default:1)", default=1) parser.add_argument( "-l", "--log-level", choices=['notset', 'debug', 'info', 'warning', 'error', 'critical'], default='warning', help="logging level") parser.add_argument("-lf", "--log-file", type=str, help="log to the given file") parser.add_argument("-r", "--remote", type=str, help="remote server running the engine") parser.add_argument("-u", "--user", type=str, help="remote user on server running the engine") parser.add_argument("-p", "--password", type=str, help="password for the remote user") parser.add_argument("-sk", "--server-key", type=str, help="key file used to connect to the remote server") parser.add_argument("-pgn", "--pgn-file", type=str, help="pgn file used to store the games", default='games.pgn') parser.add_argument("-pgn_u", "--pgn-user", type=str, help="user name for the pgn file", default=None) parser.add_argument("-ar", "--auto-reboot", action='store_true', help="reboot system after update") parser.add_argument("-web", "--web-server", dest="web_server_port", nargs="?", const=80, type=int, metavar="PORT", help="launch web server") parser.add_argument("-mail", "--email", type=str, help="email used to send pgn files", default=None) parser.add_argument("-mail_s", "--smtp_server", type=str, help="Adress of email server", default=None) parser.add_argument("-mail_u", "--smtp_user", type=str, help="Username for email server", default=None) parser.add_argument("-mail_p", "--smtp_pass", type=str, help="Password for email server", default=None) parser.add_argument("-mail_enc", "--smtp_encryption", action='store_true', help="use ssl encryption connection to smtp-Server") parser.add_argument("-mk", "--mailgun-key", type=str, help="key used to send emails via Mailgun Webservice", default=None) parser.add_argument("-uci", "--uci-option", type=str, help="pass an UCI option to the engine (name;value)", default=None) parser.add_argument("-dgt3000", "--dgt-3000-clock", action='store_true', help="do NOT use it anymore (DEPRECATED!)") parser.add_argument("-nobeep", "--disable-dgt-clock-beep", action='store_true', help="disable beeps on the dgt clock") parser.add_argument("-uvoice", "--user-voice", type=str, help="voice for user", default=None) parser.add_argument("-cvoice", "--computer-voice", type=str, help="voice for computer", default=None) parser.add_argument("-inet", "--enable-internet", action='store_true', help="enable internet lookups") parser.add_argument("-nookmove", "--disable-ok-move", action='store_false', help="disable ok move messages") parser.add_argument("-v", "--version", action='version', version='%(prog)s version {}'.format(version), help="show current version", default=None) args = parser.parse_args() # Enable logging logging.basicConfig( filename=args.log_file, level=getattr(logging, args.log_level.upper()), format= '%(asctime)s.%(msecs)d %(levelname)s %(module)s - %(funcName)s: %(message)s', datefmt="%Y-%m-%d %H:%M:%S") logging.getLogger("chess.uci").setLevel( logging.INFO) # don't want to get so many python-chess uci messages # Update if args.enable_internet: update_picochess(args.auto_reboot) gaviota = None if args.enable_gaviota: try: gaviota = chess.gaviota.open_tablebases('tablebases/gaviota') logging.debug('Tablebases gaviota loaded') except OSError: logging.error('Tablebases gaviota doesnt exist') gaviota = None # This class talks to DGTHardware or DGTVirtual DGTDisplay(args.disable_ok_move).start() if args.dgt_port: # Connect to DGT board logging.debug("Starting picochess with DGT board on [%s]", args.dgt_port) # DGTHardware(args.dgt_port, args.enable_dgt_board_leds, args.disable_dgt_clock_beep).start() DGTPi(args.dgt_port, args.enable_dgt_board_leds, args.disable_dgt_clock_beep).start() else: # Enable keyboard input and terminal display logging.warning("No DGT board port provided") KeyboardInput().start() TerminalDisplay().start() DGTVirtual(args.enable_dgt_board_leds, args.disable_dgt_clock_beep).start() # Save to PGN PgnDisplay(args.pgn_file, net=args.enable_internet, email=args.email, fromINIMailGun_Key=args.mailgun_key, fromIniSmtp_Server=args.smtp_server, fromINISmtp_User=args.smtp_user, fromINISmtp_Pass=args.smtp_pass, fromINISmtp_Enc=args.smtp_encryption).start() if args.pgn_user: user_name = args.pgn_user else: if args.email: user_name = args.email.split('@')[0] else: user_name = "Player" # Create ChessTalker for speech output talker = None if args.user_voice or args.computer_voice: logging.debug("Initializing ChessTalker [%s, %s]", str(args.user_voice), str(args.computer_voice)) talker = chesstalker.chesstalker.ChessTalker(args.user_voice, args.computer_voice) talker.start() else: logging.debug("ChessTalker disabled") # Launch web server if args.web_server_port: WebServer(args.web_server_port).start() # Gentlemen, start your engines... engine = uci.Engine(args.engine, hostname=args.remote, username=args.user, key_file=args.server_key, password=args.password) try: engine_name = engine.get().name except AttributeError: logging.debug("FATAL: no engines started") sys.exit(-1) logging.debug('Loaded engine [%s]', engine_name) logging.debug('Supported options [%s]', engine.get().options) # Startup - internal game = chess.Board() # Create the current game legal_fens = compute_legal_fens(game) # Compute the legal FENs all_books = get_opening_books() try: book_index = [book[1] for book in all_books].index('books/' + args.book) except ValueError: logging.debug("ERROR: Selected book not present, defaulting to %s", all_books[7][1]) book_index = 7 bookreader = chess.polyglot.open_reader(all_books[book_index][1]) searchmoves = AlternativeMover() interaction_mode = Mode.GAME play_mode = PlayMode.PLAY_WHITE time_control = TimeControl(ClockMode.BLITZ, minutes_per_game=5) last_computer_fen = None game_declared = False # User declared resignation or draw engine_level = None system_info_thread = threading.Timer(0, display_system_info) system_info_thread.start() # send the args options to the engine engine_startup() # Startup - external Display.show(Message.STARTUP_INFO, info={ "interaction_mode": interaction_mode, "play_mode": play_mode, "book": all_books[book_index][1], "book_index": book_index, "time_control_string": "mov 5" }) Display.show(Message.ENGINE_START, path=engine.get_path(), has_levels=engine.has_levels()) # Event loop while True: try: event = event_queue.get() except queue.Empty: pass else: logging.debug('Received event in event loop : %s', event) for case in switch(event): if case( Event.FEN ): # User sets a new position, convert it to a move if it is legal legal_fens = process_fen(event.fen, legal_fens) break if case(Event.KEYBOARD_MOVE): move = event.move logging.debug('Keyboard move [%s]', move) if move not in game.legal_moves: logging.warning('Illegal move [%s]', move) else: g = copy.deepcopy(game) g.push(move) legal_fens = process_fen(g.board_fen(), legal_fens) break if case(Event.USER_MOVE): # User sends a new move move = event.move logging.debug('User move [%s]', move) if move not in game.legal_moves: logging.warning('Illegal move [%s]', move) else: result = chess.uci.BestMove(bestmove=move, ponder=None) game = handle_move(result, game) # if check_game_state(game, interaction_mode): legal_fens = compute_legal_fens(game) break if case(Event.LEVEL): # User sets a new level engine_level = event.level logging.debug("Setting engine to level %i", engine_level) if engine.level(engine_level): engine.send() Display.show(Message.LEVEL, level=engine_level) break if case(Event.NEW_ENGINE): old_path = engine.path engine_shutdown = True # Stop the old engine cleanly engine.stop() # Closeout the engine process and threads # The all return non-zero error codes, 0=success if engine.quit(): # Ask nicely if engine.terminate(): # If you won't go nicely.... if engine.kill(): # Right that does it! logging.error( 'Serious: Engine shutdown failure') Display.show(Message.ENGINE_FAIL) engine_shutdown = False if engine_shutdown: # Load the new one and send args. # Local engines only engine_fallback = False engine = uci.Engine(event.eng[0]) try: engine_name = engine.get().name except AttributeError: # New engine failed to start, restart old engine logging.error( "New engine failed to start, reverting to %s", old_path) engine_fallback = True engine = uci.Engine(old_path) try: engine_name = engine.get().name except AttributeError: # Help - old engine failed to restart. There is no engine logging.error("FATAL: no engines started") sys.exit(-1) # Schedule cleanup of old objects gc.collect() # Restore options - this doesn't deal with any # supplementary uci options sent 'in game', see event.UCI_OPTION_SET engine_startup() # Send user selected engine level to new engine if engine_level and engine.level(engine_level): engine.send() Display.show(Message.LEVEL, level=engine_level) # Go back to analysing or observing if interaction_mode == Mode.ANALYSIS or interaction_mode == Mode.KIBITZ: analyse(game) if interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: observe(game, time_control) # All done - rock'n'roll if not engine_fallback: Display.show(Message.ENGINE_NAME, ename=engine_name) Display.show(Message.ENGINE_READY, eng=event.eng, has_levels=engine.has_levels()) else: Display.show(Message.ENGINE_FAIL) break if case(Event.SETUP_POSITION): # User sets up a position logging.debug("Setting up custom fen: {0}".format( event.fen)) if engine.has_chess960(): engine.option('UCI_Chess960', event.uci960) engine.send() else: # start normal new game if engine can't handle the user wish event.uci960 = False Display.show( Message.ENGINE_FAIL ) # @todo not really true but inform the user about the result if game.move_stack: if game.is_game_over() or game_declared: Display.show(Message.GAME_ENDS, result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game)) game = chess.Board(event.fen, event.uci960) game.custom_fen = event.fen legal_fens = compute_legal_fens(game) stop_search_and_clock() time_control.reset() interaction_mode = Mode.GAME last_computer_fen = None set_wait_state() searchmoves.reset() Display.show(Message.START_NEW_GAME) game_declared = False break if case(Event.STARTSTOP_THINK): if engine.is_thinking() and (interaction_mode != Mode.REMOTE): stop_search_and_clock() else: play_mode = PlayMode.PLAY_WHITE if play_mode == PlayMode.PLAY_BLACK else PlayMode.PLAY_BLACK Display.show(Message.PLAY_MODE, play_mode=play_mode) if check_game_state(game, play_mode) and ( interaction_mode != Mode.REMOTE): think(game, time_control) break if case(Event.ALTERNATIVE_MOVE): game.pop() Display.show(Message.ALTERNATIVE_MOVE) think(game, time_control) break if case(Event.STARTSTOP_CLOCK): if time_control.is_ticking(): stop_clock() else: Display.show(Message.RUN_CLOCK, turn=game.turn, time_control=time_control) time_control.run(game.turn) break if case(Event.NEW_GAME): # User starts a new game if game.move_stack: logging.debug("Starting a new game") if not (game.is_game_over() or game_declared): Display.show(Message.GAME_ENDS, result=GameResult.ABORT, play_mode=play_mode, game=copy.deepcopy(game)) game = chess.Board() legal_fens = compute_legal_fens(game) last_computer_fen = None stop_search_and_clock() time_control.reset() set_wait_state() searchmoves.reset() Display.show(Message.START_NEW_GAME) game_declared = False break if case(Event.DRAWRESIGN): if not game_declared: # in case user leaves kings in place while moving other pieces Display.show(Message.GAME_ENDS, result=event.result, play_mode=play_mode, game=copy.deepcopy(game)) game_declared = True break if case(Event.OPENING_BOOK): logging.debug("Changing opening book [%s]", event.book[1]) bookreader = chess.polyglot.open_reader(event.book[1]) Display.show(Message.OPENING_BOOK, book=event.book) break if case(Event.REMOTE_MOVE): if interaction_mode == Mode.REMOTE: bm = chess.uci.BestMove(bestmove=chess.Move.from_uci( event.move), ponder=None) game = handle_move(bm, game) legal_fens = compute_legal_fens(game) break if case(Event.BEST_MOVE): if event.inbook: Display.show(Message.BOOK_MOVE, result=event.result) game = handle_move(event.result, game) legal_fens = compute_legal_fens(game) break if case(Event.NEW_PV): if interaction_mode == Mode.GAME: pass else: Display.show(Message.NEW_PV, pv=event.pv, mode=interaction_mode, fen=game.fen()) break if case(Event.SCORE): if event.score == 'book': score = 'book' elif event.score == 'tb': score = 'tb {0}'.format(event.mate) else: try: score = int(event.score) if game.turn == chess.BLACK: score *= -1 except ValueError: score = event.score logging.debug('Could not convert score ' + score) except TypeError: score = 'm {0}'.format(event.mate) Display.show(Message.SCORE, score=score, mate=event.mate, mode=interaction_mode) break if case(Event.SET_MODE): if interaction_mode == Mode.GAME or interaction_mode == Mode.OBSERVE or interaction_mode == Mode.REMOTE: stop_clock( ) # only stop, if the clock is really running interaction_mode = event.mode if engine.is_thinking(): stop_search() # dont need to stop, if pondering if engine.is_pondering() and interaction_mode == Mode.GAME: stop_search( ) # if change from ponder modes to game, also stops the pondering set_wait_state() Display.show(Message.INTERACTION_MODE, mode=event.mode) break if case(Event.SET_TIME_CONTROL): time_control = event.time_control Display.show(Message.TIME_CONTROL, time_control_string=event.time_control_string) break if case(Event.OUT_OF_TIME): stop_search_and_clock() Display.show(Message.GAME_ENDS, result=GameResult.TIME_CONTROL, play_mode=play_mode, game=copy.deepcopy(game)) break if case(Event.UCI_OPTION_SET): # Nowhere calls this yet, but they will need to be saved for engine restart engine.option(event.name, event.value) break if case(Event.SHUTDOWN): if talker: talker.say_event(event) shutdown() break if case(Event.DGT_BUTTON): Display.show(Message.DGT_BUTTON, button=event.button) break if case(Event.DGT_FEN): Display.show(Message.DGT_FEN, fen=event.fen) break if case(): # Default logging.warning("Event not handled : [%s]", event) event_queue.task_done()