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

■ UpperHand Gameを作る


ゲーム盤の表示エリアを分ける(その1)

画面レイアウト 一応、人が二人いればゲームができるようにはなったのですが、 手番と残り玉の数がわからないので、かなり不便です。 そこで、図のように画面を分けることにしました (右下は後でボタンを配置するためにあけてあります)。

コンポーネントとコンテナ

Javaではボタンやスクロールバーのような画面の部品を コンポーネントと呼びます。 これに対し、複数のコンポーネントを格納し配置する役割を担当するのが コンテナです。
JDK1.0.2ではコンポーネント/コンテナのクラス階層は 下の図のようになっています。

コンポーネントのクラス階層

コンテナもコンポーネントです。 コンテナにコンテナを含めることで、 コンポーネントを入れ子に配置することが可能となります。 Appletクラスもコンテナなので その中にコンポーネントを含むことができます。 イベントはコンポーネントに対して発生します。 イベントのtargetというインスタンス変数に イベントが発生したコンポーネントが記録されています。 また、Graphicsクラスのメソッドで指定する座標は コンポーネント内の相対座標となります。
表示エリアをコンポーネントに分割することで、 イベント処理/描画処理を単純にすることができるわけです。

コンポーネントを定義する

では、コンポーネントを定義しましょう。以下の3つのクラスを定義します。

UpperHandBoardViewクラス
ゲーム盤を表示するクラス。 今までUpperHandクラスが行っていた描画/イベント処理を このクラスに移します。
UpperHandPlayerViewクラス
プレーヤーの手番を表示するクラス。 次の手番の場合、背景を浮き上がった四角、名前をボールド体で表示します。
UpperHandBallViewクラス
プレーヤーの持ち玉を表示するクラス。

上の3つのクラスはゲームの状態を参照し表示しますが、 ゲームの操作(初期化/着手など)は直接行わないことにします。 ゲームの操作はUpperHandクラスが行います。 UpperHandBoardViewクラスはマウスボタンが押されたことを検知すると、 マウスの座標を盤上の位置に変換し、 UpperHandクラスに着手を依頼します。

まず、UpperHandBoardViewクラスを定義します。
スーパークラスはCanvasクラスとしました。 Canvasクラスにはデフォルトの動作が定義されていませんので、 コンポーネントを作成する場合によくスーパークラスとして利用されるクラスです。

//=============================================================================
//  UpperHandBoardView.java
//      Copyright(c) 1998 Satoshi Kobayashi, All rights reserved.
//=============================================================================

import java.awt.*;


