def main(engine1, engine2, options1={}, options2={}, names=None, games=1, resign=None, mate_win=False, byoyomi=None, time=None, inc=None, draw=256, opening=None, opening_moves=24, opening_seed=None, opening_index=None, keep_process=False, csa=None, multi_csa=False, pgn=None, no_pgn_moves=False, is_display=False, debug=False, print_summary=True, callback=None): engine1 = Engine(engine1, connect=False) engine2 = Engine(engine2, connect=False) # byoyomi if type(byoyomi) in (list, tuple): if len(byoyomi) >= 2: byoyomi1, byoyomi2 = byoyomi else: byoyomi1 = byoyomi2 = byoyomi[0] else: byoyomi1 = byoyomi2 = byoyomi # time if type(time) in (list, tuple): if len(time) >= 2: time1, time2 = time else: time1 = time2 = time[0] else: time1 = time2 = time # inc if type(inc) in (list, tuple): if len(inc) >= 2: inc1, inc2 = inc else: inc1 = inc2 = inc[0] else: inc1 = inc2 = inc # debug if debug: class Listener: def __init__(self, id): self.id = id self.info = self.bestmove = '' def __call__(self, line): print(self.id + ':' + line) self.info = self.bestmove self.bestmove = line listener1 = Listener('1') listener2 = Listener('2') else: class Listener: def __init__(self): self.info = self.bestmove = '' def __call__(self, line): self.info = self.bestmove self.bestmove = line listener1 = listener2 = Listener() # CSA if csa and multi_csa: csa_exporter = CSA.Exporter(csa, append=True) # PGN if pgn: pgn_exporter = PGN.Exporter(pgn, append=True) # 初期局面読み込み if opening: opening_list = [] with open(opening) as f: opening_list = [line.strip()[15:].split(' ') for line in f] # インデックス指定 if opening_index is not None: opening_list = [opening_list[opening_index]] else: # シャッフル if opening_seed is not None: random.seed(opening_seed) random.shuffle(opening_list) board = Board() engine1_won = [0, 0, 0, 0, 0, 0] engine2_won = [0, 0, 0, 0, 0, 0] draw_count = 0 WIN_DRAW = 2 for n in range(games): # 先後入れ替え if n % 2 == 0: engines_order = (engine1, engine2) options_order = (options1, options2) listeners_order = (listener1, listener2) byoyomi_order = (byoyomi1, byoyomi2) btime = time1 wtime = time2 binc = inc1 winc = inc2 else: engines_order = (engine2, engine1) options_order = (options2, options1) listeners_order = (listener2, listener1) byoyomi_order = (byoyomi2, byoyomi1) btime = time2 wtime = time1 binc = inc2 winc = inc1 # 接続とエンジン設定 for engine, options, listener in zip(engines_order, options_order, listeners_order): if engine.proc is None: engine.connect(listener=listener) for name, value in options.items(): engine.setoption(name, value, listener=listener) engine.isready(listener=listener) if names: if names[0]: engine1.name = names[0] if names[1]: engine2.name = names[1] print('{} vs {} start.'.format(engines_order[0].name, engines_order[1].name)) # 初期局設定 board.reset() moves = [] usi_moves = [] repetition_hash = defaultdict(int) if csa: engine_names = [engine.name for engine in engines_order] if not multi_csa: csa_exporter = CSA.Exporter(os.path.join( csa, '+'.join(engine_names) + '+' + datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '.csa'), append=False) csa_exporter.info(board, engine_names, version='V2') if opening: for move_usi in opening_list[n // 2 % len(opening_list)]: move = board.push_usi(move_usi) if csa: csa_exporter.move(move) moves.append(move) usi_moves.append(move_usi) repetition_hash[board.zobrist_hash()] += 1 if board.move_number > opening_moves: break # 盤面表示 if is_display: print('開始局面') if is_jupyter: display(SVG(board.to_svg())) else: print(board) # 新規ゲーム for engine, listener in zip(engines_order, listeners_order): engine.usinewgame(listener=listener) # 対局 is_game_over = False is_nyugyoku = False is_illegal = False is_repetition_win = False is_repetition_lose = False is_fourfold_repetition = False is_timeup = False remain_time = [btime, wtime] inc_time = (binc, winc) while not is_game_over: engine_index = (board.move_number - 1) % 2 engine = engines_order[engine_index] listener = listeners_order[engine_index] byoyomi = byoyomi_order[engine_index] # 持将棋 if board.move_number > draw: is_game_over = True break # position engine.position(usi_moves, listener=listener) start_time = perf_counter() # go bestmove, _ = engine.go(byoyomi=byoyomi, btime=remain_time[BLACK], wtime=remain_time[WHITE], binc=binc, winc=winc, listener=listener) elapsed_time = perf_counter() - start_time if remain_time[board.turn] is not None: if inc_time[board.turn] is not None: remain_time[board.turn] += inc_time[board.turn] remain_time[board.turn] -= math.ceil(elapsed_time * 1000) if remain_time[board.turn] < 0: # 1秒未満は切れ負けにしない if remain_time[board.turn] > -1000: remain_time[board.turn] = 0 else: # 時間切れ負け is_timeup = True is_game_over = True break score = usi_info_to_score(listener.info) # 投了閾値 if resign is not None: if score is not None and score <= -resign: # 投了 is_game_over = True break # 詰みを見つけたら終了 if mate_win: if score is not None and score == 100000: move = board.move_from_usi(bestmove) if csa: csa_exporter.move(move, time=int(elapsed_time), comment=usi_info_to_csa_comment( board, listener.info)) board.push(move) is_game_over = True break if bestmove == 'resign': # 投了 is_game_over = True break elif bestmove == 'win': # 入玉勝ち宣言 is_nyugyoku = True is_game_over = True break else: move = board.move_from_usi(bestmove) if board.is_legal(move): if csa: csa_exporter.move(move, time=int(elapsed_time), comment=usi_info_to_csa_comment( board, listener.info)) board.push(move) moves.append(move) usi_moves.append(bestmove) key = board.zobrist_hash() repetition_hash[key] += 1 # 千日手 if repetition_hash[key] == 4: # 連続王手 is_draw = board.is_draw() if is_draw == REPETITION_WIN: is_repetition_win = True is_game_over = True break elif is_draw == REPETITION_LOSE: is_repetition_lose = True is_game_over = True break is_fourfold_repetition = True is_game_over = True break else: is_illegal = True is_game_over = True break # 盤面表示 if is_display: print('{}手目'.format(len(usi_moves))) if is_jupyter: display(SVG(board.to_svg(move))) else: print(board) # 終局判定 if board.is_game_over(): is_game_over = True break # エンジン終了 if not keep_process: for engine, listener in zip(engines_order, listeners_order): engine.quit(listener=listener) # 結果出力 if not board.is_game_over() and board.move_number > draw: win = WIN_DRAW print('まで{}手で持将棋'.format(board.move_number - 1)) csa_endgame = '%JISHOGI' elif is_fourfold_repetition: win = WIN_DRAW print('まで{}手で千日手'.format(board.move_number - 1)) csa_endgame = '%SENNICHITE' elif is_nyugyoku: win = board.turn print('まで{}手で入玉宣言'.format(board.move_number - 1)) csa_endgame = '%KACHI' elif is_illegal: win = opponent(board.turn) print('まで{}手で{}の反則負け'.format(board.move_number - 1, '先手' if win == WHITE else '後手')) csa_endgame = '%ILLEGAL_MOVE' elif is_repetition_win: win = board.turn print('まで{}手で{}の反則勝ち'.format(board.move_number - 1, '先手' if win == BLACK else '後手')) csa_endgame = '%+ILLEGAL_ACTION' if board.turn == WHITE else '%-ILLEGAL_ACTION' elif is_repetition_lose: win = opponent(board.turn) print('まで{}手で{}の反則負け'.format(board.move_number - 1, '先手' if win == WHITE else '後手')) csa_endgame = 'ILLEGAL_MOVE' elif is_timeup: win = opponent(board.turn) print('まで{}手で{}の切れ負け'.format(board.move_number - 1, '先手' if win == WHITE else '後手')) csa_endgame = '%TIME_UP' else: win = opponent(board.turn) print('まで{}手で{}の勝ち'.format(board.move_number - 1, '先手' if win == BLACK else '後手')) csa_endgame = '%TORYO' # 勝敗カウント if win == WIN_DRAW: draw_count += 1 engine1_won[4 + n % 2] += 1 engine2_won[4 + (n + 1) % 2] += 1 elif n % 2 == 0 and win == BLACK or n % 2 == 1 and win == WHITE: engine1_won[n % 2] += 1 engine2_won[2 + (n + 1) % 2] += 1 else: engine2_won[(n + 1) % 2] += 1 engine1_won[2 + n % 2] += 1 black_won = engine1_won[0] + engine2_won[0] white_won = engine1_won[1] + engine2_won[1] engine1_won_sum = engine1_won[0] + engine1_won[1] engine2_won_sum = engine2_won[0] + engine2_won[1] total_count = engine1_won_sum + engine2_won_sum + draw_count # 勝敗状況表示 if print_summary: print('{} of {} games finished.'.format(n + 1, games)) print('{} vs {}: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine2.name, engine1_won_sum, engine2_won_sum, draw_count, (engine1_won_sum + draw_count / 2) / total_count * 100)) print('Black vs White: {}-{}-{} ({:.1f}%)'.format( black_won, white_won, draw_count, (black_won + draw_count / 2) / total_count * 100)) print('{} playing Black: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine1_won[0], engine1_won[2], engine1_won[4], (engine1_won[0] + engine1_won[4] / 2) / (engine1_won[0] + engine1_won[2] + engine1_won[4]) * 100)) print('{} playing White: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine1_won[1], engine1_won[3], engine1_won[5], (engine1_won[1] + engine1_won[5] / 2) / (engine1_won[1] + engine1_won[3] + engine1_won[5]) * 100 if n > 0 else 0)) print('{} playing Black: {}-{}-{} ({:.1f}%)'.format( engine2.name, engine2_won[0], engine2_won[2], engine2_won[4], (engine2_won[0] + engine2_won[4] / 2) / (engine2_won[0] + engine2_won[2] + engine2_won[4]) * 100 if n > 0 else 0)) print('{} playing White: {}-{}-{} ({:.1f}%)'.format( engine2.name, engine2_won[1], engine2_won[3], engine2_won[5], (engine2_won[1] + engine2_won[5] / 2) / (engine2_won[1] + engine2_won[3] + engine2_won[5]) * 100)) elo = Elo(engine1_won_sum, engine2_won_sum, draw_count) if engine1_won_sum > 0 and engine2_won_sum > 0: try: error_margin = elo.error_margin() except ValueError: error_margin = math.nan print( 'Elo difference: {:.1f} +/- {:.1f}, LOS: {:.1f} %, DrawRatio: {:.1f} %' .format(elo.diff(), error_margin, elo.los(), elo.draw_ratio())) # CSA if csa: csa_exporter.endgame(csa_endgame) # PGN if pgn: if win == BLACK: result = BLACK_WIN elif win == WHITE: result = WHITE_WIN else: result = DRAW pgn_exporter.tag_pair([engine.name for engine in engines_order], result, round=n + 1) if not no_pgn_moves: pgn_exporter.movetext(moves) if callback: is_continue = callback({ 'engine1_name': engine1.name, 'engine2_name': engine2.name, 'engine1_won': engine1_won_sum, 'engine2_won': engine2_won_sum, 'black_won': black_won, 'white_won': white_won, 'draw': draw_count, 'total': total_count, }) if not is_continue: break # CSA if csa: csa_exporter.close() # PGN if pgn: pgn_exporter.close() # エンジン終了 if keep_process: for engine, listener in zip(engines_order, listeners_order): engine.quit(listener=listener) return { 'engine1_name': engine1.name, 'engine2_name': engine2.name, 'engine1_won': engine1_won_sum, 'engine2_won': engine2_won_sum, 'black_won': black_won, 'white_won': white_won, 'draw': draw_count, 'total': total_count, }
def main(engine1, engine2, options1={}, options2={}, names=None, games=1, resign=None, byoyomi=1000, draw=256, opening=None, opening_moves=24, opening_seed=None, pgn=None, no_pgn_moves=False, is_display=True, debug=True): engine1 = Engine(engine1, connect=False) engine2 = Engine(engine2, connect=False) # debug if debug: class Listener: def __init__(self, id): self.id = id def __call__(self, line): print(self.id + ':' + line) listener1 = Listener('1') listener2 = Listener('2') else: listener1 = None listener2 = None # PGN if pgn: pgn_exporter = PGN.Exporter(pgn) # 初期局面読み込み if opening_seed is not None: random.seed(opening_seed) if opening: opening_list = [] with open(opening) as f: opening_list = [line.strip()[15:].split(' ') for line in f] # シャッフル random.shuffle(opening_list) board = Board() engine1_won = [0, 0, 0, 0, 0, 0] engine2_won = [0, 0, 0, 0, 0, 0] draw_count = 0 WIN_DRAW = 2 for n in range(games): # 先後入れ替え if n % 2 == 0: engines_order = (engine1, engine2) options_order = (options1, options2) listeners_order = (listener1, listener2) else: engines_order = (engine2, engine1) options_order = (options2, options1) listeners_order = (listener2, listener1) # 接続とエンジン設定 for engine, options, listener in zip(engines_order, options_order, listeners_order): engine.connect(listener=listener) for name, value in options.items(): engine.setoption(name, value) engine.isready(listener=listener) if names: if names[0]: engine1.name = names[0] if names[1]: engine2.name = names[1] # 初期局設定 board.reset() moves = [] usi_moves = [] repetition_hash = defaultdict(int) if opening: for move_usi in opening_list[n // 2 % len(opening_list)]: moves.append(board.push_usi(move_usi)) usi_moves.append(move_usi) repetition_hash[board.zobrist_hash()] += 1 if board.move_number > opening_moves: break # 盤面表示 if is_display: print('開始局面') if is_jupyter: display(SVG(board.to_svg())) else: print(board) # 新規ゲーム for engine, listener in zip(engines_order, listeners_order): engine.usinewgame(listener=listener) # 対局 is_game_over = False is_nyugyoku = False is_illegal = False is_repetition_win = False is_repetition_lose = False is_fourfold_repetition = False while not is_game_over: for engine, listener in zip(engines_order, listeners_order): # 持将棋 if board.move_number > draw: is_game_over = True break # position engine.position(usi_moves, listener=listener) # go bestmove, _ = engine.go(byoyomi=byoyomi, listener=listener) if bestmove == 'resign': # 投了 is_game_over = True break elif bestmove == 'win': # 入玉勝ち宣言 is_nyugyoku = True is_game_over = True break else: move = board.move_from_usi(bestmove) if board.is_legal(move): board.push(move) moves.append(move) usi_moves.append(bestmove) key = board.zobrist_hash() repetition_hash[key] += 1 # 千日手 if repetition_hash[key] == 4: # 連続王手 is_draw = board.is_draw() if is_draw == REPETITION_WIN: is_repetition_win = True is_game_over = True break elif is_draw == REPETITION_LOSE: is_repetition_lose = True is_game_over = True break is_fourfold_repetition = True is_game_over = True break else: is_illegal = True is_game_over = True break # 盤面表示 if is_display: print('{}手目'.format(len(usi_moves))) if is_jupyter: display(SVG(board.to_svg(move))) else: print(board) # 終局判定 if board.is_game_over(): is_game_over = True break # エンジン終了 for engine, listener in zip(engines_order, listeners_order): engine.quit(listener=listener) # 結果出力 if not board.is_game_over() and board.move_number > draw: win = WIN_DRAW print('まで{}手で持将棋'.format(board.move_number - 1)) elif is_fourfold_repetition: win = WIN_DRAW print('まで{}手で千日手'.format(board.move_number - 1)) elif is_nyugyoku: win = board.turn print('まで{}手で入玉宣言'.format(board.move_number - 1)) elif is_illegal: win = opponent(board.turn) print('まで{}手で{}の反則負け'.format(board.move_number - 1, '先手' if win == WHITE else '後手')) elif is_repetition_win: win = board.turn print('まで{}手で{}の反則勝ち'.format(board.move_number - 1, '先手' if win == BLACK else '後手')) elif is_repetition_lose: win = opponent(board.turn) print('まで{}手で{}の反則負け'.format(board.move_number - 1, '先手' if win == WHITE else '後手')) else: win = opponent(board.turn) print('まで{}手で{}の勝ち'.format(board.move_number - 1, '先手' if win == BLACK else '後手')) # 勝敗カウント if win == WIN_DRAW: draw_count += 1 engine1_won[4 + n % 2] += 1 engine2_won[4 + (n + 1) % 2] += 1 elif n % 2 == 0 and win == BLACK or n % 2 == 1 and win == WHITE: engine1_won[n % 2] += 1 engine2_won[2 + (n + 1) % 2] += 1 else: engine2_won[(n + 1) % 2] += 1 engine1_won[2 + n % 2] += 1 black_won = engine1_won[0] + engine2_won[0] white_won = engine1_won[1] + engine2_won[1] engine1_won_sum = engine1_won[0] + engine1_won[1] engine2_won_sum = engine2_won[0] + engine2_won[1] total_count = engine1_won_sum + engine2_won_sum + draw_count # 勝敗状況表示 print('{} of {} games finished.'.format(n + 1, games)) print('{} vs {}: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine2.name, engine1_won_sum, engine2_won_sum, draw_count, (engine1_won_sum + draw_count / 2) / total_count * 100)) print('Black vs White: {}-{}-{} ({:.1f}%)'.format( black_won, white_won, draw_count, (black_won + draw_count / 2) / total_count * 100)) print('{} playing Black: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine1_won[0], engine1_won[2], engine1_won[4], (engine1_won[0] + engine1_won[4] / 2) / (engine1_won[0] + engine1_won[2] + engine1_won[4]) * 100)) print('{} playing White: {}-{}-{} ({:.1f}%)'.format( engine1.name, engine1_won[1], engine1_won[3], engine1_won[5], (engine1_won[1] + engine1_won[5] / 2) / (engine1_won[1] + engine1_won[3] + engine1_won[5]) * 100 if n > 0 else 0)) print('{} playing Black: {}-{}-{} ({:.1f}%)'.format( engine2.name, engine2_won[0], engine2_won[2], engine2_won[4], (engine2_won[0] + engine2_won[4] / 2) / (engine2_won[0] + engine2_won[2] + engine2_won[4]) * 100 if n > 0 else 0)) print('{} playing White: {}-{}-{} ({:.1f}%)'.format( engine2.name, engine2_won[1], engine2_won[3], engine2_won[5], (engine2_won[1] + engine2_won[5] / 2) / (engine2_won[1] + engine2_won[3] + engine2_won[5]) * 100)) elo = Elo(engine1_won_sum, engine2_won_sum, draw_count) if engine1_won_sum > 0 and engine2_won_sum > 0: try: error_margin = elo.error_margin() except ValueError: error_margin = math.nan print( 'Elo difference: {:.1f} +/- {:.1f}, LOS: {:.1f} %, DrawRatio: {:.1f} %' .format(elo.diff(), error_margin, elo.los(), elo.draw_ratio())) # PGN if pgn: if win == BLACK: result = BLACK_WIN elif win == WHITE: result = WHITE_WIN else: result = DRAW pgn_exporter.tag_pair([engine.name for engine in engines_order], result, round=n + 1) if not no_pgn_moves: pgn_exporter.movetext(moves) # PGN if pgn: pgn_exporter.close()