オセロをMinMax法を導入する方法(JavaScript編)

JavaScriptでオセロを作っていて、そのオセロのCOMにMinMax法を導入したので方法を紹介。

ただし、コードは結構汚いです。

MinMax法とは?

MinMax法とは、オセロや将棋等のゲームで、コンピュータがどのように考えて手を指すかと言う思考方法( アルゴリズム)の1つ。

上記の画像から分かるように、オセロには様々な手順の可能性が考えられるが、それぞれの局面に評価を付けていき、どの手順が最善かを調べるのがMinMax法となる。

具体的には、「自分(COM)の手を考えるときは最も評価が高い手を選び、相手の手を考えるときは最も評価が低い、つまりCOM側が指されて一番イヤな手を選ぶ」と言うのがMinMax法だと言える。

実際に書いたコード

実際に書いたコードは、以下の通り。

function minMaxCom(comColor, comBoard, putPos, thinkDepth, isPass, isComTurn, isRootFunc) {
    let comCurrentBoard = [];
    comCurrentBoard = comBoard.slice().map(function (row) { return row.slice(); });
    if (putPos) {
        putStone(-comColor, comCurrentBoard, putPos[0], putPos[1]);
    }
    let value,
        max = -64,
        min = 64,
        can_move = false,
        select_pos = [];
    if (thinkDepth === 0) {
        return countComStone(comCurrentBoard);
    }
    for (let x = 1; x <= BOARD_WIDTH; x++) {
        for (let y = 1; y <= BOARD_WIDTH; y++) {
            if (isValidPutStone(comColor, comCurrentBoard, x, y)) {
                can_move = true;
                value = minMaxCom(-comColor, comCurrentBoard, [x, y], thinkDepth - 1, false, !isComTurn, false);
                if (isComTurn) {
                    if (value > max) {
                        max = value;
                        select_pos = [x, y];
                    }
                } else {
                    if (value < min) {
                        min = value;
                        select_pos = [x, y];
                    }
                }
            }
        }
    }
    if (!can_move) {
        if (isPass) {
            return countComStone(comCurrentBoard);
        } else {
            if (isComTurn) {
                max = minMaxCom(-comColor, comCurrentBoard, false, thinkDepth - 1, true, !isComTurn, false);
            } else {
                min = minMaxCom(-comColor, comCurrentBoard, false, thinkDepth - 1, true, !isComTurn, false);
            }
        }
    }
    if (isRootFunc) {
        return select_pos;
    }
    if (isComTurn) {
        return max;
    } else {
        return min;
    }
}

凄く汚いコードだが、要は「** 再帰関数を使ってどんどん手を深く読んでいき、最終的に石の多さを評価として返している**」ようにしている。

ちなみに、 リバーシプログラムの作り方](http://www.es-cube.net/es-cube/reversi/sample/html/2_4.html)のページでは、以下のコードが[C言語で書かれていた。

static int Com_EndSearch(Com *self, int in_depth, int in_color, int in_opponent,
                         int in_pass, int *out_move) {
    int x, y;
    int value, max = -MAX_VALUE;
    int can_move = 0;
    int move;
    if(in_depth == 0) {
        self->Node++;
        return Board_CountDisks(self->Board, in_color) -
               Board_CountDisks(self->Board, in_opponent);
    }
    *out_move = NOMOVE;
    for(x = 0; x < BOARD_SIZE; x++) {
        for(y = 0; y < BOARD_SIZE; y++) {
            if(Board_Flip(self->Board, in_color, Board_Pos(x, y))) {
                if(!can_move) {
                    *out_move = Board_Pos(x, y);
                    can_move = 1;
                }
                value = -Com_MidSearch(self, in_depth - 1, in_opponent,
                                       in_color, 0, &move);
                Board_Unflip(self->Board);
                if(value > max) {
                    max = value;
                    *out_move = Board_Pos(x, y);
                }
            }
        }
    }
    if(!can_move) {
        if(in_pass) {
            *out_move = NOMOVE;
            self->Node++;
            max = Board_CountDisks(self->Board, in_color) -
                  Board_CountDisks(self->Board, in_opponent);
        } else {
            *out_move = PASS;
            max =
                -Com_MidSearch(self, in_depth, in_opponent, in_color, 1, &move);
        }
    }
    return max;
}

上記のコードは厳密にはNegaMax法と言うMinMax法の兄弟のような方法で書いているが、 JavaScriptで書く場合もこれぐらいシンプルに持っていきたい。