マガジン

コンピューターはどうやって「良い手」を決めるのか:評価の点数づけ

遊びをつくる

執筆にClaudeを使用しています。

分かれ道の前後に駒と橋を置き、サイコロの運とプレイヤーの選択を表したイラスト。
記事のテーマをもとに制作したAI生成イラスト。

オセロで「ここが良い手です」とコンピューターに示されたとき、その手はどういう基準で選ばれているのでしょうか。画面の向こうで起きているのは、盤面を見て直感で決めることではありません。候補の手を一つずつ打ったと仮定し、できあがる盤面に数字を付けて、数字がいちばん良いものを選ぶという作業です。この「盤面に点をつける」部分を評価関数と呼びます。 この記事では、オセロを例に評価関数の考え方を分解します。石の数、角、置ける手の数といった材料がどう点数になるのか、そして同じ点数が並んだときに何が起きるのかを見ていきます。最後に、短いJavaScriptで自分の評価関数を書き、遊びながら答え合わせをする手順を示します。 なお、アソバースの公式作品「潮目のオセロ」の内部でどんな評価をしているかは、確認できている範囲では分かりません。そのため、ここで書く評価の例は一般的なオセロの考え方であり、公式作品の実装ではありません。

この記事の目次
  1. 評価とは「盤面に一つの数字を付けること」
  2. 点の材料は三つ:石の数・角・置ける手の数
  3. 初手の4つはなぜ同点になるのか
  4. 最小の評価関数を書いて確かめる
  5. 自分の点づけを「理由付きヒント」と照らし合わせる
  6. 設計から読み取れること
  7. 次の行動

評価とは「盤面に一つの数字を付けること」

評価関数の仕事は単純です。盤面を受け取り、一つの数字を返します。黒に有利なら大きな正の数、白に有利なら負の数、互角なら0付近、と約束しておきます。

コンピューターが手を選ぶ流れは次の三段階です。

  1. 置ける手をすべて挙げる。
  2. それぞれの手を打った後の盤面を作る。
  3. 各盤面に評価関数で点を付け、黒番なら最大、白番なら最小の手を選ぶ。

ここで大事なのは、点を付ける対象が「手」ではなく「手を打った後の盤面」であることです。同じ場所に置く手でも、盤面が違えば価値が変わります。点づけの設計とは、「良い盤面とは何か」を数字で言い表す作業です。

もう一歩先を読む場合は、相手の返事も考えます。自分が打ち、相手が最も自分に不利な手を返すと仮定して、その後の盤面に点を付けます。相手が最善で返しても点が高く残る手を選ぶ、という発想です。この考え方はミニマックス法と呼ばれます。読む深さが増えても、最後に点を付ける評価関数の役割は変わりません。

点の材料は三つ:石の数・角・置ける手の数

オセロの評価によく使われる材料は、おおむね三つに整理できます。

石の数の差

最も素朴な材料です。黒の石数から白の石数を引いた値を点にします。終盤、盤面が埋まってくると、勝敗そのものが石数で決まるので、この材料の重みは大きくなります。

一方、序盤から石数だけで評価すると、たくさん返す手ばかり選んでしまいます。一般に、序盤は石を増やしすぎると相手の選択肢を広げやすいと言われています。石数は終盤ほど頼れる材料、と覚えておくと整理しやすくなります。

位置の価値(角と、その隣)

角は一度取ると相手に返されません。そのため角の価値は高く付けます。反対に、角のすぐ斜め隣(X打ちと呼ばれる位置)は、置くと相手に角を取られやすくなるので、低い点や負の点を付けるのが定番です。

実装では、64マスそれぞれに重みを書いた表を用意し、「黒の石がある場所の重みの合計 − 白の石がある場所の重みの合計」を点にします。

置ける手の数(モビリティ)

