情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第13章 Windows プログラミング(Java)

  1. 13.1 Window の生成・消滅とイベント処理
    1. 13.1.1 Window の生成
      1. (プログラム例 13.1 ) Window の生成
    2. 13.1.2 JAR ファイル
    3. 13.1.3 イベント処理
      1. (プログラム例 13.2 ) Window の消滅とイベント処理
      2. (プログラム例 13.3 ) マウスイベント
  2. 13.2 AWT,Swing とグラフィックス
    1. 13.2.1 AWT と Swing
      1. (プログラム例 13.4 ) ラジオボタン,チェックボックス,ドロップダウンリスト
      2. (プログラム例 13.5 ) 2 つの整数の和
    2. 13.2.2 グラフィックス
      1. (プログラム例 13.6 ) 描画の基本
  3. 13.3 アニメーション
    1. 13.3.1 アニメーションの開始と停止
      1. (プログラム例 13.7 ) アニメーションの開始と停止
    2. 13.3.2 アニメーション作成方法
      1. (プログラム例 13.8 ) ボールの運動(描画)
      2. (プログラム例 13.9 ) ボールの運動(外部の 1 画像)
      3. (プログラム例 13.10 ) ランニング(外部の複数画像)
      4. (プログラム例 13.11 ) 花火(ピクセル値の操作)
  4. 13.4 様々な例題
    1. 13.4.1 パズル & ゲーム
      1. (プログラム例 13.12 ) 8 / 15 パズル
      2. (プログラム例 13.13 ) 巡回セールスマン問題( TSP )
      3. (プログラム例 13.14 ) シューティング風ゲームと作成手順
      4. (プログラム例 13.15 ) ぷよぷよ風ゲームと作成手順
    2. 13.4.2 グラフの表示
      1. (プログラム例 13.16 ) グラフの表示
    3. 13.4.3 遺伝的アルゴリズム
      1. (プログラム例 13.17 ) GA のステップ実行
    4. 13.4.4 お絵かき
      1. (プログラム例 13.18 ) マウスによる描画
    5. 13.4.5 その他
      1. (プログラム例 13.19 ) 2次方程式の根
      2. (プログラム例 13.20 ) 剛体振り子の運動
      3. (プログラム例 13.21 ) マニピュレータと LED の点灯制御
  5. 演習問題13
  この章においては,Java による Windows プログラミングについて概説します.非常に基本的なことに対してだけの説明ですので,詳細については,他の書籍及び付録の Java のクラスとメソッド等を参照してください.

13.1 Window の生成・消滅とイベント処理

  今まで示したほとんどの例は,プログラム例 3.1 のように,コマンドプロンプト上で起動し,キーボードからデータを入力し,かつ,結果をコマンドプロンプト上に表示するためのものでした.プログラム例 7.12 のように,main 関数の引数としてデータを与える方法もありますが,コマンドプロンプト上で実行することには変わりありません.

  プログラムを使いやすくするためには,ボタン,チェックボックスなどのグラフィカルなユーザインタフェースGUI )を利用してデータを入力し,その結果も GUI 上に表示する,場合によっては,グラフなど,より視覚的に分かりやすい形で結果を表示することが望まれます.残念ながら,C/C++ の仕様にはそのような機能が含まれていません.そこで,この章においては,Java による GUI を利用したプログラムについて説明します.プログラムの起動自体はコマンドプロンプトから行いますが,入力や結果の出力は,Window 上で行います(結果が,数値や文字である場合は,コマンドプロンプト上に出力することも可能).

  また,ボタン,チェックボックスなどのグラフィカルなコンポーネントは,AWT または Swing という形で提供されています.AWT の各種コンポーネントは,「ピアモデル」という方法で設計されてきました.ピアpeer )とは,ある作業を行うために自分に協力してくれる仲間ないし相手を言います.AWT の各コンポーネント毎に,それと対をなすプラットホーム固有の部品(ピア,実際の作業を行う)が作成されていました.しかし,ピアをユーザが直接書き直すことができないため,あるコンポーネントの機能を拡張したいような場合は困難が生じます.そこで,ピアを持たないコンポーネント( lightweight component :軽量部品)もサポートするようになりました(従来のピアを持つコンポーネントを heavyweight component :重量部品と呼ぶ).それらの軽量部品は,「 javax.swing.* 」というパッケージにまとめて提供されています.

  AWT と Swing の細かな違いについては,「付録」の各コンポーネントに関する説明を見て下さい.一般的に言えば,AWT は Java 1.0 から存在する機能ですので,Java の実行環境が古くても動作します.しかし,低機能であり,描画機能やルックアンドフィールドは OS に依存します.また,Swing は,高機能でコンポーネントが豊富ですが,AWT に比較し多くのメモリを消費します.

  Window を生成する際に,Frame クラス( AWT )を一般的に使用しますが,Frame クラスにおいては,GUI の各コンポーネントや描画を Frame クラスに対して行います.しかし,対応する Swing の JFrame クラスにおいては,JFrame クラスの唯一のコンテナであるコンテントペインに対して行いますし,描画方法にも多少の違いがあります.さらに,ダブルバッファリングという高速に描画するための機能を,Swing ではデフォルトとしてサポートしています.

13.1.1 Window の生成

(プログラム例 13.1 ) Window の生成

  ここで示すのは,Frame クラス,及び,JFrame クラス( Swing のコンポーネント.一般的に,Swing のコンポーネント名は J から始まる)を使用して Window を生成し,2 つの整数の和を計算した例です.ただし,Java で利用できる GUI について全く説明してありませんので,プログラム例 7.12 と同様,2 つの整数を main 関数の引数として受け取り,結果をコマンドプロンプトに出力しています.そのため,あまり意味のあるプログラムになっていません.なお,AWT,及び,Swing については,次の節( 13.2 節)で説明します.

  このプログラムをコンパイルし,コマンドプロンプトから,例えば,
java Test 10 20		
のように入力すると,小さな Wnodow が生成されるとともに,コマンドプロンプトに,
和は 30		
のような結果が表示されます.なお,プログラムを終了させるには,「 ^c 」( ctrl キーと c を同時に押す)を入力してください.

Frame クラス( AWT )を利用した場合

/****************************/
/* Window の生成            */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要

public class Test {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win win = new Win("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win extends Frame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// 出力
		System.out.println("和は = " + (a + b));
	}
}
		

JFrame クラス( Swing )を利用した場合

/****************************/
/* Window の生成            */
/*      coded by Y.Suganuma */
/****************************/
import javax.swing.*;   // JFrame も Swing の一種であるため必要

public class Test_s {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win_s win = new Win_s("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win_s extends JFrame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win_s (String name, int a, int b)
	{
					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// 出力
		System.out.println("和は = " + (a + b));
	}
}
		

13.1.2 JAR ファイル

  一般に,プログラムが大きくなると多くのクラスファイルが生成されます.そのため,作成したプログラムを配布するような場合に面倒になります.そこで,Java では,jar コマンドを使用して,複数のファイルを圧縮しながら 1 つのファイルにまとめることができます.そのためには,まず,メインメソッドを含むクラスを指定するため,次の 1 行からなるマニフェストファイルを作成します(次の例における Test の後では,必ず改行しておくこと).なお,マニフェストファイル名は任意ですが,ここでは「 manifest.txt 」としておきます.
Main-Class: Test		
次に,jar コマンドによって JAR ファイル(名称は任意,ここでは,window.jar )を生成します.
jar cvfm window.jar manifest.txt *.class		
なお,jar コマンドの一般形は以下に示す通りであり,クラスファイルだけでなく,画像等のファイルを含めることも可能です.
jar [オプション] JARファイル [マニフェストファイル] [圧縮するファイル ・・・]		
  以上の手続きによって作成された JAR ファイルをダブルクリックするか,または,以下のコマンドを入力することによってプログラムを実行することができます.
java -jar window.jar		

13.1.3 イベント処理

(プログラム例 13.2 ) Window の消滅とイベント処理

  プログラム例 13.1 は,あまり意味がないだけではなく,大きな問題があります.Window の右上にある「×」ボタンをクリックしても Window を閉じることができません( Swing の場合は,Window は閉じますが,プログラムが終了しません).なぜなら,「×」ボタンをクリックしたときの処理が全く記述されていないからです.

  マウスがクリックされた,ボタンが押された,Window が開始された,等のことをイベント(事象)と言います.このようなイベントが発生したとき行う処理をイベント処理といいます.まず,Java において,イベント処理がどのようにして行われているかについて説明ます.

  イベントは,イベントの種類毎に,クラスに分類されています. イベントが発生すると,イベントソースは対応するイベントを記述するイベントオブジェクトを生成します.これを,イベントリスナに送出して対応する処理を行うわけですが,そのためには,イベントリスナがそのイベントを「聞く」準備ができていなくてはなりません.その準備を行うのがイベント登録メソッドイベント削除メソッドもあります)です.

  イベントの送出は,リスナのインタフェース(または,イベントアダプタ)に定義されているイベント応答メソッドハンドラメソッド)の内1つを呼び出し,引数としてイベントソースが生成したイベントオブジェクトを渡すといった方法で行われます.

  イベントリスナはインタフェースとして提供されています.これは,各イベントの処理がアプリケーションによって異なる場合が多いからです.しかし,インタフェースを実装する際には,インタフェースに定義されているすべてのメソッド(ハンドラメソッド)を実装しない限り,抽象クラスとなり,インスタンスを生成できなくなります.実際にすべてのメソッドを必要とする場合は問題ありませんが,以下の例に示すように,一部のメソッドだけを使用したい場合においても,必要としないメソッドまですべて定義しなければならず,余分な作業が必要になります.以下に示すのは,WindowListener インタフェースを利用して Window の消滅を行った例です.

Frame クラス( AWT )を利用した場合

/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test1 {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win1 win = new Win1("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win1 extends Frame implements WindowListener {

	/******************/
	/* コンストラクタ */
	/******************/
	Win1 (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// イベントリスナの登録
		addWindowListener(this);
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/************/
	/* 終了処理 */
	/************/
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}

	/********************************************/
	/* イベントリスナの他のメソッド             */
	/*   以下のメソッドも記述しておく必要がある */
	/********************************************/
	public void windowActivated(WindowEvent e) {}
	public void windowDeactivated(WindowEvent e) {}
	public void windowOpened(WindowEvent e) {}
	public void windowClosed(WindowEvent e) {}
	public void windowIconified(WindowEvent e) {}
	public void windowDeiconified(WindowEvent e) {}
}
		

JFrame クラス( Swing )を利用した場合

/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import javax.swing.*;   // JFrame も Swing の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test1_s {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win1_s win = new Win1_s("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win1_s extends JFrame implements WindowListener {

	/******************/
	/* コンストラクタ */
	/******************/
	Win1_s (String name, int a, int b)
	{
					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// イベントリスナの登録
		addWindowListener(this);
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/************/
	/* 終了処理 */
	/************/
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}

	/********************************************/
	/* イベントリスナの他のメソッド             */
	/*   以下のメソッドも記述しておく必要がある */
	/********************************************/
	public void windowActivated(WindowEvent e) {}
	public void windowDeactivated(WindowEvent e) {}
	public void windowOpened(WindowEvent e) {}
	public void windowClosed(WindowEvent e) {}
	public void windowIconified(WindowEvent e) {}
	public void windowDeiconified(WindowEvent e) {}
}
		

  上で示した例においては,WindowListener インタフェースに宣言されている必要としないメソッドまですべて定義しなければならないという問題があります.以下に示す例においては,イベントアダプタクラスを利用して,この煩わしさを避けています.イベントアダプタは,すべてのメソッドに対する標準的処理を実装しているため,このクラスのサブクラスとしてクラスを定義すれば,必要なメソッドの実装だけで済むことになります.しかし,Java においては多重継承が許されませので,内部クラスを定義してイベント処理を行うのが通常の方法です.次のプログラムは,上と同じ例に対し,イベントアダプタ( WindowAdapter クラス)を継承した内部クラス WinEnd を使用して実行しています.

Frame クラス( AWT )を利用した場合

/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import java.awt.*;   // Frame も AWT の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test2 {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win2 win = new Win2("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win2 extends Frame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win2 (String name, int a, int b)
	{
					// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// 内部クラスを利用してイベントリスナを登録
		addWindowListener(new WinEnd());
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/**************************/
	/* 終了処理(内部クラス) */
	/**************************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}
		

JFrame クラス( Swing )を利用した場合

/****************************/
/* Window の生成と消滅      */
/*      coded by Y.Suganuma */
/****************************/
import javax.swing.*;   // JFrame も Swing の一種であるため必要
import java.awt.event.*;   // イベント処理を行う場合に必要

public class Test2_s {
	public static void main (String[] args)
	{
		int a = Integer.parseInt(args[0]);
		int b = Integer.parseInt(args[1]);
		Win2_s win = new Win2_s("整数の和", a, b);
	}
}

/*******************/
/* クラスWinの定義 */
/*******************/
class Win2_s extends JFrame {

	/******************/
	/* コンストラクタ */
	/******************/
	Win2_s (String name, int a, int b)
	{
					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(300, 200);
					// ウィンドウを表示
		setVisible(true);
					// 内部クラスを利用してイベントリスナを登録
		addWindowListener(new WinEnd());
					// 出力
		System.out.println("和は = " + (a + b));
	}

	/**************************/
	/* 終了処理(内部クラス) */
	/**************************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}
		

(プログラム例 13.3 ) マウスイベント

  以下に示すプログラムは,マウスイベントに対する処理例です.プログラムを実行してもらえば明らかなように,Window 内でマウスの左ボタンをクリックすると,クリックした場所に「クリック」というメッセージが表示されます(右図参照).なお,メッセージを表示するために必要なグラフィックス機能に関しては,13.2.2 節で説明します.
01	/****************************/
02	/* マウスイベント           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.awt.*;
06	import java.awt.event.*;
07	
08	public class Test {
09		public static void main (String[] args)
10		{
11			Mouse_e win = new Mouse_e("マウスイベント");
12		}
13	}
14	
15	class Mouse_e extends Frame implements MouseListener  {
16		String str = new String("");
17		int x, y;
18						// 初期設定(計算)
19		/******************/
20		/* コンストラクタ */
21		/******************/
22		Mouse_e(String name)
23		{
24						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
25			super(name);
26						// Windowの大きさ
27			setSize(180, 220);
28						// 背景色
29			setBackground(Color.white);
30						// ウィンドウを表示
31			setVisible(true);
32						// マウスリスナの付加
33			addMouseListener(this);
34						// ウィンドウリスナの付加
35			addWindowListener(new WinEnd());
36		}
37						// マウスイベントの処理
38		public void mouseClicked(MouseEvent e)
39		{
40			x  = e.getX();
41			y  = e.getY();
42			str = new String("クリック");
43			repaint();
44		}
45		public void mousePressed(MouseEvent e) {}
46		public void mouseReleased(MouseEvent e) {}
47		public void mouseEntered(MouseEvent e) {}
48		public void mouseExited(MouseEvent e) {}
49						// 描画
50		public void paint (Graphics g)
51		{
52			Font f = new Font("TimesRoman", Font.BOLD, 20);
53			g.setFont(f);
54			g.drawString(str, x, y);
55		}
56	
57		/******************************/
58		/* 上,左,下,右の余白の設定 */
59		/******************************/
60		public Insets getInsets()
61		{
62			return new Insets(70, 20, 20, 20);
63		}
64	
65		/************/
66		/* 終了処理 */
67		/************/
68		class WinEnd extends WindowAdapter
69		{
70			public void windowClosing(WindowEvent e) {
71				System.exit(0);
72			}
73		}
74	}
		
15 行目

  MouseListener インタフェースを継承しています.

16~17 行目

  表示する文字列と,その表示位置を記憶するための変数です.値が設定されるのは,mouseClicked メソッド内の 40 ~ 42 行目ですが,paint メソッド( 50 ~ 55 行目)においても使用されるため,クラスの変数としてこの位置(メソッドの外側)で宣言しておく必要があります.

33 行目

  MouseListener を登録しています.

38~44 行目

  マウスがクリックされたときの処理です.クリックされた位置と,表示する文字列を設定し,再描画( repaint )しています.

60~63 行目

  Window の上下左右に余白を設定するための処理です.上部の余白が大きくなっているのは,上部に,Window の名前や × などの記号が表示されるためです.
  上と全く同じ処理を,MouseAdapter クラスを利用して実現しています.MouseListener インタフェースを継承していないと共に,33 行目とマウスイベントの処理方法( 38 ~ 48 行目と 38 ~ 46 行目)が異なっています.
01	/****************************/
02	/* マウスイベント           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.awt.*;
06	import java.awt.event.*;
07	
08	public class Test {
09		public static void main (String[] args)
10		{
11			Mouse_e win = new Mouse_e("マウスイベント");
12		}
13	}
14	
15	class Mouse_e extends Frame  {
16		String str = new String("");
17		int x, y;
18						// 初期設定(計算)
19		/******************/
20		/* コンストラクタ */
21		/******************/
22		Mouse_e(String name)
23		{
24						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
25			super(name);
26						// Windowの大きさ
27			setSize(180, 220);
28						// 背景色
29			setBackground(Color.white);
30						// ウィンドウを表示
31			setVisible(true);
32						// マウスリスナの付加
33			addMouseListener(new Mouse());
34						// ウィンドウリスナの付加
35			addWindowListener(new WinEnd());
36		}
37						// マウスイベントの処理
38		class Mouse extends MouseAdapter {
39			public void mouseClicked(MouseEvent e)
40			{
41				x   = e.getX();
42				y   = e.getY();
43				str = new String("クリック");
44				repaint();
45			}
46		}
47						// 描画
48		public void paint (Graphics g)
49		{
50			Font f = new Font("TimesRoman", Font.BOLD, 20);
51			g.setFont(f);
52			g.drawString(str, x, y);
53		}
54	
55		/******************************/
56		/* 上,左,下,右の余白の設定 */
57		/******************************/
58		public Insets getInsets()
59		{
60			return new Insets(70, 20, 20, 20);
61		}
62	
63		/************/
64		/* 終了処理 */
65		/************/
66		class WinEnd extends WindowAdapter
67		{
68			public void windowClosing(WindowEvent e) {
69				System.exit(0);
70			}
71		}
72	}
		

13.2 AWT,Swing とグラフィックス

13.2.1 AWT と Swing

  Java には,Window の生成,ボタンの作成,チェックボックスの作成といった GUI を作成できるようなクラスライブラリが含まれています.それが,AWT ( Absract Window Toolkit )や Swing です.まず,AWT の主要なコンポーネントは以下に示す通りです.

  1. Frame: タイトルと境界を持ったトップレベルの Window
  2. Dialog: タイトルと境界を持ったトップレベルの Window
  3. Panel: コンポーネントを入れるためのパネル
  4. Button: ボタン
  5. Checkbox: チェックボックス(複数項目から複数選択)
  6. CheckboxGroup: ラジオボタン(複数項目から1つを選択)
  7. Choice: ドロップダウンリスト(矢印をクリックと,選択項目が表示される)
  8. List: 縦に並んだ項目から選択
  9. Label: ラベル(文字列を表示)
  10. Scrollbar: スクロールバー
  11. TextArea: 複数行にわたる文字列の編集と表示
  12. TextField: 1行の文字列の編集と表示
  13. MenuBar: メニューバー
  14. MenuItem: メニューバーの各メニューが押されたときに現れるメニュー項目

  上記のコンポーネントを画面上に並べる方法(レイアウト)も重要です.AWT には,レイアウトに関して,以下に述べるようなクラスが準備されています.「 setLayout(null) 」を使用して,レイアウトマネージャを使用しない方法もあります.

  1. BorderLayout: コンテナ(画面)を5つの領域(上,下,左,中,右)に分けて,コンポーネントを配置
  2. CardLayout: 同じ領域に複数のコンポーネントを重ねて配置
  3. FlowLayout: コンポーネントを可能な限り横1行に配置
  4. GridLayout: コンポーネントを格子状に配置
  5. GridBagLayout: 格子状のセルにコンポーネントを柔軟に配置(配置方法は,GridBagConstraints クラスのメソッドで指定)

  Swing のコンポーネントは,一般的に頭文字が J で始まります.AWT の Button に対応するコンポーネントが JButton であるように,ほぼ AWT に対応するコンポーネントが存在します.さらに多くのコンポーネントも利用でき,AWT のコンポーネントよりも高機能です.代表的な Swing のコンポーネント及びレイアウトマネージャは以下に示すとおりです.

  1. JFrame: タイトルと境界を持ったトップレベルの Window
  2. JDialog: タイトルと境界を持ったトップレベルの Window
  3. JPanel: コンポーネントを入れるためのパネル
  4. Box: BoxLayoutをデフォルトで持っているコンテナ
  5. JButton: ボタン
  6. JCheckbox: チェックボックス(複数項目から複数選択)
  7. JComboBox: ドロップダウンリスト
  8. JLabel: ラベル(文字列を表示)
  9. JList: 縦に並んだ項目から選択
  10. JPasswordField: パスワードの処理
  11. JPopupMenu: ポップアップメニュー
  12. JProgressBar: 進捗状況の表示
  13. JRadioButton: ラジオボタン
  14. JScrollBar: スクロールバー
  15. JScrollPane: 自動的にコンポーネントへスクロールバーを貼り付ける
  16. JSlider: メモリ付きスライダー
  17. JTable: 2次元データの表示と編集
  18. JTextArea: 複数行にわたる文字列の編集と表示
  19. JTextField: 1行の文字列の編集と表示
  20. JToolBar: ツールバーを表示
  21. JTree: 階層構造を持ったデータを木構造で表示,編集
  22. JMenuBar: メニューバー
  23. JMenu: メニューバーに付け加えられるメニュー
  24. JMenuItem: メニューバーの各メニューが押されたときに現れるメニュー項目
  25. JCheckBoxMenuItem: チェックボックス機能を持ったメニュー項目
  26. JRadioButtonMenuItem: ラジオボタン機能を持ったメニュー項目
  27. JSplitPane: 領域を2つに分割して表示
  28. JTabbedPane: 重ねたコンポーネントにタブを付ける
  29. BoxLayout: コンポーネントを縦,または,横に配置

(プログラム例 13.4 ) ラジオボタン,チェックボックス,ドロップダウンリスト

  ラジオボタン,チェックボックス,ドロップダウンリスト(チェック)を Java を使用して作成してみます.ただし,以下に示すプログラムは,ラジオボタン,チェックボックス,及び,ドロップダウンリストにおいて,状態が変化したときその状態をテキストエリアに表示するだけの機能を持っています.名前に対応するテキストフィールドの値だけを変えてもその結果はテキストエリアに反映されません.

AWT を利用した場合

001	/********************************************************/
002	/* ラジオボタン,チェックボックス,ドロップダウンリスト */
003	/*      coded by Y.Suganuma                             */
004	/********************************************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	
008	public class Test {
009		public static void main (String[] args)
010		{
011			Win win = new Win("ラジオボタン,チェックボックス,ドロップダウンリスト(AWT)");
012		}
013	}
014	
015	class Win extends Frame implements ItemListener
016	{
017		TextField name;
018		Checkbox r1, r2;
019		Checkbox c1, c2, c3, c4;
020		Choice ch;
021		TextArea ta;
022	
023		/******************/
024		/* コンストラクタ */
025		/******************/
026		Win(String ttl)
027		{
028						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
029			super(ttl);
030						// Windowの大きさ
031			setSize(670, 370);
032						// レイアウト,背景色,フォントなど
033			setLayout(new BorderLayout(5, 10));
034			setBackground(new Color(225, 255, 225));
035			Font f = new Font("TimesRoman", Font.BOLD, 20);
036			setFont(f);
037						// 上のパネル
038			Panel pn1 = new Panel();
039			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
040			add(pn1, BorderLayout.NORTH);
041			pn1.add(new Label("名前:"));
042			name = new TextField("", 10);
043			pn1.add(name);
044			CheckboxGroup cbg = new CheckboxGroup();
045			pn1.add(new Label(" 性別:"));
046			r1 = new Checkbox("男", cbg, false);
047			pn1.add(r1);
048			r2 = new Checkbox("女", cbg, false);
049			pn1.add(r2);
050			r1.addItemListener(this);
051			r2.addItemListener(this);
052						// 中央のパネル
053			Panel pn2 = new Panel();
054			add(pn2, BorderLayout.CENTER);
055			pn2.add(new Label("好きな野菜:"));
056			c1 = new Checkbox("キャベツ");
057			pn2.add(c1);
058			c2 = new Checkbox("大根");
059			pn2.add(c2);
060			c3 = new Checkbox("ジャガイモ");
061			pn2.add(c3);
062			c4 = new Checkbox("トマト");
063			pn2.add(c4);
064			c1.addItemListener(this);
065			c2.addItemListener(this);
066			c3.addItemListener(this);
067			c4.addItemListener(this);
068			pn2.add(new Label(" 好きな果物:"));
069			ch = new Choice();
070			ch.add("蜜柑");
071			ch.add("林檎");
072			ch.add("葡萄");
073			pn2.add(ch);
074			ch.addItemListener(this);
075						// 下のパネル
076			Panel pn3 = new Panel();
077			add(pn3, BorderLayout.SOUTH);
078			ta = new TextArea(4, 50);
079			pn3.add(ta);
080						// ウィンドウを表示
081			setVisible(true);
082						// イベントアダプタ
083			addWindowListener(new WinEnd());
084		}
085	
086		/******************************/
087		/* 上,左,下,右の余白の設定 */
088		/******************************/
089		public Insets getInsets()
090		{
091			return new Insets(70, 20, 20, 20);
092		}
093	
094		/************************/
095		/* 選択されたときの処理 */
096		/************************/
097		public void itemStateChanged(ItemEvent e)
098		{
099			int sw = 0;
100			ta.setText("名前:" + name.getText() + "\n");
101			if (r1.getState())
102				ta.append("性別: 男\n");
103			else if (r2.getState())
104				ta.append("性別: 女\n");
105			if (c1.getState()) {
106				sw = 1;
107				ta.append("好きな野菜: キャベツ");
108			}
109			if (c2.getState()) {
110				if (sw == 0) {
111					sw = 1;
112					ta.append("好きな野菜: 大根");
113				}
114				else
115					ta.append(",大根");
116			}
117			if (c3.getState()) {
118				if (sw == 0) {
119					sw = 1;
120					ta.append("好きな野菜: ジャガイモ");
121				}
122				else
123					ta.append(",ジャガイモ");
124			}
125			if (c4.getState()) {
126				if (sw == 0) {
127					sw = 1;
128					ta.append("好きな野菜: トマト");
129				}
130				else
131					ta.append(",トマト");
132			}
133			if (sw > 0) {
134				sw = 0;
135				ta.append("\n");
136			}
137			if (ch.getSelectedIndex() >= 0)
138				ta.append("好きな果物: " + ch.getSelectedItem() + "\n");
139		}
140	
141		/************/
142		/* 終了処理 */
143		/************/
144		class WinEnd extends WindowAdapter
145		{
146			public void windowClosing(WindowEvent e) {
147				System.exit(0);
148			}
149		}
150	}
		
015 行目

  ラジオボタンやチェックボックスにおいて,選択項目が変化したときの処理を行うため,インタフェース ItemListener を継承しています.

033 行目~ 036 行目

  レイアウトマネージャ,背景色,及び,フォントを設定しています.

038 行目~ 051 行目

  Panel クラスのオブジェクト pn1 に,CheckboxGroup クラスCheckbox クラスを利用して,性別を選択するためのラジオボタンを追加しています.なお,各ラジオボタンには,項目が選択されたときの処理を行うため,ItemListener を付加しています( 050 行目~ 051 行目).

053 行目~ 067 行目

  Panel クラスのオブジェクト pn2 に,Checkbox クラスを利用して,好きな野菜を選択するためのチェックボックスを追加しています.

068 行目~ 074 行目

  Panel クラスのオブジェクト pn2 に,Choice クラスを利用して,好きな果物を選択するためのドロップダウンリストを追加しています.

