リバーシを作る第二回

前回のプログラムでは、コンピュータは自分の手番になると何も考えず「打てる所にランダムに」打ってきました。今回は、盤面を見てどれだけ有利か「評価」する処理を追加し、コンピュータが「どこに打てば有利になるか」を考える思考処理を加えてみましょう。
これで多少はゲームらしくなってくるはずです。

盤面の評価

リバーシは、最終的に「自分のコマの数」で勝敗が決まるゲームです。ですから、有利な状況とはまず「自分のコマが多い事」ですね。ただし、「相手の駒を挟むとひっくり返して自分の駒にできる」というルールなので「端にコマがある方が有利」「特に四隅にあると圧倒的に有利」になります。この状況を評価するために、盤面にあるコマに「点数」を付けその点数の合計で状況を評価しましょう。点数は、「四隅のコマは64」「上下左右の端1列(四隅は除く)は4」「それ以外は1」とします。そして、盤面のマス目を一つずつ見ていき自分のコマがあればその点数を加え、相手のコマがあれば引く。こうして求められた合計点で盤面の状況を評価する事にしましょう。
今回は、この判定法を関数にまとめその返り値で状況を判断するようにしました。状況の評価を行う関数hyouka()は盤面データをBYTE[8][8]の配列として、どちらの側の判定を行うかを1/2の数値として受け取ります。盤面データは、1が人間、2がコンピュータのコマになっているので、相手のコマはこれを逆にすれば良いですね。
実際の関数は以下のような感じになります。

  int hyouka(BYTE byA,BYTE ban[][8]) {

      int i,j,sc;
      BYTE byB;

      if (byA==1)
          byB=2;
      else
          byB=1;

      sc=0;

      for (i=1;i<7;i++) /* 端以外の部分の判定 */
          for (j=0;j<7;j++)
              if (ban[j][i]==byA)
                  sc++;
              else if (ban[j][i]==byB)
                  sc--;

      for (i=1;i<7;i++) { /* 端1列の判定 */

          if (ban[i][0]==byA)
              sc+=4;
          else if (ban[i][0]==byB)
              sc-=4;

          if (ban[i][7]==byA)
              sc+=4;
          else if (ban[i][7]==byB)
              sc-=4;

          if (ban[0][i]==byA)
              sc+=4;
          else if (ban[0][i]==byB)
              sc-=4;

          if (ban[7][i]==byA)
              sc+=4;
          else if (ban[7][i]==byB)
              sc-=4;

      }

      /* 四隅の判定 */
      if (ban[0][0]==byA)
          sc+=64;
      else if (ban[0][0]==byB)
          sc-=64;

      if (ban[7][0]==byA)
          sc+=64;
      else if (ban[7][0]==byB)
          sc-=64;

      if (ban[7][7]==byA)
          sc+=64;
      else if (ban[7][7]==byB)
          sc-=64;

      if (ban[0][7]==byA)
          sc+=64;
      else if (ban[0][7]==byB)
          sc-=64;

      return sc;

  }

コンピュータの思考処理

では、この評価関数を使ってコンピュータの手を決める思考処理を考えてみます。前回は、まず可能な手を列挙してその中から乱数で選んでいましたが、今回は可能な手をひとつずつ評価して評価が最大の手をさすようにしましょう。そのためには、手に一つずつ点数を付ける必要があるので手のデータ型としてコマの位置と点数を保持する構造体CSCOREを定義します。

  typedef struct {
      int x,y,sc;
  } CSCORE;

そして、関数te(2)を呼び出してBYTE[8][8]型配列byTemoに可能な手を記録したら、その手をCSCORE型配列scに記録します。ここまでは前回と同様ですね。今回追加するのは、その手に対する評価をしてそれをメンバ変数scに記録する処理です。

  for (i=0;i<8;i++)
      for (j=0;j<8;j++)
          if (byTemp[j][i]==1) {

              CopyMemory(byScore,byTemp,64);
              byScore[j][i]=2;

              sc[n].x=j;
              sc[n].y=i;
              sc[n].sc=hyouka(2,byScore);

              n++;

          }

可能な手を列挙して手数とその手に対する評価も含めた手の情報が得られたら、最大の評価を得た手の点数を取得します。

  max=sc[0].sc;

  for (i=0;i<n;i++)
      if (sc[i].sc>max)
          max=sc[i].sc;

続いて、この「最高点」と評価された手をCSCORE型配列sc2に列挙し、この列挙された手から乱数で手を選んでput()で打つようにしましょう。

  for (i=0;i<n;i++)
      if (sc[i].sc==max) {

          cc2[nn]=sc[i];
              nn++;

      }

  nn=rand() % nn;
  put(sc2[nn].x,sc2[nn].y,2);

つまり、前回は「可能な手」の中から乱数で手を選びましたが、今回は「可能な手の中で最大の評価を得た手」の中から乱数で選ぶわけですね。この方法だと「ここに打ったらどうなるか」という事しか考えない(先の事は一切考えない)のでかなり弱いですが、端を取れる時には取ってくるようになったので前回よりはだいぶましになりました。後は、盤面評価を改善しながら先の事(ここに打ったら相手はどう打つか、そしてその結果どうなるか)を考えるようにして、定石なども入れていけばもっと強い思考処理を実現できるでしょう。

プログラム

前回同様、ゲームを開始したらマウスクリックで黒のコマを置いて行ってください。コンピュータは白を担当します。

プログラムソース表示

創作プログラミングの街>ゲーム制作研究室