i-school - 2Dまるばつゲーム(三目並べ) 手順8
 引き続き、ゲーム画面にある Grid をクリック(タップ)した際の処理を作成していきます。

 Grid をクリックした際に、○×の印がついているかどうかを判定し、どちらの印もない場合には〇印をつけることができるようになりました。
この手順では、印をつけたあとに3つ〇印がついているかどうかを判定し、勝敗を決定する機能を設計していきます。
この勝敗の判定処理が非常に難しいと感じるかと思いますので、じっくりと考えて実装に挑戦してください。。

 勝利条件を満たした場合には、Debug.Log メソッドにより、Console ビューに表示を行って、処理が正常に動作しているかを判定させます。


<実装動画>
動画ファイルへのリンク


 以下の内容で順番に実装を進めていきます。

手順8 ー勝敗判定機能を実装するー
14.GameManager スクリプトを修正し、〇か×のいずれかが縦・横・斜めの3つのマスでつながっているかを判定する機能を実装する



 新しい学習内容は、以下の通りです。

 ・処理のアルゴリズムを考える
 ・int 型の値を指定した enum の列挙子のキャストする方法



14.GameManager スクリプトを修正し、〇か×のいずれかが縦・横・斜めの3つのマスでつながっているかを判定する機能を実装する

1.設計


 〇印、あるいは×印が3つ繋がっている場所があるかを確認する勝敗判定処理を実装します。

 問題は、どのようにマス目の〇印が3つ繋がっているかを判定するか、です。

 まず、縦方向・横方向・斜め方向と、印を確認すべき方向が3種類ありますので、こちらをどのようにして判定していくかを決める必要があります。
 
 今回のゲームではマス目は Grid ゲームオブジェクトを9個利用して作成しており、かつ、この Grid ゲームオブジェクトには
GridController スクリプトがそれぞれにアタッチされていて、マス目の番号を管理している状態になっています。

ゲーム画面



ゲーム内の Grid の番号

 この情報を活用していき、縦方向と横方向と斜め方向について、マス目の番号を指定して、判定を行う設計にします。



 例えば、横方向であれば、0、1、2 のマス、3、4、5のマス、6、7、8のマスの3つのパターンを判定します。
こちらについては、このパターン通りに3つの情報を if 文の条件式としてもよいですが、非常に長い処理になってしまいますので、
3つのパターンになっている数字を解析してアルゴリズム(計算方法)を考えてみます

 横方向であれば、1つの行を3回繰り返すことになりますので、こういったケースには3つの数字パターンを検証し、
繰り返しの処理を上手く活用して(for 文と配列の組み合わせ)ロジックを考えてアルゴリズムを作ることが処理の効率化につながります。

 繰り返しの処理に配列を組み合わせて利用する理由は、マス目の情報を GridController スクリプトが管理しており(マス目の番号やオーナーシンボルの情報)、
それを管理しているのが、grids 配列になっているためです。
配列の要素番号の指定部分を変数にして、n 番目の配列の要素 = GridController の情報を参照するように設計します。

 横方向について1つの行ごとに判定したいマス目の番号をみてみると、前の行の値を3ずつ増やしていけば、
次の行の値として利用できるパターンが浮かびあがってきます。
そしてそれを実際に for 文と配列を使ってロジック化すると、次のような式が作成できます。
 