076 行目~ 079 行目

  Panel クラスのオブジェクト pn3 に,TextArea クラスを利用して,テキストエリアを追加しています.なお,このテキストエリアには,ラジオボタン,チェックボックス,または,ドロップダウンリストの項目が選択されたときの処理を行うメソッドによって,その時点における各項目等の状態が出力されます.

094 行目~ 139 行目

  ラジオボタン,チェックボックス,または,ドロップダウンリストの項目が選択されたときの処理を行うメソッドです.TextField オブジェクト name の内容を TextArea オブジェクト ta に出力( 100 行目)した後,ラジオボタン( 101 行目~ 104 行目),チェックボックス( 105 行目~ 136 行目),及び,ドロップダウンリスト( 137 行目~ 138 行目)の状態を調べ,その状態を同じく ta に出力しています.

Swing を利用した場合

001	/********************************************************/
002	/* ラジオボタン,チェックボックス,ドロップダウンリスト */
003	/*      coded by Y.Suganuma                             */
004	/********************************************************/
005	import javax.swing.*;
006	import java.awt.*;
007	import java.awt.event.*;
008	
009	public class Test {
010		public static void main (String[] args)
011		{
012			Win win = new Win("ラジオボタン,チェックボックス,ドロップダウンリスト(Swing)");
013		}
014	}
015	
016	class Win extends JFrame implements ActionListener
017	{
018		JTextField name;
019		JRadioButton r1, r2;
020		JCheckBox c1, c2, c3, c4;
021		JComboBox  ch;
022		JTextArea ta;
023	
024		/******************/
025		/* コンストラクタ */
026		/******************/
027		Win(String ttl)
028		{
029						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
030			super(ttl);
031						// Windowの大きさ
032			setSize(670, 370);
033						// レイアウト,背景色,フォントなど
034			Container cp = getContentPane();
035			cp.setBackground(new Color(225, 255, 225));
036			Font f = new Font("TimesRoman", Font.BOLD, 20);
037						// 上のパネル
038			JPanel pn1 = new JPanel();
039			pn1.setBackground(new Color(225, 255, 225));
040			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
041			cp.add(pn1, BorderLayout.NORTH);
042			JLabel lb1 = new JLabel("名前:");
043			lb1.setFont(f);
044			pn1.add(lb1);
045			name = new JTextField("", 10);
046			name.setFont(f);
047			name.setBackground(Color.white);
048			pn1.add(name);
049			ButtonGroup gp = new ButtonGroup();
050			JLabel lb2 = new JLabel(" 性別:");
051			lb2.setFont(f);
052			pn1.add(lb2);
053			r1 = new JRadioButton("男");
054			r1.setFont(f);
055			r1.setBackground(new Color(225, 255, 225));
056			gp.add(r1);
057			pn1.add(r1);
058			r2 = new JRadioButton("女");
059			r2.setFont(f);
060			r2.setBackground(new Color(225, 255, 225));
061			gp.add(r2);
062			pn1.add(r2);
063			r1.addActionListener(this);
064			r2.addActionListener(this);
065						// 中央のパネル
066			JPanel pn2 = new JPanel();
067			pn2.setBackground(new Color(225, 255, 225));
068			cp.add(pn2, BorderLayout.CENTER);
069			JLabel lb3 = new JLabel("好きな野菜:");
070			lb3.setFont(f);
071			pn2.add(lb3);
072			c1 = new JCheckBox("キャベツ");
073			c1.setFont(f);
074			c1.setBackground(new Color(225, 255, 225));
075			pn2.add(c1);
076			c2 = new JCheckBox("大根");
077			c2.setFont(f);
078			c2.setBackground(new Color(225, 255, 225));
079			pn2.add(c2);
080			c3 = new JCheckBox("ジャガイモ");
081			c3.setFont(f);
082			c3.setBackground(new Color(225, 255, 225));
083			pn2.add(c3);
084			c4 = new JCheckBox("トマト");
085			c4.setFont(f);
086			c4.setBackground(new Color(225, 255, 225));
087			pn2.add(c4);
088			c1.addActionListener(this);
089			c2.addActionListener(this);
090			c3.addActionListener(this);
091			c4.addActionListener(this);
092			JLabel lb4 = new JLabel(" 好きな果物:");
093			lb4.setFont(f);
094			pn2.add(lb4);
095			ch = new JComboBox  ();
096			ch.setFont(f);
097			ch.setBackground(Color.white);
098			ch.addItem("蜜柑");
099			ch.addItem("林檎");
100			ch.addItem("葡萄");
101			pn2.add(ch);
102			ch.addActionListener(this);
103						// 下のパネル
104			JPanel pn3 = new JPanel();
105			pn3.setBackground(new Color(225, 255, 225));
106			cp.add(pn3, BorderLayout.SOUTH);
107			ta = new JTextArea(4, 30);
108			ta.setFont(f);
109			ta.setBackground(Color.white);
110			JScrollPane sp = new JScrollPane(ta);
111			pn3.add(sp);
112						// ウィンドウを表示
113			setVisible(true);
114						// イベントアダプタ
115			addWindowListener(new WinEnd());
116		}
117	
118		/******************************/
119		/* 上,左,下,右の余白の設定 */
120		/******************************/
121		public Insets getInsets()
122		{
123			return new Insets(70, 20, 20, 20);
124		}
125		/************************/
126		/* 選択されたときの処理 */
127		/************************/
128		public void actionPerformed(ActionEvent e)
129		{
130			int sw = 0;
131			ta.setText("名前:" + name.getText() + "\n");
132			if (r1.isSelected())
133				ta.append("性別: 男\n");
134			else if (r2.isSelected())
135				ta.append("性別: 女\n");
136			if (c1.isSelected()) {
137				sw = 1;
138				ta.append("好きな野菜: キャベツ");
139			}
140			if (c2.isSelected()) {
141				if (sw == 0) {
142					sw = 1;
143					ta.append("好きな野菜: 大根");
144				}
145				else
146					ta.append(",大根");
147			}
148			if (c3.isSelected()) {
149				if (sw == 0) {
150					sw = 1;
151					ta.append("好きな野菜: ジャガイモ");
152				}
153				else
154					ta.append(",ジャガイモ");
155			}
156			if (c4.isSelected()) {
157				if (sw == 0) {
158					sw = 1;
159					ta.append("好きな野菜: トマト");
160				}
161				else
162					ta.append(",トマト");
163			}
164			if (sw > 0) {
165				sw = 0;
166				ta.append("\n");
167			}
168			if (ch.getSelectedIndex() >= 0)
169				ta.append("好きな果物: " + ch.getSelectedItem() + "\n");
170		}
171	
172		/************/
173		/* 終了処理 */
174		/************/
175		class WinEnd extends WindowAdapter
176		{
177			public void windowClosing(WindowEvent e) {
178				System.exit(0);
179			}
180		}
181	}
		
016 行目

  ラジオボタンやチェックボックスにおいて,選択項目が変化したときの処理を行うため,インタフェース ActionListener を継承しています.

034,035 行目

  JFrame クラスにおいては,JFrame クラスの唯一のコンテナであるコンテントペインに対して,コンポーネントの追加や描画を行います.34 行目では,コンテントペイン cp を JFrame クラスのメソッドである getContentPane によって取得しています.また,35 行目では,取得したコンテントペインの背景色を設定しています.なお,コンテントペインのデフォルトレイアウトは,BorderLayout です.

038 行目~ 041 行目

  パネル pn1 ( JPanel クラスのオブジェクト)の背景色とレイアウトマネージャを設定した後,コンテントペイン cp の NORTH に貼り付けています.

042 行目~ 064 行目

  pn1 に,ButtonGroup クラス(ラジオボタンをグループ化するためのクラス)と JRadioButton クラスを利用して,性別を選択するためのラジオボタンを追加しています.なお,各ラジオボタンには,項目が選択されたときの処理を行うため,ActionListener を付加しています( 063,064 行目).

066 行目~ 091 行目

  JPanel クラスのオブジェクト pn2 に,JCheckBox クラスを利用して,好きな野菜を選択するためのチェックボックスを追加しています.

092 行目~ 102 行目

  JPanel クラスのオブジェクト pn2 に,JComboBox クラスを利用して,好きな果物を選択するためのドロップダウンリストを追加しています.

104 行目~ 111 行目

  テキストエリアに自動的にスクロールバーを貼り付けるため,JTextArea クラスのオブジェクト ta に基づき,JScrollPane クラスのオブジェクト sp を生成し,それを JPanel クラスのオブジェクト pn3 に追加しています.なお,このテキストエリアには,ラジオボタン,チェックボックス,または,ドロップダウンリストの項目が選択されたときの処理を行うメソッドによって,その時点における各項目等の状態が出力されます.

125 行目~ 170 行目

  ラジオボタン,チェックボックス,または,ドロップダウンリストの項目が選択されたときの処理を行うメソッドです.JTextField オブジェクト name の内容を JTextArea オブジェクト ta に出力( 131 行目)した後,ラジオボタン( 132 行目~ 135 行目),チェックボックス( 136 行目~ 167 行目),及び,ドロップダウンリスト( 168 行目~ 169 行目)の状態を調べ,その状態を同じく ta に出力しています.

(プログラム例 13.5 ) 2 つの整数の和

  整数の加算の問題を AWT 及び Swing を利用して書いてみました.左側の 2 つのテキストフィールドにデータを入力した後,「=」ボタンをクリックすると右側のテキストフィールドに結果が表示されます.

AWT を利用した場合

01	/**************************/
02	/* 2 つの整数の和         */
03	/*    coded by Y.Suganuma */
04	/**************************/
05	import java.awt.*;
06	import java.awt.event.*;
07	
08	public class Test {
09		public static void main (String[] args)
10		{
11			Win win = new Win("整数の和");
12		}
13	}
14	
15	class Win extends Frame implements ActionListener {
16	
17		Button bt;
18		TextField tx1, tx2, tx3;
19	
20		/******************/
21		/* コンストラクタ */
22		/******************/
23		Win(String name)
24		{
25						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
26			super(name);
27						// Windowの大きさ
28			setSize(600, 180);
29						// レイアウトの変更(フローレイアウト)
30			setLayout(new FlowLayout(FlowLayout.CENTER));
31						// フォントと背景色の設定
32			Font f = new Font("MS 明朝", Font.PLAIN, 20);
33			setFont(f);
34			setBackground(Color.cyan);
35						// テキストフィールドとボタンの追加
36							// テキストフィールド
37			tx1 = new TextField(10);
38			tx1.setBackground(Color.white);
39			add(tx1);
40							// ラベル
41			Label lb = new Label("+");
42			lb.setBackground(Color.cyan);
43			add(lb);
44							// テキストフィールド
45			tx2 = new TextField(10);
46			tx2.setBackground(Color.white);
47			add(tx2);
48							// ボタン
49			bt = new Button("=");
50			bt.setBackground(Color.pink);
51			bt.addActionListener(this);   // リスナー
52			add(bt);
53							// テキストフィールド
54			tx3 = new TextField(10);
55			tx3.setBackground(Color.white);
56			add(tx3);
57						// ウィンドウを表示
58			setVisible(true);
59						// イベントアダプタ
60			addWindowListener(new WinEnd());
61		}
62	
63		/******************************/
64		/* ボタンが押されたときの処理 */
65		/******************************/
66		public void actionPerformed(ActionEvent e)
67		{
68			if (e.getSource() == bt) {
69				int a = Integer.parseInt(tx1.getText());
70				int b = Integer.parseInt(tx2.getText());
71				tx3.setText(Integer.toString(a+b));
72			}
73		}
74	
75		/******************************/
76		/* 上,左,下,右の余白の設定 */
77		/******************************/
78		public Insets getInsets()
79		{
80			return new Insets(70, 20, 20, 20);
81		}
82	
83		/************/
84		/* 終了処理 */
85		/************/
86		class WinEnd extends WindowAdapter
87		{
88			public void windowClosing(WindowEvent e) {
89				System.exit(0);
90			}
91		}
92	}
		
15 行目

  ボタンをクリックしたときの処理を行うため,ActionListener を継承しています.

30 行目

  レイアウトマネージャを FlowLayout に変更し,各部品を中央に配置するようにしています.

32~34 行目

  Font クラスを利用して,フォントを設定しています.また,パネルの背景色も設定しています.

37~39,45~47,54~56 行目

  TextField クラス のオブジェクトを付加しています.その際,背景色も指定しています.

41~43 行目

  「+」という Label クラス のオブジェクトを付加しています.その際,背景色も指定しています.

49~52 行目

  「=」というラベルを持った Button クラス のオブジェクトを付加しています.その際,背景色も指定しています.51 行目では,ボタンがクリックされたときの処理を行うため,ボタンに対し ActionListener を設定しています.

63~73 行目

  ActionListener のメソッド actionPerformed 内で,ActionEvent クラスのメソッドを使用し,ボタンがクリックされたときの処理を記述しています.68 行目の if 文は,どのボタンが押されたかを判断するためのものです.複数のボタン等が存在するときは必要ですが,このプログラムでは,必要ありません.

Swing を利用した場合

001	/**************************/
002	/* 2 つの整数の和         */
003	/*    coded by Y.Suganuma */
004	/**************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import javax.swing.*;
008	
009	public class Test {
010		public static void main (String[] args)
011		{
012			Win win = new Win("整数の和");
013		}
014	}
015						// 初期設定
016	class Win extends JFrame
017	{
018		/******************/
019		/* コンストラクタ */
020		/******************/
021		Win(String name)
022		{
023						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
024			super(name);
025						// Windowの大きさ
026			setSize(600, 150);
027			C_Panel pn = new C_Panel();
028			getContentPane().add(pn);
029						// ウィンドウを表示
030			setVisible(true);
031						// イベントアダプタ
032			addWindowListener(new WinEnd());
033		}
034	
035		/******************************/
036		/* 上,左,下,右の余白の設定 */
037		/******************************/
038		public Insets getInsets()
039		{
040			return new Insets(50, 20, 20, 20);
041		}
042	
043		/************/
044		/* 終了処理 */
045		/************/
046		class WinEnd extends WindowAdapter
047		{
048			public void windowClosing(WindowEvent e) {
049				System.exit(0);
050			}
051		}
052	}
053						// 計算機
054	class C_Panel extends JPanel implements ActionListener
055	{
056		JButton bt;
057		JTextField tx1, tx2, tx3;
058								// コンストラクタ
059		C_Panel()
060		{
061										// レイアウトの変更(フローレイアウト)
062			setLayout(new FlowLayout(FlowLayout.CENTER));
063										// フォントの設定
064			Font f = new Font("MS 明朝", Font.PLAIN, 30);
065										// 背景色
066			setBackground(Color.cyan);
067										// テキストフィールド
068			tx1 = new JTextField(10);
069			tx1.setBackground(Color.white);
070			tx1.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
071			tx1.setFont(f);
072			add(tx1);
073										// ラベル
074			JLabel lb = new JLabel("+");
075			lb.setBackground(Color.cyan);
076			add(lb);
077										// テキストフィールド
078			tx2 = new JTextField(10);
079			tx2.setBackground(Color.white);
080			tx2.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
081			tx2.setFont(f);
082			add(tx2);
083										// ボタン
084			bt = new JButton("=");
085			bt.setBackground(Color.pink);
086			bt.addActionListener(this);   // リスナー
087			add(bt);
088										// テキストフィールド
089			tx3 = new JTextField(10);
090			tx3.setBackground(Color.white);
091			tx3.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
092			tx3.setFont(f);
093			add(tx3);
094		}
095								// ボタンが押されたときの処理
096		public void actionPerformed(ActionEvent e)
097		{
098			if (e.getSource() == bt) {
099				int a = Integer.parseInt(tx1.getText());
100				int b = Integer.parseInt(tx2.getText());
101				tx3.setText(Integer.toString(a+b));
102			}
103		}
104	}
		
054 行目

  ボタンをクリックしたときの処理を行うため,ActionListener を継承しています.

062 行目

  レイアウトマネージャを FlowLayout に変更し,各部品を中央に配置するようにしています.

064~066 行目

  Font クラスのオブジェクトを生成すると共に,パネルの背景色を設定しています.

068~072,078~082,089~093 行目

  JTextField クラス のオブジェクトを付加しています.その際,背景色,文字の水平位置,及び,フォントを指定しています.

074~076 行目

  「+」という JLabel クラス のオブジェクトを付加しています.その際,背景色も指定しています.

084~087 行目

  「=」というラベルを持った JButton クラス のオブジェクトを付加しています.その際,背景色も指定しています.46 行目では,ボタンがクリックされたときの処理を行うため,ボタンに対し ActionListener を設定しています.

096~103 行目

  ActionListener のメソッド actionPerformed 内で,ボタンがクリックされたときの処理を記述しています.58 行目の if 文は,どのボタンが押されたかを判断するためのものです.複数のボタン等が存在するときは必要ですが,このプログラムでは,必要ありません.

13.2.2 グラフィックス

(プログラム例 13.6 ) 描画の基本

  Java には,直線,四角形,円などを描画する機能があります.この例では,AWT を利用して,簡単な図形を様々な方法で表示しています.まず,トップに文字列を描画しています.次に,一番左側の図形は Graphics クラスのメソッドを利用して矩形を描いた例です.二番目の図形は,線の太さを変更するために,Graphics2D クラスのメソッドを利用して,同じ図形を描いたものです.三番目の図形では,外部から読み込んだ画像を表示しています.また,最後の図形では,ピクセル値を直接操作して,三番目と同じ図形を生成し,それを表示しています.

AWT を利用した場合

001	/*************************/
002	/* 描画の基本            */
003	/*   coded by Y.Suganuma */
004	/*************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import java.awt.image.*;
008	import java.awt.geom.*;
009	
010	public class Test {
011		public static void main (String[] args)
012		{
013			Win win = new Win("描画の基本");
014		}
015	}
016	
017	class Win extends Frame
018	{
019		int w = 40, h = 40, size;   // 図形の大きさ
020		int pixels[];   // 図形
021		Dimension d;   // 画面の大きさ
022		MemoryImageSource mis;
023		Image im1, im2;
024						// init
025		/******************/
026		/* コンストラクタ */
027		/******************/
028		Win(String name)
029		{
030						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
031			super(name);
032						// Windowの大きさ
033			setSize(320, 180);
034								// 背景色
035			setBackground(new Color(255, 255, 255));
036								// 初期設定
037			d      = getSize();
038			size   = w * h;
039			pixels = new int [size];
040								// 画像の読み込み
041			im1 = getToolkit().getImage("sq.gif");
042								// ピクセル操作
043			for (int i1 = 0; i1 < h; i1++) {
044				for (int i2 = 0; i2 < w; i2++) {
045					if (i1 < w/2)
046						pixels[i1*w+i2] = 0xffff0000;
047					else
048						pixels[i1*w+i2] = 0xff00ff00;
049				}
050			}
051			mis = new MemoryImageSource(w, h, pixels, 0, w);
052			im2 = createImage(mis);
053						// ウィンドウを表示
054			setVisible(true);
055						// イベントアダプタ
056			addWindowListener(new WinEnd());
057		}
058						// 描画
059		public void paint (Graphics g)
060		{
061			int x, y = d.height / 2 - h / 2;
062								// 描画( String )
063			Font f = new Font("TimesRoman", Font.BOLD, 20);
064			g.setFont(f);
065			g.drawString("描画の基本", d.width / 2 - 50, y);
066								// 描画( Graphics )
067			y += 20;
068			x = d.width / 8 - w / 2;
069			g.setColor(Color.red);   // 描く色
070			g.drawRect(x, y, w, h/2);   // 四角形の描画
071			g.setColor(Color.green);   // 描く色
072			g.drawRect(x, y+h/2, w, h/2);   // 四角形の描画
073								// 描画( Graphics2D )
074			x = d.width / 4 + d.width / 8 - w / 2;
075			Graphics2D g2 = (Graphics2D)g;   // Graphics2D オブジェクトの取得
076			g2.setStroke(new BasicStroke(5.0f));   // 線の種類と太さ
077			g2.setColor(Color.red);   // 描く色
078			g2.draw(new Rectangle2D.Double(x, y, w, h/2));   // 四角形の描画
079			g2.setColor(Color.green);   // 描く色
080			g2.draw(new Rectangle2D.Double(x, y+h/2, w, h/2));   // 四角形の描画
081								// 外部画像
082			x = d.width / 4 * 2 + d.width / 8 - w / 2;
083			g.drawImage(im1, x, y, this);
084								// ピクセル操作
085			x = d.width / 4 * 3 + d.width / 8 - w / 2;
086			g.drawImage(im2, x, y, this);
087		}
088	
089		/******************************/
090		/* 上,左,下,右の余白の設定 */
091		/******************************/
092		public Insets getInsets()
093		{
094			return new Insets(70, 20, 20, 20);
095		}
096	
097		/************/
098		/* 終了処理 */
099		/************/
100		class WinEnd extends WindowAdapter
101		{
102			public void windowClosing(WindowEvent e) {
103				System.exit(0);
104			}
105		}
106	}
		
008 行目

  Graphics2D クラスのオブジェクトを使用して描画する際に,Shape インタフェースを必要とするため,この記述が必要になります.

038 行目~ 039 行目

  画像( 40 × 40 ピクセル)の各ピクセル情報を記憶するための配列を定義しています.

041 行目

  Toolkit クラスのメソッド getImage を利用して,画像ファイル( sq.gif )の読み込んでいます.

043 行目~ 050 行目

  画像の上半分が赤に,また,下半分が青になるように,画像データ pixels を設定しています.各ピクセルの透明度,赤,緑,青に対する情報が,4 バイト 1 組になり,1 次元配列 pixels に設定されます.本来,画像は,画像の高さが h ピクセル,幅が w ピクセルであった場合,h 行 w 列の 2 次元配列としてとらえた方が理解しやすいのですが,ここでは,それを 1 元配列として表現しています.一般に,2 次元配列が連続した領域にとられた場合,2 次元配列の i 行 j 列は,1 次元配列の添え字 (i * w + j) の位置に相当します.

051 行目

  画像データ pixels から,MemoryImageSource クラスのオブジェクトを生成しています.

052 行目

  MemoryImageSource クラスのオブジェクトを Image クラスのオブジェクトに変換しています.

063 行目~ 065 行目

  "描画の基本" という文字列を描画しています.

067 行目~ 072 行目

  Graphics クラスのオブジェクトを利用して,赤線(上半分),及び,青線(下半分)の矩形を描画しています.

074 行目~ 080 行目

  Graphics2D クラスのオブジェクトを利用して,赤線,及び,青線の矩形を描画しています.BasicStroke クラスは,単純な図形の輪郭線を描画する属性の基本セットを定義します.BasicStroke クラスで定義される描画属性は,Graphics2D オブジェクトによって Shape の輪郭を描画する際,線の太さ,端部の装飾,輪郭線セグメントの接合方法などに利用されます.また,Rectangle2D.Double クラスは,Shape インタフェースを継承しており,double 座標で指定される矩形を定義します.

082 行目~ 086 行目

  外部から読み込んだ画像,及び,ピクセル操作で作成した画像を表示している.

  以下に示すプログラムは,上と同じ描画を,Swing を使用して行った例です.JFrame クラスにおいては,JFrame クラスの唯一のコンテナであるコンテントペインに対して,コンポーネントの追加や描画を行います.なお,コンテナとは,様々な要素を入れる箱のようなものです.そのため,コンテントペインが,JFrame のすべての子の親になり,部品の貼り付けやレイアウトの変更は,コンテントペインに対して行ってやる必要があります.従って,JFrame を継承した場合,コンテントペインオブジェクトを取得し,その上に描画等を行ってやる必要があります.

  また,Swing では,AWT と異なり,フレームの再描画の時,クライアント領域を背景色で塗りつぶしません.AWT の Frame クラスのクライアント領域は,Update() が呼び出されると背景色で塗りつぶして初期化されますが,Swing では Update() をオーバーライドし,初期化せずに再描画するようになっています.一般的に,swing においては,描画する際,paint ではなく,paintComponent をオーバーライドすることになります.

Swing を利用した場合

001	/*************************/
002	/* 描画の基本            */
003	/*   coded by Y.Suganuma */
004	/*************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import java.awt.image.*;
008	import javax.swing.*;
009	import java.awt.geom.*;
010	
011	public class Test {
012		public static void main (String[] args)
013		{
014			Win win = new Win("描画の基本");
015		}
016	}
017	
018	class Win extends JFrame
019	{
020		/******************/
021		/* コンストラクタ */
022		/******************/
023		Win(String name)
024		{
025						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
026			super(name);
027						// Windowの大きさ
028			setSize(360, 180);
029						// 描画パネル
030			DrawPanel pn = new DrawPanel(this);
031			getContentPane().add(pn);   
032						// ウィンドウを表示
033			setVisible(true);
034						// イベントアダプタ
035			addWindowListener(new WinEnd());
036		}
037	
038		/******************************/
039		/* 上,左,下,右の余白の設定 */
040		/******************************/
041		public Insets getInsets()
042		{
043			return new Insets(0, 5, 5, 5);
044		}
045	
046		/************/
047		/* 終了処理 */
048		/************/
049		class WinEnd extends WindowAdapter
050		{
051			public void windowClosing(WindowEvent e) {
052				System.exit(0);
053			}
054		}
055	}
056						// DrawPanel
057	class DrawPanel extends JPanel
058	{
059		int w = 40, h = 40, size;   // 図形の大きさ
060		int pixels[];   // 図形
061		Dimension d;   // 画面の大きさ
062		MemoryImageSource mis;
063		Image im1, im2;
064	
065		DrawPanel(Win dr) {
066			d = dr.getSize();   // 画面の大きさ
067								// 背景色
068			setBackground(new Color(255, 255, 255));
069								// 初期設定
070			size   = w * h;
071			pixels = new int [size];
072								// 画像の読み込み
073			im1 = dr.getToolkit().getImage("sq.gif");
074								// ピクセル操作
075			for (int i1 = 0; i1 < h; i1++) {
076				for (int i2 = 0; i2 < w; i2++) {
077					if (i1 < w/2)
078						pixels[i1*w+i2] = 0xffff0000;
079					else
080						pixels[i1*w+i2] = 0xff00ff00;
081				}
082			}
083			mis = new MemoryImageSource(w, h, pixels, 0, w);
084			im2 = createImage(mis);
085		}
086						// 描画
087		public void paintComponent(Graphics g)
088		{
089			super.paintComponent(g);   // 親クラスの描画
090			int x, y = d.height / 2 - h / 2;
091								// 描画( String )
092			Font f = new Font("TimesRoman", Font.BOLD, 20);
093			g.setFont(f);
094			g.drawString("描画の基本", d.width / 2 - 50, y);
095								// 描画( Graphics )
096			y += 20;
097			x = d.width / 8 - w / 2 - 10;
098			g.setColor(Color.red);   // 描く色
099			g.drawRect(x, y, w, h/2);   // 四角形の描画
100			g.setColor(Color.green);   // 描く色
101			g.drawRect(x, y+h/2, w, h/2);   // 四角形の描画
102								// 描画( Graphics2D )
103			x = d.width / 4 + d.width / 8 - w / 2;
104			Graphics2D g2 = (Graphics2D)g;   // Graphics2D オブジェクトの取得
105			g2.setStroke(new BasicStroke(5.0f));   // 線の種類と太さ
106			g2.setColor(Color.red);   // 描く色
107			g2.draw(new Rectangle2D.Double(x, y, w, h/2));   // 四角形の描画
108			g2.setColor(Color.green);   // 描く色
109			g2.draw(new Rectangle2D.Double(x, y+h/2, w, h/2));   // 四角形の描画
110								// 外部画像
111			x = d.width / 4 * 2 + d.width / 8 - w / 2;
112			g.drawImage(im1, x, y, this);
113								// ピクセル操作
114			x = d.width / 4 * 3 + d.width / 8 - w / 2;
115			g.drawImage(im2, x, y, this);
116		}
117	}
		
009 行目

  Graphics2D クラスのオブジェクトを使用して描画する際に,Shape インタフェースを必要とするため,この記述が必要になります.

030 行目~ 031 行目

  具体的なコンテントペインオブジェクトを,JFrame クラスのメソッドである getContentPane によって取得し,そこに,DrawPanel クラスのオブジェクト pn (パネル)を貼り付けています,

