def setUpClass(cls) -> None:
        cls.exe_dir = Path('../../../YaneuraOu/build/Deep-TensorRT')
        exe_path = cls.exe_dir / 'YaneuraOu-Deep-TensorRT.exe'

        cls.engine = Engine(str(exe_path))

        cls.engine.usi()
        cls.engine.isready()
from cshogi.usi import Engine
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('engine')
parser.add_argument('model')
parser.add_argument('--gpus', type=int, default=1)
parser.add_argument('--threads', type=int, default=2)
parser.add_argument('--nodelimit', type=int, default=10000000)
parser.add_argument('--batch', type=int, default=128)
parser.add_argument('--byoyomi', type=int, default=1000)
parser.add_argument('--options')
args = parser.parse_args()

engine = Engine(args.engine, debug=True)
engine.setoption('USI_Ponder', 'false')
engine.setoption('Resign_Threshold', '0')
engine.setoption('PV_Interval', '0')
engine.setoption('DNN_Model', args.model)
engine.setoption('Byoyomi_Margin', '0')
engine.setoption('UCT_NodeLimit', str(args.nodelimit))
engine.setoption('DNN_Batch_Size', str(args.batch))
engine.setoption('ReuseSubtree', 'false')
engine.setoption('UCT_Threads', str(args.threads))
for i in range(2, args.gpus + 1):
    engine.setoption('UCT_Threads' + str(i), str(args.threads))
if args.options:
    for option in args.options.split(','):
        name, value = option.split(':')
        engine.setoption(name, value)
engine.isready()
Example #3
0
parser.add_argument('delhcp')
parser.add_argument('usi')
parser.add_argument('usi_options')
parser.add_argument('--nodes', type=int, default=80000)
parser.add_argument('--th', type=int, default=2500)
args = parser.parse_args()

with open(args.hcp, 'rb') as f:
    f.seek(args.start * HuffmanCodedPos.itemsize)
    hcps = np.fromfile(f, HuffmanCodedPos, args.end - args.start)

print('read num', len(hcps))
delhcps = np.zeros(len(hcps), HuffmanCodedPos)

os.chdir(os.path.dirname(args.usi))
engine = Engine(args.usi)

for option in args.usi_options.split(','):
    k, v = option.split(':')
    engine.setoption(k, v)

engine.isready(print)

ptn = re.compile(r'score (cp|mate) ([+\-0-9]+)')

class Listener:
    def __init__(self):
        self.info1 = None
        self.info2 = None

    def __call__(self, line):
Example #4
0
from cshogi.usi import Engine
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--gpus', type=int, default=1)
parser.add_argument('--threads', type=int, default=2)
parser.add_argument('--hash', type=int, default=1048576)
parser.add_argument('--batch', type=int, default=128)
parser.add_argument('--engine',
                    default=r'H:\src\DeepLearningShogi\x64\Release\usi.exe')
parser.add_argument(
    '--model',
    default=r'F:\model\model_rl_val_fused_wideresnet10_selfplay_179')
args = parser.parse_args()

engine = Engine(args.engine, debug=True)
engine.setoption('USI_Ponder', 'false')
engine.setoption('DNN_Model', args.model)
engine.setoption('Byoyomi_Margin', '0')
engine.setoption('UCT_Hash', str(args.hash))
engine.setoption('DNN_Batch_Size', str(args.batch))
engine.setoption('ReuseSubtree', 'false')
engine.setoption('UCT_Threads', str(args.threads))
for i in range(2, args.gpus + 1):
    engine.setoption('UCT_Threads' + str(i), str(args.threads))
engine.isready()

positions = [
    '',
    '7g7f 7a6b 2g2f 4a3b 2f2e 8c8d 6i7h 5a4a 2e2d 2c2d 2h2d P*2c 2d2h 8d8e 3i3h 3c3d 3h2g 8e8f 8g8f 8b8f 2g3f 8f8d 3f4e 4a5b P*8e 8d8e 4e3d 8e3e 8h2b+ 3a2b B*5f 2c2d P*8b B*5e 8b8a+ 5e9i+ 8i7g L*5d 3g3f 3e3f 3d4e 5d5f 4e3f 5f5g+ N*6i 5g5f 8a9a P*3e 3f3e P*8h 7i8h 9i8i 5i6h B*6d 2h2f',
    '2g2f 8c8d 2f2e 4a3b 7g7f 8d8e 2e2d 2c2d 2h2d P*2c 2d2f 3c3d 6i7h 7a7b P*2d 2c2d 2f2d 5a4b 2d3d 2b3c 3d3f 3a2b 5i5h 3c8h+ 7i8h B*2g 3f2f 2g5d+ 8h7g 6c6d 3i3h 8e8f 8g8f 7c7d 3g3f 7d7e 7f7e P*7f 7g8h 8b8f P*8g 8f8d P*2d P*2c 2d2c+ 2b2c 3f3e P*2d',
Example #5
0
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,
    }
Example #6
0
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()