<for 文と配列を使った横方向の判定処理のアルゴリズム>
 // 横方向
  // 0,1,2 || 3,4,5 || 6,7,8 のマスを確認する
  for (int x = 0; x < 3; x++) {
      if (grids[x * 3]            // 左のマスの判定値      x * 3 の結果なので、grids[0(0 * 3)] / grids[3(1 * 3)] / grids [6(2 * 3)] となる
          && grids[x * 3 + 1]     // 真ん中のマスの判定値 x * 3 + 1の結果なので、grids[1(0 * 3 + 1)] / grids[4(1 * 3 + 1)] / grids [7(2 * 3 + 1)] となる
          && grids[x * 3 + 2])    // 右のマスの判定値      x * 3 +2 の結果なので、grids[2(0 * 3 + 2)] / grids[5(1 * 3 + 2)] / grids [8(2 * 3 + 2)] となる
      {
          // 上記の条件をすべて満たせば、3つそろっているので勝ちになる

      }

 for 文がループするたびに x の値が加算されるため、このロジックを使うことにより、横方向の3パターンについては、すべて確認を行うことが可能になっています。
if 文内の条件式が && でつながっていますので、3つの条件をすべて満たしているかどうかを比較評価しています。

 同じ要領で、縦方向と斜め方向についても、調べたいマス目をすべて書き出し、それを繰り返す処理を使ってアルゴリズムを作るにはどうすればいいのかを考えてみてください。

 実際の処理は、上記の for 文に手を加えて、Grid のオーナーシンボルの情報を精査します。
その際も、〇と×で処理を分けてしまうと、〇と×以外の判定部分についてはすべて同じ処理を記述しないとならなくなるため、
この部分も for 文を利用し、1つのメソッド内で両方の処理が行えるような設計にします。

 ロジックに加えて、アルゴリズムを作ることは非常に難しいですが、テーブルゲームの多くはアルゴリズムを考えることが重要なので、とてもよい頭の体操になります。


2.GameManager スクリプトを修正する


 勝敗が確定したかを判定するための機能を有する JudgeWinner メソッドと、勝利後の処理を実行する ShowResult メソッドを作成します。
ShowResult メソッドについては、次以降の手順で実装を行うため、TODO で実装したい処理を残しておきます。

 この JudgeWinner メソッドは、先ほど説明したアルゴリズムを作って3つの方向の全パターンを判定できるようにロジックを組んでいます。
特に斜め方向のアルゴリズムについては、for 文内の条件書式から他の2つとは異なるため、注意が必要です。
自分で処理のイメージを思い浮かべながら書くようにしていくと、より深い学習になると思います。

 JudgeWinner メソッドは印をつけた処理のあとに実行することにより、〇印を設置→勝敗判定という処理の流れを作ります。
これは、コンピュータ側も同じ処理の流れで実装することになりますので、どのような順番で処理が動いているかを把握しておくことが大切です。

 いずれかの方向に同じ印が3つ並んでおり、勝利が確定した場合には、勝者の情報を引数として ShowResult メソッドに渡すことで、
1つのメソッドで異なる勝者の情報に対応できるような設計にしています。


GameManager.cs

 <= クリックすると開きます。



3.アルゴリズムを読み解く


 ざっとみただけでは、変数ベースで処理が記述されているため、実際にどのような内容になっているのか非常にわかりにくいと思います。
処理内には書いてありませんが、自分で Debug.Log メソッドなどを利用して、各変数の値(i や x)を調べてみると、Grid のどのマス目を調べているのかが分かってきます。

 またこの記述だけがアルゴリズムではありませんので、他にも考えてみるといいでしょう。


4.<int 型を指定した enum の列挙子のキャストする方法>


 今回実装した処理の中において、手順5で説明していた enum の列挙子の値を int 型にキャスト(型変換)する処理の、逆パターンが利用されています。

 ここでは、int 型の値を enum である GridOwnerType 型にキャストしています。


<int 型の情報を enum(GridOwnerType) の列挙子に型変換>
  (GridOwnerType)gridOwnerTypeNo

 この処理を行うことで、int 型の gridOwnerTypeNo 変数の情報を GridOwnerType に登録されている列挙子にキャスト(型変換)しています。
キャストされる際には、enum 側に設定されている int 型の情報を元に変換されます。列挙子を登録する際に特に指定していなければ、自動的に 0 から採番されています。

 そのため、今回のケースでは、次のようなキャストが行われています。

 gridOwnerTypeNo = 0 ならば、GridOwnerType の None 
 gridOwnerTypeNo = 1 ならば、GridOwnerType の Player
 gridOwnerTypeNo = 2 ならば、GridOwnerType の Opponent
 gridOwnerTypeNo = 3 ならば、GridOwnerType の Draw 

 そして enum になった情報を評価している処理に利用していますので、比較処理を行えるようになっています。


処理抜粋
grids[x * 3].CurrentGridOwnerType == (GridOwnerType)gridOwnerTypeNo
   ↓
grids[x * 3].CurrentGridOwnerType == gridOwnerTypeNo 変数からキャストされた GridOwnerType として比較している


 キャストの処理を覚えておくことで、処理の記述のバリエーションが広がります。


5.ゲームを実行して動作を確認する


 処理が完成しましたので、最初から今回学習した内容を振り返り、処理の内容の理解を深めてください。

 今回の処理は、Grid をクリックして〇印をつけていき、その都度、JudgeWinner メソッドが実行されて、すべてのマス目に勝利条件を満たしているかの判定が行われます。
そして、いずれかのマス目が3つつながった時点で ShowResult メソッドが実行されて、Debug.Log メソッドにより、Console ビューに勝利した側の情報が表示されるようになっています。


<実装動画>
動画ファイルへのリンク


 デバッグにあたっては、すべてのパターンの勝利条件を確認するようにしてください。マス目をクリックする順番は任意で構いません。
よって、横方向3行、縦方向3行、斜め2方向の8回のパターンの確認作業が必要になります。




 以上でこの手順は完成です。

 次は 手順9 −コンピュータ側の処理を実装する− です。