070 行目~ 071 行目

  画像( 40 × 40 ピクセル)の各ピクセル情報を記憶するための配列を定義しています.

073 行目

  Toolkit クラスのメソッド getImage を利用して,画像ファイル( sq.gif )の読み込んでいます.

075 行目~ 082 行目

  画像の上半分が赤に,また,下半分が青になるように,画像データ pixels を設定しています.各ピクセルの透明度,赤,緑,青に対する情報が,4 バイト 1 組になり,1 次元配列 pixels に設定されます.本来,画像は,画像の高さが h ピクセル,幅が w ピクセルであった場合,h 行 w 列の 2 次元配列としてとらえた方が理解しやすいのですが,ここでは,それを 1 元配列として表現しています.一般に,2 次元配列が連続した領域にとられた場合,2 次元配列の i 行 j 列は,1 次元配列の添え字 (i * w + j) の位置に相当します.

083 行目

  画像データ pixels から,MemoryImageSource クラスのオブジェクトを生成しています.

084 行目

  MemoryImageSource クラスのオブジェクトを Image クラスのオブジェクトに変換しています.

089 行目

  親クラスのコンストラクタを呼んでいます.paintComponent をオーバーライドするときは必ず必要です.なお,superは,親クラスを表すキーワードです.

092 行目~ 094 行目

  "描画の基本" という文字列を描画しています.

096 行目~ 101 行目

  Graphics クラスのオブジェクトを利用して,赤線,及び,青線の矩形を描画しています.

103 行目~ 109 行目

  Graphics2D クラスのオブジェクトを利用して,赤線,及び,青線の矩形を描画しています.BasicStroke クラスは,単純な図形の輪郭線を描画する属性の基本セットを定義します.BasicStroke クラスで定義される描画属性は,Graphics2D オブジェクトによって Shape の輪郭を描画する際,線の太さ,端部の装飾,輪郭線セグメントの接合方法などに利用されます.また,Rectangle2D.Double クラスは,Shape インタフェースを継承しており,double 座標で指定される矩形を定義します.

111 行目~ 115 行目

  外部から読み込んだ画像,及び,ピクセル操作で作成した画像を表示している.

13.3 アニメーション

  アニメーションの基本は,Thread クラスを使用し,sleep メソッドによって一定時間毎に何らかの処理を行うことです.ただし,9.2 節で述べたように,多重継承ができないため,Runnable インタフェースを利用しています.また,ダブルバッファリング機能によって高速に描画しなければならないことも多くありますが,Swing は,ダブルバッファリングをデフォルトでサポートしているため,ダブルバッファリングを自分で記述する必要がありません.そこで,アニメーションの例においては,基本的に Swing を使用していきます.なお,JComponent クラスには,ダブルバッファリングの状態を調べたり,または,設定するため,
public boolean isDoubleBuffered()
public void setDoubleBuffered(boolean sw)		
のようなメソッドが用意されています.なお,アニメーションの場合,静止画像では分かり難いと思いますので,可能な場合は,JavaScript による類似のプログラムも添付しておきます(クリックすれば,実行可能).

13.3.1 アニメーションと開始と停止

(プログラム例 13.7 ) アニメーションと開始と停止JavaScript の場合

  この例においては,100 ms 毎に半径の異なる円を描画し,アニメーションを作成しています.また,ボタンをクリックすることによって,スレッドの停止・開始の制御を行うことができます.
001	/******************************/
002	/* アニメーションと開始と停止 */
003	/*   coded by Y.Suganuma      */
004	/******************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import javax.swing.*;
008	
009	public class Test {
010		public static void main (String[] args)
011		{
012			Win win = new Win("開始と停止");
013		}
014	}
015	
016	class Win extends JFrame implements Runnable, ActionListener
017	{
018		boolean state;
019		JButton b_start, b_stop;
020		Test_Panel dp;
021		Thread th;
022	
023		/******************/
024		/* コンストラクタ */
025		/******************/
026		Win(String name)
027		{
028						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
029			super(name);
030						// Windowの大きさ
031			setSize(460, 320);
032						// コンテントペインの取得
033			Container cP = getContentPane();
034						// レイアウト,背景色,フォント
035			JPanel pn = new JPanel();
036			pn.setLayout(new BorderLayout(5, 10));
037			pn.setBackground(new Color(225, 255, 225));
038			cP.add(pn);
039			Font f = new Font("TimesRoman", Font.BOLD, 20);
040						// 上のパネル(ボタンの設定)
041			JPanel pn1 = new JPanel();
042			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
043			pn1.setBackground(new Color(225, 255, 225));
044			pn.add(pn1, BorderLayout.NORTH);
045			b_start = new JButton("開始");
046			b_start.addActionListener(this);
047			b_start.setFont(f);
048			pn1.add(b_start);
049			b_stop = new JButton("停止");
050			b_stop.addActionListener(this);
051			b_stop.setFont(f);
052			pn1.add(b_stop);
053						// 中央のパネル(描画領域)
054			JPanel pn2 = new JPanel();
055			pn2.setBackground(new Color(225, 255, 225));
056			pn2.setLayout(null);
057			pn.add(pn2, BorderLayout.CENTER);
058			dp = new Test_Panel();
059			dp.setSize(400, 175);
060			dp.setLocation(10, 10);
061			pn2.add(dp);
062						// スレッドの生成
063			th = new Thread(this);
064						// ウィンドウを表示
065			setVisible(true);
066						// イベントアダプタ
067			addWindowListener(new WinEnd());
068		}
069	
070		public void run()   // Runnable インタフェースのメソッド(必須)
071		{
072			while (state) {
073				dp.count++;
074				if (dp.count > 10)
075					dp.count = 0;
076				dp.repaint();   // 再描画
077				try {
078					th.sleep(100);   // 100 ms 毎の描画
079				}
080				catch (InterruptedException e) {}
081			}
082		}
083	
084		public void actionPerformed(ActionEvent e)
085		{
086			if (e.getSource() == b_start) {   // 開始
087				state = true;
088				th.start();
089			}
090			else {   // 停止
091				state    = false;
092				th       = new Thread(this);
093				dp.count = 0;
094				dp.repaint();
095			}
096		}
097	
098		/******************************/
099		/* 上,左,下,右の余白の設定 */
100		/******************************/
101		public Insets getInsets()
102		{
103			return new Insets(50, 20, 20, 20);
104		}
105	
106		/************/
107		/* 終了処理 */
108		/************/
109		class WinEnd extends WindowAdapter
110		{
111			public void windowClosing(WindowEvent e) {
112				System.exit(0);
113			}
114		}
115	}
116	
117	class Test_Panel extends JPanel
118	{
119		int count;
120		Test_Panel()
121		{
122			setBackground(Color.white);
123			count = 0;
124		}
125		public void paintComponent (Graphics g)   // 描画
126		{
127			super.paintComponent(g);   // 親クラスの描画(必ず必要)
128			int i1, r;
129			r = 10;
130			for (i1 = 0; i1 < count; i1++) {
131				g.drawOval(0, 0, 2*r, 2*r);
132				r = (int)(1.5 * r);
133			}
134		}
135	}
		
016 行目

  Runnable インタフェースを継承しています.Runnable インタフェースは,マルチスレッドを実現するために使用されるインタフェースです.アニメーションを作成する場合,必ずしも連続的に描画する必要はなく,ある一定時間間隔で描画を実行すれば,我々はそれを連続的に動いているように認識できます.そこで,現在動作しているスレッドとは別に,アニメーションの描画を行うスレッドを生成し,そのスレッドをある一定時間間隔で動作させるといった方法がよく使用されます.その際に必要なのが,Runnable インタフェースです.また,ボタンをクリックしたときの処理を行うため,ActionListener インタフェースも継承しています.

035 行目~ 039 行目

  ContentPane に貼り付けられたパネル pn のレイアウトマネージャを BorderLayout に変更し,その背景色を設定すると共に,Font クラスのオブジェクトを生成しています.

041 行目~ 052 行目

  BorderLayout の上の部分に,パネルを貼り付け,そこに,「開始」及び「停止」ボタン( JButton クラスのオブジェクト)を追加しています.また,各ボタンには,ボタンをクリックしたときの処理を行うため,ActionListener を付加しています.

054 行目~ 061 行目

  JPanel クラスのオブジェクト pn2 を BorderLayout の中央に追加しています.アニメーションを描画するためのパネル,Test_Panel クラス( 117 行目~ 135 行目)のオブジェクトを pn2 の上に配置しています.これは,白い描画領域の回りに,薄い緑色の領域を作るためです.

063 行目

  Thread クラスのオブジェクトを生成しています.

070 行目~ 082 行目

  Thread クラスのメソッド run に対するオーバーライドです.Runnable インタフェースを継承してスレッドを利用する場合,必ずこのメソッドを定義してやる必要があります.
  • 072 行目: state が true である間,073 行目~ 080 行目の処理が実行されます.
  • 073 行目~ 075 行目: Test_Panel クラスのプロパティである count を 1 だけ増やし,その値が 10 を越えたら 0 に戻しています.
  • 076 行目: Test_Panel クラスのオブジェクトを再描画しています.
  • 077 行目~ 080 行目: この設定により,このスレッドは 100 ms 毎に実行されることになります.

086 行目~ 089 行目

  「開始」ボタンがクリックされたときの処理です.state を true に設定し,スレッドを開始しています.

090 行目~ 095 行目

  「停止」ボタンがクリックされたときの処理です.state を false に設定し(スレットの停止),次の開始のために新しいスレッドを生成し,カウンタ( Test_Panel クラスのプロパティ count )の初期設定を行い,最後に,再描画しています.

117 行目~ 135 行目

  Test_Panel クラスの定義です.
  • 120 行目~ 124 行目: コンストラクタであり,背景の設定とカウンタの初期設定を行っています.
  • 125 行目~ 134 行目: メソッド paintComponent のオーバーライドであり,count 個だけ,半径の異なる円を描画しています.

13.3.2 アニメーション作成方法

  以下,アニメーションをいくつかの方法で作成した例を示します.勿論,それらの方法を同時に使ってアニメーションを作成することも可能です.

(プログラム例 13.8 ) ボールの運動(描画)JavaScript の場合

  先に述べたように,一定時間毎に何らかの処理を行うことによってアニメーションを作成することができます.プログラム例 13.7 では,「半径の異なる円を順に描く」という方法で作成しました.以下に示す例も同様の方法で作成してあります.ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.
001	/*************************/
002	/* ボールの運動(描画)  */
003	/*   coded by Y.Suganuma */
004	/*************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import javax.swing.*;
008	
009	public class Test {
010		public static void main (String[] args)
011		{
012			Win win = new Win("ボールの運動(描画)");
013		}
014	}
015	
016	class Win extends JFrame
017	{
018		Ball_Panel pn;
019	
020		/******************/
021		/* コンストラクタ */
022		/******************/
023		Win(String name)
024		{
025						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
026			super(name);
027						// Windowの大きさ
028			setSize(620, 480);
029						// コンテントペインの取得
030			Dimension d = getSize();
031			pn = new Ball_Panel(d);
032			getContentPane().add(pn);
033						// ウィンドウを表示
034			setVisible(true);
035						// イベントアダプタ
036			addWindowListener(new WinEnd());
037		}
038	
039		/******************************/
040		/* 上,左,下,右の余白の設定 */
041		/******************************/
042		public Insets getInsets()
043		{
044			return new Insets(50, 20, 20, 20);
045		}
046	
047		/************/
048		/* 終了処理 */
049		/************/
050		class WinEnd extends WindowAdapter
051		{
052			public void windowClosing(WindowEvent e) {
053				System.exit(0);
054			}
055		}
056	}
057	
058	class Ball_Panel extends JPanel implements Runnable
059	{
060		boolean state = true;
061		private double g = 9.8;   // y 軸方向の加速度
062		private double v0 = 0;   // y 軸方向の初期速度
063		private double v = 0;   // y 軸方向の現在速度
064		private double t = 0;   // 時間の初期値
065		private double h0;   // ボールの初期位置の y 座標(上が正,初期高さ)
066		private double x, y;   // ボールの現在位置の x 座標,y 座標(上が正)
067		private int sw = 1;   // ボールの状態( 0:停止している,1:動いている)
068		private Dimension d;
069		private Thread th;
070	
071		Ball_Panel(Dimension d1)
072		{
073			d = d1;
074						// 背景色
075			setBackground(new Color(238, 255, 238));
076						// 初期設定
077			h0 = d.height + 40;
078			x  = -40;
079			y  = -40;
080			addMouseListener(new Mouse());
081						// スレッドの生成
082			th = new Thread(this);
083			th.start();
084		}
085	
086		public void run()
087		{
088			while (state) {
089				try {
090					th.sleep(33);
091				}
092				catch (InterruptedException e) {}
093				if (x < d.width + 80 && sw > 0) {
094					x += 1.5;
095					t += 0.1;
096					v  = -g * t + v0;
097					y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
098					if (y >= d.height - 150 && v < 0) {
099						y  = d.height - 150;
100						v0 = -0.8 * v;
101						h0 = 150;
102						t  = 0;
103					}
104					repaint();
105				}
106			}
107		}
108	
109		public void paintComponent (Graphics g)   // 描画
110		{
111			super.paintComponent(g);   // 親クラスの描画(必ず必要)
112			g.setColor(Color.green);
113			g.fillOval((int)x, (int)y, 80, 80);
114		}
115	
116		class Mouse extends MouseAdapter {
117			public void mouseClicked(MouseEvent e)
118			{
119				int mx = e.getX();
120				int my = e.getY();
121				double x1 = x + 40 - mx;
122				double y1 = y + 40 - my;
123				double r  = Math.sqrt(x1 * x1 + y1 * y1);
124				if (r < 40) {
125					if (sw > 0)
126						sw = 0;
127					else
128						sw = 1;
129				}
130			}
131		}
132	}
		
077 行目~ 080 行目

  ボールの初期位置を設定し,MouseListener インタフェースを追加しています(内部クラス Mouse の利用).

093 行目~ 105 行目

  ボールが動いており( sw > 0 ),かつ,画面内に存在したときの処理です.
  • 094 行目: ボールの x 座標の変更( 1.5 ピクセル / 33 ms )
  • 095 行目: 時間の変更( 0.1 秒 / 33 ms )
  • 096 行目: ボールの速度の変更
  • 097 行目: ボールの y 座標の変更.画面座標に合うように座標変換も行っています.
  • 098 行目~ 103 行目: ボールが地面に落ちたときの跳ね返り処理を行っています.その際,速度が 0.8 倍されます.
  • 104 行目: 再描画

116 行目~ 131 行目

  画面をクリックすると実行されるメソッドです.ただし,クリックした場所がボールの外側である場合は何も行われません.ボールの内側であるときは,ボールが動いている場合は停止,停止している場合は再び動かします.

(プログラム例 13.9 ) ボールの運動(外部の 1 画像)

  複雑な画像を描きたいような場合は,上に示したような方法では難しくなります.この例では,円に対応する外部画像( ball.gif,右図参照 )を,Image クラスのオブジェクトとして読み込み,それを画面に表示しています.上の例と同様,ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.外部画像を読み込み表示する以外,上で述べたプログラムとほとんど同じですので説明は省略します.
/**********************************/
/* ボールの運動(外部の 1 画像)  */
/*   coded by Y.Suganuma          */
/**********************************/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Test {
	public static void main (String[] args)
	{
		Win win = new Win("ボールの運動(外部の 1 画像)");
	}
}

class Win extends JFrame
{
	/******************/
	/* コンストラクタ */
	/******************/
	Win(String name)
	{
					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
		super(name);
					// Windowの大きさ
		setSize(620, 480);
					// コンテントペインの取得
		Dimension d      = getSize();
		Image im         = getToolkit().getImage("ball.gif");   // 画像の読み込み
		Ani_3_s_Panel pn = new Ani_3_s_Panel(d, im);
		getContentPane().add(pn);
					// ウィンドウを表示
		setVisible(true);
					// イベントアダプタ
		addWindowListener(new WinEnd());
	}

	/******************************/
	/* 上,左,下,右の余白の設定 */
	/******************************/
	public Insets getInsets()
	{
		return new Insets(50, 20, 20, 20);
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}

class Ani_3_s_Panel extends JPanel implements Runnable
{
	private boolean state = true;
	private double g = 9.8;
	private double v0 = 0;
	private double v = 0;
	private double t = 0;
	private double h0, x, y;
	private int sw = 1;
	private Dimension d;
	private Thread th;
	private Image im;

	Ani_3_s_Panel(Dimension d1, Image im1)
	{
		d  = d1;
		im = im1;
					// 背景色
		setBackground(new Color(238, 255, 238));
					// 初期設定
		h0 = d.height + 40;
		x  = -40;
		y  = -40;
		addMouseListener(new Mouse());
					// スレッドの生成
		th = new Thread(this);
		th.start();
	}

	public void stop()
	{
		state = false;
	}

	public void run()
	{
		while (state) {
			try {
				th.sleep(33);
			}
			catch (InterruptedException e) {}
			if (x < d.width + 80 && sw > 0) {
				x += 1.5;
				t += 0.1;
				v  = -g * t + v0;
				y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
				if (y >= d.height - 150 && v < 0) {
					y  = d.height - 150;
					v0 = -0.8 * v;
					h0 = 150;
					t  = 0;
				}
				repaint();
			}
		}
	}

	public void paintComponent (Graphics g)   // 描画
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
		g.drawImage(im, (int)x, (int)y, this);
	}

	class Mouse extends MouseAdapter {
		public void mouseClicked(MouseEvent e)
		{
			int mx, my;
			double x1, y1, r;

			mx = e.getX();
			my = e.getY();
			x1 = x + 40 - mx;
			y1 = y + 40 - my;
			r  = Math.sqrt(x1 * x1 + y1 * y1);
			if (r < 40) {
				if (sw > 0)
					sw = 0;
				else
					sw = 1;
			}
		}
	}
}
		

(プログラム例 13.10 ) ランニング(外部の複数画像)JavaScript の場合

  プログラム例 13.9 と全く同じ方法ですが,以下に示すように,6 枚の画像を利用しています.

  

  

001	/****************************/
002	/* ランニング               */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import javax.swing.*;
008	
009	public class Test {
010		public static void main (String[] args)
011		{
012			Win win = new Win("ランニング");
013		}
014	}
015	
016	class Win extends JFrame
017	{
018		Ani_7_Panel pn;
019	
020		/******************/
021		/* コンストラクタ */
022		/******************/
023		Win(String name)
024		{
025						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
026			super(name);
027						// Windowの大きさ
028			setSize(640, 470);
029						// 画像の読み込み
030			Image a[] = new Image [6];
031			a[0] = getToolkit().getImage("fig0.gif");
032			a[1] = getToolkit().getImage("fig1.gif");
033			a[2] = getToolkit().getImage("fig2.gif");
034			a[3] = getToolkit().getImage("fig3.gif");
035			a[4] = getToolkit().getImage("fig4.gif");
036			a[5] = getToolkit().getImage("fig5.gif");
037						// コンテントペインの取得
038			pn = new Ani_7_Panel(a);
039			getContentPane().add(pn);
040						// ウィンドウを表示
041			setVisible(true);
042						// イベントアダプタ
043			addWindowListener(new WinEnd());
044		}
045	
046		/******************************/
047		/* 上,左,下,右の余白の設定 */
048		/******************************/
049		public Insets getInsets()
050		{
051			return new Insets(50, 20, 20, 20);
052		}
053	
054		/************/
055		/* 終了処理 */
056		/************/
057		class WinEnd extends WindowAdapter
058		{
059			public void windowClosing(WindowEvent e) {
060				System.exit(0);
061			}
062		}
063	}
064	
065	class Ani_7_Panel extends JPanel implements Runnable
066	{
067		boolean win_state = true;
068		Image a[] = new Image [6];
069		Image now_im;
070		int now_id = 0, x, y;
071		Thread load;
072	
073		Ani_7_Panel(Image a1[])
074		{
075			a = a1;
076			setBackground(Color.white);
077			load = new Thread(this);
078			load.start();
079		}
080	
081		public void run()
082		{
083			while (win_state) {
084				now_im = a[now_id%6];
085				if (now_id == 0) {
086					x = -20;
087					y = 200 - 238 / 2;
088				}
089				else if (now_id == 1) {
090					x = 5;
091					y = 200 - 249 / 2;
092				}
093				else if (now_id == 2) {
094					x = 70;
095					y = 200 - 258 / 2;
096				}
097				else if (now_id == 3) {
098					x = 140;
099					y = 200 - 258 / 2;
100				}
101				else if (now_id == 4) {
102					x = 150;
103					y = 200 - 258 / 2;
104				}
105				else if (now_id == 5) {
106					x = 160;
107					y = 200 - 247 / 2;
108				}
109				else if (now_id == 6) {
110					x = 200;
111					y = 200 - 238 / 2;
112				}
113				else if (now_id == 7) {
114					x = 225;
115					y = 200 - 249 / 2;
116				}
117				else if (now_id == 8) {
118					x = 290;
119					y = 200 - 258 / 2;
120				}
121				else if (now_id == 9) {
122					x = 360;
123					y = 200 - 258 / 2;
124				}
125				else if (now_id == 10) {
126					x = 370;
127					y = 200 - 258 / 2;
128				}
129				else if (now_id == 11) {
130					x = 380;
131					y = 200 - 247 / 2;
132				}
133				else if (now_id == 12) {
134					x = 420;
135					y = 200 - 238 / 2;
136				}
137				else if (now_id == 13) {
138					x = 445;
139					y = 200 - 249 / 2;
140				}
141				else if (now_id == 14) {
142					x = 510;
143					y = 200 - 258 / 2;
144				}
145				else if (now_id == 15) {
146					x = 580;
147					y = 200 - 258 / 2;
148				}
149				else if (now_id == 16) {
150					x = 590;
151					y = 200 - 258 / 2;
152				}
153				else {
154					x = 600;
155					y = 200 - 247 / 2;
156				}
157				repaint();
158				try {
159					load.sleep(100);
160				}
161				catch (InterruptedException e) {}
162				now_id++;
163				if (now_id > 17)
164					now_id = 0;
165			}
166		}
167	
168		public void paintComponent (Graphics g)   // 描画
169		{
170			super.paintComponent(g);   // 親クラスの描画(必ず必要)
171			if (now_im != null)
172				g.drawImage(now_im, x, y, this);   // 位置(左上)
173		}
174	}
		
030 行目~ 036 行目

  Image クラスのオブジェクトを記憶する配列に,画像 fig0.gif ~ fig5.gif を読み込んでいます.

077 行目~ 078 行目

  Thread クラスのオブジェクトを生成し,それをスタートさせています.

084 行目

  表示する画像を,変数 now_im に設定しています( 6 回毎に同じ画像を繰り返す).

085 行目~ 156 行目

  now_id の値によって,画像を描くべき位置を設定しています.

162 行目~ 164 行目

  変数 now_id を増加させ,その値が 17 より大きくなった場合(表示位置が画面の外側になった場合)は,0 に戻しています.

172 行目

  画像を指定した位置に描画しています.

(プログラム例 13.11 ) 花火(ピクセル値の操作)JavaScript の場合

  アニメーションを作成する方法としては,今まで述べた図形を描画する方法や外部から読み込んだ図形を順に表示する方法の他に,MemoryImageSource クラスを利用して,イメージのピクセル値を直接編集する方法が考えられます(もちろん,これらの方法を同時に使用する方法も考えられます).この例は,ピクセル値を直接編集する方法によって作成しています.
001	/****************************/
002	/* 花火(ピクセル値の操作) */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.awt.*;
006	import java.awt.event.*;
007	import java.awt.image.*;
008	import javax.swing.*;
009	import java.util.Random;
010	
011	public class Test {
012		public static void main (String[] args)
013		{
014			Win win = new Win("整数の和");
015		}
016	}
017	
018	class Win extends JFrame
019	{
020		Fire_Panel pn;
021	
022		/******************/
023		/* コンストラクタ */
024		/******************/
025		Win(String name)
026		{
027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
028			super(name);
029						// Windowの大きさ
030			setSize(640, 470);
031						// コンテントペインの取得
032			Dimension d = getSize();
033			pn = new Fire_Panel(d);
034			getContentPane().add(pn);
035						// ウィンドウを表示
036			setVisible(true);
037						// イベントアダプタ
038			addWindowListener(new WinEnd());
039		}
040	
041		/******************************/
042		/* 上,左,下,右の余白の設定 */
043		/******************************/
044		public Insets getInsets()
045		{
046			return new Insets(50, 20, 20, 20);
047		}
048	
049		/************/
050		/* 終了処理 */
051		/************/
052		class WinEnd extends WindowAdapter
053		{
054			public void windowClosing(WindowEvent e) {
055				System.exit(0);
056			}
057		}
058	}
059	
060	class Fire_Panel extends JPanel implements Runnable
061	{
062		private int max = 20;   // 花火の数
063		private int m_pr = 7;   // 打ち上げ間隔の最大値
064		private int m_cl = 10;   // 花火の色の最大値
065		private int f_l = 300;   // 花火の直径
066		private int count = 0;   // カウンタ
067		private int next = 0;   // 次の打ち上げ時期
068		private int x[];   // 花火のx座標
069		private int y[];   // 花火のy座標
070		private int cl[];   // 花火の色
071		private int k[];   // 花火の状態
072						   // (-1:打ち上げ前,0:打ち上げ当初,>0:描いた輪の数)
073		private int height, width, size, f_size, pixels[][], color[];
074		boolean state = true;
075		private Thread th;
076		private MemoryImageSource mis[];
077		private Image im[];
078		private Random rn;
079	
080		Fire_Panel(Dimension d)
081		{
082			int i1;
083						// 背景色
084			setBackground(new Color(0, 0, 0));
085						// 初期設定
086			width  = d.width;
087			height = d.height;
088			size   = width * height;
089			f_size = f_l * f_l;
090			pixels = new int [max][f_size];
091			k      = new int [max];
092			x      = new int [max];
093			y      = new int [max];
094			cl     = new int [max];
095			mis    = new MemoryImageSource [max];
096			im     = new Image [max];
097			for (i1 = 0; i1 < max; i1++)
098				k[i1] = -1;
099			rn    = new Random();
100			color = new int [m_cl];
101			color[0] = 0xffff0000;
102			color[1] = 0xff00ff00;
103			color[2] = 0xff0000ff;
104			color[3] = 0xffffff00;
105			color[4] = 0xffff00ff;
106			color[5] = 0xff00ffff;
107			color[6] = 0xffeeffee;
108			color[7] = 0xffffaaaa;
109			color[8] = 0xffaaffaa;
110			color[9] = 0xffaaaaff;
111						// スレッドの生成
112			th = new Thread(this);
113			th.start();
114		}
115	
116		public void run()
117		{
118			double ang, s;
119			int i0, i1, i2, i3, kx, ky, kxy, sw;
120	
121			while (state) {
122				try {
123					th.sleep(200);
124				}
125				catch (InterruptedException e) {}
126	
127				sw = 0;
128				for (i0 = 0; i0 < max; i0++) {
129					if (k[i0] < 0) {
130						if (count >= next && sw == 0) {
131							sw     = 1;
132							count  = 0;
133							cl[i0] = (int)(m_cl * rn.nextDouble());
134							if (cl[i0] >= m_cl)
135								cl[i0] = m_cl - 1;
136							for (i1 = 0; i1 < f_size; i1++)
137								pixels[i0][i1] = 0x00000000;
138							x[i0]   = (int)(width * rn.nextDouble()) - f_l / 2;
139							y[i0]   = (int)(height * rn.nextDouble()) - f_l / 2;
140							k[i0]   = 0;
141							mis[i0] = new MemoryImageSource(f_l, f_l, pixels[i0], 0, f_l);
142							mis[i0].setAnimated(true);
143							im[i0] = createImage(mis[i0]);
144							next   = (int)(m_pr * rn.nextDouble());
145							if (next <= 0)
146								next = 1;
147						}
148					}
149					else {
150						k[i0]++;
151						if (k[i0] > m_pr)
152							k[i0] = -1;
153						else {
154							s   = Math.PI / 6;
155							ang = 0;
156							for (i1 = 0; i1 < 12; i1++) {
157								kx = f_l / 2 + (int)(20 * k[i0] * Math.cos(ang));
158								ky = f_l / 2 + (int)(20 * k[i0] * Math.sin(ang));
159								for (i2 = kx-5; i2 < kx+5; i2++) {
160									for (i3 = ky-5; i3 < ky+5; i3++) {
161										kxy = f_l * i2 + i3;
162										if (kxy >= 0 && kxy < f_size)
163											pixels[i0][kxy] = color[cl[i0]];
164									}
165								}
166								pixels[i0][f_l*(kx-1)+ky-6] = color[cl[i0]];
167								pixels[i0][f_l*kx+ky-6] = color[cl[i0]];
168								pixels[i0][f_l*(kx+1)+ky-6] = color[cl[i0]];
169								pixels[i0][f_l*(kx-1)+ky+5] = color[cl[i0]];
170								pixels[i0][f_l*kx+ky+5] = color[cl[i0]];
171								pixels[i0][f_l*(kx+1)+ky+5] = color[cl[i0]];
172								pixels[i0][f_l*(kx-6)+ky-1] = color[cl[i0]];
173								pixels[i0][f_l*(kx-6)+ky] = color[cl[i0]];
174								pixels[i0][f_l*(kx-6)+ky+1] = color[cl[i0]];
175								pixels[i0][f_l*(kx+5)+ky-1] = color[cl[i0]];
176								pixels[i0][f_l*(kx+5)+ky] = color[cl[i0]];
177								pixels[i0][f_l*(kx+5)+ky+1] = color[cl[i0]];
178								ang += s;
179							}
180							im[i0] = createImage(mis[i0]);
181						}
182					}
183				}
184				count++;
185				repaint();
186			}
187		}
188	
189		public void paintComponent (Graphics g)   // 描画
190		{
191			super.paintComponent(g);   // 親クラスの描画(必ず必要)
192			int i0;
193			for (i0 = 0; i0 < max; i0++) {
194				if (k[i0] >= 0)
195					g.drawImage(im[i0], x[i0], y[i0], this);
196			}
197		}
198	}
		
