「十六式いろは」のあれこれ

コンピュータ将棋ソフトのエンジン「十六式いろは」の開発者のブログです。

Pythonで将棋ソフト制作-評価値編の補足

youtu.be

動画内での正規表現

上記の動画で作成したソースコードです。正規表現を使います。
(この記事の後半に、処理速度を速くした版もあります)

main.py

import cshogi
import random
# 文字列の検索のために正規表現を使う。
import re


def koma_count(board):
    """
    局面の先手、後手の各駒の数を数えてdictで返す。(王を除く)

    Args:
        board (cshogi.Board): cshogiでの局面

    Returns:
        koma_cnt (dict): 各駒とその数
        例:
        {'先手の歩': 7, '先手の香': 2, '先手の桂': 1, '先手の銀': 3,
         '先手の角': 0, '先手の飛': 1, '先手の金': 1, '先手のと': 1,
         '先手の杏': 0, '先手の圭': 0, '先手の全': 0, '先手の馬': 0,
         '先手の竜': 0, '先手持ち駒の歩': 0, '先手持ち駒の香': 0,
         '先手持ち駒の桂': 0, '先手持ち駒の銀': 0, '先手持ち駒の角': 0,
         '先手持ち駒の飛': 1, '先手持ち駒の金': 1, '後手の歩': 5,
         '後手の香': 2, '後手の桂': 2, '後手の銀': 0, '後手の角': 2,
         '後手の飛': 0, '後手の金': 1, '後手のと': 0, '後手の杏': 0,
         '後手の圭': 0, '後手の全': 0, '後手の馬': 0, '後手の竜': 0,
         '後手持ち駒の歩': 5, '後手持ち駒の香': 0, '後手持ち駒の桂': 1,
         '後手持ち駒の銀': 1, '後手持ち駒の角': 0, '後手持ち駒の飛': 0,
         '後手持ち駒の金': 1} 

    """

    board_str = str(board)  # 局面の文字列化
    # cshogiのソースコードposition.cppに駒の種類が書いている。
    koma_cnt = {"先手の歩": 0}  # 初期化
    # 先手
    koma_cnt["先手の歩"] = board_str.count("+FU")
    koma_cnt["先手の香"] = board_str.count("+KY")
    koma_cnt["先手の桂"] = board_str.count("+KE")
    koma_cnt["先手の銀"] = board_str.count("+GI")
    koma_cnt["先手の角"] = board_str.count("+KA")
    koma_cnt["先手の飛"] = board_str.count("+HI")
    koma_cnt["先手の金"] = board_str.count("+KI")
    koma_cnt["先手のと"] = board_str.count("+TO")
    koma_cnt["先手の杏"] = board_str.count("+NY")
    koma_cnt["先手の圭"] = board_str.count("+NK")
    koma_cnt["先手の全"] = board_str.count("+NG")
    koma_cnt["先手の馬"] = board_str.count("+UM")
    koma_cnt["先手の竜"] = board_str.count("+RY")

    # print(koma_cnt["先手の歩"])  # 盤上の先手の歩の数を表示する。

    # 先手の持ち駒
    line_str = str(re.search(r"P\+00FU.*\n", board_str))
    koma_cnt["先手持ち駒の歩"] = line_str.count("FU")
    line_str = str(re.search(r"P\+00KY.*\n", board_str))
    koma_cnt["先手持ち駒の香"] = line_str.count("KY")
    line_str = str(re.search(r"P\+00KE.*\n", board_str))
    koma_cnt["先手持ち駒の桂"] = line_str.count("KE")
    line_str = str(re.search(r"P\+00GI.*\n", board_str))
    koma_cnt["先手持ち駒の銀"] = line_str.count("GI")
    line_str = str(re.search(r"P\+00KA.*\n", board_str))
    koma_cnt["先手持ち駒の角"] = line_str.count("KA")
    line_str = str(re.search(r"P\+00HI.*\n", board_str))
    koma_cnt["先手持ち駒の飛"] = line_str.count("HI")
    line_str = str(re.search(r"P\+00KI.*\n", board_str))
    koma_cnt["先手持ち駒の金"] = line_str.count("KI")

    # 後手
    koma_cnt["後手の歩"] = board_str.count("-FU")
    koma_cnt["後手の香"] = board_str.count("-KY")
    koma_cnt["後手の桂"] = board_str.count("-KE")
    koma_cnt["後手の銀"] = board_str.count("-GI")
    koma_cnt["後手の角"] = board_str.count("-KA")
    koma_cnt["後手の飛"] = board_str.count("-HI")
    koma_cnt["後手の金"] = board_str.count("-KI")
    koma_cnt["後手のと"] = board_str.count("-TO")
    koma_cnt["後手の杏"] = board_str.count("-NY")
    koma_cnt["後手の圭"] = board_str.count("-NK")
    koma_cnt["後手の全"] = board_str.count("-NG")
    koma_cnt["後手の馬"] = board_str.count("-UM")
    koma_cnt["後手の竜"] = board_str.count("-RY")

    # 後手の持ち駒
    line_str = str(re.search(r"P-00FU.*\n", board_str))
    koma_cnt["後手持ち駒の歩"] = line_str.count("FU")
    line_str = str(re.search(r"P-00KY.*\n", board_str))
    koma_cnt["後手持ち駒の香"] = line_str.count("KY")
    line_str = str(re.search(r"P-00KE.*\n", board_str))
    koma_cnt["後手持ち駒の桂"] = line_str.count("KE")
    line_str = str(re.search(r"P-00GI.*\n", board_str))
    koma_cnt["後手持ち駒の銀"] = line_str.count("GI")
    line_str = str(re.search(r"P-00KA.*\n", board_str))
    koma_cnt["後手持ち駒の角"] = line_str.count("KA")
    line_str = str(re.search(r"P-00HI.*\n", board_str))
    koma_cnt["後手持ち駒の飛"] = line_str.count("HI")
    line_str = str(re.search(r"P-00KI.*\n", board_str))
    koma_cnt["後手持ち駒の金"] = line_str.count("KI")

    return koma_cnt