//=============================================================================
//  class UpperHandBoardView
//      ゲーム盤を表示するクラス。
//=============================================================================
public class UpperHandBoardView extends Canvas
{
//-----------------------------------------------------------------------------
//  クラス変数定義
//-----------------------------------------------------------------------------

//  玉の大きさ
private final static int    size    = 32;

//  玉の描画エリア
private static Rectangle    area[];

//-----------------------------------------------------------------------------
//  クラス変数の初期化
//-----------------------------------------------------------------------------
static {
    // 玉の描画エリアを初期化する。
    //  位置pと、その(x,y,z)座標の対応をとり、sizeから位置pの描画エリアを
    //  計算している。
    area = new Rectangle[55];
    int p = 0;
    for (int z = 4; z >= 0; z--)
    {
        for (int y = 0; y <= z; y++)
        {
            for (int x = 0; x <= z; x++)
            {
                area[p] = new Rectangle(
                                (4 - z) * size / 2 + size * x,
                                (4 - z) * size / 2 + size * y,
                                size, size);
                p++;
            }
        }
    }
}

//-----------------------------------------------------------------------------
//  インスタンス変数定義
//-----------------------------------------------------------------------------

//  表示対象のゲーム
private UpperHandGame   game;

//  ゲームを制御するオブジェクト
private UpperHand       app;

//-----------------------------------------------------------------------------
//  コンストラクタ定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public UpperHandBoardView(UpperHandGame game, UpperHand app)
//      UpperHandBoardViewのインスタンスを生成し、初期化を行う。
//          UpperHandGame   game    - 表示対象のゲーム
//          UpperHand       app     - ゲームを制御するオブジェクト
//-----------------------------------------------------------------------------
public UpperHandBoardView(UpperHandGame game, UpperHand app)
{
    this.game = game;
    this.app  = app;

    //  コンポーネントの背景色を設定する。
    setBackground(UpperHand.color[UpperHand.BG_COLOR]);
}

//-----------------------------------------------------------------------------
//  メソッド定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public void paint(Graphics g)
//      現在のゲームの状態を描画する。
//-----------------------------------------------------------------------------
public void paint(Graphics g)
{
    paintBoard(g);      // 盤を描画する。

    // 玉を描画する。
    for (int p = 0; p < area.length; p++)
    {
        paintBall(g, p);
    }
}

//-----------------------------------------------------------------------------
//  private void paintBoard(Graphics g)
//      ゲーム盤のみを描画する。
//-----------------------------------------------------------------------------
private void paintBoard(Graphics g)
{
    Color   c1;         //  左と上の辺の色
    Color   c2;         //  右と下の辺の色

    c1 = UpperHand.color[UpperHand.BG_COLOR].brighter().brighter();
    c2 = UpperHand.color[UpperHand.BG_COLOR].darker().darker();

    //  5×5の盤面に浮き上がった四角形を書く。
    for (int y = 0; y < 5; y++)
    {
        for (int x = 0; x < 5; x++)
        {
            int X = x * size;
            int Y = y * size;
            g.setColor(c1);
            g.drawLine(X, Y, X + size - 1, Y);
            g.drawLine(X, Y, X, Y + size - 1);
            g.setColor(c2);
            g.drawLine(X, Y + size - 1, X + size - 1, Y + size - 1);
            g.drawLine(X + size - 1, Y, X + size - 1, Y + size - 1);
        }
    }
}

//-----------------------------------------------------------------------------
//  private void paintBall(Graphics g, int p)
//      pで指定された位置の玉を描画する。
//-----------------------------------------------------------------------------
private void paintBall(Graphics g, int p)
{
    Color c;

    //  玉の色を決定する。
    switch (game.boardStatus(p))
    {
    case UpperHandGame.FIRST:
        c = UpperHand.color[UpperHand.FIRST_COLOR];
        break;
    case UpperHandGame.SECOND:
        c = UpperHand.color[UpperHand.SECOND_COLOR];
        break;
    case UpperHandGame.NUTRAL:
        c = UpperHand.color[UpperHand.NUTRAL_COLOR];
        break;
    case UpperHandGame.MOVABLE:
        c = UpperHand.color[UpperHand.BG_COLOR];
        break;
    default:
        return;
    }


    if (game.boardStatus(p) != UpperHandGame.MOVABLE)
    {                                       //  玉を置くとき
        //  描画エリアいっぱいに背景色で円を描く。
        g.setColor(UpperHand.color[UpperHand.BG_COLOR]);
        g.fillOval(area[p].x, area[p].y,
                    area[p].width, area[p].height);

        //  描画エリアより少し小さめに円を描く。
        g.setColor(c);
        g.fillOval(area[p].x + 1, area[p].y + 1,
                    area[p].width - 2, area[p].height - 2);
    }
    else
    {                                       //  玉が置けるようになったとき
        //  描画エリアの半分の大きさで背景色の円を描く。
        g.setColor(c);
        g.fillOval(area[p].x + area[p].width / 4,
                    area[p].y + area[p].height / 4,
                    area[p].width / 2, area[p].height / 2);
    }
}

//-----------------------------------------------------------------------------
//  public boolean mouseDown(Event evt, int x, int y)
//      マウスのボタンが押されたときの処理を行う。
//-----------------------------------------------------------------------------
public boolean mouseDown(Event evt, int x, int y)
{
    if (!game.isFinish())
    {                           //  ゲームが終了していない場合
        //  着手可能な手の一覧を取得する。
        int move[] = game.moves();

        //  マウスのボタンが押された座標が、
        //  着手可能な位置の描画エリア内か調べる。
        for (int i = 0; i < move.length; i++)
        {
            int p = move[i];
            if (area[p].inside(x, y))
            {                       //  エリア内の場合
                app.makeMove(p);    //  着手を依頼する。
                return true;        //  イベントを処理したのでtrueを返す。
            }
        }
    }

    return false;   //  イベントを親コンポーネントに渡す。
}

}