086 行目~ 089 行目

  画面の大きさから必要なピクセル数を決定し,花火毎にその大きさを持った配列を定義しています.

095 行目

  MemoryImageSource クラスのオブジェクトが入る配列を定義しています.

096 行目

  Image クラスのオブジェクトが入る配列を定義しています.

097 行目~ 098 行目

  すべての花火を打ち上げ前の状態に設定しています.

099 行目

  乱数の初期設定です.

100 行目~ 110 行目

  選択できる花火の 10 種類の色を 透明度と RGB 値で設定しています.

129 行目~ 148 行目

  花火が打ち上げ前の状態( k[i0] < 0 )であるときの処理です.カウンタ count の値が 次の打ち上げ時刻 next 以降であり,かつ,次の花火の打ち上げ時刻が設定されていない場合( sw == 0 )は,新しい花火を打ち上げるために,以下の処理が行われます.
  • 131 行目: 次の花火の打ち上げ時刻設定済み
  • 132 行目: カウンタの初期設定
  • 133 行目~ 135 行目: 花火の色をランダムに選択
  • 136 行目~ 137 行目: 画面の初期設定
  • 138 行目~ 139 行目: 花火の位置をランダムに選択
  • 140 行目: 花火を打ち上げた状態に設定
  • 141 行目: 設定されたピクセル値に基づき,MemoryImageSource クラスのオブジェクトを生成
  • 142 行目: アニメーションを実行するために必要
  • 143 行目: MemoryImageSource クラスのオブジェクトを,Image クラスのオブジェクトに変換
  • 144 行目~ 146 行目: 次の打ち上げ時刻をランダムに選択

150 行目~ 152 行目

  花火を打ち上げた後の処理であり,150 行目において描画する輪の数を増加させ,その数が指定の数( m_pr )より大きくなった場合は,花火を消去します(描画されないようにする).

154 行目~ 179 行目

  指定した輪の数( k[i0] )だけ,半径を 20 ピクセルずつ変化させ,かつ,円周を 12 等分した位置に 幅 9 ピクセル,高さ 9 ピクセルの矩形を描いています.なお,166 行目~ 177 行目は,上で描いた矩形に多少丸みを付けるための処理であり,必ずしも必要ではありません.

180 行目

  MemoryImageSource クラスのオブジェクトを,Image クラスのオブジェクトに変換しています.

184 行目

  カウンタ(時間)の増加処理です.

185 行目

  再描画しています.

13.4 様々な例題

13.4.1 パズル & ゲーム

  ここでは,簡単なパズルやゲームの例を紹介します.もう少し複雑な例に関してはゲームプログラミングを参照して下さい.

(プログラム例 13.12 ) 8 / 15 パズル

  表示される Window に適当な値を設定してやることによって,8 パズル,または.15 パズルを実行できます.プログラムを開始すると下の左図のような Window が表示されます.入力方法は,以下に示すとおりです. → プログラム

   

  一番上(ゲーム)で,まず,8 パズルか 15 パズルかを選びます.

  2 番目(初期設定)は,初期状態を指定するものです.「ランダム」を選べば,コンピュータが自動的に問題を作成してくれます.また,「入力」を選択した場合は,上の右図のような Window ( 8 パズルの場合)が表示され,自分で初期状態を入力することになります.8 パズルであれば 1 から 8 まで,15 パズルであれば 1 から 15 までの数字を入力します.いずれの場合も,一カ所だけ空白ができますが,そのままにしておいても,または,0 を入力してもいずれでも構いません.

  3 番目(ゴール)では,目標状態を入力します.8 パズルの場合,「回転」を選択すれば下に示す上の段の左側の図,そうでなければ,その右に示す図のように目標状態が設定されます.また,15 パズルの場合は,各々,下の段に示す図のようになります.その後,「 OK 」 ボタンをクリックすると(初期状態を入力する場合は,初期状態を入力した後,その Window の 「 OK 」 ボタンをクリックすると),下に示すような目標状態が表示されます.


  目標状態の画面で,「ゲームの実行」ボタンをクリックすると,ゲームが開始されます.移動したいコマをクリックすれば,コマが移動します.コマの移動を繰り返した後,目標状態に達すれば,その時点までにコマを移動した回数が表示されます.目標状態に達しても,ゲームが終了するわけではありません.終了したい場合は,Window の終了ボタン(「×」)をクリックしてください.

  例え 8 パズルであっても,問題によっては解くためにかなりの時間がかかります.そこで,コンピュータによって解くことを考えてみます.8 パズルの解法を実行し,表示されている Window に適当な値を設定した後「OK」ボタンをクリックすると,初期状態を入力する画面が生成されますので,解きたい問題を入力した後,その画面の「OK」ボタンをクリックしてください.

  問題が解けた場合は,

  状態番号 45 深さ=5 評価=4
   1 2 3
   8 0 4
   7 6 5
  成功!!

のように表示されるはずです.「状態番号」の後ろにある数字がコマ(数字)を動かした回数(操作回数)に相当します.なお,場合によっては,

  解決できませんでした!

のような結果になるかもしれません.最大試行回数,探索方法,最大探索深さなどを変更して再度挑戦してみてください.なお,評価関数としては,以下のようなものを利用しています.

  1. 評価関数1 f(n)=g(n)+h(n)
    • g(n):節点nの深さ
    • h(n):節点nのパターンの中で,誤って置かれている駒の数

  2. 評価関数2 f(n)=g(n)+h(n)
    • g(n):節点nの深さ
    • h(n)=p(n)+3s(n)
      p(n):各駒の正しい位置からの距離の和
      s(n):中心以外の駒に対し,その次の駒が正しい順序になっていなければ2,そうでなければ0,中心の駒には1を与える事によって得られる得点

(プログラム例 13.13 ) 巡回セールスマン問題( TSP )

  10 及び 20 都市の巡回セールスマン問題( TSP )に対して,自分で都市間を繋ぎながら解くためのプログラムです.プログラムをスタートさせると,下に示すような Window が表示されますので,どちらかの問題を選択して下さい. → プログラム

  下に示すのは,10 都市を選択した場合に表示される Window です.この Window において,マウスによって適当な都市間を接続してください.接続の方法は以下の通りです.

  1. 適当な都市を選びマウスでクリックします.画面の左上に「 Next Position ? 」というメッセージが現れます(現れなかったら,もう一度マウスでクリックしてください).

  2. 接続したい相手の都市を選びマウスでクリックしてください.2 つの都市が直線で結ばれ,画面の左上にすでに結ばれた都市間の距離の合計が表示されます(表示されない場合は,もう一度マウスでクリックしてください).

  3. 以上の処理を,すべての都市が結ばれるまで繰り返してください.なお,すでに直線で結ばれている 2 つの都市を選択すると,接続が解除されます.

(プログラム例 13.14 ) シューティング風ゲーム

  このプログラムは,キーイベントを使用した簡単なシューティング風ゲームです.左右の矢印キーによって下中央に描かれた黒い矩形(砲台)を左右に動かすことができます.Shift キーをクリックするとレーザ砲が発射され,ターゲット(緑の円)に命中すると,ターゲットの色が一時的にピンクに変化し消滅します.また,ターゲットが,黒い矩形に当たるとゲームオーバーになります.

  ここでは,複数人で作成する大きなプログラムではなく,一人で比較的小さなプログラムを作成する場合について,その作成手順について考えてみます.少なくとも,すべてのプログラムを作成してからコンパイルし,実行してみるといった方法はあまり良い方法ではありません.作成するプログラムにもよりますが,私は,部分的な機能を実現するプログラムを作成し,その機能を確認した後,新しい機能を追加していくといった方法をよく利用します.たとえば,この例の場合は,以下のような手順になります.

  1. ターゲットと砲台の表示( JavaScript の場合):  どのような位置に表示しても構いませんが,後から,これらの位置を変更する必要がありますので,表示位置は変数((xt, yt) と (x, y))で与えておいた方がよいと思います.また,このプログラムでは,変数 game を false に設定することによって,ゲームオーバー画面も確認することができます.
    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import java.awt.event.*;
    06	import javax.swing.*;
    07	
    08	public class Test {
    09		public static void main (String[] args)
    10		{
    11			Game1 g1 = new Game1("シューティングゲーム");
    12		}
    13	}
    14	
    15	class Game1 extends JFrame
    16	{
    17		/******************/
    18		/* コンストラクタ */
    19		/******************/
    20		Game1(String name)
    21		{
    22						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    23			super(name);
    24						// Windowの大きさ
    25			setSize(540, 370);
    26						// 背景色
    27			setBackground(new Color(238, 255, 238));
    28						// 初期設定
    29			Container cp = getContentPane();
    30			cp.setBackground(Color.white);
    31			Draw dp = new Draw(this);
    32			dp.setBackground(new Color(238, 255, 238));
    33			cp.add(dp, BorderLayout.CENTER);
    34						// ウィンドウを表示
    35			setVisible(true);
    36						// イベントアダプタ
    37			addWindowListener(new WinEnd());
    38		}
    39	
    40		/******************************/
    41		/* 上,左,下,右の余白の設定 */
    42		/******************************/
    43		public Insets getInsets()
    44		{
    45			return new Insets(50, 20, 20, 20);
    46		}
    47	
    48		/************/
    49		/* 終了処理 */
    50		/************/
    51		class WinEnd extends WindowAdapter
    52		{
    53			public void windowClosing(WindowEvent e) {
    54				System.exit(0);
    55			}
    56		}
    57	}
    58	
    59	class Draw extends JPanel
    60	{
    61		boolean game = true;   // ゲーム実行中か否か
    62		int xt = 20, yt = 50;   // ターゲットの位置
    63		int x, y;   // 砲台の位置
    64		int r = 25;   // ターゲットの半径
    65		Dimension d;
    66		Insets in;
    67		Game1 gm;
    68	
    69		Draw(Game1 gm1) {
    70			gm = gm1;
    71						// 初期設定
    72			in = gm.getInsets();
    73			d  = gm.getSize();
    74			d.width  = d.width - in.left - in.right;
    75			d.height = d.height - in.top - in.bottom;
    76			x  = d.width / 2 - 10;
    77			y  = d.height - 20;
    78		}
    79	
    80		public void paintComponent (Graphics g)
    81		{
    82			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    83	
    84							// ゲーム中
    85			if (game) {
    86									// 砲台の表示
    87				g.fill3DRect(x, y, 20, 20, true);
    88									// ターゲットの表示
    89				g.setColor(Color.green);
    90				g.fillOval(xt, yt, 2*r, 2*r);
    91			}
    92							// ゲームオーバ
    93			else {
    94				Font f = new Font("TimesRoman", Font.BOLD, 50);
    95				g.setFont(f);
    96				g.drawString("Game Over", d.width/2-130, d.height/2);
    97			}
    98		}
    99	}
    			
    87 行目

      76 行目~ 77 行目で指定した位置に,砲台(塗りつぶした黒の矩形)を描画

    89 行目~ 90 行目

      62 行目で指定した位置に,ターゲット(塗りつぶした緑の円)を描画

    94 行目~ 96 行目

      画面に「 Game Over 」を表示し,ゲームオーバーであることを示します.

  2. ターゲットの移動( JavaScript の場合):  スレッドを利用して,33 ms 毎に,ターゲットを下方向へ移動してみます.Runnable インターフェースを継承し,run メソッドの中でターゲットの y 座標を変数 sp に設定されている値だけ増加していくことになります.前のステップで作成したプログラムを少し変更するだけですが,スレッドを利用したアニーションの機能を確認することができます.
    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	
    008	public class Test {
    009		public static void main (String[] args)
    010		{
    011			Game1 g1 = new Game1("シューティングゲーム");
    012		}
    013	}
    014	
    015	class Game1 extends JFrame
    016	{
    017		Draw dp;
    018		/******************/
    019		/* コンストラクタ */
    020		/******************/
    021		Game1(String name)
    022		{
    023						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    024			super(name);
    025						// Windowの大きさ
    026			setSize(540, 370);
    027						// 背景色
    028			setBackground(new Color(238, 255, 238));
    029						// 初期設定
    030			Container cp = getContentPane();
    031			cp.setBackground(Color.white);
    032			dp = new Draw(this);
    033			dp.setBackground(new Color(238, 255, 238));
    034			cp.add(dp, BorderLayout.CENTER);
    035						// ウィンドウを表示
    036			setVisible(true);
    037						// イベントアダプタ
    038			addWindowListener(new WinEnd());
    039		}
    040	
    041		/******************************/
    042		/* 上,左,下,右の余白の設定 */
    043		/******************************/
    044		public Insets getInsets()
    045		{
    046			return new Insets(50, 20, 20, 20);
    047		}
    048	
    049		/************/
    050		/* 終了処理 */
    051		/************/
    052		class WinEnd extends WindowAdapter
    053		{
    054			public void windowClosing(WindowEvent e) {
    055				System.exit(0);
    056			}
    057		}
    058	}
    059	
    060	class Draw extends JPanel implements Runnable
    061	{
    062		boolean state = true;   // スレッドが動作中か否か
    063		boolean game = true;   // ゲーム実行中か否か
    064		int xt = 20, yt = 50;   // ターゲットの位置
    065		int x, y;   // 砲台の位置
    066		int r = 25;   // ターゲットの半径
    067		int sp = 5;   // ターゲットの速さ
    068		Thread th;
    069		Dimension d;
    070		Insets in;
    071		Game1 gm;
    072	
    073		Draw(Game1 gm1) {
    074			gm = gm1;
    075						// 初期設定
    076			in = gm.getInsets();
    077			d  = gm.getSize();
    078			d.width  = d.width - in.left - in.right;
    079			d.height = d.height - in.top - in.bottom;
    080			x  = d.width / 2 - 10;
    081			y  = d.height - 20;
    082						// スレッドの生成
    083			th = new Thread(this);
    084			th.start();
    085		}
    086	
    087		public void run()
    088		{
    089			while (state) {
    090				try {
    091					th.sleep(33);
    092				}
    093				catch (InterruptedException e) {}
    094							// ターゲットの移動
    095				yt += sp;
    096							// 再描画
    097				repaint();
    098			}
    099		}
    100	
    101		public void paintComponent (Graphics g)
    102		{
    103			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    104	
    105							// ゲーム中
    106			if (game) {
    107									// 砲台の表示
    108				g.fill3DRect(x, y, 20, 20, true);
    109									// ターゲットの表示
    110				g.setColor(Color.green);
    111				g.fillOval(xt, yt, 2*r, 2*r);
    112			}
    113							// ゲームオーバ
    114			else {
    115				Font f = new Font("TimesRoman", Font.BOLD, 50);
    116				g.setFont(f);
    117				g.drawString("Game Over", d.width/2-130, d.height/2);
    118			}
    119		}
    120	}
    			
    060 行目

      スレッドを利用するため,Runnable インターフェースを継承しています.

    083 行目~ 084 行目

      Thread クラスのオブジェクトを生成し,それをスタートさせています.

    087 行目~ 099 行目

      Thread クラスのメソッドであり,スレッドを 33 ms 毎に実行することになります( 095 行目).実行する内容は,ピースの y 座標を変化させ( 062 行目),再描画しているだけです.

  3. ターゲットの生成・移動・消滅( JavaScript の場合):  ターゲットは,

    • 画面上に存在し,移動している状態( target = 1 )
    • 画面上に存在しない状態( target = 0 )
    • 命中し色が変わった状態( target = 2 )

    という 3 つの状態のいずれかになります.その状態を記憶しているのが変数 target です.ここでは,ターゲットを画面上部の任意の場所に生成し,画面の外に出たら消滅するように変更します.消滅すると,再び,新しいターゲットが生成されます.また,移動方向は,π/4 ~ 3π/4 の間の値からランダムに選択します.なお,位置や方向をランダムにするために,Random クラスを使用しています.
    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	import java.util.*;
    008	
    009	public class Test {
    010		public static void main (String[] args)
    011		{
    012			Game1 g1 = new Game1("シューティングゲーム");
    013		}
    014	}
    015	
    016	class Game1 extends JFrame
    017	{
    018		Draw dp;
    019		/******************/
    020		/* コンストラクタ */
    021		/******************/
    022		Game1(String name)
    023		{
    024						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    025			super(name);
    026						// Windowの大きさ
    027			setSize(540, 370);
    028						// 背景色
    029			setBackground(new Color(238, 255, 238));
    030						// 初期設定
    031			Container cp = getContentPane();
    032			cp.setBackground(Color.white);
    033			dp = new Draw(this);
    034			dp.setBackground(new Color(238, 255, 238));
    035			cp.add(dp, BorderLayout.CENTER);
    036						// ウィンドウを表示
    037			setVisible(true);
    038						// イベントアダプタ
    039			addWindowListener(new WinEnd());
    040		}
    041	
    042		/******************************/
    043		/* 上,左,下,右の余白の設定 */
    044		/******************************/
    045		public Insets getInsets()
    046		{
    047			return new Insets(50, 20, 20, 20);
    048		}
    049	
    050		/************/
    051		/* 終了処理 */
    052		/************/
    053		class WinEnd extends WindowAdapter
    054		{
    055			public void windowClosing(WindowEvent e) {
    056				System.exit(0);
    057			}
    058		}
    059	}
    060	
    061	class Draw extends JPanel implements Runnable
    062	{
    063		boolean state = true;   // スレッドが動作中か否か
    064		boolean game = true;   // ゲーム実行中か否か
    065		int xt = 20, yt = 50;   // ターゲットの位置
    066		int x, y;   // 砲台の位置
    067		int r = 25;   // ターゲットの半径
    068		int sp = 5;   // ターゲットの速さ
    069		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    070		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    071		Thread th;
    072		Dimension d;
    073		Insets in;
    074		Random rn;
    075		Game1 gm;
    076	
    077		Draw(Game1 gm1) {
    078			gm = gm1;
    079						// 初期設定
    080			in = gm.getInsets();
    081			d  = gm.getSize();
    082			d.width  = d.width - in.left - in.right;
    083			d.height = d.height - in.top - in.bottom;
    084			x  = d.width / 2 - 10;
    085			y  = d.height - 20;
    086			rn = new Random();
    087						// スレッドの生成
    088			th = new Thread(this);
    089			th.start();
    090		}
    091	
    092		public void run()
    093		{
    094			double ang;
    095	
    096			while (state) {
    097				try {
    098					th.sleep(33);
    099				}
    100				catch (InterruptedException e) {}
    101							// ターゲットの移動と消滅
    102				if (target == 1) {
    103					xt += vx;
    104					yt += vy;
    105					if (xt < -2*r || xt > d.width || yt > d.height)
    106						target = 0;
    107				}
    108							// ターゲットの生成
    109				else if (target == 0){
    110					target = 1;
    111					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    112					yt     = -r;
    113					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    114					vx     = (int)(sp * Math.cos(ang));
    115					vy     = (int)(sp * Math.sin(ang));
    116				}
    117							// 再描画
    118				repaint();
    119			}
    120		}
    121	
    122		public void paintComponent (Graphics g)
    123		{
    124			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    125	
    126							// ゲーム中
    127			if (game) {
    128									// 砲台の表示
    129				g.fill3DRect(x, y, 20, 20, true);
    130									// ターゲットの表示
    131				g.setColor(Color.green);
    132				g.fillOval(xt, yt, 2*r, 2*r);
    133			}
    134							// ゲームオーバ
    135			else {
    136				Font f = new Font("TimesRoman", Font.BOLD, 50);
    137				g.setFont(f);
    138				g.drawString("Game Over", d.width/2-130, d.height/2);
    139			}
    140		}
    141	}
    			
    102 行目~ 107 行目

      ターゲットが存在する場合の処理です.各軸方向の速度を利用して,ターゲットの x 座標,及び,y 座標の値を変化させ,画面外に出た場合は消滅させます.

    109 行目~ 116 行目

      ターゲットが存在しないときの処理であり,新しいターゲットを生成しています.110 行目において,変数 target の値を 1 に設定し,111 行目~ 112 行目において,ターゲットの初期位置を決めています.ただし,x 座標はランダムに選択されます.また,113 行目において,ターゲットが進む方向がランダムに選択され,114 行目~ 115 行目において,その角度から各軸方向の速度を計算しています.

  4. ゲームオーバー( JavaScript の場合):  このゲームでは,ターゲットと砲台が衝突するとゲームオーバーとなります.ここでは,その判定を追加します.まだ,砲台を移動できませんので,砲台に衝突するようなターゲットが現れるまでしばらく待ち,機能を確認します.
    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	import java.util.*;
    008	
    009	public class Test {
    010		public static void main (String[] args)
    011		{
    012			Game1 g1 = new Game1("シューティングゲーム");
    013		}
    014	}
    015	
    016	class Game1 extends JFrame
    017	{
    018		Draw dp;
    019		/******************/
    020		/* コンストラクタ */
    021		/******************/
    022		Game1(String name)
    023		{
    024						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    025			super(name);
    026						// Windowの大きさ
    027			setSize(540, 370);
    028						// 背景色
    029			setBackground(new Color(238, 255, 238));
    030						// 初期設定
    031			Container cp = getContentPane();
    032			cp.setBackground(Color.white);
    033			dp = new Draw(this);
    034			dp.setBackground(new Color(238, 255, 238));
    035			cp.add(dp, BorderLayout.CENTER);
    036						// ウィンドウを表示
    037			setVisible(true);
    038						// イベントアダプタ
    039			addWindowListener(new WinEnd());
    040		}
    041	
    042		/******************************/
    043		/* 上,左,下,右の余白の設定 */
    044		/******************************/
    045		public Insets getInsets()
    046		{
    047			return new Insets(50, 20, 20, 20);
    048		}
    049	
    050		/************/
    051		/* 終了処理 */
    052		/************/
    053		class WinEnd extends WindowAdapter
    054		{
    055			public void windowClosing(WindowEvent e) {
    056				System.exit(0);
    057			}
    058		}
    059	}
    060	
    061	class Draw extends JPanel implements Runnable
    062	{
    063		boolean state = true, game = true;
    064		int xt, yt, vx, vy, x, y, r = 25, sp = 5, target = 0;
    065		Thread th;
    066		Dimension d;
    067		Insets in;
    068		Random rn;
    069		Game1 gm;
    070	
    071		Draw(Game1 gm1) {
    072			gm = gm1;
    073						// 初期設定
    074			in = gm.getInsets();
    075			d  = gm.getSize();
    076			d.width  = d.width - in.left - in.right;
    077			d.height = d.height - in.top - in.bottom;
    078			x  = d.width / 2 - 10;
    079			y  = d.height - 20;
    080			rn = new Random();
    081						// スレッドの生成
    082			th = new Thread(this);
    083			th.start();
    084		}
    085	
    086		public void run()
    087		{
    088			double ang, x1, y1;
    089	
    090			while (state) {
    091				try {
    092					th.sleep(33);
    093				}
    094				catch (InterruptedException e) {}
    095							// ターゲットの移動と消滅
    096				if (target == 1) {
    097									// ゲームオーバ
    098					x1 = xt + r - (x + 10);
    099					y1 = yt + r - (y + 10);
    100					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    101						game = false;
    102									// ゲームオーバでない
    103					else {
    104						xt += vx;
    105						yt += vy;
    106						if (xt < -2*r || xt > d.width || yt > d.height)
    107							target = 0;
    108					}
    109				}
    110							// ターゲットの生成
    111				else if (target == 0){
    112					target = 1;
    113					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    114					yt     = -r;
    115					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    116					vx     = (int)(sp * Math.cos(ang));
    117					vy     = (int)(sp * Math.sin(ang));
    118				}
    119							// 再描画
    120				repaint();
    121			}
    122		}
    123	
    124		public void paintComponent (Graphics g)
    125		{
    126			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    127	
    128							// ゲーム中
    129			if (game) {
    130									// 砲台の表示
    131				g.fill3DRect(x, y, 20, 20, true);
    132									// ターゲットの表示
    133				g.setColor(Color.green);
    134				g.fillOval(xt, yt, 50, 50);
    135			}
    136							// ゲームオーバ
    137			else {
    138				Font f = new Font("TimesRoman", Font.BOLD, 50);
    139				g.setFont(f);
    140				g.drawString("Game Over", d.width/2-130, d.height/2);
    141				state = false;
    142			}
    143		}
    144	}
    			
    098 行目~ 101 行目

      ターゲットが砲台に衝突したか否かの判定を行い,衝突した場合は,ゲームオーバーにしています.ゲームオーバーになると( game = false ),138 行目~ 141 行目の処理が実行されます.

    141 行目

      スレッドを停止しています.

  5. キーイベント( JavaScript の場合):  最後に,キーイベントに対する処理を追加します.左矢印または右矢印キーを押すと,砲台が左または右に 20 ピクセルだけ移動します.また,Shift キーを押すと,レーザ光が発射されます(変数 fire を true にする).
    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game1 g1 = new Game1("シューティングゲーム");
    014		}
    015	}
    016	
    017	class Game1 extends JFrame
    018	{
    019		Draw dp;
    020		/******************/
    021		/* コンストラクタ */
    022		/******************/
    023		Game1(String name)
    024		{
    025						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    026			super(name);
    027						// Windowの大きさ
    028			setSize(540, 370);
    029						// 背景色
    030			setBackground(new Color(238, 255, 238));
    031						// 初期設定
    032			Container cp = getContentPane();
    033			cp.setBackground(Color.white);
    034			dp = new Draw(this);
    035			dp.setBackground(new Color(238, 255, 238));
    036			cp.add(dp, BorderLayout.CENTER);
    037						// ウィンドウを表示
    038			setVisible(true);
    039						// イベントアダプタ
    040			addWindowListener(new WinEnd());
    041		}
    042	
    043		/******************************/
    044		/* 上,左,下,右の余白の設定 */
    045		/******************************/
    046		public Insets getInsets()
    047		{
    048			return new Insets(50, 20, 20, 20);
    049		}
    050	
    051		/************/
    052		/* 終了処理 */
    053		/************/
    054		class WinEnd extends WindowAdapter
    055		{
    056			public void windowClosing(WindowEvent e) {
    057				System.exit(0);
    058			}
    059		}
    060	}
    061	
    062	class Draw extends JPanel implements Runnable
    063	{
    064		boolean fire = false;   // レーザ砲が発射されているか否か
    065		boolean state = true;   // スレッドが動作中か否か
    066		boolean game = true;   // ゲーム実行中か否か
    067		int xt = 20, yt = 50;   // ターゲットの位置
    068		int x, y;   // 砲台の位置
    069		int r = 25;   // ターゲットの半径
    070		int sp = 5;   // ターゲットの速さ
    071		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    072		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    073		Dimension d;
    074		Insets in;
    075		Thread th;
    076		Random rn;
    077		Game1 gm;
    078	
    079		Draw(Game1 gm1) {
    080			gm = gm1;
    081						// 初期設定
    082			in = gm.getInsets();
    083			d  = gm.getSize();
    084			d.width  = d.width - in.left - in.right;
    085			d.height = d.height - in.top - in.bottom;
    086			x  = d.width / 2 - 10;
    087			y  = d.height - 20;
    088			rn = new Random();
    089						// キーリスナの付加
    090			addKeyListener(new Key_e());
    091						// スレッドの生成
    092			th = new Thread(this);
    093			th.start();
    094		}
    095	
    096		public void run()
    097		{
    098			double ang, x1, y1;
    099	
    100			while (state) {
    101				try {
    102					th.sleep(33);
    103				}
    104				catch (InterruptedException e) {}
    105							// ターゲットの移動と消滅
    106				if (target == 1) {
    107									// ゲームオーバ
    108					x1 = xt + r - (x + 10);
    109					y1 = yt + r - (y + 10);
    110					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    111						game = false;
    112									// ゲームオーバでない
    113											// 命中
    114					else if (fire && xt <= x+10 && xt > x+10-2*r)
    115						target = 2;
    116											// 命中しない
    117					else {
    118						xt += vx;
    119						yt += vy;
    120						if (xt < -2*r || xt > d.width || yt > d.height)
    121							target = 0;
    122					}
    123				}
    124							// ターゲットの生成
    125				else if (target == 0){
    126					target = 1;
    127					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    128					yt     = -r;
    129					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    130					vx     = (int)(sp * Math.cos(ang));
    131					vy     = (int)(sp * Math.sin(ang));
    132				}
    133							// 再描画
    134				repaint();
    135			}
    136		}
    137	
    138		public void paintComponent (Graphics g)
    139		{
    140			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    141	
    142							// ゲーム中
    143			if (game) {
    144									// 砲台の表示
    145				g.fill3DRect(x, y, 20, 20, true);
    146									// ターゲットの表示
    147				if (target > 0) {
    148					if (target == 1)
    149						g.setColor(Color.green);
    150					else {
    151						g.setColor(Color.pink);
    152						target = 0;
    153					}
    154					g.fillOval(xt, yt, 2*r, 2*r);
    155				}
    156									// レーザ砲の発射
    157				if (fire){
    158					g.setColor(Color.red);
    159					g.drawLine(x+9, 0, x+9, d.height-10);
    160					g.drawLine(x+10, 0, x+10, d.height-10);
    161					g.drawLine(x+11, 0, x+11, d.height-10);
    162					fire = false;
    163				}
    164			}
    165							// ゲームオーバ
    166			else {
    167				Font f = new Font("TimesRoman", Font.BOLD, 50);
    168				g.setFont(f);
    169				g.drawString("Game Over", d.width/2-130, d.height/2);
    170				state = false;
    171			}
    172		}
    173	
    174		public boolean isFocusable() { return true; }
    175	
    176		class Key_e extends KeyAdapter {
    177			public void keyPressed(KeyEvent e)
    178			{
    179				if (e.getKeyCode() == 37)  // 左矢印
    180					x -= 20;
    181				else if (e.getKeyCode() == 39)   // 右矢印
    182					x += 20;
    183				if (e.isShiftDown())   // Shift キー
    184					fire = true;
    185			}
    186		}
    187	}
    			
    090 行目

      キーイベントを受け付けるために,KeyListener を追加しています.なお,174 行目も記述しておいて下さい.

    114 行目~ 115 行目

      レーザ砲が発射されており,かつ,それがターゲットに命中した場合は,変数 target の値を 2 に変更しています.

    147 行目~ 155 行目

      変数 target の値が 1 の場合は緑で,また,1 以外の場合はピンクでターゲットを描画しています.さらに,変数 target の値が 1 以外の場合は,その値を 0 に変更しています.

    157 行目~ 163 行目

      レーザ砲が発射されている場合は,それを描画し,発射されていない状態に戻しています( 162 行目).

    177 行目~ 185 行目

      キーが押されたときの処理です.左矢印キーのときは,砲台を 20 ピクセルだけ左に,また,右矢印キーのときは,砲台を 20 ピクセルだけ右に移動させます.さらに,シフトキーのときは,レーザ砲を発射状態に設定します.