def komadoku_evaluate(koma_cnt):
    """
    その局面の駒得による評価値を計算する。

    Args:
        koma_cnt(dict): 各駒とその数

    Returns:
        phase_value (int): その局面の駒得による評価値

    """

    koma_value = {"先手の歩":   75,
                  "先手の香":  175,
                  "先手の桂":  275,
                  "先手の銀":  475,
                  "先手の角":  775,
                  "先手の飛":  875,
                  "先手の金":  575,
                  "先手のと":  575,
                  "先手の杏":  575,
                  "先手の圭":  575,
                  "先手の全":  575,
                  "先手の馬": 1175,
                  "先手の竜": 1275,
                  "先手持ち駒の歩": 125,
                  "先手持ち駒の香": 225,
                  "先手持ち駒の桂": 325,
                  "先手持ち駒の銀": 525,
                  "先手持ち駒の角": 825,
                  "先手持ち駒の飛": 925,
                  "先手持ち駒の金": 625,
                  "後手の歩": -75,
                  "後手の香": -175,
                  "後手の桂": -275,
                  "後手の銀": -475,
                  "後手の角": -775,
                  "後手の飛": -875,
                  "後手の金": -575,
                  "後手のと": -575,
                  "後手の杏": -575,
                  "後手の圭": -575,
                  "後手の全": -575,
                  "後手の馬": -1175,
                  "後手の竜": -1275,
                  "後手持ち駒の歩": -125,
                  "後手持ち駒の香": -225,
                  "後手持ち駒の桂": -325,
                  "後手持ち駒の銀": -525,
                  "後手持ち駒の角": -825,
                  "後手持ち駒の飛": -925,
                  "後手持ち駒の金": -625}

    phase_value = 0  # 出力する評価値を初期化
    for key in koma_cnt.keys():
        #print(key, value)

        # 駒の種類 * その駒の数
        phase_value += koma_value[key] * koma_cnt[key]
        #print(phase_value, key, koma_cnt[key])

    return phase_value


def order_list(move_lst):
    """
    cshogiから返ってきた合法手のリストをオーダリングする。

    今のところ、とりあえずランダム。
    これで、アルファベータカットしても
    事前にシャッフルしているので、ランダムで指す。

    Args:
        move_lst(int list): 合法手のリスト

    Returns:
        int list: オーダリング(並べ替えた)した合法手のリスト

    """

    random.shuffle(move_lst)

    return move_lst