ほとんどは、前回までUpperHandクラスが行っていた処理ですが 数ヵ所変更しています。
1つは、paintBoard()メソッドで 浮き上がった四角をfill3DRect()を使用して描画していた部分を、 自力でdrawLine()を使って描画するように変更しました。 特に意味はありません (fill3DRect()の表示がなんだか「ぼんやり」していて気に入らなかっただけ)。
もう1つは重要な変更です。
今まで、mouseDown()メソッドで着手を行っていた部分を UpperHandクラスに依頼するようにしました。 また、着手の依頼を行わなかった場合はfalseを返すよう変更しました。 falseを返す意味については後で説明します。

次にUpperHandPlayerViewクラスを定義しましょう。 スーパークラスは同じくCanvasクラスです。
自分の手番の場合、背景に浮き上がった四角を表示し 名前はボールド体で表示します。

//=============================================================================
//  UpperHandPlayerView.java
//      Copyright(c) 1998 Satoshi Kobayashi, All rights reserved.
//=============================================================================

import java.awt.*;


//=============================================================================
//  class UpperHandPlayerView
//      プレーヤーの名前、手番を表示するクラス。
//=============================================================================
public class UpperHandPlayerView    extends Canvas
{
//-----------------------------------------------------------------------------
//  クラス変数定義
//-----------------------------------------------------------------------------

//  名前の表示に使用するフォントの名前
private final static String fontName    = "Helvetica";

//  名前の表示に使用するフォントのサイズ
private final static int    fontSize    = 10;

//-----------------------------------------------------------------------------
//  インスタンス変数定義
//-----------------------------------------------------------------------------

//  表示対象のゲーム
private UpperHandGame       game;

//  表示対象のプレーヤー
private int                 player;

//  プレーヤーの名前
private String              playerName;

//-----------------------------------------------------------------------------
//  コンストラクタ定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public UpperHandPlayerView(UpperHandGame game, int player)
//      UpperHandBPlayerViewのインスタンスを生成し、初期化を行う。
//          UpperHandGame   game    - 表示対象のゲーム
//          int             player  - 表示対象のプレーヤー
//-----------------------------------------------------------------------------
public UpperHandPlayerView(UpperHandGame game, int player)
{
    this.game   = game;
    this.player = player;

    //  コンポーネントの背景色を設定する。
    setBackground(UpperHand.color[UpperHand.BG_COLOR]);
}

//-----------------------------------------------------------------------------
//  メソッド定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public void playerName(String name)
//      プレーヤーの名前を設定する。
//-----------------------------------------------------------------------------
public void playerName(String name)
{
    playerName = name;
}

//-----------------------------------------------------------------------------
//  public void paint(Graphics g)
//      プレーヤーの名前を手番がわかる形で描画する。
//-----------------------------------------------------------------------------
public void paint(Graphics g)
{
    Color   bg = UpperHand.color[UpperHand.BG_COLOR];   //  背景色
    Color   fg = UpperHand.color[UpperHand.FG_COLOR];   //  前景色
    Color   c1;         //  左と上の辺の色
    Color   c2;         //  右と下の辺の色
    int     style;      //  フォントスタイル(標準/ボールド体)

    //  自分自身の位置を得る。
    Rectangle rect = bounds();

    if (!game.isFinish() && game.nextPlayer() == player)
    {                                           //  自分の手番の場合
        //  浮き上がった四角になるように色を設定する。
        c1 = bg.brighter().brighter();
        c2 = bg.darker().darker();
        //  フォントスタイルをボールド体とする。
        style = Font.BOLD;
    }
    else
    {                                           //  自分の手番以外の場合
        //  へこんだ四角になるように色を設定する。
        c1 = bg.darker().darker();
        c2 = bg.brighter().brighter();
        //  フォントスタイルを標準とする。
        style = Font.PLAIN;
    }

    //  背景を描く。
    g.setColor(c1);
    g.drawLine(0, 0, 0, rect.height - 1);
    g.drawLine(0, 0, rect.width - 1, 0);
    g.setColor(c2);
    g.drawLine(rect.width - 1, 0, rect.width - 1, rect.height - 1);
    g.drawLine(0, rect.height - 1, rect.width - 1, rect.height - 1);

    //  プレーヤーの名前を描く。
    g.setFont(new Font(fontName, style, fontSize));
    g.setColor(fg);
    g.drawString(playerName, 3, rect.height - 3);
}

}

最後にUpperHandBallViewクラスです。 スーパークラスはやはりCanvasクラスです。

//=============================================================================
//  UpperHandBallView.java
//      Copyright(c) 1998 Satoshi Kobayashi, All rights reserved.
//=============================================================================

