リバーシを作る

 思考ルーチンの実験として、リバーシを作ってみます。とりあえず、今回は盤面を作ってコマを置けるかどうかの判定や可能な手数を数え上げる処理を試してみましょう。

盤面の処理

 リバーシの盤面は、8×8なのでBYTE型8×8の2次元配列を用意します。現在の盤面を記憶するbyBoard[8][8]と作業用のbyTemp[8][8]を用意しましょう。byBoard[8][8]内では、0が何もなし、1が黒、2が白とします。そして、この盤面データはdrawBoardBITMAPに描画します。

 盤面の各種処理では、まず「ある場所にコマを置いたらひっくり返せるか」を判定する処理が必要になります。そのためには、「ある場所からある方向に相手のコマが連続していてその先に自分のコマがあるか」という判定を行う必要がありますね。この処理は、

  1.  開始点からある方向に移動
  2.  移動先が相手のコマなら、さらに移動
  3.  相手のコマが途切れるか盤面の端まで移動を繰り返す
  4.  相手のコマが途切れた場所に自分のコマがあれば、連続していると判定

 という感じになるでしょう。ここで、いくつ相手の石が連続しているかは、何回移動したかを見ればわかりますね。判定する方向は上下左右斜め8方向なので、判定関数の引数dx, dyで方向を指定するようにすれば汎用的な関数にまとめられるはずです。関数の形は、

  int ren(int x,int y,int dx,int dy,BYTE byA) { /* コマのつながりを調べる */

 という感じで、判定する場所と方向、判定対象のコマを指定すると、指定された場所から指定された方向に指定されたコマとの間に相手のコマがいくつ連続しているかを返すようにします。関数内では、現在の調査対象の座標をxx, yyに保持し、dx, dyを加える事で指定された方向に移動していくようにしましょう。移動しながら、byBoard[xx][yy]が相手のコマ、つまりbyBであるかどうかを調べ、byBoard[xx][yy]が相手のコマでかつ盤面の端に達していない間移動を繰り返します。

  while (xx>=0 && xx<8 && yy>=0 && yy<8 && byBoard[xx][yy]==byB) {

      xx+=dx;
      yy+=dy;
      n++;

  }

 これで、移動できた回数、つまり相手のコマが連続している数がnに求まりました。もし、nが1以上であれば、指定された場所から指定された方向に相手の石が(連続的に)並んでいる、という事なので相手の石が途切れた場所に自分のコマがあるか、また盤面の端に達したかを判定します。判定の結果、自分のコマがあれば、ひっくり返せるので、ひっくり返せるコマの数としてnを返し、自分のコマがなければひっくり返せないので0を返します。

  if (xx>=0 && xx<8 && yy>=0 && yy<8 && byBoard[xx][yy]==byA)
      return n;
  else
      return 0;

 というわけで、ある場所に自分のコマを置いた場合に指定された方向のひっくり返せるコマの数を返す関数renは以下のようになります。

  int ren(int x,int y,int dx,int dy,BYTE byA) { /* コマのつながりを調べる */

      int xx,yy,n=0;
      BYTE byB;

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

      xx=x+dx;
      yy=y+dy;

      if (xx<0 || xx>7 || yy<0 || yy>7 || (dx==0 && dy==0) ||
        byBoard[x][y]!=0 || byBoard[xx][yy]!=byB)
          return 0;

      while (xx>=0 && xx<8 && yy>=0 && yy<8 && byBoard[xx][yy]==byB) {

          xx+=dx;
          yy+=dy;
          n++;

      }

      if (xx>=0 && xx<8 && yy>=0 && yy<8 && byBoard[xx][yy]==byA)
          return n;
      else
          return 0;

  }

 次に、この関数を使ってある場所にコマを置けるか判定するkanou()を作りましょう。リバーシでは、ある場所から上下左右斜め8方向に走査して一つでも相手のコマをひっくり返せる方向があれば、そこにコマを置ける事ので8方向に関数ren()を呼び出せば良いでしょう。8方向に対して呼び出すには、変数i, jを−1〜1まで変化させ、変数(j, i)の組でrenを呼び出せば良いですね。これだと、(0, 0)も含まれますが、関数ren()は、走査方向に(0, 0)を指定されても何もしないので問題はありません。

  int kanou(int x,int y,BYTE byA) { /* (x,y)にコマを置けるか判定 */

      int i,j,n=0;

      for (i=-1;i<2;i++)
          for (j=-1;j<2;j++)
              if (ren(x,y,j,i,byA)>0)
                  return 1;

      return 0;

  }

 これで、盤面にコマを置けるか判定する処理が出来たので、実際にコマを置く関数put()を作りましょう。この関数は、位置と置くコマを指定すると、コマを置いて相手のコマをひっくり返します。

  void put(int x,int y,BYTE byA) { /* コマを盤面に置く */

      int i,j,k,n,nn=0,xx,yy;

      if (x<0 || x>> || y<0 || y>7 || byBoard[x][y]!=0)
		return;

      /* 8方向のコマの繋がりを判定し、処理 */
      for (i=-1;i<2;i++)
          for (j=-1;j<2;j++) {

              xx=x+j;
              yy=y+i;
              n=ren(x,y,j,i,byA);

              for (k=0;k<n;k++) { /* コマをひっくり返す */

                  byBoard[xx][yy]=byA;

                  xx+=j;
                  yy+=i;
                  nn++;

              }

          }

      if (nn>0) /* ひっくり返せるならコマを置く */
          byBoard[x][y]=byA;

  }

コンピュータの手

 自分のコマを置く事ができるようになったら、次はコンピュータの処理です。今回は、マウスクリックでコマを配置し、その後にコンピュータが打つようにしましょう。マウスクリックへの応答という形でゲームを進めるわけですね。

 コンピュータの方は、「コマを置ける所にランダムに置く」事にします。つまり、「何も考えない」で置くわけですね。これは、kanou()で置ける所を調べてその中からランダムに置けば良いでしょう。そのためには置ける場所を調べる必要があるので関数te()を定義し、そこで置ける所に1の印をつける形でbyTempを書き換えて置ける場所の数を返します。

  int te(BYTE byA) { /* 可能な手を調べる */

      int i,j,n=0;

      for (i=0;i<8;i++)
          for (j=0;j<8;j++)
              if (kanou(j,i,byA)>0) {

                  byTemp[j][i]=1;
                  n++;

              } else
                  byTemp[j][i]=0;

      return n;

  }

 可能な手がbyTempに求まったらその情報を元にコンピュータの手を決めてコマを置くcomp()を作ります。これは、置ける場所のリストを作ってその中から乱数で手を決め打てばよいでしょう。

  void comp(BYTE byA) {

      int i,j,n=0;
      POINT pt[60];

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

                  pt[n].x=j;
                  pt[n].y=i;
                  n++;

              }

      if (n<1)
          return;

      n=rand() % n;
      put(pt[n].x,pt[n].y,2);

      drawBoard();

      InvalidateRgn(hwMain,NULL,FALSE);
      UpdateWindow (hwMain); /* 再描画 */

  }

 実際のゲームでは、自分のコマを置いた後にパス判定を行い、コンピュータが打てれば手を打っていきます。プログラムでは、マウスの左ボタンクリックでゲームを進めるようにしました。

  case WM_LBUTTONDOWN:

      x=(LOWORD(lParam)-40)/32;
      y=(HIWORD(lParam)-40)/32;

      if (x<0 || x>7 || y<0 || y>7 || kanou(x,y,1)==0)
          return 0;

      put(x,y,1);
      drawBoard();

      InvalidateRgn(hwnd,NULL,FALSE);
      UpdateWindow (hwnd); /* 再描画 */

      Sleep(100);

      if (te(2)>0)
          comp(2);

      if (te(1)==0 && te(2)==0) {

          MessageBox(hwnd,"ゲーム終了","ゲーム終了",MB_OK);
          return 0;

      }

      while (te(1)==0) {

          MessageBox(hwnd,"パス","パス",MB_OK);
          comp(2);

      }

      return 0;

プログラム

 マウスクリックで黒のコマを置いて行ってください。コンピュータは白を担当します。

プログラムソース表示

戻る