while True:
    input_cmd = input()

    # USIプロトコル対応の将棋GUI(将棋所)に
    # エンジン登録するときに通信する処理。
    #
    # 普通にprintを使うと、改行はしてくれるので
    # 将棋エンジンを作る場合、問題なし。
    # バッファリングさせない(フラッシュさせる)ために
    # printの引数に「flush=True」を使う。
    if input_cmd == "usi":
        # ソフト名
        print("id name 16-168ui", flush=True)
        # 開発者名
        print("id author R.Sueyoshi", flush=True)
        print("usiok", flush=True)

    # 対局準備
    if input_cmd == "isready":
        # 設定の読み込みが必要ならここで処理する。
        print("readyok", flush=True)

    # 対局開始の合図
    if input_cmd == "usinewgame":
        pass

    # 局面の受け取り
    # 平手はstartpos
    # 例)
    # position startpos moves 7g7f 3c3d 2g2f
    # 最初の8文字を使う。
    if input_cmd[0:8] == "position" or input_cmd[0:1] == "a" or input_cmd[0:1] == "w":
        cmd_lst = list(input_cmd.split(" "))  # スペース区切りのリスト。
        if len(cmd_lst) == 2 or len(cmd_lst) % 2 == 1:
            my_turn = "先手"
        else:
            my_turn = "後手"

        # 局面にセットする。
        # cmd_lst[-1:][0]は最後の文字列(相手の手等)
        if cmd_lst[0] == "a":  # 裏コマンド、平手をセットする。
            board = cshogi.Board()

        elif cmd_lst[0] == "w":  # 裏コマンドその2。指し手生成祭(後手)
            # 「指し手生成祭」の局面
            b_phase = "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1"
            b_cmd_lst = list(b_phase.split(" "))  # スペース区切りのリスト。
            board = cshogi.Board(b_phase)  # 指定局面のセット
            # 手番の確認
            if b_cmd_lst[1] == "b":
                my_turn = "先手"
            else:
                my_turn = "後手"

        elif cmd_lst[1] == "startpos":  # 平手
            board = cshogi.Board()
            # 送られてきた局面まで局面をセットしなおす。
            if len(cmd_lst) > 2:
                for i in range(3, len(cmd_lst)):
                    board.push_usi(cmd_lst[i])  # 指す
            # print(board)

        elif cmd_lst[1] == "sfen":  # 指定局面
            sfen_str = (cmd_lst[2] + " " + cmd_lst[3] +
                        " " + cmd_lst[4] + " " + cmd_lst[5])
            if sfen_str[1] == "b":
                my_turn = "先手"
            else:
                my_turn = "後手"
            board = cshogi.Board(sfen_str)
            # 送られてきた局面まで局面をセットしなおす。
            if len(cmd_lst) > 6:
                for i in range(7, len(cmd_lst)):
                    board.push_usi(cmd_lst[i])  # 指す
            # print(board)

        else:
            # 想定外の時はエンジンを終了する。
            break

    # 思考開始の合図
    # 最初の3文字を使う。
    if input_cmd[0:2] == "go":
        # 自分の次の手を考えさせる。
        # board.legal_movesを使うと
        # 合法手がリストで戻り値として返ってくる。
        move_lst = list(board.legal_moves)  # 合法手をリスト化する
        move_lst = order_list(move_lst)  # 合法手をオーダリングする

        # 王手をかけられたとき
        # if board.is_check() == True:
        #   print("bestmove resign", flush=True)
        # 詰みのとき(負け)
        if len(move_lst) == 0:
            print("bestmove resign", flush=True)
        else:
            # 次の一手を将棋所等の将棋GUIに伝える。
            # (廃止→)返事の最初に並んでいる手を指す。
            koma_cnt_dict = koma_count(board)  # 先手、後手の駒の数を数える。
            #print(type(koma_cnt_dict), koma_cnt_dict)
            evaluation_value = komadoku_evaluate(koma_cnt_dict)  # 駒得の評価値を計算する。

            # 後手の時は評価値を反転させる必要がある
            if my_turn == "後手":
                evaluation_value = -evaluation_value

            print(
                f"info pv {cshogi.move_to_usi(move_lst[0])} score cp {evaluation_value}", flush=True)

            print("bestmove", cshogi.move_to_usi(move_lst[0]), flush=True)
            # 将棋エンジン内の局面を変更する。
            move = board.push(move_lst[0])  # 指す
            # print(board)

    # 1ゲームが終わった場合
    if input_cmd[:8] == "gameover":
        # コマンド受付を終了する。
        break

    # エンジン停止の合図
    if input_cmd == "quit":
        # コマンド受付を終了する。
        break

