一応、人が二人いればゲームができるようにはなったのですが、 手番と残り玉の数がわからないので、かなり不便です。 そこで、図のように画面を分けることにしました (右下は後でボタンを配置するためにあけてあります)。
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ソースコード (Ver. 1.1a5)