自分が置ける場所が多く、相手が置ける場所が少ない盤面は、選択肢の多い側が有利と考えられます。打った後の盤面で相手の合法手を数え、少ないほど良い、と点に反映させます。

この三つは単独でも使えますが、実際には「序盤は位置とモビリティ、終盤は石数」のように、局面に応じて混ぜ方を変えることが多いです。どの材料をどの割合で混ぜるかが、評価関数の個性になります。

初手の4つはなぜ同点になるのか

評価関数の面白さが分かりやすく出る場面があります。オセロの最初の一手です。

初期配置は、d4と e5が白、d5と e4が黒です。黒番の合法手は d3、c4、f5、e6 の4つで、どれも石を1枚返します。盤面を90度回したり折り返したりすると、この4つは互いに重なります。つまり、盤の対称性によって、4つの手は本質的に同じ局面を作ります。

ここから次のことが言えます。

  • 左右対称・上下対称な重み表を使う評価関数は、この4手に必ず同じ点を付けます。
  • 置ける手の数のような材料も、対称な局面では同じ値になります。
  • したがって、評価関数がどれほど凝っていても、初手の4つの間には差が付きません。

では同点のとき、コンピューターはどうやって一つに決めるのでしょうか。一般的な実装は、最初に見つかった手を選ぶか、同点の中から無作為に選ぶかのどちらかです。「潮目のオセロ」がどちらかは確認できていないため、この部分は作品を開いて確かめる項目です。同じ初手を繰り返し打たれるか、毎回違う手が選ばれるかが手がかりになります。

評価の点が同じでも、その後の展開が同じとは限りません。点づけは「良い手」を決める道具であって、「手を一つに絞る」道具ではない、という点は覚えておく価値があります。

最小の評価関数を書いて確かめる

ここからは手を動かします。次のコードは、位置の重み表で盤面に点を付け、黒の初手4つについて「評価点」と「打った後に白が置ける手の数」を表示します。外部ライブラリは不要で、ブラウザのコンソールで動きます。重み表の数値は例示用に決めたもので、公式の値ではありません。

html
<script>
const q=[[100,-20,10,5],[-20,-50,-2,-2],[10,-2,1,1],[5,-2,1,0]];
const w=(x,y)=>q[Math.min(y,7-y)][Math.min(x,7-x)];
const D=[[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]];
const on=(x,y)=>x>=0&&x<8&&y>=0&&y<8;
function flips(b,x,y,p){
  if(b[y][x]!==0)return[];
  const out=[];
  for(const[dx,dy]of D){
    const line=[];let cx=x+dx,cy=y+dy;
    while(on(cx,cy)&&b[cy][cx]===-p){line.push([cx,cy]);cx+=dx;cy+=dy;}
    if(line.length&&on(cx,cy)&&b[cy][cx]===p)out.push(...line);
  }
  return out;
}
function play(b,x,y,p){
  const c=b.map(r=>r.slice());
  for(const[a,d]of flips(b,x,y,p))c[d][a]=p;
  c[y][x]=p;return c;
}
function moves(b,p){
  const m=[];
  for(let y=0;y<8;y++)for(let x=0;x<8;x++)if(flips(b,x,y,p).length)m.push([x,y]);
  return m;
}
function score(b){
  let s=0;
  for(let y=0;y<8;y++)for(let x=0;x<8;x++)s+=w(x,y)*b[y][x];
  return s;
}
const b0=Array.from({length:8},()=>Array(8).fill(0));
b0[3][3]=b0[4][4]=-1;b0[3][4]=b0[4][3]=1;
for(const[x,y]of moves(b0,1)){
  const b1=play(b0,x,y,1);
  console.log("abcdefgh"[x]+(y+1),score(b1),moves(b1,-1).length);
}
</script>

黒を1、白を−1、空きを0として、b[y][x]で盤面を表しています。wは左上の4分の1だけを書いた表を折り返して64マス分に広げる関数なので、重み表は自動的に左右上下対称になります。