import java.awt.*;


//=============================================================================
//  class UpperHandBallView
//      プレーヤーの持ち玉を表示するクラス。
//=============================================================================
public class UpperHandBallView  extends Canvas
{
//-----------------------------------------------------------------------------
//  クラス変数定義
//-----------------------------------------------------------------------------

//  玉の大きさ
private final static int size   = 14;

//  縦横に並べる玉の数
private final static int width  = 7;
private final static int height = 4;

//-----------------------------------------------------------------------------
//  インスタンス変数定義
//-----------------------------------------------------------------------------

//  表示対象のゲーム
private UpperHandGame   game;

//  表示対象のプレーヤー
private int             player;

//  玉の色
private Color           color;

//-----------------------------------------------------------------------------
//  コンストラクタ定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public UpperHandBallView(UpperHandGame game, int player))
//      UpperHandBallViewのインスタンスを生成し、初期化を行う。
//          UpperHandGame   game    - 表示対象のゲーム
//          int             player  - 表示対象のプレーヤー
//-----------------------------------------------------------------------------
public UpperHandBallView(UpperHandGame game, int player)
{
    this.game   = game;
    this.player = player;

    //  玉の色を決める。
    switch (player)
    {
    case UpperHandGame.FIRST:
        this.color  = UpperHand.color[UpperHand.FIRST_COLOR];
        break;
    case UpperHandGame.SECOND:
        this.color  = UpperHand.color[UpperHand.SECOND_COLOR];
        break;
    default:
        throw new Error("Bad player");
    }

    //  コンポーネントの背景色を設定する。
    setBackground(UpperHand.color[UpperHand.BG_COLOR]);
}

//-----------------------------------------------------------------------------
//  メソッド定義
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//  public void paint(Graphics g)
//      現在の持ち玉を描画する。
//-----------------------------------------------------------------------------
public void paint(Graphics g)
{
    Color   c1;         //  左と上の辺の色
    Color   c2;         //  右と下の辺の色

    //  背景を描く。
    c1 = UpperHand.color[UpperHand.BG_COLOR].darker().darker();
    c2 = UpperHand.color[UpperHand.BG_COLOR].brighter().brighter();
    g.setColor(c1);
    g.drawLine(0, 0, 0, height * size - 1);
    g.drawLine(0, 0, width * size - 1, 0);
    g.setColor(c2);
    g.drawLine(width * size - 1, 0, width * size - 1, height * size - 1);
    g.drawLine(0, height * size - 1, width * size - 1, height * size - 1);

    //  玉を描く。
    g.setColor(color);
    int p = 0;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            if (p < game.ball(player))
            {
                g.fillOval(size * x + 1, size * y + 1, size - 2, size - 2);
            }
            p++;
        }
    }
}

}

コンポーネントの定義は終わったのですが、 複数のコンポーネントがゲームを参照するようになったため、 ゲームの再開処理に変更が必要になりました。 今まではUpperHandGameクラスのインスタンスを 新規に生成していましたが、この方法だと コンポーネントが参照していたゲームが過去のゲームにになってしまいます。 そこで、コンストラクタが行っていた初期化処理を init()というメソッドに移し、 コンストラクタを削除しました。 ゲームを再開始するときはinit()を呼び出します。

イベントの伝播

UpperHandBoardViewクラスでイベントを処理せずfalse を返している箇所がありましたが、あのイベントはどこに行ったのでしょうか。
JDK1.0.2ではコンポーネントが処理しなかったイベントはコンテナに伝播されます。 UpperHandBoardViewクラスが処理しなかったイベントは UpperHandクラスで処理することができます。

[警告] JDK1.1ではイベントは伝播しません。
//-----------------------------------------------------------------------------
//  public boolean mouseDown(Event evt, int x, int y)
//      マウスのボタンが押されたときの処理を行う。
//-----------------------------------------------------------------------------
public boolean mouseDown(Event evt, int x, int y)
{
    if (game.isFinish())
    {                           //  ゲームが終了している場合
        startGame();        //  新しいゲームを始める。
    }

    return true;
}

//-----------------------------------------------------------------------------
//  public boolean mouseEnter(Event evt, int x, int y)
//      マウスがApplet内に入ったときの処理を行う。
//-----------------------------------------------------------------------------
public boolean mouseEnter(Event evt, int x, int y)
{
    //  Appletの情報をステータスバーに表示する。
    showStatus(getAppletInfo());
    return true;
}