(プログラム例 13.15 ) ぷよぷよ風ゲーム

  このプログラムは,キーイベントを使用した簡単なぷよぷよ風ゲームです.左右の矢印キーによって落下してくるピースを左右に動かすことができます.また,下矢印キーで 90 度,または,-90 度の回転,上矢印キーで左右,または,上下の色を交換できます.

  ここでは,複数人で作成する大きなプログラムではなく,一人で比較的小さなプログラムを作成する場合について,その作成手順について考えてみます.少なくとも,すべてのプログラムを作成してからコンパイルし,実行してみるといった方法はあまり良い方法ではありません.作成するプログラムにもよりますが,私は,部分的な機能を実現するプログラムを作成し,その機能を確認した後,新しい機能を追加していくといった方法をよく利用します.たとえば,この例の場合は,以下のような手順になります.

  1. ピースの表示( JavaScript の場合):  このプログラムでは,画面を格子状に区切り,この格子を配列に対応させ,各要素の値によってピースの色を制御しています( 0 の場合は,ピースが存在しない).どのような位置に,どのような色で表示しても構いませんが,後からのことを考えると,位置や色に対する一般的な処理を行っておいた方がよいと思います.なお,変数 p_x 及び p_y はピースの位置を表すための変数です.また,このプログラムでは,変数 game を false に設定することによって,ゲームオーバー画面も確認することができます.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020	
    021		/******************/
    022		/* コンストラクタ */
    023		/******************/
    024		Game2(String name)
    025		{
    026						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    027			super(name);
    028						// Windowの大きさ
    029			setSize(310, 450);
    030						// 背景色
    031			setBackground(new Color(238, 255, 238));
    032						// ゲームパネルの配置
    033			Container cp = getContentPane();
    034			cp.setBackground(new Color(238, 255, 238));
    035			cp.setLayout(null);
    036			Puyo py = new Puyo(this);
    037			cp.add(py);
    038			py.setSize(30*col, 30*row);
    039			py.setLocation(10, 10);
    040			py.setBackground(Color.white);
    041						// ウィンドウを表示
    042			setVisible(true);
    043						// イベントアダプタ
    044			addWindowListener(new WinEnd());
    045		}
    046	
    047		/******************************/
    048		/* 上,左,下,右の余白の設定 */
    049		/******************************/
    050		public Insets getInsets()
    051		{
    052			return new Insets(50, 20, 20, 20);
    053		}
    054	
    055		/************/
    056		/* 終了処理 */
    057		/************/
    058		class WinEnd extends WindowAdapter
    059		{
    060			public void windowClosing(WindowEvent e) {
    061				System.exit(0);
    062			}
    063		}
    064	}
    065	
    066	/****************/
    067	/* ゲームパネル */
    068	/****************/
    069	class Puyo extends JPanel
    070	{
    071		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    072		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    073		boolean game = true;   // ゲーム中か否か
    074		Game2 gm;
    075	
    076		/******************/
    077		/* コンストラクタ */
    078		/******************/
    079		Puyo (Game2 gm_t)
    080		{
    081			gm = gm_t;
    082			p  = new int [gm.row][gm.col];
    083			p[p_y][p_x]   = 2;
    084			p[p_y][p_x+1] = 3;
    085		}
    086	
    087		/********/
    088		/* 描画 */
    089		/********/
    090		public void paintComponent (Graphics g)
    091		{
    092			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    093	
    094			int i1, i2;
    095								// ゲーム中
    096			if (game) {
    097				for (i1 = 0; i1 < gm.row; i1++) {
    098					for (i2 = 0; i2 < gm.col; i2++) {
    099						if (p[i1][i2] > 0) {
    100							switch (p[i1][i2]) {
    101								case 1:
    102									g.setColor(Color.red);
    103									break;
    104								case 2:
    105									g.setColor(Color.pink);
    106									break;
    107								case 3:
    108									g.setColor(Color.green);
    109									break;
    110								case 4:
    111									g.setColor(Color.blue);
    112									break;
    113							}
    114							g.fillRect(i2*30, i1*30, 30, 30);
    115						}
    116					}
    117				}
    118			}
    119								// ゲームオーバー
    120			else {
    121				Font f = new Font("TimesRoman", Font.BOLD, 40);
    122				g.setFont(f);
    123				g.drawString("Game", 20, 200);
    124				g.drawString("Over!", 25, 250);
    125			}
    126		}
    127	}
    			
    035 行目

      レイアウトマネージャを無効にしています.この結果,コンポーネントを任意の位置に,かつ,任意の大きさで追加できるようになります.

    036 行目~ 040 行目

      画面の左側にゲーム画面(ピースを描く画面)となる Puyo クラス( 34 行目~ 92 行目)のオブジェクトを追加しています.

    083 行目~ 084 行目

      左側のピースをピンク,右側のピースを緑に設定しています( 65 行目~ 78 行目参照).

    097 行目~ 117 行目

      配列変数 p の値が 0 より大きい(色が指定してある)セルの位置に,指定された色で,30 ピクセル × 30 ピクセルの矩形を描画しています.

    121 行目~ 124 行目

      ゲームオーバーになった場合に対する処理であり,画面に「 Game Over! 」を表示しています.

  2. ピースの落下( JavaScript の場合):  先ほど表示したピースを,一定時間毎( この例では,500 ms )に落下させてみます.Runnable インターフェースを継承し,run メソッドの中でピースの位置を配列の 1 要素分だけ下に移動させています(配列の外に出ないように注意すること).前のステップで作成したプログラムを少し変更するだけですが,スレッドを利用したアニーションの機能を確認することができます.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020		Puyo py;
    021	
    022		/******************/
    023		/* コンストラクタ */
    024		/******************/
    025		Game2(String name)
    026		{
    027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    028			super(name);
    029						// Windowの大きさ
    030			setSize(310, 450);
    031						// 背景色
    032			setBackground(new Color(238, 255, 238));
    033						// ゲームパネルの配置
    034			Container cp = getContentPane();
    035			cp.setBackground(new Color(238, 255, 238));
    036			cp.setLayout(null);
    037			py = new Puyo(this);
    038			cp.add(py);
    039			py.setSize(30*col, 30*row);
    040			py.setLocation(10, 10);
    041			py.setBackground(Color.white);
    042						// ウィンドウを表示
    043			setVisible(true);
    044						// イベントアダプタ
    045			addWindowListener(new WinEnd());
    046		}
    047	
    048		/******************************/
    049		/* 上,左,下,右の余白の設定 */
    050		/******************************/
    051		public Insets getInsets()
    052		{
    053			return new Insets(50, 20, 20, 20);
    054		}
    055	
    056		/************/
    057		/* 終了処理 */
    058		/************/
    059		class WinEnd extends WindowAdapter
    060		{
    061			public void windowClosing(WindowEvent e) {
    062				System.exit(0);
    063			}
    064		}
    065	}
    066	
    067	/****************/
    068	/* ゲームパネル */
    069	/****************/
    070	class Puyo extends JPanel implements Runnable
    071	{
    072		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    073		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    074		boolean game = true;   // ゲーム中か否か
    075		boolean state = true;   // スレッドが動作中か否か
    076		Thread th;
    077		Game2 gm;
    078	
    079		/******************/
    080		/* コンストラクタ */
    081		/******************/
    082		Puyo (Game2 gm_t)
    083		{
    084			gm = gm_t;
    085			p  = new int [gm.row][gm.col];
    086			p[p_y][p_x]   = 2;
    087			p[p_y][p_x+1] = 3;
    088						// スレッドの生成
    089			th = new Thread(this);
    090			th.start();
    091		}
    092	
    093		/******************/
    094		/* スレッドの実行 */
    095		/******************/
    096		public void run()
    097		{
    098			while (state) {
    099				try {
    100					th.sleep(500);
    101				}
    102				catch (InterruptedException e) {}
    103							// ピースの落下
    104				if (p_y < gm.row-1) {
    105					p[p_y+1][p_x]   = p[p_y][p_x];
    106					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    107					p[p_y][p_x]     = 0;
    108					p[p_y][p_x+1]   = 0;
    109					p_y++;
    110				}
    111							// 再描画
    112				repaint();
    113			}
    114		}
    115	
    116		/********/
    117		/* 描画 */
    118		/********/
    119		public void paintComponent (Graphics g)
    120		{
    121			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    122	
    123			int i1, i2;
    124								// ゲーム中
    125			if (game) {
    126				for (i1 = 0; i1 < gm.row; i1++) {
    127					for (i2 = 0; i2 < gm.col; i2++) {
    128						if (p[i1][i2] > 0) {
    129							switch (p[i1][i2]) {
    130								case 1:
    131									g.setColor(Color.red);
    132									break;
    133								case 2:
    134									g.setColor(Color.pink);
    135									break;
    136								case 3:
    137									g.setColor(Color.green);
    138									break;
    139								case 4:
    140									g.setColor(Color.blue);
    141									break;
    142							}
    143							g.fillRect(i2*30, i1*30, 30, 30);
    144						}
    145					}
    146				}
    147			}
    148								// ゲームオーバー
    149			else {
    150				Font f = new Font("TimesRoman", Font.BOLD, 40);
    151				g.setFont(f);
    152				g.drawString("Game", 20, 200);
    153				g.drawString("Over!", 25, 250);
    154				state = false;
    155			}
    156		}
    157	}
    			
    070 行目

      スレッドを利用するため,Runnable インターフェースを継承しています.

    089 行目~ 090 行目

      Thread クラスのオブジェクトを生成し,それをスタートさせています.

    096 行目~ 114 行目

      Thread クラスのメソッドであり,スレッドを 500 ms 毎に実行することになります( 100 行目).104 行目~ 110 行目において,ピースが床に達していないときは,一つ下のセルに移動しています.

    154 行目

      ゲームオーバーになった場合は,スレッドを停止しています.

  3. ピースの生成と落下( JavaScript の場合):  ピースを画面上部(水平位置はランダム)に発生させ,落下させます.ランダムな値を設定するために,Random クラスを使用しています.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020		Puyo py;
    021	
    022		/******************/
    023		/* コンストラクタ */
    024		/******************/
    025		Game2(String name)
    026		{
    027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    028			super(name);
    029						// Windowの大きさ
    030			setSize(310, 450);
    031						// 背景色
    032			setBackground(new Color(238, 255, 238));
    033						// ゲームパネルの配置
    034			Container cp = getContentPane();
    035			cp.setBackground(new Color(238, 255, 238));
    036			cp.setLayout(null);
    037			py = new Puyo(this);
    038			cp.add(py);
    039			py.setSize(30*col, 30*row);
    040			py.setLocation(10, 10);
    041			py.setBackground(Color.white);
    042						// ウィンドウを表示
    043			setVisible(true);
    044						// イベントアダプタ
    045			addWindowListener(new WinEnd());
    046		}
    047	
    048		/******************************/
    049		/* 上,左,下,右の余白の設定 */
    050		/******************************/
    051		public Insets getInsets()
    052		{
    053			return new Insets(50, 20, 20, 20);
    054		}
    055	
    056		/************/
    057		/* 終了処理 */
    058		/************/
    059		class WinEnd extends WindowAdapter
    060		{
    061			public void windowClosing(WindowEvent e) {
    062				System.exit(0);
    063			}
    064		}
    065	}
    066	
    067	/****************/
    068	/* ゲームパネル */
    069	/****************/
    070	class Puyo extends JPanel implements Runnable
    071	{
    072		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    073		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    074		boolean game = true;   // ゲーム中か否か
    075		boolean state = true;   // スレッドが動作中か否か
    076		Thread th;
    077		Random rn;
    078		Game2 gm;
    079	
    080		/******************/
    081		/* コンストラクタ */
    082		/******************/
    083		Puyo (Game2 gm_t)
    084		{
    085			gm = gm_t;
    086			p  = new int [gm.row][gm.col];
    087			rn = new Random();
    088						// ピースの選択
    089			select();
    090						// スレッドの生成
    091			th = new Thread(this);
    092			th.start();
    093		}
    094	
    095		/******************/
    096		/* スレッドの実行 */
    097		/******************/
    098		public void run()
    099		{
    100			while (state) {
    101				try {
    102					th.sleep(500);
    103				}
    104				catch (InterruptedException e) {}
    105							// ピースの落下
    106				if (p_y < gm.row-1) {
    107					p[p_y+1][p_x]   = p[p_y][p_x];
    108					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    109					p[p_y][p_x]     = 0;
    110					p[p_y][p_x+1]   = 0;
    111					p_y++;
    112				}
    113				else
    114					select();
    115							// 再描画
    116				repaint();
    117			}
    118		}
    119	
    120		/****************/
    121		/* ピースの選択 */
    122		/****************/
    123		void select()
    124		{
    125			int color;
    126	
    127			p_y = 0;
    128			p_x = (int)(rn.nextDouble() * (gm.col - 1));
    129			if (p_x > gm.col-2)
    130				p_x = gm.col - 2;
    131	
    132			color = (int)(rn.nextDouble() * 4) + 1;
    133			if (color > 4)
    134				color = 4;
    135			p[0][p_x] = color;
    136			color = (int)(rn.nextDouble() * 4) + 1;
    137			if (color > 4)
    138				color = 4;
    139			p[0][p_x+1] = color;
    140		}
    141	
    142		/********/
    143		/* 描画 */
    144		/********/
    145		public void paintComponent (Graphics g)
    146		{
    147			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    148	
    149			int i1, i2;
    150								// ゲーム中
    151			if (game) {
    152				for (i1 = 0; i1 < gm.row; i1++) {
    153					for (i2 = 0; i2 < gm.col; i2++) {
    154						if (p[i1][i2] > 0) {
    155							switch (p[i1][i2]) {
    156								case 1:
    157									g.setColor(Color.red);
    158									break;
    159								case 2:
    160									g.setColor(Color.pink);
    161									break;
    162								case 3:
    163									g.setColor(Color.green);
    164									break;
    165								case 4:
    166									g.setColor(Color.blue);
    167									break;
    168							}
    169							g.fillRect(i2*30, i1*30, 30, 30);
    170						}
    171					}
    172				}
    173			}
    174								// ゲームオーバー
    175			else {
    176				Font f = new Font("TimesRoman", Font.BOLD, 40);
    177				g.setFont(f);
    178				g.drawString("Game", 20, 200);
    179				g.drawString("Over!", 25, 250);
    180				state = false;
    181			}
    182		}
    183	}
    			
    087 行目

      乱数の初期化.

    089 行目

      ピースの初期位置や色をランダムに選択するメソッド select( 123 行目~ 140 行目)を呼んでいます.

    113 行目~ 114 行目

      ピースが最下段に到達した場合は,次のピースを選択するために,メソッド select( 123 行目~ 140 行目)を呼んでいます.

    128 行目~ 130 行目

      ピースの初期位置(水平方向)をランダムに選択

    132 行目~ 139 行目

      2 つのピースの色をランダムに選択

  4. キーイベント( JavaScript の場合):  キーイベントに対する処理を追加します.移動や回転を行う場合は,移動や回転が可能か否かのチェックが必要となります.変数 rot の値が 0 の時は横並び,1 の時は縦並びであることを意味します.当然,rot の値によって,一番下に到着したか否かのチェック方法も異なってきますので,ピースの落下を制御する部分も変更する必要があります.また,一番下に到着すると,変数 ok を false に設定し,キーイベントを受け付けないようにしています.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020		Puyo py;
    021	
    022		/******************/
    023		/* コンストラクタ */
    024		/******************/
    025		Game2(String name)
    026		{
    027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    028			super(name);
    029						// Windowの大きさ
    030			setSize(310, 450);
    031						// 背景色
    032			setBackground(new Color(238, 255, 238));
    033						// ゲームパネルの配置
    034			Container cp = getContentPane();
    035			cp.setBackground(new Color(238, 255, 238));
    036			cp.setLayout(null);
    037			py = new Puyo(this);
    038			cp.add(py);
    039			py.setSize(30*col, 30*row);
    040			py.setLocation(10, 10);
    041			py.setBackground(Color.white);
    042						// ウィンドウを表示
    043			setVisible(true);
    044						// イベントアダプタ
    045			addWindowListener(new WinEnd());
    046		}
    047	
    048		/******************************/
    049		/* 上,左,下,右の余白の設定 */
    050		/******************************/
    051		public Insets getInsets()
    052		{
    053			return new Insets(50, 20, 20, 20);
    054		}
    055	
    056		/************/
    057		/* 終了処理 */
    058		/************/
    059		class WinEnd extends WindowAdapter
    060		{
    061			public void windowClosing(WindowEvent e) {
    062				System.exit(0);
    063			}
    064		}
    065	}
    066	
    067	/****************/
    068	/* ゲームパネル */
    069	/****************/
    070	class Puyo extends JPanel implements Runnable
    071	{
    072		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    073		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    074		int rot = 0;   // 横か縦か(0:横,1:縦)
    075		boolean game = true;   // ゲーム中か否か
    076		boolean state = true;   // スレッドが動作中か否か
    077		boolean ok = true;   // キーイベントを受け付けるか否か
    078		Thread th;
    079		Random rn;
    080		Game2 gm;
    081	
    082		/******************/
    083		/* コンストラクタ */
    084		/******************/
    085		Puyo (Game2 gm_t)
    086		{
    087			gm = gm_t;
    088			rn = new Random();
    089			p  = new int [gm.row][gm.col];
    090			addKeyListener(new Key_e());
    091						// ピースの選択
    092			select();
    093						// スレッドの生成
    094			th = new Thread(this);
    095			th.start();
    096		}
    097	
    098		/******************/
    099		/* スレッドの実行 */
    100		/******************/
    101		public void run()
    102		{
    103			int ct;
    104	
    105			while (state) {
    106				try {
    107					th.sleep(500);
    108				}
    109				catch (InterruptedException e) {}
    110							// ピースの落下
    111				ct = 0;   // 落下したか否かを示す
    112				if (rot == 0) {   // 横並び
    113					if (p_y < gm.row-1) {
    114						ct              = 1;
    115						p[p_y+1][p_x]   = p[p_y][p_x];
    116						p[p_y+1][p_x+1] = p[p_y][p_x+1];
    117						p[p_y][p_x]     = 0;
    118						p[p_y][p_x+1]   = 0;
    119						p_y++;
    120					}
    121					else
    122						ok = false;
    123				}
    124				else {   // 縦並び
    125					if (p_y < gm.row-2) {
    126						ct = 1;
    127						p[p_y+2][p_x] = p[p_y+1][p_x];
    128						p[p_y+1][p_x] = p[p_y][p_x];
    129						p[p_y][p_x]   = 0;
    130						p_y++;
    131					}
    132					else
    133						ok = false;
    134				}
    135							// 消去と次のピース
    136				if (ct == 0)
    137					select();
    138							// 再描画
    139				repaint();
    140			}
    141		}
    142	
    143		/****************/
    144		/* ピースの選択 */
    145		/****************/
    146		void select()
    147		{
    148			int color;
    149	
    150			ok  = true;
    151			rot = 0;
    152			p_y = 0;
    153			p_x = (int)(rn.nextDouble() * (gm.col - 1));
    154			if (p_x > gm.col-2)
    155				p_x = gm.col - 2;
    156	
    157			color = (int)(rn.nextDouble() * 4) + 1;
    158			if (color > 4)
    159				color = 4;
    160			p[0][p_x] = color;
    161			color = (int)(rn.nextDouble() * 4) + 1;
    162			if (color > 4)
    163				color = 4;
    164			p[0][p_x+1] = color;
    165		}
    166	
    167		/********/
    168		/* 描画 */
    169		/********/
    170		public void paintComponent (Graphics g)
    171		{
    172			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    173	
    174			int i1, i2;
    175								// ゲーム中
    176			if (game) {
    177				for (i1 = 0; i1 < gm.row; i1++) {
    178					for (i2 = 0; i2 < gm.col; i2++) {
    179						if (p[i1][i2] > 0) {
    180							switch (p[i1][i2]) {
    181								case 1:
    182									g.setColor(Color.red);
    183									break;
    184								case 2:
    185									g.setColor(Color.pink);
    186									break;
    187								case 3:
    188									g.setColor(Color.green);
    189									break;
    190								case 4:
    191									g.setColor(Color.blue);
    192									break;
    193							}
    194							g.fillRect(i2*30, i1*30, 30, 30);
    195						}
    196					}
    197				}
    198			}
    199								// ゲームオーバー
    200			else {
    201				Font f = new Font("TimesRoman", Font.BOLD, 40);
    202				g.setFont(f);
    203				g.drawString("Game", 20, 200);
    204				g.drawString("Over!", 25, 250);
    205				state = false;
    206			}
    207		}
    208	
    209		/************************/
    210		/* キーイベントの有効化 */
    211		/************************/
    212		public boolean isFocusable() { return true; }
    213	
    214		/**********************/
    215		/* キーイベントの処理 */
    216		/**********************/
    217		class Key_e extends KeyAdapter {
    218			public void keyPressed(KeyEvent e)
    219			{
    220				int k;
    221				if (ok) {
    222					if (e.getKeyCode() == KeyEvent.VK_LEFT) {   // 左矢印(左移動)
    223						if (p_x > 0) {
    224							if (rot == 0 && p[p_y][p_x-1] == 0) {
    225								p[p_y][p_x-1] = p[p_y][p_x];
    226								p[p_y][p_x]   = p[p_y][p_x+1];
    227								p[p_y][p_x+1] = 0;
    228								p_x--;
    229								repaint();
    230							}
    231							else if (p[p_y][p_x-1] == 0 && p[p_y+1][p_x-1] == 0) {
    232								p[p_y][p_x-1]   = p[p_y][p_x];
    233								p[p_y+1][p_x-1] = p[p_y+1][p_x];
    234								p[p_y][p_x]     = 0;
    235								p[p_y+1][p_x]   = 0;
    236								p_x--;
    237								repaint();
    238							}
    239						}
    240					}
    241					else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {   // 右矢印(右移動)
    242						if (rot == 0) {
    243							if (p_x < gm.col-2 && p[p_y][p_x+2] == 0) {
    244								p[p_y][p_x+2] = p[p_y][p_x+1];
    245								p[p_y][p_x+1] = p[p_y][p_x];
    246								p[p_y][p_x]   = 0;
    247								p_x++;
    248								repaint();
    249							}
    250						}
    251						else {
    252							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0 && p[p_y+1][p_x+1] == 0) {
    253								p[p_y][p_x+1]   = p[p_y][p_x];
    254								p[p_y+1][p_x+1] = p[p_y+1][p_x];
    255								p[p_y][p_x]     = 0;
    256								p[p_y+1][p_x]   = 0;
    257								p_x++;
    258								repaint();
    259							}
    260						}
    261					}
    262					else if (e.getKeyCode() == KeyEvent.VK_UP) {   // 上矢印(上下または左右入れ替え)
    263						if (rot == 0) {
    264							k             = p[p_y][p_x];
    265							p[p_y][p_x]   = p[p_y][p_x+1];
    266							p[p_y][p_x+1] = k;
    267						}
    268						else {
    269							k             = p[p_y][p_x];
    270							p[p_y][p_x]   = p[p_y+1][p_x];
    271							p[p_y+1][p_x] = k;
    272						}
    273						repaint();
    274					}
    275					else if (e.getKeyCode() == KeyEvent.VK_DOWN) {   // 下矢印(90度または-90度回転)
    276						if (rot == 0 && p[p_y+1][p_x] == 0) {
    277							if (p_y < gm.row-1) {
    278								p[p_y+1][p_x] = p[p_y][p_x+1];
    279								p[p_y][p_x+1] = 0;
    280								rot           = 1;
    281								repaint();
    282							}
    283						}
    284						else {
    285							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0) {
    286								p[p_y][p_x+1] = p[p_y+1][p_x];
    287								p[p_y+1][p_x] = 0;
    288								rot           = 0;
    289								repaint();
    290							}
    291						}
    292					}
    293				}
    294			}
    295		}
    296	}
    			
    090 行目

      キーイベントを受け付けるために,KeyListener を追加しています.なお,212 行目も記述しておいて下さい.

    125 行目~ 133 行目

      ピースが縦並びの場合の落下処理.横並びの場合も同様ですが,最下段に到達したときは,キーイベントを受け付けないようにしています( 133 行目)

    212 行目

      キーイベントを有効にしています.

    225 行目~ 229 行目

      左矢印キーが押され,ピースが横並びであった場合の処理です.左側に他のピースが存在しない場合は,左側に移動します.

    232 行目~ 237 行目

      左矢印キーが押され,ピースが縦並びであった場合の処理です.左側に他のピースが存在しない場合は,左側に移動します.

    242 行目~ 260 行目

      右矢印キーによって右移動するための処理です.左矢印キーが押された場合と同様の処理を行っています.

    263 行目~ 273 行目

      上矢印キーが押された場合の処理であり,上下,または,左右の色を入れ替えています.

    276 行目~ 291 行目

      下矢印キーが押された場合の処理であり,回転可能な場合は,回転させる処理を行っています.

  5. ピースの積み上げ( JavaScript の場合):  このままでは,ピースが積み上がっていきません.落下しているピースが,他のピースの上に乗ったらそこで止めるための処理が必要になります.特に,横並びの場合,一つのピースだけが他のピースの上に乗った場合,キーイベントの処理を受け付けないようにした後(変数 ok を false ),乗っていないピースだけを落下させる処理が必要になる点に注意して下さい.また,上まで積み上がるとゲームオーバーになる処理も追加しています.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020		Puyo py;
    021	
    022		/******************/
    023		/* コンストラクタ */
    024		/******************/
    025		Game2(String name)
    026		{
    027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    028			super(name);
    029						// Windowの大きさ
    030			setSize(310, 450);
    031						// 背景色
    032			setBackground(new Color(238, 255, 238));
    033						// ゲームパネルの配置
    034			Container cp = getContentPane();
    035			cp.setBackground(new Color(238, 255, 238));
    036			cp.setLayout(null);
    037			py = new Puyo(this);
    038			cp.add(py);
    039			py.setSize(30*col, 30*row);
    040			py.setLocation(10, 10);
    041			py.setBackground(Color.white);
    042						// ウィンドウを表示
    043			setVisible(true);
    044						// イベントアダプタ
    045			addWindowListener(new WinEnd());
    046		}
    047	
    048		/******************************/
    049		/* 上,左,下,右の余白の設定 */
    050		/******************************/
    051		public Insets getInsets()
    052		{
    053			return new Insets(50, 20, 20, 20);
    054		}
    055	
    056		/************/
    057		/* 終了処理 */
    058		/************/
    059		class WinEnd extends WindowAdapter
    060		{
    061			public void windowClosing(WindowEvent e) {
    062				System.exit(0);
    063			}
    064		}
    065	}
    066	
    067	/****************/
    068	/* ゲームパネル */
    069	/****************/
    070	class Puyo extends JPanel implements Runnable
    071	{
    072		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    073		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    074		int rot = 0;   // 横か縦か(0:横,1:縦)
    075		boolean game = true;   // ゲーム中か否か
    076		boolean state = true;   // スレッドが動作中か否か
    077		boolean ok = true;   // キーイベントを受け付けるか否か
    078		Thread th;
    079		Random rn;
    080		Game2 gm;
    081	
    082		/******************/
    083		/* コンストラクタ */
    084		/******************/
    085		Puyo (Game2 gm_t)
    086		{
    087			gm = gm_t;
    088			rn = new Random();
    089			p  = new int [gm.row][gm.col];
    090			addKeyListener(new Key_e());
    091						// ピースの選択
    092			select();
    093						// スレッドの生成
    094			th = new Thread(this);
    095			th.start();
    096		}
    097	
    098		/******************/
    099		/* スレッドの実行 */
    100		/******************/
    101		public void run()
    102		{
    103			int ct;
    104	
    105			while (state) {
    106				try {
    107					th.sleep(500);
    108				}
    109				catch (InterruptedException e) {}
    110							// ピースの落下
    111				ct = 0;   // 落下したか否かを示す
    112				if (rot == 0) {   // 横並び
    113					if (p_y < gm.row-1) {
    114						if (ok) {
    115							if (p[p_y+1][p_x] == 0 && p[p_y+1][p_x+1] == 0) {
    116								ct              = 1;
    117								p[p_y+1][p_x]   = p[p_y][p_x];
    118								p[p_y+1][p_x+1] = p[p_y][p_x+1];
    119								p[p_y][p_x]     = 0;
    120								p[p_y][p_x+1]   = 0;
    121								p_y++;
    122							}
    123							else {
    124								ok = false;
    125								if (p[p_y+1][p_x] == 0) {
    126									ct            = 1;
    127									p[p_y+1][p_x] = p[p_y][p_x];
    128									p[p_y][p_x]   = 0;
    129									p_y++;
    130								}
    131								else if (p[p_y+1][p_x+1] == 0) {
    132									ct              = 1;
    133									p[p_y+1][p_x+1] = p[p_y][p_x+1];
    134									p[p_y][p_x+1]   = 0;
    135									p_x++;
    136									p_y++;
    137								}
    138							}
    139						}
    140						else {
    141							if (p[p_y+1][p_x] == 0) {
    142								ct            = 1;
    143								p[p_y+1][p_x] = p[p_y][p_x];
    144								p[p_y][p_x]   = 0;
    145								p_y++;
    146							}
    147						}
    148					}
    149				}
    150				else {   // 縦並び
    151					if (p_y < gm.row-2 && p[p_y+2][p_x] == 0) {
    152						ct = 1;
    153						p[p_y+2][p_x] = p[p_y+1][p_x];
    154						p[p_y+1][p_x] = p[p_y][p_x];
    155						p[p_y][p_x]   = 0;
    156						p_y++;
    157					}
    158					else
    159						ok = false;
    160				}
    161							// 消去と次のピース
    162				if (ct == 0)
    163					select();
    164							// 再描画
    165				repaint();
    166			}
    167		}
    168	
    169		/****************/
    170		/* ピースの選択 */
    171		/****************/
    172		void select()
    173		{
    174			int color;
    175	
    176			ok  = true;
    177			rot = 0;
    178			p_y = 0;
    179			p_x = (int)(rn.nextDouble() * (gm.col - 1));
    180			if (p_x > gm.col-2)
    181				p_x = gm.col - 2;
    182	
    183			if (p[0][p_x] == 0 && p[0][p_x+1] == 0) {
    184				color = (int)(rn.nextDouble() * 4) + 1;
    185				if (color > 4)
    186					color = 4;
    187				p[0][p_x] = color;
    188				color = (int)(rn.nextDouble() * 4) + 1;
    189				if (color > 4)
    190					color = 4;
    191				p[0][p_x+1] = color;
    192			}
    193			else
    194				game = false;
    195		}
    196	
    197		/********/
    198		/* 描画 */
    199		/********/
    200		public void paintComponent (Graphics g)
    201		{
    202			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    203	
    204			int i1, i2;
    205								// ゲーム中
    206			if (game) {
    207				for (i1 = 0; i1 < gm.row; i1++) {
    208					for (i2 = 0; i2 < gm.col; i2++) {
    209						if (p[i1][i2] > 0) {
    210							switch (p[i1][i2]) {
    211								case 1:
    212									g.setColor(Color.red);
    213									break;
    214								case 2:
    215									g.setColor(Color.pink);
    216									break;
    217								case 3:
    218									g.setColor(Color.green);
    219									break;
    220								case 4:
    221									g.setColor(Color.blue);
    222									break;
    223							}
    224							g.fillRect(i2*30, i1*30, 30, 30);
    225						}
    226					}
    227				}
    228			}
    229								// ゲームオーバー
    230			else {
    231				Font f = new Font("TimesRoman", Font.BOLD, 40);
    232				g.setFont(f);
    233				g.drawString("Game", 20, 200);
    234				g.drawString("Over!", 25, 250);
    235				state = false;
    236			}
    237		}
    238	
    239		/************************/
    240		/* キーイベントの有効化 */
    241		/************************/
    242		public boolean isFocusable() { return true; }
    243	
    244		/**********************/
    245		/* キーイベントの処理 */
    246		/**********************/
    247		class Key_e extends KeyAdapter {
    248			public void keyPressed(KeyEvent e)
    249			{
    250				int k;
    251				if (ok) {
    252					if (e.getKeyCode() == KeyEvent.VK_LEFT) {   // 左矢印(左移動)
    253						if (p_x > 0) {
    254							if (rot == 0 && p[p_y][p_x-1] == 0) {
    255								p[p_y][p_x-1] = p[p_y][p_x];
    256								p[p_y][p_x]   = p[p_y][p_x+1];
    257								p[p_y][p_x+1] = 0;
    258								p_x--;
    259								repaint();
    260							}
    261							else if (p[p_y][p_x-1] == 0 && p[p_y+1][p_x-1] == 0) {
    262								p[p_y][p_x-1]   = p[p_y][p_x];
    263								p[p_y+1][p_x-1] = p[p_y+1][p_x];
    264								p[p_y][p_x]     = 0;
    265								p[p_y+1][p_x]   = 0;
    266								p_x--;
    267								repaint();
    268							}
    269						}
    270					}
    271					else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {   // 右矢印(右移動)
    272						if (rot == 0) {
    273							if (p_x < gm.col-2 && p[p_y][p_x+2] == 0) {
    274								p[p_y][p_x+2] = p[p_y][p_x+1];
    275								p[p_y][p_x+1] = p[p_y][p_x];
    276								p[p_y][p_x]   = 0;
    277								p_x++;
    278								repaint();
    279							}
    280						}
    281						else {
    282							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0 && p[p_y+1][p_x+1] == 0) {
    283								p[p_y][p_x+1]   = p[p_y][p_x];
    284								p[p_y+1][p_x+1] = p[p_y+1][p_x];
    285								p[p_y][p_x]     = 0;
    286								p[p_y+1][p_x]   = 0;
    287								p_x++;
    288								repaint();
    289							}
    290						}
    291					}
    292					else if (e.getKeyCode() == KeyEvent.VK_UP) {   // 上矢印(上下または左右入れ替え)
    293						if (rot == 0) {
    294							k             = p[p_y][p_x];
    295							p[p_y][p_x]   = p[p_y][p_x+1];
    296							p[p_y][p_x+1] = k;
    297						}
    298						else {
    299							k             = p[p_y][p_x];
    300							p[p_y][p_x]   = p[p_y+1][p_x];
    301							p[p_y+1][p_x] = k;
    302						}
    303						repaint();
    304					}
    305					else if (e.getKeyCode() == KeyEvent.VK_DOWN) {   // 下矢印(90度または-90度回転)
    306						if (rot == 0 && p[p_y+1][p_x] == 0) {
    307							if (p_y < gm.row-1) {
    308								p[p_y+1][p_x] = p[p_y][p_x+1];
    309								p[p_y][p_x+1] = 0;
    310								rot           = 1;
    311								repaint();
    312							}
    313						}
    314						else {
    315							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0) {
    316								p[p_y][p_x+1] = p[p_y+1][p_x];
    317								p[p_y+1][p_x] = 0;
    318								rot           = 0;
    319								repaint();
    320							}
    321						}
    322					}
    323				}
    324			}
    325		}
    326	}
    			
    115 行目~ 122 行目

      ピースが横並びで,かつ,2 つのピースが同時に落下する場合の処理です.

    124 行目

      125 行目~ 137 行目は,左右いずれかのピースが他のピースの上に乗った場合の処理です.ここでは,変数 ok を false に設定し,イベントを受け付けないようにしています.従って,ok が false であることは,ピースが最下段に到達するか,または,横並びの 2 つのピースが分離し,1 つのピースだけが落下している状態であることを意味します.

    126 行目~ 129 行目

      右側のピースが他のピースの上に乗り,左側のピースだけが落下する状態を開始する処理です.

    132 行目~ 136 行目

      上と同様,左側のピースが他のピースの上に乗り,右側のピースだけが落下する状態を開始する処理です.

    141 行目~ 146 行目

      1 つのピースだけが落下している場合に対する処理です.

    151 行目~ 159 行目

      ピースが縦に並んでいる場合に対する落下処理です.

    183 行目,193 行目~ 194 行目

      ピースを生成した位置に,他のピースが存在した場合は,ゲームオーバーにしています.

  6. ピースの削除( JavaScript の場合):  最後に行わなければならないのがピースの削除です.同じ色のピースが縦横 4 個以上並んだ場合,それらを削除し,それらの上に乗っていたピースを可能なところまで落下させます.メソッド search で同じ色のピースを数え,メソッド delete で削除しています.
    001	/*************************/
    002	/* ぷよぷよ              */
    003	/*   coded by Y.Suganuma */
    004	/*************************/
    005	import java.awt.*;
    006	import java.awt.event.*;
    007	import javax.swing.*;
    008	import java.util.*;
    009	
    010	public class Test {
    011		public static void main (String[] args)
    012		{
    013			Game2 win = new Game2("ぷよぷよ");
    014		}
    015	}
    016	
    017	class Game2 extends JFrame
    018	{
    019		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    020		Puyo py;
    021	
    022		/******************/
    023		/* コンストラクタ */
    024		/******************/
    025		Game2(String name)
    026		{
    027						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
    028			super(name);
    029						// Windowの大きさ
    030			setSize(310, 450);
    031						// 背景色
    032			setBackground(new Color(238, 255, 238));
    033						// ゲームパネルの配置
    034			Container cp = getContentPane();
    035			cp.setBackground(new Color(238, 255, 238));
    036			cp.setLayout(null);
    037			py = new Puyo(this);
    038			cp.add(py);
    039			py.setSize(30*col, 30*row);
    040			py.setLocation(10, 10);
    041			py.setBackground(Color.white);
    042						// ウィンドウを表示
    043			setVisible(true);
    044						// イベントアダプタ
    045			addWindowListener(new WinEnd());
    046		}
    047	
    048		/******************************/
    049		/* 上,左,下,右の余白の設定 */
    050		/******************************/
    051		public Insets getInsets()
    052		{
    053			return new Insets(50, 20, 20, 20);
    054		}
    055	
    056		/************/
    057		/* 終了処理 */
    058		/************/
    059		class WinEnd extends WindowAdapter
    060		{
    061			public void windowClosing(WindowEvent e) {
    062				System.exit(0);
    063			}
    064		}
    065	}
    066	
    067	/****************/
    068	/* ゲームパネル */
    069	/****************/
    070	class Puyo extends JPanel implements Runnable
    071	{
    072		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    073		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    074		int rot = 0;   // 横か縦か(0:横,1:縦)
    075		boolean game = true;   // ゲーム中か否か
    076		boolean state = true;   // スレッドが動作中か否か
    077		boolean ok = true;   // キーイベントを受け付けるか否か
    078		Random rn;
    079		Thread th;
    080		Game2 gm;
    081	
    082		/******************/
    083		/* コンストラクタ */
    084		/******************/
    085		Puyo (Game2 gm_t)
    086		{
    087			gm = gm_t;
    088			rn = new Random();
    089			addKeyListener(new Key_e());
    090			p = new int [gm.row][gm.col];
    091						// ピースの選択
    092			select();
    093						// スレッドの生成
    094			th = new Thread(this);
    095			th.start();
    096		}
    097	
    098		/******************/
    099		/* スレッドの実行 */
    100		/******************/
    101		public void run()
    102		{
    103			int i1, i2, i3, i4, ct;
    104			int pp[][] = new int [gm.row][gm.col];
    105	
    106			while (state) {
    107				try {
    108					th.sleep(500);
    109				}
    110				catch (InterruptedException e) {}
    111							// ピースの落下
    112				ct = 0;   // 落下したか否かを示す
    113				if (rot == 0) {   // 横並び
    114					if (p_y < gm.row-1) {
    115						if (ok) {
    116							if (p[p_y+1][p_x] == 0 && p[p_y+1][p_x+1] == 0) {
    117								ct              = 1;
    118								p[p_y+1][p_x]   = p[p_y][p_x];
    119								p[p_y+1][p_x+1] = p[p_y][p_x+1];
    120								p[p_y][p_x]     = 0;
    121								p[p_y][p_x+1]   = 0;
    122								p_y++;
    123							}
    124							else {
    125								ok = false;
    126								if (p[p_y+1][p_x] == 0) {
    127									ct            = 1;
    128									p[p_y+1][p_x] = p[p_y][p_x];
    129									p[p_y][p_x]   = 0;
    130									p_y++;
    131								}
    132								else if (p[p_y+1][p_x+1] == 0) {
    133									ct              = 1;
    134									p[p_y+1][p_x+1] = p[p_y][p_x+1];
    135									p[p_y][p_x+1]   = 0;
    136									p_x++;
    137									p_y++;
    138								}
    139							}
    140						}
    141						else {
    142							if (p[p_y+1][p_x] == 0) {
    143								ct            = 1;
    144								p[p_y+1][p_x] = p[p_y][p_x];
    145								p[p_y][p_x]   = 0;
    146								p_y++;
    147							}
    148						}
    149					}
    150				}
    151				else {   // 縦並び
    152					if (p_y < gm.row-2 && p[p_y+2][p_x] == 0) {
    153						ct = 1;
    154						p[p_y+2][p_x] = p[p_y+1][p_x];
    155						p[p_y+1][p_x] = p[p_y][p_x];
    156						p[p_y][p_x]   = 0;
    157						p_y++;
    158					}
    159					else
    160						ok = false;
    161				}
    162							// 消去と次のピース
    163				if (ct == 0) {
    164					ct = 4;
    165					while (ct >= 4) {
    166						ct = 0;
    167						for (i1 = gm.row-1; i1 >= 0 && ct < 4; i1--) {
    168							for (i2 = 0; i2 < gm.col && ct < 4; i2++) {
    169								for (i3 = 0; i3 < gm.row; i3++) {
    170									for (i4 = 0; i4 < gm.col; i4++)
    171										pp[i3][i4] = 0;
    172								}
    173								if (p[i1][i2] > 0) {
    174									pp[i1][i2] = 1;
    175									ct         = search(pp, i1, i2, 1);
    176								}
    177							}
    178						}
    179						if (ct >= 4)
    180							delete(p, pp);
    181					}
    182					select();
    183				}
    184							// 再描画
    185				repaint();
    186			}
    187		}
    188	
    189		/****************/
    190		/* ピースの選択 */
    191		/****************/
    192		void select()
    193		{
    194			int color;
    195	
    196			ok  = true;
    197			rot = 0;
    198			p_y = 0;
    199			p_x = (int)(rn.nextDouble() * (gm.col - 1));
    200			if (p_x > gm.col-2)
    201				p_x = gm.col - 2;
    202	
    203			if (p[0][p_x] == 0 && p[0][p_x+1] == 0) {
    204				color = (int)(rn.nextDouble() * 4) + 1;
    205				if (color > 4)
    206					color = 4;
    207				p[0][p_x] = color;
    208				color = (int)(rn.nextDouble() * 4) + 1;
    209				if (color > 4)
    210					color = 4;
    211				p[0][p_x+1] = color;
    212			}
    213			else
    214				game = false;
    215		}
    216	
    217		/*************************************/
    218		/* 同じ色のピースを探す              */
    219		/*      pp : 同じ色のピース位置      */
    220		/*      k1,k2 : 対象としているピース */
    221		/*      c1 : 同じ色のピースの数      */
    222		/*      return : 同じ色のピースの数  */
    223		/*************************************/
    224		int search(int pp[][], int k1, int k2, int c1)
    225		{
    226			int ct = c1;
    227	
    228			if (k1 > 0 && p[k1-1][k2] == p[k1][k2] && pp[k1-1][k2] == 0) {
    229				pp[k1-1][k2] = 1;
    230				ct           = search(pp, k1-1, k2, ct+1);
    231			}
    232			if (k1 < gm.row-1 && p[k1+1][k2] == p[k1][k2] && pp[k1+1][k2] == 0) {
    233				pp[k1+1][k2] = 1;
    234				ct           = search(pp, k1+1, k2, ct+1);
    235			}
    236			if (k2 > 0 && p[k1][k2-1] == p[k1][k2] && pp[k1][k2-1] == 0) {
    237				pp[k1][k2-1] = 1;
    238				ct           = search(pp, k1, k2-1, ct+1);
    239			}
    240			if (k2 < gm.col-1 && p[k1][k2+1] == p[k1][k2] && pp[k1][k2+1] == 0) {
    241				pp[k1][k2+1] = 1;
    242				ct           = search(pp, k1, k2+1, ct+1);
    243			}
    244	
    245			return ct;
    246		}
    247	
    248		/********************************/
    249		/* 同じ色のピースを削除         */
    250		/*      p : ピース位置          */
    251		/*      pp : 同じ色のピース位置 */
    252		/********************************/
    253		void delete(int p[][], int pp[][])
    254		{
    255			int i1, i2, i3, k1, k2, k3;
    256						// 削除
    257			for (i1 = 0; i1 < gm.row; i1++) {
    258				for (i2 = 0; i2 < gm.col; i2++) {
    259					if (pp[i1][i2]  > 0)
    260						p[i1][i2] = 0;
    261				}
    262			}
    263						// 詰める
    264			for (i1 = 0; i1 < gm.col; i1++) {
    265				k1 = 1;
    266				for (i2 = gm.row-1; i2 > 0 && k1 >= 0; i2--) {
    267					if (p[i2][i1] == 0) {
    268						k1 = -1;
    269						for (i3 = i2-1; i3 >= 0 && k1 < 0; i3--) {
    270							if (p[i3][i1] > 0)
    271								k1 = i3;
    272						}
    273						if (k1 >= 0) {
    274							k2 = i2;
    275							k3 = k2 - k1;
    276							while (k1 >= 0) {
    277								p[k2][i1] = p[k1][i1];
    278								k1--;
    279								k2--;
    280							}
    281							k1++;
    282							for (i3 = 0; i3 < k3; i3++)
    283								p[i3][i1] = 0;
    284						}
    285					}
    286				}
    287			}
    288		}
    289	
    290		/********/
    291		/* 描画 */
    292		/********/
    293		public void paintComponent (Graphics g)
    294		{
    295			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    296	
    297			int i1, i2;
    298								// ゲーム中
    299			if (game) {
    300				for (i1 = 0; i1 < gm.row; i1++) {
    301					for (i2 = 0; i2 < gm.col; i2++) {
    302						if (p[i1][i2] > 0) {
    303							switch (p[i1][i2]) {
    304								case 1:
    305									g.setColor(Color.red);
    306									break;
    307								case 2:
    308									g.setColor(Color.pink);
    309									break;
    310								case 3:
    311									g.setColor(Color.green);
    312									break;
    313								case 4:
    314									g.setColor(Color.blue);
    315									break;
    316							}
    317							g.fillRect(i2*30, i1*30, 30, 30);
    318						}
    319					}
    320				}
    321			}
    322								// ゲームオーバー
    323			else {
    324				Font f = new Font("TimesRoman", Font.BOLD, 40);
    325				g.setFont(f);
    326				g.drawString("Game", 20, 200);
    327				g.drawString("Over!", 25, 250);
    328				state = false;
    329			}
    330		}
    331	
    332		/************************/
    333		/* キーイベントの有効化 */
    334		/************************/
    335		public boolean isFocusable() { return true; }
    336	
    337		/**********************/
    338		/* キーイベントの処理 */
    339		/**********************/
    340		class Key_e extends KeyAdapter {
    341			public void keyPressed(KeyEvent e)
    342			{
    343				int k;
    344				if (ok) {
    345					if (e.getKeyCode() == KeyEvent.VK_LEFT) {   // 左矢印(左移動)
    346						if (p_x > 0) {
    347							if (rot == 0 && p[p_y][p_x-1] == 0) {
    348								p[p_y][p_x-1] = p[p_y][p_x];
    349								p[p_y][p_x]   = p[p_y][p_x+1];
    350								p[p_y][p_x+1] = 0;
    351								p_x--;
    352								repaint();
    353							}
    354							else if (p[p_y][p_x-1] == 0 && p[p_y+1][p_x-1] == 0) {
    355								p[p_y][p_x-1]   = p[p_y][p_x];
    356								p[p_y+1][p_x-1] = p[p_y+1][p_x];
    357								p[p_y][p_x]     = 0;
    358								p[p_y+1][p_x]   = 0;
    359								p_x--;
    360								repaint();
    361							}
    362						}
    363					}
    364					else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {   // 右矢印(右移動)
    365						if (rot == 0) {
    366							if (p_x < gm.col-2 && p[p_y][p_x+2] == 0) {
    367								p[p_y][p_x+2] = p[p_y][p_x+1];
    368								p[p_y][p_x+1] = p[p_y][p_x];
    369								p[p_y][p_x]   = 0;
    370								p_x++;
    371								repaint();
    372							}
    373						}
    374						else {
    375							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0 && p[p_y+1][p_x+1] == 0) {
    376								p[p_y][p_x+1]   = p[p_y][p_x];
    377								p[p_y+1][p_x+1] = p[p_y+1][p_x];
    378								p[p_y][p_x]     = 0;
    379								p[p_y+1][p_x]   = 0;
    380								p_x++;
    381								repaint();
    382							}
    383						}
    384					}
    385					else if (e.getKeyCode() == KeyEvent.VK_UP) {   // 上矢印(上下または左右入れ替え)
    386						if (rot == 0) {
    387							k             = p[p_y][p_x];
    388							p[p_y][p_x]   = p[p_y][p_x+1];
    389							p[p_y][p_x+1] = k;
    390						}
    391						else {
    392							k             = p[p_y][p_x];
    393							p[p_y][p_x]   = p[p_y+1][p_x];
    394							p[p_y+1][p_x] = k;
    395						}
    396						repaint();
    397					}
    398					else if (e.getKeyCode() == KeyEvent.VK_DOWN) {   // 下矢印(90度または-90度回転)
    399						if (rot == 0 && p[p_y+1][p_x] == 0) {
    400							if (p_y < gm.row-1) {
    401								p[p_y+1][p_x] = p[p_y][p_x+1];
    402								p[p_y][p_x+1] = 0;
    403								rot           = 1;
    404								repaint();
    405							}
    406						}
    407						else {
    408							if (p_x < gm.col-1 && p[p_y][p_x+1] == 0) {
    409								p[p_y][p_x+1] = p[p_y+1][p_x];
    410								p[p_y+1][p_x] = 0;
    411								rot           = 0;
    412								repaint();
    413							}
    414						}
    415					}
    416				}
    417			}
    418		}
    419	}
    			
    104 行目

      作業域として使用するため,ピースの存在を示す 2 次元配列 p と同じ大きさの配列 pp を定義しています.

    164 行目~ 182 行目

      ピースが再下端に達するか,または,他のピースの上に乗って停止した場合に対する処理です.166 行目~ 180 行目において,メソッド search( 224 行目~ 246 行目)によって,ピース p[i1][i2] と隣り合っている同じ色のピースの数を調べ,その値が 4 以上である場合は,メソッド delete( 253 行目~ 288 行目)によってそれらのピースを削除しています.これら全体を 165 行目の while 文によって繰り返しているのは,削除の結果,再び同じ色のピースが繋がる可能性があるからです.

    224 行目~ 246 行目

      ピース p[k1][k2] の上下左右に同じ色のピースがあるか否かを,再帰呼び出しを利用して調べています.最終的に,隣り合った同じ色のピースの数が返されます.

    257 行目~ 262 行目

      隣り合った同じ色のピースを削除しています.

    264 行目~ 287 行目

      ピースが存在しない空白を詰める処理です.
    • 267 行目: p[i2][i1] が空白のとき以下の処理が行われます
    • 268 行目~ 273 行目: 空白の上にピースが存在するか否かを調べています.存在する場合は,その行番号が変数 k1 に設定され,274 行目以降が実行されます.
    • 274 行目~ 283 行目: k2 行,及び,k2 行から k1 行の間にある空白を詰めます.