# エンジンを終了させる。
quit()

cshogiにある駒の数を取り出す機能版(処理速度を速くした版)

以下は、上記のソースコードに対して、48さんからの助言で
正規表現ではなくcshogiにある駒の数を取り出す機能を
使ったものに変えたものです。
★koma_count関数のところだけ変更しました。

たぶん、こっちの方が処理速度が速いと思うので
今後はこちらを使う予定です。

main.py

import cshogi
import random


def koma_count(board):
    """
    局面の先手、後手の各駒の数を数えてdictで返す。(王を除く)

    Args:
        board (cshogi.Board): cshogiでの局面

    Returns:
        koma_cnt (dict): 各駒とその数
        例:
        {'先手の歩': 7, '先手の香': 2, '先手の桂': 1, '先手の銀': 3,
         '先手の角': 0, '先手の飛': 1, '先手の金': 1, '先手のと': 1,
         '先手の杏': 0, '先手の圭': 0, '先手の全': 0, '先手の馬': 0,
         '先手の竜': 0, '先手持ち駒の歩': 0, '先手持ち駒の香': 0,
         '先手持ち駒の桂': 0, '先手持ち駒の銀': 0, '先手持ち駒の角': 0,
         '先手持ち駒の飛': 1, '先手持ち駒の金': 1, '後手の歩': 5,
         '後手の香': 2, '後手の桂': 2, '後手の銀': 0, '後手の角': 2,
         '後手の飛': 0, '後手の金': 1, '後手のと': 0, '後手の杏': 0,
         '後手の圭': 0, '後手の全': 0, '後手の馬': 0, '後手の竜': 0,
         '後手持ち駒の歩': 5, '後手持ち駒の香': 0, '後手持ち駒の桂': 1,
         '後手持ち駒の銀': 1, '後手持ち駒の角': 0, '後手持ち駒の飛': 0,
         '後手持ち駒の金': 1} 

    """

    # position.cppより
    koma_cnt = {"先手の歩": 0}  # 初期化
    # 先手
    koma_cnt["先手の歩"] = board.pieces.count(1)
    koma_cnt["先手の香"] = board.pieces.count(2)
    koma_cnt["先手の桂"] = board.pieces.count(3)
    koma_cnt["先手の銀"] = board.pieces.count(4)
    koma_cnt["先手の角"] = board.pieces.count(5)
    koma_cnt["先手の飛"] = board.pieces.count(6)
    koma_cnt["先手の金"] = board.pieces.count(7)
    koma_cnt["先手のと"] = board.pieces.count(9)
    koma_cnt["先手の杏"] = board.pieces.count(10)
    koma_cnt["先手の圭"] = board.pieces.count(11)
    koma_cnt["先手の全"] = board.pieces.count(12)
    koma_cnt["先手の馬"] = board.pieces.count(13)
    koma_cnt["先手の竜"] = board.pieces.count(14)

    # 先手の持ち駒
    koma_cnt["先手持ち駒の歩"] = board.pieces_in_hand[0][0]
    koma_cnt["先手持ち駒の香"] = board.pieces_in_hand[0][1]
    koma_cnt["先手持ち駒の桂"] = board.pieces_in_hand[0][2]
    koma_cnt["先手持ち駒の銀"] = board.pieces_in_hand[0][3]
    koma_cnt["先手持ち駒の角"] = board.pieces_in_hand[0][5]
    koma_cnt["先手持ち駒の飛"] = board.pieces_in_hand[0][6]
    koma_cnt["先手持ち駒の金"] = board.pieces_in_hand[0][4]

    # 後手
    koma_cnt["後手の歩"] = board.pieces.count(17)
    koma_cnt["後手の香"] = board.pieces.count(18)
    koma_cnt["後手の桂"] = board.pieces.count(19)
    koma_cnt["後手の銀"] = board.pieces.count(20)
    koma_cnt["後手の角"] = board.pieces.count(21)
    koma_cnt["後手の飛"] = board.pieces.count(22)
    koma_cnt["後手の金"] = board.pieces.count(23)
    koma_cnt["後手のと"] = board.pieces.count(25)
    koma_cnt["後手の杏"] = board.pieces.count(26)
    koma_cnt["後手の圭"] = board.pieces.count(27)
    koma_cnt["後手の全"] = board.pieces.count(28)
    koma_cnt["後手の馬"] = board.pieces.count(29)
    koma_cnt["後手の竜"] = board.pieces.count(30)

    # 後手の持ち駒
    koma_cnt["後手持ち駒の歩"] = board.pieces_in_hand[1][0]
    koma_cnt["後手持ち駒の香"] = board.pieces_in_hand[1][1]
    koma_cnt["後手持ち駒の桂"] = board.pieces_in_hand[1][2]
    koma_cnt["後手持ち駒の銀"] = board.pieces_in_hand[1][3]
    koma_cnt["後手持ち駒の角"] = board.pieces_in_hand[1][5]
    koma_cnt["後手持ち駒の飛"] = board.pieces_in_hand[1][6]
    koma_cnt["後手持ち駒の金"] = board.pieces_in_hand[1][4]

    return koma_cnt


