ゲーム木

ゲーム木は、探索で調べる局面のつながりを木構造として表したものである。 根は現在局面、枝は指し手、ノードは各局面を表す。 コンピュータ将棋では、ミニマックス法アルファベータ法が、この構造をたどりながら最善手を探す。

概要

ゲーム木では、現在局面を根として、

  • 自分の指し手で1手先の局面へ進む
  • 相手の応手でさらに次の局面へ進む
  • これを深さぶんだけ繰り返す

という形で局面を展開する。

ただし実際の将棋AIでは、同じ局面に別の手順から到達することがある。 このような現象はトランスポジションと呼ばれ、厳密には単純な木というより有向グラフとして扱う方が実態に近い。 それでも説明上は「ゲーム木」と呼ぶことが多い。

主な構成要素

  • ルートノード 現在評価したい局面
  • 内部ノード まだ先の局面へ展開される途中のノード
  • 葉ノード 終局または探索打ち切り地点
  • 枝 ある局面から次の局面への指し手
  • 深さ ルートから何手先まで読んだか
  • 分岐数 その局面で候補となる合法手の数

将棋は分岐数が大きいため、深く読むほどノード数が急激に増える。 この爆発を抑えるために、手の並べ替え置換表静止探索などの工夫が必要になる。

将棋AIで重要になる点

分岐数が大きい

将棋では持ち駒を打つ手もあるため、局面によっては合法手数が非常に多い。 そのため、単純な全幅探索はすぐに計算量の限界へ達する。

同一局面への合流がある

手順が違っても同じ局面になることがあり、これを再利用できると探索を大きく節約できる。 このため、実装では置換表ゾブリストハッシュがよく使われる。

反復局面を考慮する必要がある

千日手や連続王手の千日手のように、局面が繰り返されるケースもある。 そのため、探索木をそのまま木として扱うだけでは不十分で、履歴情報や反復検出が必要になる。

探索アルゴリズムとの関係

実装例

教育用には、木を明示的に持たずに再帰でたどる形が分かりやすい。

struct Node {
    Position pos;
    int depth;
};

void dfs(const Position& pos, int depth) {
    if (depth == 0 || pos.isTerminal()) {
        return;
    }

    for (Move move : generateMoves(pos)) {
        Position next = doMove(pos, move);
        dfs(next, depth - 1);
    }
}

実際の将棋AIでは、ノード全体をヒープ上に木として構築するより、 局面を進めて戻す形で深さ優先探索を行うことが多い。

注意点

  • ゲーム木は説明概念として便利だが、実装ではトランスポジションや反復のため木そのものではない
  • 将棋では分岐数が大きく、枝刈りなしでは深く読めない
  • 木の形だけでなく、どの順で枝を調べるかも性能に大きく効く

関連項目

参考にしたホームページ