13.4.2 グラフの表示

(プログラム例 13.16 ) グラフの表示JavaScript の場合

  このプログラムでは,棒グラフ,折れ線グラフ( 2 種類),積み上げ式棒グラフ,円グラフ,散布図,レーダーチャート,ボード線図を描くことができます.また,表示画面の大きさを変えることも可能です.さらに,右上の「横」または「縦」と記述された部分をクリックすると縦表示と横表示が切り替わります.同様に,「色」と記述された部分をクリックすることによって,グラフの色,折れ線グラフ等の線の太さやマークの有無等を変更することができます.

  このプログラムでは,データを適当に与えて全てのグラフを表示できるようにしていますが,例えば,BarGraph.java と Modify.java を使用して,棒グラフだけを描くことも可能です.勿論,目的の結果を得るための main メソッドを含むクラスを作成する必要があります.

13.4.3 遺伝的アルゴリズム

  ここで紹介するプログラムは,遺伝的アルゴリズム( GA )の基本的な流れを理解してもらうために,GA をステップごとに実行するものです.

(プログラム例 13.17 ) GAのステップ実行JavaScript の場合

  このプログラムでは,ビット列にある 1 の数を適応度として実行しています.従って,世代が進むほど,ビット列の中の 1 の数が増加していくはずです.2 組の親をランダムに選択して 1 点交叉を行い,淘汰方法としては,エリート選択を使用しています.

  なお,親や交叉位置の選択は,人間が行うことになりますので,できるだけ無作為に選択してみてください.1 の数が,世代毎に多くなっていくのが確認できると思います.