//-----------------------------------------------------------------------------
//  public void startGame()
//      ゲームを開始する。
//-----------------------------------------------------------------------------
public void startGame()
{
    //  プレーヤーの名前を設定する。
    playerView[UpperHandGame.FIRST].playerName("First player");
    playerView[UpperHandGame.SECOND].playerName("Second player");

    //  ゲームを初期化する。
    game.init();

    //  全コンポーネントに再描画の「きっかけ」をわたす。
    Component comp[] = getComponents();
    for (int i = 0; i < comp.length; i++)
    {
        comp[i].repaint();
    }
}

//-----------------------------------------------------------------------------
//  public void makeMove(int p)
//      位置pに着手する。
//-----------------------------------------------------------------------------
public void makeMove(int p)
{
    // 着手する。
    game.makeMove(p);

    //  全コンポーネントに再描画の「きっかけ」をわたす。
    Component comp[] = getComponents();
    for (int i = 0; i < comp.length; i++)
    {
        comp[i].repaint();
    }
}

MOUSE_ENTERイベントはだれも処理していないため、 UpperHandクラスに到達します。 MOUSE_DOWNイベントはゲーム終了後は UpperHandクラスに到達し、 UpperHandクラスがゲーム再開の処理を行います。
また、今まではrepaint()を呼び出して 自分自身の再描画のきっかけをもらっていたのですが、 複数のコンポーネントがあるため、すべてのコンポーネントに対して repaint()を呼び出しています。

では、作成したコンポーネントをApplet内に配置しましょう。
UpperHandクラスのメソッドinit()で コンポーネントを生成し、配置します。

//-----------------------------------------------------------------------------
//  public void init()
//      Appletの初期化処理を行う。
//-----------------------------------------------------------------------------
public void init()
{
    //  ゲーム盤を新規に作成し、初期化する。
    game = new UpperHandGame();
    game.init();

    //  背景色を設定する。
    setBackground(UpperHand.color[UpperHand.BG_COLOR]);

    //  コンポーネントを作成する。
    createComponent();

    //  コンポーネントを配置する。
    layoutComponent();

    //  ゲームを開始する。
    startGame();
}

//-----------------------------------------------------------------------------
//  private void createComponent()
//      コンポーネントを作成する。
//-----------------------------------------------------------------------------
private void createComponent()
{
    //  すべてのコンポーネントを取り除く。
    removeAll();

    //  ゲーム盤を表示するコンポーネントを作成する。
    boardView = new UpperHandBoardView(game, this);

    for (int p = UpperHandGame.FIRST; p <= UpperHandGame.SECOND; p++)
    {
        //  プレーヤーを表示するコンポーネントを作成する。
        playerView[p] = new UpperHandPlayerView(game, p);

        //  持ち玉を表示するコンポーネントを作成する。
        ballView[p] = new UpperHandBallView(game, p);
    }
}

//-----------------------------------------------------------------------------
//  private void layoutComponent()
//      コンポーネントを配置する。
//-----------------------------------------------------------------------------
private void layoutComponent()
{
    setLayout(null);

    boardView.reshape(0, 0, 160, 160);
    add(boardView);

    playerView[UpperHandGame.FIRST].reshape(160, 0, 98, 16);
    add(playerView[UpperHandGame.FIRST]);

    ballView[UpperHandGame.FIRST].reshape(160, 16, 98, 56);
    add(ballView[UpperHandGame.FIRST]);

    playerView[UpperHandGame.SECOND].reshape(160, 72, 98, 16);
    add(playerView[UpperHandGame.SECOND]);

    ballView[UpperHandGame.SECOND].reshape(160, 88, 98, 56);
    add(ballView[UpperHandGame.SECOND]);
}

createComponent()の先頭でremoveAll()を呼び出して コンポーネントの削除を行っています。 Applet Viewerで「Restart」を実行するとinit()が呼び出され コンポーネントを重複して作成してしまうので、 これを避けるために追加したコードです。
layoutComponent()でのコンポーネントの配置は 暫定的にApplet内の座標指定で行っています。 これはあまりよい方法ではありません。 コンポーネントの配置方法については次回説明します。


ここまでにできあがったもの

あなたのブラウザはJavaに対応していません。

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



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