[Next] [Prev] [Back] [Home]

■ UpperHand Gameを作る


ゲーム盤を作る

とりあえず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の持ち玉の数を返す。

PlayerMovePositionStatusは暫定的に導入したクラスです。 あとで、定義し直すことにしましょう。
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型で代用しました。

次にプレーヤーの定義ですが、FIRSTSECOND で代用することにしました。
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];
}

Appletからゲームを初期化する

完成した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に対応していません。

Javaソースコード (Ver. 1.1a2)



[Next] [Prev] [Back] [Home]
Satoshi Kobayashi (koba@yk.rim.or.jp)