実行したら、次の点を確かめてください。

  • 4行の出力で、評価点と白の合法手の数がそろっているか。対称性の話どおりなら、4行は同じ値になるはずです。
  • 中央の4マスの重みは0に設定してあります。返る石が中央にあるだけの局面では、評価点に石の効果が出ません。この設定が点にどう影響するか、q[3]の値を書き換えて見比べてください。
  • 重み表の-50(X打ちの位置)を50に変えると、何が変わるか。初手では差が出ませんが、次の項で述べる確かめ方に進むと違いが見えてきます。

自分の点づけを「理由付きヒント」と照らし合わせる

次は、コードの評価を実際の対局と比べる手順です。「潮目のオセロ」のひとり対局には、返る石のプレビューと、理由が付いたヒントがあります。ヒントがどんな理由を出すかは確認できている範囲では分からないため、作品を開いて読んでみる必要があります。

手順は次のとおりです。

  1. ひとり対局を始め、序盤から中盤の局面を10か所ほど選びます。1局面につき1回、ヒントを出す前に、自分が良いと思う手を決めて紙に書き留めます。
  2. 次に、先ほどのコードの評価で、その局面の候補手に点を付けて並べてみます(盤面を配列に写す手間がかかるので、ひとまず候補を2〜3手に絞って構いません)。
  3. ヒントを見て、勧められた手と理由を記録します。「自分の勘」「コードの評価」「ヒント」の三つが一致したか、表にまとめます。

結果の読み方の目安は次のとおりです。

  • 三つが一致する局面が多いなら、重み表と素朴な評価でも、その局面では十分に説明できる、という意味です。
  • コードの評価だけが食い違うなら、使っていない材料があるかもしれません。置ける手の数を足して再度比べてください。
  • ヒントの理由が角やモビリティに触れているのに、自分のコードがその材料を持っていない場合は、材料を足す手がかりになります。

起こりやすい誤りもあります。

  • 黒と白の視点を取り違えて、符号が逆になること。白番の局面では「最小を選ぶ」ことを忘れがちです。
  • 10局面で傾向を決めつけること。局面の種類(序盤、角の近く、終盤)を分けて集計してください。
  • 終盤の局面を混ぜること。終盤は石数が勝敗に直結するため、位置の重み表とは別物として扱います。

設計から読み取れること

確認できている「潮目のオセロ」の仕様から、点づけの考え方と結びつく部分を整理します。

  • 3段階の相手:強さが三つに分かれていることは分かります。差が読みの深さにあるのか、評価の材料にあるのか、あるいは選択のばらつきにあるのかは、この範囲では分かりません。三段階それぞれと数局対戦し、角の取り方や序盤の打ち方に違いがあるか観察すると、手がかりになります。
  • 返る石のプレビュー:打つ前に結果の盤面を見せる機能です。「手を打った後の盤面を見て判断する」という評価の考え方を、プレイヤーが自分で体験できる作りと言えます。
  • 理由付きヒント:手だけでなく理由も添える設計は、点づけの中身を言葉に直して見せる試みと読めます。どんな理由が出るかは作品で確かめてください。
  • 一手戻す(ひとり対局のみ):同じ局面から別の手を試せます。評価の食い違いを確かめるとき、同じ局面で複数の手を比べる道具として使えます。

自作のオセロやボードゲームを作る人にとっては、「プレビュー・ヒント・戻す」の三点がそろうと、評価関数の良し悪しを自分で検証しやすくなる、という示唆が得られます。

次の行動

まず上のコードを動かし、初手4つが同じ点になることを自分の目で確かめてください。次に、重み表の数値を一つだけ変えて、変化を観察します。そのうえで「潮目のオセロ」のひとり対局で10局面のヒントを記録すれば、自分の点づけの癖が見えてきます。自作のオセロを作っているなら、評価関数を差し替えられる形にしておくと、強さの調整が楽になります。