def komadoku_evaluate(koma_cnt):
    """
    その局面の駒得による評価値を計算する。

    Args:
        koma_cnt(dict): 各駒とその数

    Returns:
        phase_value (int): その局面の駒得による評価値

    """

    koma_value = {"先手の歩":   75,
                  "先手の香":  175,
                  "先手の桂":  275,
                  "先手の銀":  475,
                  "先手の角":  775,
                  "先手の飛":  875,
                  "先手の金":  575,
                  "先手のと":  575,
                  "先手の杏":  575,
                  "先手の圭":  575,
                  "先手の全":  575,
                  "先手の馬": 1175,
                  "先手の竜": 1275,
                  "先手持ち駒の歩": 125,
                  "先手持ち駒の香": 225,
                  "先手持ち駒の桂": 325,
                  "先手持ち駒の銀": 525,
                  "先手持ち駒の角": 825,
                  "先手持ち駒の飛": 925,
                  "先手持ち駒の金": 625,
                  "後手の歩": -75,
                  "後手の香": -175,
                  "後手の桂": -275,
                  "後手の銀": -475,
                  "後手の角": -775,
                  "後手の飛": -875,
                  "後手の金": -575,
                  "後手のと": -575,
                  "後手の杏": -575,
                  "後手の圭": -575,
                  "後手の全": -575,
                  "後手の馬": -1175,
                  "後手の竜": -1275,
                  "後手持ち駒の歩": -125,
                  "後手持ち駒の香": -225,
                  "後手持ち駒の桂": -325,
                  "後手持ち駒の銀": -525,
                  "後手持ち駒の角": -825,
                  "後手持ち駒の飛": -925,
                  "後手持ち駒の金": -625}

    phase_value = 0  # 出力する評価値を初期化
    for key in koma_cnt.keys():
        #print(key, value)

        # 駒の種類 * その駒の数
        phase_value += koma_value[key] * koma_cnt[key]
        #print(phase_value, key, koma_cnt[key])

    return phase_value


def order_list(move_lst):
    """
    cshogiから返ってきた合法手のリストをオーダリングする。

    今のところ、とりあえずランダム。
    これで、アルファベータカットしても
    事前にシャッフルしているので、ランダムで指す。

    Args:
        move_lst(int list): 合法手のリスト

    Returns:
        int list: オーダリング(並べ替えた)した合法手のリスト

    """

    random.shuffle(move_lst)

    return move_lst


