とりあえずAppletは動いたので、次はゲーム盤を作ってみましょう。
ゲーム盤を定義するコードをUpperHand
クラスの中に
どんどん追加していってもいいのですが、
ここではゲーム盤を定義するクラスUpperHandGame
を
新規に追加することとします。
Javaでは、Object
クラスを除くすべてのクラスは他のクラスの
サブクラスとなっている必要があります。
ここが、スーパークラスを持たないクラスを定義できるC++と違う部分です。
UpperHandGame
クラスも他のクラスのサブクラスに
しなければならないのですが、
適当なクラスがないので、Object
クラスのサブクラスにしました。
//============================================================================= // UpperHandGame // UpperHandのゲーム盤を定義するクラス。 //============================================================================= public class UpperHandGame extends Object { }
さて、UpperHandGame
クラスの定義をしましょう。
ゲームのルールは
こちらを参照してください。
ゲーム盤を表わすデータの定義から始めてもいいのですが、
ちょっとオブジェクト指向的に
ゲームをするのに必要なメソッド一覧を考えてみましょう。
オブジェクト指向が「データ主導」の設計方法だというのはうそです。 ある対象(オブジェクト)に必要な操作(メソッド)の集合を考え、 操作の集合の違いによりクラスを分けていく 「操作の集合主導」の設計方法だと思っています。
以下がメソッドの一覧です。
メソッド名 意味 UpperHandGame()
ゲーム盤を新規に作成し、初期状態にする。 boolean isFinish()
ゲームが終了しているとき true
を返す。Player nextPlayer()
次のプレーヤーを返す。 Move[] moves()
現在、着手可能な手の一覧を返す。 void makeMove(Move m)
m
で指定された手を打つ。Status boardStatus(Position p)
位置 p
の状態を返す。int ball(Player pl)
プレーヤー pl
の持ち玉の数を返す。
Player
、Move
、Position
、
Status
は暫定的に導入したクラスです。
あとで、定義し直すことにしましょう。
UpperHandGame()
は特殊なメソッドです。
クラス名と同じ名前のメソッドは
コンストラクタといって、
インスタンスが生成された直後に行う処理を記述します。
ゲーム盤の表現法を考えてみましょう。
まず気になるのは、
ピラミッド構造になっている玉の位置をどう表現するかでしょう。
簡単に思いつくのは縦/横/高さの座標を5×5×5の配列で表現する方法でしょうが、
実際に玉を置けるのは55箇所なのに配列は125個の要素を持つことになってしまい
非常に無駄ですし、実際には存在しない場所を定義するのもめんどくさそうです。
そこで、位置に0〜54の番号を振ることにしました。
位置同士の関係は、
各々の位置の下の4つの位置の番号を持つ55×4の配列で表現します。
この配列は、クラス変数として定義します(実際はクラス定数)。
クラス変数とインスタンス変数の違いは、
クラス変数が
Cのグローバル変数のようにアプリケーション上に1つしか存在しないのに対し、
インスタンス変数は
インスタンスごとに複数存在する点です。
実際の定義は以下です。
//----------------------------------------------------------------------------- // クラス変数定義 //----------------------------------------------------------------------------- // 1つ下の位置を示す定数の定義 public final static int downPosition[][] = { // 位置 - ( x, y, z) {-1, -1, -1, -1}, // 0 - ( 0, 0, 4) 途中は省略します { 0, 1, 5, 6}, // 25 - ( 0, 0, 3) 途中は省略します {50, 51, 52, 53} // 54 - ( 0, 0, 0) };
これでPosition
クラスは
int
型で代用できることがわかりました。
次に各位置の状態を定義しましょう。
//----------------------------------------------------------------------------- // クラス変数定義 //----------------------------------------------------------------------------- // 状態を示す定数の定義 public final static int FIRST = 0; // 先手の玉がある状態 public final static int SECOND = 1; // 後手の玉がある状態 public final static int NUTRAL = 2; // 中立の玉がある状態 public final static int MOVABLE = 3; // 玉が置ける状態 public final static int UNMOVABLE = 4; // 玉が置けない状態 //----------------------------------------------------------------------------- // インスタンス変数定義 //----------------------------------------------------------------------------- // 位置ごとの状態 private int boardStatus[] = new int [55];
Status
クラスは
int
型で代用しました。
次にプレーヤーの定義ですが、FIRST
、SECOND
で代用することにしました。
Player
クラスはint
型です。
//----------------------------------------------------------------------------- // インスタンス変数定義 //----------------------------------------------------------------------------- // プレーヤーごとの持ち玉の数 private int ball[] = new int[2]; // 次のプレーヤー private int nextPlayer;
最後に手の定義です。
プレーヤーと位置の組で表現することもできますが、
インスタンス変数nextPlayer
に次のプレーヤーが記録されているので、
位置のみで手を表現することにしました。
したがって、Move
クラスもint
型とします。
メソッドを1つ1つ実装していきましょう。
//----------------------------------------------------------------------------- // public UpperHandGame() // ゲーム盤を初期状態にする。 //----------------------------------------------------------------------------- public UpperHandGame() { System.err.println("UpperHandGame() called."); // 位置ごとの状態を初期化する。 for (int p = 0; p < boardStatus.length; p++) { if (downPosition[p][0] == -1) { // 最下段の場合 boardStatus[p] = MOVABLE; // 玉が置ける } else { // その他 boardStatus[p] = UNMOVABLE; // 玉は置けない } } boardStatus[12] = NUTRAL; // 最下段の中央に中立の玉を置く。 // 持ち玉の数を初期化する。 ball[FIRST] = 27; ball[SECOND] = 27; // 次のプレーヤーを初期化する。 nextPlayer = FIRST; System.err.println("UpperHandGame() done."); }
コンストラクタが呼ばれたことが確認できるように、デバッグ文が入れてあります。
//----------------------------------------------------------------------------- // public boolean isFinish() // ゲームが終了しているときtrueを返す。 //----------------------------------------------------------------------------- public boolean isFinish() { // どちらかのプレーヤーの持ち玉がないとき、ゲームは終了している。 return (ball[FIRST] == 0 || ball[SECOND] == 0); }
//----------------------------------------------------------------------------- // public int nextPlayer() // 次のプレーヤーを返す。 //----------------------------------------------------------------------------- public int nextPlayer() { if (isFinish()) { // ゲームが終了していたとき。 throw new Error("Game already finished"); // エラーとする。 } return nextPlayer; }
ゲームがすでに終了しているのにnextPlayer()
を呼び出したときは
エラーとしています。
エラーは回復不能な異常、例外は回復可能な異常を表わします
(簡単にいえばエラーはバグです)。
//----------------------------------------------------------------------------- // public int[] moves() // 着手可能な手の一覧を返す。 //----------------------------------------------------------------------------- public int[] moves() { if (isFinish()) { // ゲームが終了していたとき。 throw new Error("Game already finished"); // エラーとする。 } // すべての位置について着手可能か調べ、着手可能な手を配列に入れる。 // すべての位置について調べ終わったとき、nには着手可能な手の数が入って // いる。 int tmpMoves[] = new int[boardStatus.length]; int n = 0; for (int p = 0; p < boardStatus.length; p++) { if (boardStatus[p] == MOVABLE) { tmpMoves[n] = p; n++; } } // 着手可能な手と同じ数の要素をもつ配列に移し替える。 int moves[] = new int[n]; for (int i = 0; i < moves.length; i++) { moves[i] = tmpMoves[i]; } return moves; }
//----------------------------------------------------------------------------- // private void putBall(int p, int player) // pで指定された位置にplayerの玉を置く。 //----------------------------------------------------------------------------- private void putBall(int p, int player) { if (ball[player] == 0) { // 持ち玉がない場合 return; // 何もしない。 } // 持ち玉を減らす。 ball[player]--; // 盤に玉を置く。 boardStatus[p] = player; } //----------------------------------------------------------------------------- // public void makeMove(int p) // pで指定された手を打つ。 //----------------------------------------------------------------------------- public void makeMove(int p) { if (isFinish()) { // ゲームが終了していたとき。 throw new Error("Game already finished"); // エラーとする。 } else if (boardStatus[p] != MOVABLE) { // 着手不能な場所の場合 throw new Error("Bad move"); // エラーとする。 } // まず、指定された位置に玉を置く。 putBall(p, nextPlayer()); // 次に、ボーナス玉を置く。 checkBonus: for (int i = 0; i < boardStatus.length; i++) { if (boardStatus[i] == UNMOVABLE) { // 着手不可能な位置の場合、ボーナス玉の判定を行う。 int first = 0; // 1つ下の先手の玉の数(0で初期化) int second = 0; // 1つ下の後手の玉の数(0で初期化) // 1つ下の玉の状態を調べる。 for (int d = 0; d < 4; d++) { switch (boardStatus(downPosition[i][d])) { case FIRST: first++; break; case SECOND: second++; break; case NUTRAL: break; default: // 1箇所でも下に玉がない場合、ボーナス玉の判定は不要。 // 次の位置のボーナス玉の判定を行う。 continue checkBonus; } } if (first >= 3) { // 先手の玉が3つ以上あった場合 putBall(i, FIRST); // 先手のボーナス玉を置く。 } else if (second >= 3) { // 後手の玉が3つ以上あった場合 putBall(i, SECOND); // 後手のボーナス玉を置く。 } else { // どちらの玉も3つ以上ない場合 boardStatus[i] = MOVABLE; // 玉が置けるようにする。 } } } // プレーヤーを交代する。 nextPlayer = (nextPlayer == FIRST) ? SECOND : FIRST; }
putBall()
はUpperHandGame
クラスの
内部メソッドです(makeMove()
が使う)。
private
と修飾することで、他のクラスからは使用できなくなります。
//----------------------------------------------------------------------------- // public int boardStatus(int p) // 位置pの状態を返す。 //----------------------------------------------------------------------------- public int boardStatus(int p) { return boardStatus[p]; }
//----------------------------------------------------------------------------- // public int ball(int player) // プレーヤーplayerの持ち玉の数を返す。 //----------------------------------------------------------------------------- public int ball(int player) { return ball[player]; }
完成したUpperHandGame
クラスのインスタンスを
Appletから生成してみましょう。
UpperHand
クラスを以下に修正しました。
//----------------------------------------------------------------------------- // インスタンス変数定義 //----------------------------------------------------------------------------- // ゲーム盤 private UpperHandGame game;
//----------------------------------------------------------------------------- // public void init() // Appletの初期化処理を行う。 //----------------------------------------------------------------------------- public void init() { System.err.println("UpperHand.init() called."); // ゲーム盤を新規に作成し、初期化する。 game = new UpperHandGame(); }
UpperHand.init()
の中から
UpperHandGame()
が呼び出されたことが確認できます。
Javaソースコード (Ver. 1.1a2)