13.4.4 お絵かき

(プログラム例 13.18 ) マウスによる描画

  このプログラムは,マウスのドラッグを利用した簡単なお絵かきツールです.画面上でマウスをドラッグすると,その動きに沿った自由曲線が描画されます.( JavaScript の場合
01	/*************************/
02	/* マウスによる描画      */
03	/*   coded by Y.Suganuma */
04	/*************************/
05	import java.awt.*;
06	import java.awt.event.*;
07	import javax.swing.*;
08	import java.util.*;
09	
10	public class Test {
11		public static void main (String[] args)
12		{
13			Win win = new Win("整数の和");
14		}
15	}
16	
17	class Win extends JFrame
18	{
19		/******************/
20		/* コンストラクタ */
21		/******************/
22		Win(String name)
23		{
24						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
25			super(name);
26						// Windowの大きさ
27			setSize(240, 270);
28						// コンテントペインの取得
29			Test_Panel pn = new Test_Panel();
30			getContentPane().add(pn);
31						// ウィンドウを表示
32			setVisible(true);
33						// イベントアダプタ
34			addWindowListener(new WinEnd());
35		}
36	
37		/******************************/
38		/* 上,左,下,右の余白の設定 */
39		/******************************/
40		public Insets getInsets()
41		{
42			return new Insets(50, 20, 20, 20);
43		}
44	
45		/************/
46		/* 終了処理 */
47		/************/
48		class WinEnd extends WindowAdapter
49		{
50			public void windowClosing(WindowEvent e) {
51				System.exit(0);
52			}
53		}
54	}
55						// 描画パネル
56	class Test_Panel extends JPanel implements MouseMotionListener
57	{
58		ArrayList <Point> v = new ArrayList <Point> ();
59								// コンストラクタ
60		Test_Panel()
61		{
62			addMouseMotionListener(this);
63			setBackground(Color.white);
64		}
65								// マウスイベントの処理
66		public void mouseDragged(MouseEvent e)
67		{
68			v.add(new Point(e.getX(), e.getY()));
69			repaint();
70		}
71		public void mouseMoved(MouseEvent e)
72		{
73			if (v.size() > 0) {
74				Point p = (Point)v.get(v.size()-1);
75				if (p.x >= 0)
76					v.add(new Point(-1, -1));
77			}
78		}
79								// 描画
80		public void paintComponent (Graphics g)
81		{
82			super.paintComponent(g);   // 親クラスの描画(必ず必要)
83			int i1;
84			Point p1, p2;
85			if (v.size() > 0) {
86				p1 = (Point)v.get(0);
87				for (i1 = 1; i1 < v.size(); i1++) {
88					p2 = (Point)v.get(i1);
89					if (p1.x >= 0 && p2.x >= 0)
90						g.drawLine(p1.x, p1.y, p2.x, p2.y);
91					p1 = p2;
92				}
93			}
94		}
95	}
		
29 行目~ 30 行目

  Test_Panel クラス(クラス定義は 56 行目~ 95 行目,JPanel を継承)のオブジェクトを生成し,JRootPane クラスのオブジェクトに追加しています.

56 行目

  JPanel クラスを継承すると共に,マウスが動いたときに発生するイベントの処理を行うため,インタフェース MouseMotionListener を継承しています.

58 行目

  ArrayList クラスのオブジェクトを生成しています.ArrayList クラスは,可変長の配列を処理するためのクラスです.その要素として,様々なオブジェクトを利用可能ですが,ここでは,「 <Point> 」の指定により,Point クラスのオブジェクトが入ります.なお,Vector クラスを使用することも可能ですが,Vector クラスのメソッドは同期をとるため,単一のスレッドから Vector にアクセスする場合は,この例のように,ArrayList クラスを使用するべきです.

62 行目~ 63 行目

  背景色を白とし,MouseMotionListener を追加しています.

66 行目~ 70 行目

  マウスでドラッグしたときの処理であり,現在のマウスの位置から Point クラスのオブジェクトを生成し,それを,ArrayList クラスのオブジェクト v に追加しています( 68 行目).その後,画面を再描画しています( paintComponent メソッドの実行).

71 行目~ 78 行目

  マウスをドラッグしないで移動したときの処理です.ドラッグが行われているとき,刻々のマウスの位置を ArrayList クラスのオブジェクト v に保存し,それらを直線で繋いで描画するだけでは,すべての点が繋がった一本の曲線になってしまいます.ドラッグして一つの曲線を描き,マウスを移動して,他の曲線を描きたいような場合は,ArrayList クラスのオブジェクト v の中に二つの曲線を区切るような情報を入れておく必要があります.このプログラムでは,ドラッグが終了した時,(-1, -1) という点を v に保存することによって対処しています,

80 行目~ 94 行目

  描画を行う JPanel クラスのメソッド paintComponent をオーバーライドしています.親クラスの描画を行った( 82 行目)後,基本的には,ArrayList クラスのオブジェクト v に保存されている点を順に直線で結んでいる( 90 行目)だけです.ただし,上で述べたように,点の座標が (-1, -1) である場合は,その点の前後を結ばないようにしています.

  以下に示す例は,上で示した例にメニューを付加し,多少お絵かきソフトらしく修正したものです.マウスドラッグによる曲線と始点と終点をクリックすることによって決まる直線を描くことができます.また,線の太さや色の変更も可能です.( JavaScript の場合
001	/****************************/
002	/* お絵かきソフト           */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.awt.*;
006	import java.awt.geom.*;
007	import java.awt.event.*;
008	import javax.swing.*;
009	import java.util.*;
010	
011	public class Test {
012		public static void main (String[] args)
013		{
014			PaintPanel pt = new PaintPanel("ペイント");
015		}
016	}
017	
018	/****************************/
019	/* PaintPanel クラス        */
020	/*      coded by Y.Suganuma */
021	/****************************/
022	class PaintPanel extends JFrame
023	{
024		PaintPanel(String name)
025		{
026						// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
027			super(name);
028						// Windowの大きさ
029			setSize(500, 300);
030						// ウィンドウを表示
031			setVisible(true);
032						// MainPanel オブジェクトを ContentPane に追加
033			MainPanel pn = new MainPanel(this);
034			getContentPane().add(pn);   
035			setVisible(true);   // Window を表示
036						// イベントの登録
037			addWindowListener(new WinEnd());
038		}
039		/************/
040		/* 終了処理 */
041		/************/
042		class WinEnd extends WindowAdapter
043		{
044			public void windowClosing(WindowEvent e) {
045				System.exit(0);
046			}
047		}
048	}
049	
050	/****************************/
051	/* MainPanel クラス         */
052	/*      coded by Y.Suganuma */
053	/****************************/
054	class MainPanel extends JPanel
055	{
056		MainPanel(PaintPanel pt)
057		{
058						// 背景色の設定
059			setBackground(Color.white);
060						// レイアウトマネージャの停止
061			setLayout(null);
062						// Windowの大きさの取得
063			Dimension size = pt.getSize();
064			Insets insets = pt.getInsets();
065			size.width = size.width - insets.left - insets.right;
066			size.height = size.height - insets.top - insets.bottom;
067						// Draw パネルの追加
068			int ct_width = 140;
069			Draw dr = new Draw();
070			add(dr);   
071			dr.setSize(size.width - ct_width, size.height);
072			dr.setLocation(ct_width, 0);
073						// Control パネルの追加
074			Control ct = new Control(dr);
075			add(ct);
076			ct.setSize(ct_width, size.height);
077			ct.setLocation(0, 0);
078		}
079	}
080	
081	/****************************/
082	/* Draw クラス              */
083	/*      coded by Y.Suganuma */
084	/****************************/
085	class Draw extends JPanel implements MouseListener, MouseMotionListener
086	{
087		int type = 0;   // 図形の種類(0:自由曲線,1:直線)
088		Color cl = Color.black;   // 描画色
089		Stroke bs = new BasicStroke(1.0f);   // 線のタイプ
090		ArrayList <Segment> s = new ArrayList <Segment> ();   // 描画データ
091		ArrayList <Point> v = new ArrayList <Point> ();   // 描画位置データ
092		boolean draw = false;   // 作図中か否か
093						// コンストラクタ
094		Draw()
095		{
096								// 背景色の設定
097			setBackground(Color.white);
098								// MouseListenerの追加
099			addMouseListener(this);
100								// MouseMotionListenerの追加
101			addMouseMotionListener(this);
102		}
103						// MouseMotionListenerに対する処理
104		public void mouseDragged(MouseEvent e)
105		{
106								// 自由曲線
107			if (type == 0) {
108				draw = true;
109				v.add(new Point(e.getX(), e.getY()));
110				repaint();
111			}
112		}
113		public void mouseMoved(MouseEvent e)
114		{
115								// 自由曲線
116			if (type == 0) {
117				if (draw) {
118					draw = false;
119					Segment sg = new Segment(type, cl, bs, v);
120					s.add(sg);
121					v.clear();
122				}
123			}
124		}
125						// MouseListenerによる処理
126		public void mouseClicked(MouseEvent e)
127		{
128								// 直線
129			if (type == 1) {
130										// 描画と保存
131				if (draw) {
132					v.add(new Point(e.getX(), e.getY()));
133					repaint();
134				}
135										// スタート
136				else {
137					draw = true;
138					v.add(new Point(e.getX(), e.getY()));
139					type = -type;
140					repaint();
141				}
142			}
143		}
144		public void mousePressed(MouseEvent e) {}
145		public void mouseReleased(MouseEvent e) {}
146		public void mouseEntered(MouseEvent e) {}
147		public void mouseExited(MouseEvent e) {}
148						// 描画
149		public void paintComponent(Graphics g)
150		{
151			super.paintComponent(g);   // 親クラスの描画
152								// Graphics2Dの取得
153			Graphics2D g2 = (Graphics2D)g;
154								// 既に描いた図形の描画
155			for (int i1 = 0; i1 < s.size(); i1++) {
156				g2.setStroke(s.get(i1).bs);
157				g2.setColor(s.get(i1).cl);
158										// 自由曲線,直線
159				if (s.get(i1).type == 0 || s.get(i1).type == 1) {
160					Point p1, p2;
161					p1 = s.get(i1).v.get(0);
162					for (int i2 = 1; i2 < s.get(i1).v.size(); i2++) {
163						p2 = s.get(i1).v.get(i2);
164						g2.draw(new Line2D.Double(p1.x, p1.y, p2.x, p2.y));
165						p1 = p2;
166					}
167				}
168			}
169								// 作図中
170			if (draw) {
171				g2.setStroke(bs);
172				g2.setColor(cl);
173										// スタート
174				if (type < 0) {
175					type = -type;
176					g2.setColor(Color.black);
177					g2.setStroke(new BasicStroke(1.0f));
178					Point p = v.get(0);
179					g2.draw(new Line2D.Double(p.x-10, p.y, p.x+10, p.y));
180					g2.draw(new Line2D.Double(p.x, p.y-10, p.x, p.y+10));
181				}
182										// 自由曲線,直線
183				else if (type == 0 || type == 1) {
184					Point p1, p2;
185					p1 = v.get(0);
186					for (int i1 = 1; i1 < v.size(); i1++) {
187						p2 = v.get(i1);
188						g2.draw(new Line2D.Double(p1.x, p1.y, p2.x, p2.y));
189						p1 = p2;
190					}
191					if (type == 1) {
192						draw = false;
193						Segment sg = new Segment(type, cl, bs, v);
194						s.add(sg);
195						v.clear();
196					}
197				}
198			}
199		}
200	}
201	
202	/****************************/
203	/* Control クラス           */
204	/*      coded by Y.Suganuma */
205	/****************************/
206	class Control extends JPanel
207	{
208		Draw dr;   // Draw クラス
209		JButton type0, type1, color1, color2, line1, line2;
210						// コンストラクタ
211		Control(Draw dr)
212		{
213			this.dr = dr;
214								// 背景色の設定
215			setBackground(new Color(238, 255, 238));
216								// ボタンの配置
217			type0 = new JButton("曲線");
218			type0.addMouseListener(new ClickMouse());
219			add(type0);
220	
221			type1 = new JButton("直線");
222			type1.addMouseListener(new ClickMouse());
223			add(type1);
224	
225			color1 = new JButton("赤");
226			color1.setBackground(Color.red);
227			color1.addMouseListener(new ClickMouse());
228			add(color1);
229	
230			color2 = new JButton("緑");
231			color2.setBackground(Color.green);
232			color2.addMouseListener(new ClickMouse());
233			add(color2);
234	
235			line1 = new JButton("細い");
236			line1.setBackground(Color.yellow);
237			line1.addMouseListener(new ClickMouse());
238			add(line1);
239	
240			line2 = new JButton("太い");
241			line2.setBackground(Color.yellow);
242			line2.addMouseListener(new ClickMouse());
243			add(line2);
244		}
245						// ボタンが押されたときの処理
246		class ClickMouse extends MouseAdapter {
247			public void mouseClicked(MouseEvent e)
248			{
249				if (e.getSource() == type0)
250					dr.type = 0;
251				else if (e.getSource() == type1)
252					dr.type = 1;
253				else if (e.getSource() == color1)
254					dr.cl = Color.red;
255				else if (e.getSource() == color2)
256					dr.cl = Color.green;
257				else if (e.getSource() == line1)
258					dr.bs = new BasicStroke(1.0f);
259				else if (e.getSource() == line2)
260					dr.bs = new BasicStroke(5.0f);
261			}
262		}
263	}
264	
265	/****************************/
266	/* Segment クラス           */
267	/*      coded by Y.Suganuma */
268	/****************************/
269	class Segment
270	{
271		int type;   // 図形の種類(0:自由曲線,1:直線,2:矩形,3:塗りつぶした矩形)
272		Color cl;   // 描画色
273		Stroke bs;   // 線のタイプ
274		ArrayList <Point> v = new ArrayList <Point> ();   // 描画位置データ
275						// コンストラクタ
276		Segment(int type, Color cl, Stroke bs, ArrayList <Point> v)
277		{
278			this.type = type;
279			this.cl = cl;
280			this.bs = bs;
281			this.v.addAll(v);
282		}
283	}
		
014 行目

  PaintPanel クラス(クラス定義は 022 行目~ 048 行目,JFrame を継承)のオブジェクトを生成しています.この結果,描画を行うための新しい Window が表示されます.

033 行目~ 034 行目

  MainPanel クラス(クラス定義は 054 行目~ 079 行目,JPanel を継承)のオブジェクトを生成し,getContentPane によって取得した ContentPane クラスのオブジェクトに追加しています.

037 行目

  Window の「×」ボタンをクリックしたとき,Window を終了させるために WindowListener を追加しています.

042 行目~ 047 行目

  Window の「×」ボタンをクリックしたときの処理です( Window の終了).

061 行目

  レイアウトマネージャを無効にしています.その結果,コンポーネントを任意の位置に,任意の大きさで配置できるようになります.

063 行目~ 066 行目

  Window の大きさ( 063 行目)から,枠等のサイズを引き,実際の表示領域を Dimension クラスのオブジェクト size に設定しています( 064 行目~ 066 行目).

068 行目~ 072 行目

  MainPanel の右側に,実際に描画を行うパネル( Draw クラス,085 行目~ 200 行目)を追加しています.

074 行目~ 077 行目

  MainPanel の左側に,線の太さ,線の色等を制御するパネル( Control クラス,206 行目~ 263 行目)を追加しています.

108 行目~ 110 行目

  マウスでドラッグしたときの処理であり,自由曲線を描く場合( type = 0 )は,先に述べた例と同様,現在のマウスの位置から Point クラスのオブジェクトを生成し,それを,ArrayList クラスのオブジェクト v に追加しています.

113 行目~ 124 行目

  マウスが移動したときの処理です.自由曲線を描く場合( type = 0 )で,かつ,それまでドラッグしていた場合( draw = true )は,描画を中止し( 118 行目),ArrayList クラスのオブジェクト v に保存されている自由曲線データと線の種類等に関するデータに基づき,Segment クラス( 269 行目~ 283 行目)のオブジェクトを生成し,ArrayList クラスのオブジェクト s に追加しています.また,ArrayList クラスのオブジェクト v の内容をクリアしています.

126 行目~ 143 行目

  マウスをクリックしたときの処理です.直線を描く場合( type = 1 )において,描画中の場合( draw = true )は,終点を ArrayList クラスのオブジェクト v に追加し,再描画しています( 132 行目~ 133 行目).また,描画中でない場合( draw = false )は,始点を ArrayList クラスのオブジェクト v に追加し,再描画しています( 137 行目~ 140 行目).

144 行目~ 147 行目

  これらの行は,MouseListener に定義されている関数であり,実際に処理する内容はありませんが.このような形で定義しておく必要があります.

153 行目

  Graphics2D クラスのオブジェクトを生成しています.Graphics2D クラスは,線の幅等を設定するために必要になります.

155 行目~ 168 行目

  ArrayList クラスのオブジェクト s に保存されている Segment クラスのオブジェクトに基づき,自由曲線または直線を描画しています.

175 行目~ 180 行目

  type の値が負であることは,139 行目より,直線の始点を指定したことを意味しています.ここでは,黒い細い線で,始点に十字を描いています.

184 行目~ 190 行目

  ArrayList クラスのオブジェクト v に保存された点を結ぶ直線を描いています.

192 行目~ 195 行目

  ArrayList クラスのオブジェクト v に保存されている直線データと線の種類等に関するデータに基づき,Segment クラスのオブジェクトを生成し,ArrayList クラスのオブジェクト s に追加しています.また,ArrayList クラスのオブジェクト v の内容をクリアしています.

217 行目~ 243 行目

  線の種類,色,太さを変更するために,JButton クラスのオブジェクトを追加しています.

246 行目~ 262 行目

  上で設定した各ボタンがクリックされたときの処理です.クリックされたボタンに従って,Draw クラスのオブジェクトのプロパティ type(線の種類),cl(線の色),または,bs(線の太さ)の値を再設定しています.

269 行目~ 283 行目

  線の種類( type ),線の色( cl ),線の太さ( bs ),線の位置( v )をプロパティとして持つクラスです.

13.4.5 その他

(プログラム例 13.19 ) 2 次方程式の根

  このプログラムは,2 次方程式,

   ax2 + bx + c = 0

の根を求めるためのものです.適当な数値を入力して,結果を確認してみてください.2 次方程式の根を求める程度のことであっても,コンピュータが勝手にやってくれるわけではありません.その計算手順を記述したものをプログラムとしてコンピュータに教えてやる必要があります(「2次方程式を解く」以下の部分を参照).
001	/********************************************/
002	/* クラスEquationの定義(二次方程式を解く) */
003	/*      coded by Y.Suganuma                 */
004	/********************************************/
005	import java.io.*;
006	import java.awt.*;
007	import java.awt.event.*;
008	import javax.swing.*;
009	
010	public class Test {
011		public static void main (String[] args)
012		{
013			Equation win = new Equation("二次方程式を解く");
014		}
015	}
016	
017	class Equation extends JFrame {
018	
019		double a, b, c;   // 2次方程式の係数
020	
021		private JTextField a1, b1, c1;
022		private Equation dt = this;
023	
024		/******************/
025		/* コンストラクタ */
026		/******************/
027		Equation(String name)
028		{
029							// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
030			super(name);
031							// Windowの大きさ
032			setSize(340, 270);
033							// フォント
034			Font f = new Font("TimesRoman", Font.BOLD, 20);
035							// レイアウトの変更
036			Container cp = getContentPane();
037			cp.setBackground(Color.white);
038			JPanel jp = new JPanel();
039			jp.setLayout(new GridLayout(4, 2, 5, 10));
040			jp.setBackground(Color.white);
041			cp.add(jp, BorderLayout.CENTER);
042							// ラベル & テキストフィールド
043			JLabel fill1 = new JLabel("2次の係数");
044			fill1.setFont(f);
045			fill1.setBackground(Color.white);
046			jp.add(fill1);
047			a1 = new JTextField(10);
048			a1.setFont(f);
049			a1.setBackground(Color.white);
050			a1.setHorizontalAlignment(JTextField.RIGHT);
051			jp.add(a1);
052	
053			JLabel fill2 = new JLabel("1次の係数");
054			fill2.setFont(f);
055			fill2.setBackground(Color.white);
056			jp.add(fill2);
057			b1 = new JTextField(10);
058			b1.setFont(f);
059			b1.setBackground(Color.white);
060			b1.setHorizontalAlignment(JTextField.RIGHT);
061			jp.add(b1);
062	
063			JLabel fill3 = new JLabel("定数項");
064			fill3.setFont(f);
065			fill3.setBackground(Color.white);
066			jp.add(fill3);
067			c1 = new JTextField(10);
068			c1.setFont(f);
069			c1.setBackground(Color.white);
070			c1.setHorizontalAlignment(JTextField.RIGHT);
071			jp.add(c1);
072							// OK ボタンの設定
073			JLabel fill4 = new JLabel("  実行?");
074			fill4.setFont(f);
075			fill4.setBackground(Color.white);
076			jp.add(fill4);
077			JButton bt = new JButton("OK");
078			bt.setFont(f);
079			bt.addMouseListener(new ClickMouse());
080			jp.add(bt);
081						// ウィンドウを表示
082			setVisible(true);
083						// イベントアダプタ
084			addWindowListener(new WinEnd());
085		}
086	
087		/******************************/
088		/* 上,左,下,右の余白の設定 */
089		/******************************/
090		public Insets getInsets()
091		{
092			return new Insets(50, 20, 20, 20);
093		}
094	
095		/************/
096		/* 終了処理 */
097		/************/
098		class WinEnd extends WindowAdapter
099		{
100			public void windowClosing(WindowEvent e) {
101				System.exit(0);
102			}
103		}
104	
105		/********************************/
106		/* OKボタンが押されたときの処理 */
107		/********************************/
108		class ClickMouse extends MouseAdapter
109		{
110			/************************************/
111			/* マウスがクリックされたときの処理 */
112			/************************************/
113			public void mouseClicked(MouseEvent e)
114			{
115								// データの設定
116				if ((a1.getText()).length() <= 0)
117					a = 0.0;
118				else
119					a = (Double.valueOf(a1.getText())).doubleValue();
120	
121				if ((b1.getText()).length() <= 0)
122					b = 0.0;
123				else
124					b = (Double.valueOf(b1.getText())).doubleValue();
125	
126				if ((c1.getText()).length() <= 0)
127					c = 0.0;
128				else
129					c = (Double.valueOf(c1.getText())).doubleValue();
130								// 実行
131				Run run = new Run(dt);
132			}
133		}
134	}
135	
136	/************************/
137	/* クラスRunの定義      */
138	/* (2次方程式を解く) */
139	/************************/
140	class Run extends JFrame {
141	
142		private Equation dt;
143		private Run rn = this;
144		private JTextArea text;
145	
146		/********************************************/
147		/* コンストラクタ                           */
148		/*      dt_i : Equationクラスのオブジェクト */
149		/********************************************/
150		Run(Equation dt_i)
151		{
152							// Frameクラスのコンストラクタの呼び出し
153			super("2次方程式");
154							// ContentPane
155			Container cp = getContentPane();
156			cp.setBackground(Color.white);
157			cp.setLayout(null);
158			JPanel jp = new JPanel();
159			jp.setBackground(Color.white);
160			cp.add(jp);
161							// テキストフィールドの設定
162			Font f = new Font("TimesRoman", Font.PLAIN, 20);
163			dt = dt_i;
164			text = new JTextArea("", 5, 25);
165			text.setFont(f);
166			text.setBackground(Color.white);
167			jp.add(text);
168							// Windowサイズを設定
169			setSize(450, 250);
170			setLocation(100, 150);
171							// ウィンドウを表示
172			setVisible(true);
173			Insets in = getInsets();
174			jp.setLocation(10, in.top+10);
175			jp.setSize(450-in.left-in.right-20, 250-in.top-in.bottom-20);
176							// イベントアダプタ
177			addWindowListener(new WinEnd());
178							// 実行
179			solve();
180		}
181	
182		/********************/
183		/* 2次方程式を解く */
184		/********************/
185		public void solve()
186		{
187			double a = dt.a, b = dt.b, c = dt.c, D, x, x1, x2;
188			String str;
189		/*
190		          一次方程式 or 解なし
191		*/
192			if (Math.abs(a) <= 1.0e-10) {
193				if (Math.abs(b) <= 1.0e-10)
194					text.insert("解を求めることができません!\n", 0);
195				else {
196					x = -c / b;
197					str = "x = " + Double.toString(x) + "\n";
198					text.insert(str, 0);
199				}
200			}
201		/*
202		         二次方程式
203		*/
204			else {
205	
206				D = b * b - 4.0 * a * c;
207							// 実根
208				if (D >= 0.0) {
209					D  = Math.sqrt(D);
210					x1 = 0.5 * (-b - D) / a;
211					x2 = 0.5 * (-b + D) / a;
212					str = "x = " + Double.toString(x1) + ", " + Double.toString(x2) + "\n";
213					text.insert(str, 0);
214				}
215							// 虚根
216				else {
217					D  = Math.sqrt(-D);
218					x1 = -0.5 * b / a;
219					x2 = 0.5 * D / a;
220					str = "x = " + Double.toString(x1) + " ± i " + Double.toString(x2) + "\n";
221					text.insert(str, 0);
222				}
223			}
224		}
225	
226		/************/
227		/* 終了処理 */
228		/************/
229		class WinEnd extends WindowAdapter
230		{
231			public void windowClosing(WindowEvent e) {
232				rn.setVisible(false);
233			}
234		}
235	}
		
036 行目~ 041 行目

  ContentPane に JPanel クラスのオブジェクトを貼り付け,そのレイアウトマネージャを,4 行 2 列の GridLayout に変更しています.

043 行目~ 080 行目

  係数を設定するテキストフィールド,実行に移るためのボタン,及び,各要素の説明となるラベルを配置しています.GridLayout においては,追加された各要素が,1 行 1 列,1 行 2 列,2 行 1 列,・・・の順に並んでいきます.なお,テキストフィールドに入力される文字列は,右寄せに設定しています.

090 行目~ 093 行目

  Window と実際に描画される領域間の余白を設定しています.

113 行目~ 132 行目

  OK ボタンがクリックされたときの処理です.設定された各係数の値を読み込み,Run クラス( 140 行目~ 235 行目)のオブジェクトを生成しています( 131 行目).

153 行目~ 177 行目

  答を表示するための window を生成しています.

179 行目

  答を計算するため,メソッド solve ( 185 行目~ 224 行目)を呼んでいます.

194 行目

  a = 0,b = 0 の場合に対する処理です.

196 行目~ 198 行目

  a = 0,b ≠ 0 の場合に対する処理です.

206 行目

  以下,a ≠ 0 の場合に対する処理です.ここでは,判別式の計算を行っています.

209 行目~ 213 行目

  判別式の値が 0 以上の場合に対する処理です.

217 行目~ 221 行目

  判別式の値が 0 未満の場合に対する処理です.

(プログラム例 13.20 ) 剛体振り子の運動

       

  表示される Window (上図の左)に適当な値を設定してやることによって,剛体の振り子(上図の中),または,倒立振り子(上図の右)のシミュレーションが可能です.振り子と倒立振り子の運動は,以下に示す微分方程式によって記述されます.この方程式や図からも明らかなように,これらは全く同じシステムです.ただ,角度の計り方が異なるだけです.

振り子
     (I+mr2d2θ/dt2+kdθ/dt+mrgsinθ=-k1θ

倒立振り子
     (I+mr2d2θ/dt2+kdθ/dt-mrgsinθ=-k1θ

ただし,

   m:棒の質量
   l:棒の長さ
   r:棒の重心の位置(=l/2)
   i:棒の慣性能率(l2/12)
   k:摩擦係数(≧0)
   g:重力の加速度(=9.8)
   k1:フィードバックゲイン

とします. → プログラム

(プログラム例 13.21 ) マニピュレータとLEDの点灯制御

[マニピュレータの制御]

  C/C++は,様々なものを制御するための言語としてよく使用されます.しかし,制御対象となるハードウェアを準備することは必ずしも容易ではありません.この例では,マニピュレータのモデル( 3 リンクのマニピュレータ)を Java で作成し(下の図),それを C/C++ のプログラムで制御しています.制御部分のプログラムを Java で記述することも可能です.

  まず,Java のプログラムと制御用の C/C++ のプログラムを同じディレクトリに置き,コマンドプロンプトから Java のプログラムを起動した後,他のコマンドプロンプトから C/C++ のプログラムを実行することによってマニピュレータを制御できます.

  Java のプログラムを起動すると,ファイル angle.txt に以下のようなデータが出力されます.最初の数値は,0 ~ 2 の値をとり,0 は C/C++ のプログラムからマニピュレータへの命令が出力されたことを表し,1 はマニピュレータ( Java のプログラム)からマニピュレータの現時点における状態が出力されたことを表しています.また,それ以外の値は,エラーを表します.
1 0 0 0		
  続く 3 つの数値は,各リンクの角度を表しています.1 番目のリンクに対しては,垂直方向から時計方向に測った角度,2 番目以降のリンクに対しては,一つ前のリンクに対する相対角度(時計方向が正)になっています.ただし,8 ビットで表現可能な数値( 0 ~ 255 )を 0 ~ 90 度に対応させています.これらのデータを使用して,どのようにして制御が行われるかについては,以下に示す各プログラムに対する説明を参照してください.

C/C++ のプログラム

01	/****************************/
02	/* マニピュレータの制御     */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include  <stdio.h>
06	#include  <time.h>
07	
08	/***************************/
09	/* xミリ秒経過するのを待つ */
10	/***************************/
11	int sleep(unsigned long x)
12	{
13		clock_t  s = clock();
14		clock_t  c;
15		int sw = 1;
16	
17		do {
18			if ((c = clock()) == (clock_t)-1) {       /* エラー */
19				sw = 0;
20				break;
21			}
22		} while (1000UL * (c - s) / CLOCKS_PER_SEC <= x); 
23	
24		return sw;
25	}
26	
27	/*************************/
28	/* 入出力領域への入出力  */
29	/*      str1 : コピー先  */
30	/*      str2 : コピー元  */
31	/*      n : コピー文字数 */
32	/*      sw : 1 : 入力    */
33	/*           2 : 出力    */
34	/*************************/
35	void *memcpy(void *str1, void *str2, size_t n, int sw)
36	{
37		int i1, k;
38		unsigned char *c1 = (unsigned char *)str1, *c2 = (unsigned char *)str2;
39		FILE *in, *out;
40						// 入力
41		if (sw == 1) {
42			in = fopen("angle.txt", "r");
43			for (i1 = 0; i1 < (int)n; i1++) {
44				fscanf(in, "%d", &k);
45				c2[i1] = (unsigned char)k;
46			}
47			for (i1 = 0; i1 < (int)n; i1++)
48				c1[i1] = c2[i1];
49			fclose(in);
50		}
51						// 出力
52		else {
53			for (i1 = 0; i1 < (int)n; i1++)
54				c1[i1] = c2[i1];
55			out = fopen("angle.txt", "w");
56			k   = (int)c1[0];
57			fprintf(out, "%d", k);
58			for (i1 = 1; i1 < (int)n; i1++) {
59				k = (int)c1[i1];
60				fprintf(out, " %d", k);
61			}
62			fprintf(out, "\n");
63			fclose(out);
64		}
65	
66		return str1;
67	}
68	
69	/********/
70	/* main */
71	/********/
72	int main(void)
73	{
74		int k = 0, ang[3] = {0};
75		unsigned char state[4], io[4];
76	
77		while (k < 3) {
78			sleep(500);
79			memcpy(state, io, 4, 1);
80			if (state[0] == 1) {
81				ang[0]   += 10;
82				ang[1]   += 20;
83				ang[2]   += 30;
84				state[0]  = 0;
85				state[1]  = (unsigned char)(255.0 * ang[0] / 90.0 + 0.5);
86				state[2]  = (unsigned char)(255.0 * ang[1] / 90.0 + 0.5);
87				state[3]  = (unsigned char)(255.0 * ang[2] / 90.0 + 0.5);
88				memcpy(io, state, 4, 2);
89				printf("angle %d %d %d\n", ang[0], ang[1], ang[2]);
90				k++;
91			}
92			else if (state[0] > 1)
93				break;
94		}
95	
96		return 0;
97	}
		
11 行目~ 25 行目

  x ms 待つための関数です.

35 行目~ 67 行目

  実際の制御においては,メモリ上のいずれかの場所を介して,制御対象との情報交換が行われるはずです.そのような意味から,多少奇妙な名前と引数を持った関数になっていますが,実際は,ファイル angle.txt に対して,4 つのデータの入出力を行っているだけです.

77 行目

  この例では,角度を 3 回変更する制御を行っています.それが,この while 文の意味です.変数 k の値は 90 行目で増加させています.

78 行目

  500 ms だけ処理を待っています.

79 行目

  ファイル angle.txt から,配列 state に 4 つのデータを読み込んでいます.

80 行目

  state[0] の値が 1 であることは,マニピュレータの状態がファイル angle.txt に出力されたことを意味しています.入力した state[0] の値が 0 であることは,C/C++ のプログラムからの命令に対して,マニピュレータが対応していないことを意味しています.そのため,何も行いません.なお,92,93 行目は,エラー処理です.

81 行目~ 83 行目

  各リンクに設定する角度を,10 度,20 度,及び,30 度ずつ増加させています.

84 行目

  state[0] の値が 0 であることは,C/C++ のプログラムからマニピュレータへの命令が決定されたことを意味しています.

85 行目~ 87 行目

  各リンクに設定する角度を,state[1] ~ state[3] に設定しています.ただし,0 ~ 90 の値を 0 ~ 255 の値に変換しています.

88 行目

  ファイル angle.txt へ,配列 state に設定された 4 つのデータを出力しています.

Java のプログラム

001	/****************************/
002	/* マニピュレータ           */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.io.*;
006	import java.awt.*;
007	import javax.swing.*;
008	import java.awt.event.*;
009	import java.util.StringTokenizer;
010	
011	public class Test {
012		public static void main (String[] args) throws IOException
013		{
014			LinkControl lc = new LinkControl("マニピュレータ");
015		}
016	}
017	
018	class LinkControl extends JFrame {
019	
020		int n_l = 3;
021		int len[] = {200, 100, 50};
022	
023		/************************/
024		/* コンストラクタ       */
025		/*      name : タイトル */
026		/************************/
027		LinkControl(String name)
028		{
029						// JFrameクラスのコンストラクタの呼び出し
030			super(name);
031						// Windowの大きさの設定
032			int i1, s = 0;
033			for (i1 = 0; i1 < n_l; i1++)
034				s += len[i1];
035			setSize(s+40, s+70);
036						// ウィンドウを表示
037			Container cp = getContentPane();
038			cp.setBackground(Color.white);
039			cp.setLayout(null);
040			setVisible(true);
041			Insets in = getInsets();
042			Draw_link dp = new Draw_link(this);
043			dp.setBackground(Color.white);
044			dp.setLocation(0, 0);
045			dp.setSize(s+40-in.left-in.right, s+70-in.top-in.bottom);
046			cp.add(dp);
047						// イベントアダプタ
048			addWindowListener(new WinEnd());
049		}
050	
051		/************/
052		/* 終了処理 */
053		/************/
054		class WinEnd extends WindowAdapter
055		{
056			public void windowClosing(WindowEvent e) {
057				System.exit(0);
058			}
059		}
060	}
061	
062	class Draw_link extends JPanel implements Runnable, ActionListener {
063	
064		Thread th;
065		boolean state = true;
066		Dimension d;
067		int init = 1, err = 0;
068		int now[];
069		int now_o[];
070		int next[];
071		JButton bt;
072		Insets in;
073		LinkControl lc;
074	
075		/************************/
076		/* コンストラクタ       */
077		/*      name : タイトル */
078		/************************/
079		Draw_link(LinkControl lc1)
080		{
081			lc    = lc1;
082			now   = new int [lc.n_l];
083			now_o = new int [lc.n_l];
084			next  = new int [lc.n_l];
085						// Windowの大きさ
086			d  = lc.getSize();
087			in = lc.getInsets();
088						// リセットボタンの追加
089			setLayout(null);
090			Font f = new Font("MS 明朝", Font.PLAIN, 20);
091			bt = new JButton("Reset");
092			bt.setFont(f);
093			bt.setBackground(Color.yellow);
094			bt.addActionListener(this);   // リスナー
095			bt.setLocation(d.width-100-in.right, 20);
096			bt.setSize(new Dimension(90, 30));
097			add(bt);
098						// スレッドの定義と開始
099			th = new Thread(this);
100			th.start();
101		}
102	
103		/******************************/
104		/* ボタンが押されたときの処理 */
105		/******************************/
106		public void actionPerformed(ActionEvent e)
107		{
108			init = 1;
109		}
110	
111		/******************/
112		/* スレッドの実行 */
113		/******************/
114		public void run()
115		{
116			int i1, k, draw = 1, sw = 0, chg, y1, y2, s = 0;
117			double a;
118			String str;
119			StringTokenizer token;
120	
121			while (state) {
122				try {
123					th.sleep(500);
124				}
125				catch (InterruptedException e) {}
126						// 初期設定
127				if (init > 0) {
128					init = 0;
129					chg  = 1;
130					for (i1 = 0; i1 < lc.n_l; i1++) {
131						now[i1]  = 0;
132						next[i1] = 0;
133					}
134					repaint();
135				}
136						// 初期設定以外
137				else {
138								// 角度の読み込み
139					chg = 0;
140					try {
141						BufferedReader in = new BufferedReader(new FileReader("angle.txt"));
142						str   = in.readLine();
143						token = new StringTokenizer(str, " \n");
144						k     = -1;
145						while (token.hasMoreTokens()) {
146							if (k < 0) {
147								draw = Integer.parseInt(token.nextToken());
148								if (draw > 0)
149									break;
150							}
151							else
152								next[k] = (int)Math.round(90.0 * Integer.parseInt(token.nextToken()) / 255.0);
153							k++;
154						}
155						in.close();
156					}
157					catch (IOException er_i) {}
158								// 描画
159					if (draw == 0 && err == 0) {
160						sw = 1;
161						while (sw > 0) {
162							sw = 0;
163							try {
164								th.sleep(10);
165							}
166							catch (InterruptedException e) {}
167							for (i1 = 0; i1 < lc.n_l; i1++) {
168								if (next[i1] != now[i1]) {
169									sw  = 1;
170									chg = 1;
171									if (next[i1] > now[i1])
172										now[i1]++;
173									else
174										now[i1]--;
175								}
176							}
177							if (sw > 0) {
178								repaint();
179								y1 = d.height - 10;   // 地面の高さ
180								y2 = y1;
181								for (i1 = 0; i1 < lc.n_l; i1++) {
182									if (i1 == 0) {
183										a  = now[i1] * Math.PI / 180.0;
184										s  = 0;
185									}
186									else
187										a  = (s + now[i1]) * Math.PI / 180.0;
188									y2  = y2 - (int)Math.round(lc.len[i1] * Math.cos(a));
189									s  += now[i1];
190								}
191								if (y2 >= y1) {
192									err = 1;
193									break;
194								}
195							}
196						}
197					}
198				}
199						// 位置情報の出力
200				if (chg > 0) {
201					str = "angle";
202					for (i1 = 0; i1 < lc.n_l; i1++) {
203						now_o[i1]  = (int)Math.round(now[i1] * 255.0 / 90.0);
204						str       += (" " + now[i1]);
205					}
206					System.out.println(str);
207					str = (err == 0) ? "1" : "2";
208					try {
209						PrintStream out = new PrintStream(new FileOutputStream("angle.txt"));
210						for (i1 = 0; i1 < lc.n_l; i1++)
211							str += (" " + now_o[i1]);
212						out.println(str);
213						out.close();
214					}
215					catch (IOException er_o) {}
216				}
217			}
218		}
219	
220		/********/
221		/* 描画 */
222		/********/
223		public void paintComponent (Graphics g)
224		{
225			super.paintComponent(g);   // 親クラスの描画(必ず必要)
226	
227			int i1, x1, y1, x2, y2, s = 0;
228			int xp[] = new int [4];
229			int yp[] = new int [4];
230			double a = 0.0;
231						// 座標軸の描画
232			g.setColor(Color.black);
233			x1 = 10;
234			y1 = d.height - in.bottom - in.top - 10;
235			y2 = 5;
236			g.drawLine(x1, y1, x1, y2);
237			x2 = d.width - in.right - in.left - 5;
238			g.drawLine(x1, y1, x2, y1);
239						// リンクの描画
240			for (i1 = 0; i1 < lc.n_l; i1++) {
241				if (i1 == 0) {
242					a = now[i1] * Math.PI / 180.0;
243					s = 0;
244				}
245				else {
246					a  = (s + now[i1]) * Math.PI / 180.0;
247					x1 = x2;
248					y1 = y2;
249				}
250				x2 = x1 + (int)Math.round(lc.len[i1] * Math.sin(a));
251				y2 = y1 - (int)Math.round(lc.len[i1] * Math.cos(a));
252				g.setColor(Color.pink);
253				xp[0] = x1 - (int)Math.round(2 * Math.sin(a-0.5*Math.PI));
254				yp[0] = y1 + (int)Math.round(2 * Math.cos(a-0.5*Math.PI));
255				xp[1] = x1 - (int)Math.round(2 * Math.sin(a+0.5*Math.PI));
256				yp[1] = y1 + (int)Math.round(2 * Math.cos(a+0.5*Math.PI));
257				xp[2] = x2 - (int)Math.round(2 * Math.sin(a+0.5*Math.PI));
258				yp[2] = y2 + (int)Math.round(2 * Math.cos(a+0.5*Math.PI));
259				xp[3] = x2 - (int)Math.round(2 * Math.sin(a-0.5*Math.PI));
260				yp[3] = y2 + (int)Math.round(2 * Math.cos(a-0.5*Math.PI));
261				g.fillPolygon(xp, yp, 4);
262				g.setColor(Color.red);
263				g.fillOval(x1-5, y1-5, 10, 10);
264				s += now[i1];
265			}
266		}
267	}
		
020 行目

  リンクの数です.

021 行目

  各リンクの長さです.

027 行目~ 049 行目

  Window を生成すると共に,Draw_link クラス( 062 行目~ 267 行目)を使用してマニピュレータを描画しています( 042 行目).

079 行目~ 101 行目

  Draw_link クラスのコンストラクタであり,リセットボタンを追加し,スレッドを開始しています.

141 行目~ 155 行目

  ファイル angel.txt から 4 つのデータを読み込んでいます.最初のデータが正の時は( 148 行目),C/C++ のプログラムからの命令が設定されていないことを意味するため,何も行いません.そうでない場合は,命令された角度を配列 next に読み込んでいます.

161 行目~ 196 行目

  各リンクの角度( now )を 1 度ずつ変化させ,すべてのリンクの角度が命令された角度になるまで,10 ms 毎に描画しています( 178 行目).ただし,各リンクの先端が,地面より下になった場合はエラーとなります( 179 行目~ 194 行目).

200 行目~ 216 行目

  命令よって状態が変化した場合は,1 に設定した一つ目のデータ( 207 行目)と各リンクの角度をファイル angle.txt に出力します.

223 行目~ 266 行目

  各リンクを塗りつぶした矩形で表現しているため,多少面倒な処理を行っています.各リンクを太い直線で表現した方が簡単だと思います.

[LED の点灯制御]

  同様に,以下に示すのは LED ボタンの点灯制御をシミュレートするためのものです.上の例と同様,Java のプログラムと C/C++ のプログラムから構成されており,LED.txt というファイルを通して制御が実行されます.もちろん,この場合においても,制御部分のプログラムを Java で記述することが可能です.

  まず,Java のプログラムと制御用の C/C++ のプログラムを同じディレクトリに置き,コマンドプロンプトから Java のプログラムを起動した後,他のコマンドプロンプトから C/C++ のプログラムを実行することによって LED ボタンを制御できます.まず,Java のプログラムを起動すると,以下に示すような LED ボタンの並びが表示されます.ただし,下の図とは異なり,いずれのボタンも点灯していない状態です.

  続いて,C/C++ のプログラムを起動すると,ファイル LED.txt に以下のようなデータが出力されます.最初の数値は,0 ~ 1 の値をとり,0 は C/C++ のプログラムから LED ボタンへの命令が出力されたことを表し,1 は LED ボタン( Java のプログラム)から LED ボタンの現時点における状態が出力されたことを表しています.
0 128		
  続く数値は,点灯すべき LED ボタンの位置を 1 バイトの数値で表しています.2 進数表現された数値の 1 となるビットの位置が点灯すべき LED ボタンを表しています.例えば,128 は,一番左側のボタンを点灯することを示しています.これらのデータを使用して,どのようにして制御が行われるかについては,以下に示す各プログラムに対する説明を参照してください.

C/C++ のプログラム

01	/****************************/
02	/* LED の点灯               */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	#include  <stdio.h>
06	#include  <time.h>
07	
08	/***************************/
09	/* xミリ秒経過するのを待つ */
10	/***************************/
11	int sleep(unsigned long x)
12	{
13		clock_t  s = clock();
14		clock_t  c;
15		int sw = 1;
16	
17		do {
18			if ((c = clock()) == (clock_t)-1) {       /* エラー */
19				sw = 0;
20				break;
21			}
22		} while (1000UL * (c - s) / CLOCKS_PER_SEC <= x); 
23	
24		return sw;
25	}
26	
27	/*************************/
28	/* 入出力領域への入出力  */
29	/*      str1 : コピー先  */
30	/*      str2 : コピー元  */
31	/*      n : コピー文字数 */
32	/*      sw : 1 : 入力    */
33	/*           2 : 出力    */
34	/*************************/
35	void *memcpy(void *str1, void *str2, size_t n, int sw)
36	{
37		int i1, k;
38		unsigned char *c1 = (unsigned char *)str1, *c2 = (unsigned char *)str2;
39		FILE *in, *out;
40						// 入力
41		if (sw == 1) {
42			in = fopen("LED.txt", "r");
43			for (i1 = 0; i1 < (int)n; i1++) {
44				fscanf(in, "%d", &k);
45				c2[i1] = (unsigned char)k;
46			}
47			for (i1 = 0; i1 < (int)n; i1++)
48				c1[i1] = c2[i1];
49			fclose(in);
50		}
51						// 出力
52		else {
53			for (i1 = 0; i1 < (int)n; i1++)
54				c1[i1] = c2[i1];
55			out = fopen("LED.txt", "w");
56			k   = (int)c1[0];
57			fprintf(out, "%d", k);
58			for (i1 = 1; i1 < (int)n; i1++) {
59				k = (int)c1[i1];
60				fprintf(out, " %d", k);
61			}
62			fprintf(out, "\n");
63			fclose(out);
64		}
65	
66		return str1;
67	}
68	
69	/********/
70	/* main */
71	/********/
72	int main(void)
73	{
74	    int k = 0;
75		unsigned char state[2], io[2], next = 128;
76	
77		while (k < 20) {
78			if (k > 0)
79				memcpy(state, io, 2, 1);
80			else
81				state[0] = 1;
82			if (state[0] > 0) {
83				state[0] = 0;
84				state[1] = next;
85				memcpy(io, state, 2, 2);
86				next >>= 1;
87				if (next == 0)
88					next = 128;
89				k++;
90			}
91			sleep(500);
92		}
93	
94		return 0;
95	}
		
11 行目~ 25 行目

  x ms 待つための関数です.

35 行目~ 67 行目

  実際の制御においては,メモリ上のいずれかの場所を介して,制御対象との情報交換が行われるはずです.そのような意味から,多少奇妙な名前と引数を持った関数になっていますが,実際は,ファイル LED.txt に対して,2 つのデータの入出力を行っているだけです.

77 行目

  この例では,点灯すべき LED ボタンの位置を 20 回変更する制御を行っています.それが,この while 文の意味です.変数 k の値は 89 行目で増加させています.

78 行目~ 81 行目

  ファイル LED.txt から,配列 state に 2 つのデータを読み込んでいます.ただし,最初だけは,state[0] の値を 1 に設定しています.

82 行目

  state[0] の値が 1 であることは,LED ボタンの状態がファイル LED.txt に出力されたことを意味しています.入力した state[0] の値が 0 であることは,C/C++ のプログラムからの命令に対して,LED ボタンが対応していないことを意味しています.そのため,何も行いません.

84 行目

  点灯すべき LED ボタンの位置を設定しています.

85 行目

  ファイル LED.txt へ,配列 state に設定された 2 つのデータを出力しています.

86 行目~ 88 行目

  点灯すべき LED ボタンの位置を変更しています.その位置が,右端より外である場合は,左端に戻しています.

91 行目

  500 ms だけ処理を待っています.

Java のプログラム

001	/****************************/
002	/* LED の点灯               */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.io.*;
006	import java.awt.*;
007	import javax.swing.*;
008	import java.awt.event.*;
009	import java.util.StringTokenizer;
010	
011	public class Test {
012		public static void main (String[] args) throws IOException
013		{
014			LEDControl lc = new LEDControl("LED");
015		}
016	}
017	
018	class LEDControl extends JFrame {
019	
020		/************************/
021		/* コンストラクタ       */
022		/*      name : タイトル */
023		/************************/
024		LEDControl(String name)
025		{
026						// JFrameクラスのコンストラクタの呼び出し
027			super(name);
028						// Windowの大きさの設定
029			setSize(270, 130);
030						// ウィンドウを表示
031			Container cp = getContentPane();
032			cp.setBackground(Color.white);
033			cp.setLayout(null);
034			setVisible(true);
035			Insets in = getInsets();
036			Draw_LED dp = new Draw_LED(this);
037			dp.setBackground(Color.black);
038			dp.setLocation(0, 0);
039			dp.setSize(270-in.left-in.right, 130-in.top-in.bottom);
040			cp.add(dp);
041						// イベントアダプタ
042			addWindowListener(new WinEnd());
043		}
044	
045		/************/
046		/* 終了処理 */
047		/************/
048		class WinEnd extends WindowAdapter
049		{
050			public void windowClosing(WindowEvent e) {
051				System.exit(0);
052			}
053		}
054	}
055	
056	class Draw_LED extends JPanel implements Runnable, ActionListener {
057	
058		Thread th;
059		boolean state = true;
060		Dimension d;
061		Insets in;
062		int init = 1, led = 0, led_o = 0;
063		JButton bt;
064		LEDControl lc;
065	
066		/************************/
067		/* コンストラクタ       */
068		/*      name : タイトル */
069		/************************/
070		Draw_LED(LEDControl lc1)
071		{
072			lc    = lc1;
073						// Windowの大きさ
074			d  = lc.getSize();
075			in = lc.getInsets();
076						// リセットボタンの追加
077			setLayout(null);
078			Font f = new Font("MS 明朝", Font.PLAIN, 20);
079			bt = new JButton("Reset");
080			bt.setFont(f);
081			bt.setBackground(Color.green);
082			bt.addActionListener(this);   // リスナー
083			bt.setLocation(d.width-100-in.right, 20);
084			bt.setSize(new Dimension(90, 30));
085			add(bt);
086						// スレッドの定義と開始
087			th = new Thread(this);
088			th.start();
089		}
090	
091		/******************************/
092		/* ボタンが押されたときの処理 */
093		/******************************/
094		public void actionPerformed(ActionEvent e)
095		{
096			led = 0;
097			repaint();
098		}
099	
100		/******************/
101		/* スレッドの実行 */
102		/******************/
103		public void run()
104		{
105			int i1, draw = 1;
106			String str;
107			StringTokenizer token;
108	
109			while (state) {
110				try {
111					BufferedReader in = new BufferedReader(new FileReader("LED.txt"));
112					str   = in.readLine();
113					token = new StringTokenizer(str, " \n");
114					draw  = Integer.parseInt(token.nextToken());
115					if (draw == 0) {
116						led = Integer.parseInt(token.nextToken());
117						in.close();
118						if (led != led_o) {
119							repaint();
120							led_o = led;
121						}
122						PrintStream out = new PrintStream(new FileOutputStream("LED.txt"));
123						out.println("1 " + led);
124						out.close();
125					}
126					else
127						in.close();
128				}
129				catch (IOException er) {}
130				try {
131					th.sleep(500);
132				}
133				catch (InterruptedException e) {}
134			}
135		}
136	
137		/********/
138		/* 描画 */
139		/********/
140		public void paintComponent (Graphics g)
141		{
142			super.paintComponent(g);   // 親クラスの描画(必ず必要)
143	
144			int i1, x, y, b = 128, k;
145						// LEDボタンの描画
146			x = 10;
147			y = d.height - in.bottom - in.top - 30;
148			k = led;
149			for (i1 = 0; i1 < 8; i1++) {
150				if (k / b > 0)
151					g.setColor(Color.yellow);
152				else
153					g.setColor(Color.lightGray);
154				g.fillOval(x, y, 20, 20);
155				x += 30;
156				k %= b;
157				b /= 2;
158			}
159		}
160	}
		
027 行目~ 042 行目

  Window を生成すると共に,Draw_LED クラス( 056 行目~ 160 行目)を使用して LED ボタンを描画しています( 036 行目).

070 行目~ 089 行目

  Draw_LED クラスのコンストラクタであり,リセットボタンを追加し,スレッドを開始しています.

111 行目~ 114 行目

  ファイル LED.txt から,最初のデータを変数 draw に読み込んでいます.ファイルが存在しない場合は,例外が発生しますが,このプログラムでは何も行っていません( 129 行目).

115 行目

  変数 draw の値が正の時は,C/C++ のプログラムからの命令が設定されていないことを意味するため,何も行いません.

116 行目

  命令された点灯すべき位置を変数 led に読み込んでいます.

118 行目~ 121 行目

  点灯すべき位置が現在の状態と異なっている場合は,再描画します.

122 行目~ 124 行目

  最初のデータを 1 として,LED ボタンの現在の状態をファイル LED.txt に出力しています.

140 行目~ 159 行目

  LED ボタンを描画しています.

演習問題13

[問1]適当な図形を 3 つ描き,それらの図形に対する説明を各図形の上に表示する表示するプログラムを書け.

[問2]2 つの整数データに対する加算または減算を行い,その正誤を判定するプログラムを作成せよ.データや加算または減算の選択は各問題毎に乱数を使用し,正解だった場合は,新たな問題を表示するものとする.

[問3]ピクセル値を直接操作することによって,適当な画像を描いてみよ.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引