while True:
    input_cmd = input()

    # USIプロトコル対応の将棋GUI(将棋所)に
    # エンジン登録するときに通信する処理。
    #
    # 普通にprintを使うと、改行はしてくれるので
    # 将棋エンジンを作る場合、問題なし。
    # バッファリングさせない(フラッシュさせる)ために
    # printの引数に「flush=True」を使う。
    if input_cmd == "usi":
        # ソフト名
        print("id name 16-168ui", flush=True)
        # 開発者名
        print("id author R.Sueyoshi", flush=True)
        print("usiok", flush=True)

    # 対局準備
    if input_cmd == "isready":
        # 設定の読み込みが必要ならここで処理する。
        print("readyok", flush=True)

    # 対局開始の合図
    if input_cmd == "usinewgame":
        pass

    # 局面の受け取り
    # 平手はstartpos
    # 例)
    # position startpos moves 7g7f 3c3d 2g2f
    # 最初の8文字を使う。
    if input_cmd[0:8] == "position" or input_cmd[0:1] == "a" or input_cmd[0:1] == "w":
        cmd_lst = list(input_cmd.split(" "))  # スペース区切りのリスト。
        if len(cmd_lst) == 2 or len(cmd_lst) % 2 == 1:
            my_turn = "先手"
        else:
            my_turn = "後手"

        # 局面にセットする。
        # cmd_lst[-1:][0]は最後の文字列(相手の手等)
        if cmd_lst[0] == "a":  # 裏コマンド、平手をセットする。
            board = cshogi.Board()

        elif cmd_lst[0] == "w":  # 裏コマンドその2。指し手生成祭(後手)
            # 「指し手生成祭」の局面
            b_phase = "l6nl/5+P1gk/2np1S3/p1p4Pp/3P2Sp1/1PPb2P1P/P5GS1/R8/LN4bKL w GR5pnsg 1"
            b_cmd_lst = list(b_phase.split(" "))  # スペース区切りのリスト。
            board = cshogi.Board(b_phase)  # 指定局面のセット
            # 手番の確認
            if b_cmd_lst[1] == "b":
                my_turn = "先手"
            else:
                my_turn = "後手"

        elif cmd_lst[1] == "startpos":  # 平手
            board = cshogi.Board()
            # 送られてきた局面まで局面をセットしなおす。
            if len(cmd_lst) > 2:
                for i in range(3, len(cmd_lst)):
                    board.push_usi(cmd_lst[i])  # 指す
            # print(board)

        elif cmd_lst[1] == "sfen":  # 指定局面
            sfen_str = (cmd_lst[2] + " " + cmd_lst[3] +
                        " " + cmd_lst[4] + " " + cmd_lst[5])
            if sfen_str[1] == "b":
                my_turn = "先手"
            else:
                my_turn = "後手"
            board = cshogi.Board(sfen_str)
            # 送られてきた局面まで局面をセットしなおす。
            if len(cmd_lst) > 6:
                for i in range(7, len(cmd_lst)):
                    board.push_usi(cmd_lst[i])  # 指す
            # print(board)

        else:
            # 想定外の時はエンジンを終了する。
            break

    # 思考開始の合図
    # 最初の3文字を使う。
    if input_cmd[0:2] == "go":
        # 自分の次の手を考えさせる。
        # board.legal_movesを使うと
        # 合法手がリストで戻り値として返ってくる。
        move_lst = list(board.legal_moves)  # 合法手をリスト化する
        move_lst = order_list(move_lst)  # 合法手をオーダリングする

        # 王手をかけられたとき
        # if board.is_check() == True:
        #   print("bestmove resign", flush=True)
        # 詰みのとき(負け)
        if len(move_lst) == 0:
            print("bestmove resign", flush=True)
        else:
            # 次の一手を将棋所等の将棋GUIに伝える。
            # (廃止→)返事の最初に並んでいる手を指す。
            koma_cnt_dict = koma_count(board)  # 先手、後手の駒の数を数える。
            #print(type(koma_cnt_dict), koma_cnt_dict)
            evaluation_value = komadoku_evaluate(koma_cnt_dict)  # 駒得の評価値を計算する。

            # 後手の時は評価値を反転させる必要がある
            if my_turn == "後手":
                evaluation_value = -evaluation_value

            print(
                f"info pv {cshogi.move_to_usi(move_lst[0])} score cp {evaluation_value}", flush=True)

            print("bestmove", cshogi.move_to_usi(move_lst[0]), flush=True)
            # 将棋エンジン内の局面を変更する。
            move = board.push(move_lst[0])  # 指す
            # print(board)

    # 1ゲームが終わった場合
    if input_cmd[:8] == "gameover":
        # コマンド受付を終了する。
        break

    # エンジン停止の合図
    if input_cmd == "quit":
        # コマンド受付を終了する。
        break

# エンジンを終了させる。
quit()