静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引

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

  1. 20.1 Window とアプレット
    1. 20.1.1 アプリケーション( Window の生成・消滅とイベント処理)
    2. (プログラム例 20.1 ) Window の生成
    3. (プログラム例 20.2 ) Window の消滅とイベント処理
    4. 20.1.2 JAR ファイル
    5. 20.1.3 アプレット
    6. (プログラム例 20.3 ) アプレット
  2. 20.2 グラフィックスと AWT,Swing
    1. 20.2.1 グラフィックス
    2. (プログラム例 20.4 ) 2 つの整数の和
    3. 20.2.2 Java AWT,Swing
    4. (プログラム例 20.13 ) 2 つの整数の和
    5. 20.2.3 ダブルバッファリング
    6. (プログラム例 20.19 ) ダブルバッファリング
  3. 20.3 アニメーション
    1. 20.3.1 アニメーションの開始と停止
    2. (プログラム例 20.15 ) アニメーションの開始と停止
    3. 20.3.2 アニメーション作成方法
    4. (プログラム例 20.16 ) ボールの運動(描画)
    5. (プログラム例 20.17 ) ボールの運動(外部の 1 画像)
    6. (プログラム例 20.26 ) ランニング(外部の複数画像)
    7. (プログラム例 20.18 ) 花火(ピクセル値の操作)
  4. 20.4 ネットワーク
  5. (プログラム例 20.20 ) URL へリンク
  6. (プログラム例 20.21 ) URL データの読み込み
  7. (プログラム例 20.22 ) データの転送とサーバへの保存
  8. (プログラム例 20.29 ) 演習問題への解答
  9. (プログラム例 20.28 ) Web ページ
  10. 20.5 様々な例題
    1. 20.5.1 パズル & ゲーム
    2. (プログラム例 20.6 ) 8 / 15 パズル
    3. (プログラム例 20.8 ) 巡回セールスマン問題( TSP )
    4. (プログラム例 20.24 ) シューティング風ゲームと作成手順
    5. (プログラム例 20.25 ) ぷよぷよ風ゲームと作成手順
    6. 20.5.2 グラフの表示
    7. (プログラム例 20.9 ) グラフの表示
    8. (プログラム例 20.27 ) データベースとグラフ
    9. 20.5.3 遺伝的アルゴリズム
    10. (プログラム例 20.11 ) 1 の数
    11. (プログラム例 20.12 ) 関数の最大値
    12. 20.5.4 お絵かき
    13. (プログラム例 20.5 ) マウスによる描画
    14. 20.5.5 その他
    15. (プログラム例 20.10 ) 2次方程式の根
    16. (プログラム例 20.7 ) 剛体振り子の運動
    17. (プログラム例 20.23 ) マニピュレータと LED の点灯制御
  この章においては,Java による Windows プログラミングについて概説します.非常に基本的なことに対してだけの説明ですので,詳細については,他の書籍及び付録の Java のクラスとメソッド等を参照してください.

20.1 Window とアプレット

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

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

  Java において GUI を利用する方法として,2 つの方法が考えられます.一つは,通常のアプリケーションプログラムとして作成し,そのプログラム内で Window を生成し,GUI を利用する方法です.従って,プログラムの起動自体はコマンドプロンプトから行いますが,入力や結果の出力は,Window 上で行います(結果が,数値や文字である場合は,コマンドプロンプト上に出力することも可能).あと一つは,Web ページ上に Java で作成したプログラム(アプレット -Applet- と呼びます)を埋め込む方法です.この場合は,Internet Explorer,Firefox などのブラウザによって起動することになります.

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

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

20.1.1 アプリケーション( Window の生成・消滅とイベント処理)

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

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

	/******************/
	/* コンストラクタ */
	/******************/
	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 {
	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_sの定義 */
/*********************/
class Win_s extends JFrame {   // JFrame クラスを継承した Window

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

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

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

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

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

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

  イベントリスナはインタフェースとして提供されています.これは,各イベントの処理がアプリケーションによって異なる場合が多いからです.しかし,インタフェースを実装する際には,インタフェースに定義されているすべてのメソッド(ハンドラメソッド)を実装しない限り,抽象クラスとなり,インスタンスを生成できなくなります.実際にすべてのメソッドを必要とする場合は問題ありませんが,以下に示す例においては,「×」ボタンがクリックされたときの処理( Window を閉じる)を行うため,イベント応答メソッド windowClosing だけを必要としています.それにもかかわらず,WindowListener インタフェースに宣言されているすべてのメソッドの具体的定義を行う必要があります(実際には,中身のないメソッドを定義するだけですが).以下に示すのは,WindowListener インタフェースを利用して Window の消滅を行った例です.

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

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

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

/********************/
/* クラスWin1の定義 */
/********************/
class Win1 extends Frame implements WindowListener {   // 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 Test {
	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);
	}
}

/**********************/
/* クラスWin1_sの定義 */
/**********************/
class Win1_s extends JFrame implements WindowListener {   // WindowListener インタフェースを継承

	/******************/
	/* コンストラクタ */
	/******************/
	Win1_s (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) {}
}
		

  イベントアダプタクラスを利用すれば,この煩わしさを避けることが可能です.イベントアダプタは,すべてのメソッドに対する標準的処理を実装しているため,このクラスのサブクラスとしてクラスを定義すれば,必要なメソッドの実装だけで済むことになります.しかし,例えば,クラス Win1 は Frame クラスのサブクラスになっており,多重継承が許されない Java においては,さらに WindowAdapter クラスを継承することは不可能です.そこで,よく行われるのが内部クラスの利用です.次のプログラムは,上と同じ例に対し,イベントアダプタ( WindowAdapter クラス)を継承した内部クラス WinEnd を使用して実行しています.

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

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

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

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

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

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter   // 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 Test {
	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);
	}
}

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

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

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

20.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

20.1.3 アプレット

  Java プログラムを Web ページ内で実行させるには,以下に示してあるように,HTML の OBJECT 要素を使用します.なお,2 番目の PARAM 要素は,class ファイルが HTML ファイルと同じ場所にある場合は省略可能です.
<OBJECT TYPE="application/x-java-applet" WIDTH="600" HEIGHT="400">
	<PARAM NAME="code" VALUE="Ball.class">
	<PARAM NAME="codebase" VALUE="applet/" >
</OBJECT>
			

  アプレットに対しても JAR ファイルを利用することが可能です.例えば,
jar cvf Ball.jar *.class

のようにして作成された JAR ファイルをアプレットで利用する場合は,以下のようになります.最初の PARAM 要素は JAR ファイルが存在する場所( HTML ファイルと同じ場所にある場合は省略可能),2 番目の PARAM 要素は JAR ファイル名,また,3 番目の PARAM 要素はアプレットとして実行するクラスファイル名(アプレットにおける事実上の main program )です.
<OBJECT TYPE="application/x-java-applet" WIDTH="600" HEIGHT="400">
	<PARAM NAME="codebase" VALUE="applet/">
	<PARAM NAME="archive" VALUE="Ball.jar">
	<PARAM NAME="code" VALUE="Ball">
</OBJECT>
			
(プログラム例 20.3 ) 2 つの整数の和(アプレット)

  ここで示すプログラム例は,先に示した 2 つの整数の和を計算する例をアプレットに書き換えたものです(表示方法1( AWT )表示方法1( Swing )).アプリケーションの場合と同様,GUI に対する説明がまだですので,入力は HTML の PARAM 要素を通して行われ,また,出力は Java コンソールに表示されます.従って,アプレット自身には何も表示されません.

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

import java.io.*;
import java.applet.*;

public class Test extends Applet {
    public void init()
    {
        int a = Integer.parseInt(getParameter("x"));   // HTMLファイルから読み込み
        int b = Integer.parseInt(getParameter("y"));   // HTMLファイルから読み込み

        System.out.println("和は " + (a+b));   // Javaコンソールに出力
    }
}
		

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

import java.io.*;
import javax.swing.*;

public class Test extends JApplet {
    public void init()
    {
        int a = Integer.parseInt(getParameter("x"));   // HTMLファイルから読み込み
        int b = Integer.parseInt(getParameter("y"));   // HTMLファイルから読み込み

        System.out.println("和は " + (a+b));   // Javaコンソールに出力
    }
}
		

  例えば,AWT を利用した場合,HTML ファイル内のアプレットを表示する部分は以下のように記述してあります.NAME 属性が x ( y ) である PARAM 要素の VALUE 属性の値 10 ( 20 )が,メソッド getParameter("x") ( getParameter("y") )によって,Java プログラムに文字列として読み込まれます.
<OBJECT TYPE="application/x-java-applet" WIDTH="200" HEIGHT="100">
	<PARAM NAME="code" VALUE="Test.class">
	<PARAM NAME="x" VALUE="10">
	<PARAM NAME="y" VALUE="20">
</object>
		
  なお,HTML ファイル内のアプレットだけを見たいような場合は,appletviewer コマンドを使用して,コマンドプロンプトから以下のように入力すれば,アプレットだけを表示させることができます.
appletviewer HTMLファイル名
		
  この例においては,他の数値の加算を行いたい場合は,html ファイルを書き直さざるを得ませんが,JavaScript を利用して,表示方法2のようにすれば,任意の整数の加算が可能になります( 2 つのフィールドに数字を入力した後,「加算」ボタンをクリック).もちろん,このような例に対しては,好ましい方法ではありません.

表示方法2( test_2.html )

<!DOCTYPE HTML>
<HTML>
<HEAD>
	<TITLE>計算(加算)</TITLE>
	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
	<LINK REL="stylesheet" TYPE="text/css" HREF="../../../master.css">
	<SCRIPT TYPE="text/javascript">
		function add(form)
		{
			str = '<object type="application/x-java-applet" width="200" height="100">';
			str += '<param name="code" value="Test.class">';
			str += '<param name="x" value="' + form.x.value + '">';
			str += '<param name="y" value="' + form.y.value + '">';
			str += '</object>';
			document.getElementById("here").innerHTML = str;
		}
	</SCRIPT>
</HEAD>
<BODY CLASS="ccffcc">
	<H1 STYLE="text-align: center">計算(加算)</H1>
	<FORM>
		<P CLASS="center">
			<INPUT TYPE="text" NAME="x" STYLE="font-size: 90%"> + 
			<INPUT TYPE="text" NAME="y" STYLE="font-size: 90%"> 
			<INPUT TYPE="button" VALUE="加算" STYLE="font-size: 90%" onClick="add(this.form)"><BR>
		</P>
	</FORM>
	<DIV CLASS="center" ID="here"></DIV>
</BODY>
</HTML>
		

  GUI の使用方法に関しては,アプリケーションとして作成した場合とほとんど同じですので,以下に示す多くの例では,アプレットを基本に説明していきます.なお,アプレットから,プログラム例 20.2 で示したような Window を生成することも可能です.

  プログラム例 20.3 からも明らかなように,Web ページが閉じられればアプレットも終了しますので,アプレットを閉じるための記述は必要ありません.アプレットを使用すると,下に示す Applet クラスの 4 つのメソッドが自動的に呼ばれます.必要な関数をオーバーライド(書き直す)する必要があります.プログラム例 20.3 では,init だけをオーバーライドしています.

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

20.2.1 グラフィックス

  Java には,直線,四角形,円などを描画する機能があります.ただし,AWT と Swing では描画方法に多少の違いがあります.Swing では,JApplet クラスJFrame クラス等の再描画の時,クライアント領域を背景色で塗りつぶしません.AWT の Applet クラスFrame クラス等のクライアント領域は,Update() が呼び出されると背景色で塗りつぶして初期化されますが,Swing では Update() をオーバーライドし,初期化せずに再描画するようになっています.また,Swing では,後に述べるダブルバッファリング機能もデフォルトとしてサポートしています.

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

  この例では,プログラム例 20.3 の計算結果,及び,四角形と円を画面に描画しています.AWT の利用,または,Swing の利用をクリックすると,表示されます.

AWT を利用した場合

01	import java.awt.*;
02	import java.applet.*;
03
04	public class Test extends Applet {
05	
06		int wa;
07						// 初期設定(計算)
08		public void init()
09		{
10			int a = Integer.parseInt(getParameter("x"));   // HTMLファイルから読み込み
11			int b = Integer.parseInt(getParameter("y"));   // HTMLファイルから読み込み
12			wa = a + b;
13			setBackground(Color.white);
14		}
15						// 描画
16		public void paint (Graphics g)
17		{
18			Font f = new Font("TimesRoman", Font.BOLD, 30);   // 30ポイント
19			g.setFont(f);   // フォントの設定
20			g.drawString("和は " + wa + " です", 10, 30);   // 文字列と開始位置(左下)
21			g.setColor(new Color(0, 0, 255));   // 色の設定
22			g.fillRect(40, 40, 50, 50);   // 塗りつぶした正方形(左上,幅,高さ)
23			g.setColor(Color.red);   // 色の設定
24			g.drawOval(115, 40, 50, 50);   // 円(左上,幅,高さ)
25		}
26	}
		
1 行目

  Font クラス,及び,Color クラスを使用するには,この行が必ず必要です.

6 行目

  変数 wa は,複数のメソッドで使用していますので( 12 行目,及び,20 行目),クラス Test のフィールド(メンバー変数)として,ここで(メソッドの外側で)定義しておく必要があります.

13 行目

  アプレットの背景色を設定しています.

16~25 行目

  Applet クラスのメソッド paint をオーバーライドしています,その中で,Graphics クラスのメソッドを使用して,文字列,正方形,及び円を描いています.なお,paint メソッドは,アプレット開始時に自動的に呼ばれます.プログラム内から直接呼んではいけません.

Swing を利用した場合

01	import java.awt.*;
02	import javax.swing.*;
03	
04	public class Test extends JApplet {
05	
06		int wa;
07						// 初期設定
08		public void init()
09		{
10								// 計算
11			int a = Integer.parseInt(getParameter("x"));   // HTMLファイルから読み込み
12			int b = Integer.parseInt(getParameter("y"));   // HTMLファイルから読み込み
13			wa = a + b;
14								// パネルの追加
15			Container cP = getContentPane();
16			Test_1_Panel pn = new Test_1_Panel(this);
17			pn.setBackground(Color.white);
18			cP.add(pn);
19		}
20	}
21						// 描画パネル
22	class Test_1_Panel extends JPanel {
23		int wa;
24		Test_1_Panel(Test ts) { wa = ts.wa; }
25		public void paintComponent (Graphics g)
26		{
27			super.paintComponent(g);   // 親クラスの描画(必ず必要)
28			Font f = new Font("TimesRoman", Font.BOLD, 30);   // 30ポイント
29			g.setFont(f);   // フォントの設定
30			g.drawString("和は " + wa + " です", 10, 30);   // 文字列と開始位置(左下)
31			g.setColor(new Color(0, 0, 255));   // 色の設定
32			g.fillRect(40, 40, 50, 50);   // 塗りつぶした正方形(左上,幅,高さ)
33			g.setColor(Color.red);   // 色の設定
34			g.drawOval(115, 40, 50, 50);   // 円(左上,幅,高さ)
35		}
36	}
		
1 行目

  Font クラス,及び,Color クラスを使用するには,この行が必ず必要です.

2 行目

  JApplet クラス,及び,JPanel クラスを使用するには,この行が必ず必要です.

6 行目

  変数 wa は,他のクラス( Test_1_Panel )で使用していますので,クラス Test_1_s のフィールドとして,ここで(メソッドの外側で)定義しておく必要があります.

15 行目

  JApplet には,JRootPane が唯一の子として格納されます.このクラスのコンテナである contentPane が,JApplet のすべての子の親になります.部品の貼り付けやレイアウトの変更( contentPane には,BorderLayout マネージャーがデフォルトで設定されている)は,contentPane に対して行ってやる必要がありますので,ここでは,その contentPane を取得しています.

16~18 行目

  描画は,contentPane に貼り付けられた部品に対して行う必要があるため,JPanel クラスを継承した Test_1_Panel クラスを貼り付け,その背景色を白にしています.

22~36 行目

  描画を行うためのクラスの定義です.24 行目はコンストラクタであり,Test クラスのフィールド wa を自分のクラスのフィールド wa に代入しています.25~35 行目は,JApplet クラスのメソッド paintComponent をオーバーライドしています,その中で,Graphics クラスのメソッドを使用して,文字列,正方形,及び円を描いています.27 行目の処理が必ず必要であることに注意してください.

20.2.2 Java 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: コンポーネントを縦,または,横に配置

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

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

AWT を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04	
05	public class Test extends Applet implements ActionListener {
06	
07		Button bt;
08		TextField tx1, tx2, tx3;
09	
10		/************/
11		/* 初期設定 */
12		/************/
13		public void init()
14		{
15						// レイアウトの変更(フローレイアウト)
16			setLayout(new FlowLayout(FlowLayout.CENTER));
17						// フォントと背景色の設定
18			Font f = new Font("MS 明朝", Font.PLAIN, 20);
19			setFont(f);
20			setBackground(Color.cyan);
21						// テキストフィールドとボタンの追加
22							// テキストフィールド
23			tx1 = new TextField(10);
24			tx1.setBackground(Color.white);
25			add(tx1);
26							// ラベル
27			Label lb = new Label("+");
28			lb.setBackground(Color.cyan);
29			add(lb);
30							// テキストフィールド
31			tx2 = new TextField(10);
32			tx2.setBackground(Color.white);
33			add(tx2);
34							// ボタン
35			bt = new Button("=");
36			bt.setBackground(Color.pink);
37			bt.addActionListener(this);   // リスナー
38			add(bt);
39							// テキストフィールド
40			tx3 = new TextField(10);
41			tx3.setBackground(Color.white);
42			add(tx3);
43		}
44	
45		/******************************/
46		/* ボタンが押されたときの処理 */
47		/******************************/
48		public void actionPerformed(ActionEvent e)
49		{
50			if (e.getSource() == bt) {
51				int a = Integer.parseInt(tx1.getText());
52				int b = Integer.parseInt(tx2.getText());
53				tx3.setText(Integer.toString(a+b));
54			}
55		}
56	}
		
5 行目

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

16 行目

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

18~20 行目

  Font クラスを利用して,フォントを設定しています.また,アプレットの背景色も設定しています.

23~25,31~33,40~42 行目

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

27~29 行目

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

35~38 行目

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

48~55 行目

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

Swing を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import javax.swing.*;
04						// 初期設定
05	public class Test extends JApplet
06	{
07		public void init()
08		{
09			C_Panel pn = new C_Panel();
10			getContentPane().add(pn);
11		}
12	}
13						// 計算機
14	class C_Panel extends JPanel implements ActionListener
15	{
16		JButton bt;
17		JTextField tx1, tx2, tx3;
18								// コンストラクタ
19		C_Panel()
20		{
21										// レイアウトの変更(フローレイアウト)
22			setLayout(new FlowLayout(FlowLayout.CENTER));
23										// フォントの設定
24			Font f = new Font("MS 明朝", Font.PLAIN, 30);
25										// 背景色
26			setBackground(Color.cyan);
27										// テキストフィールド
28			tx1 = new JTextField(10);
29			tx1.setBackground(Color.white);
30			tx1.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
31			tx1.setFont(f);
32			add(tx1);
33										// ラベル
34			JLabel lb = new JLabel("+");
35			lb.setBackground(Color.cyan);
36			add(lb);
37										// テキストフィールド
38			tx2 = new JTextField(10);
39			tx2.setBackground(Color.white);
40			tx2.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
41			tx2.setFont(f);
42			add(tx2);
43										// ボタン
44			bt = new JButton("=");
45			bt.setBackground(Color.pink);
46			bt.addActionListener(this);   // リスナー
47			add(bt);
48										// テキストフィールド
49			tx3 = new JTextField(10);
50			tx3.setBackground(Color.white);
51			tx3.setHorizontalAlignment(JTextField.RIGHT);   // TextFieldには無い機能
52			tx3.setFont(f);
53			add(tx3);
54		}
55								// ボタンが押されたときの処理
56		public void actionPerformed(ActionEvent e)
57		{
58			if (e.getSource() == bt) {
59				int a = Integer.parseInt(tx1.getText());
60				int b = Integer.parseInt(tx2.getText());
61				tx3.setText(Integer.toString(a+b));
62			}
63		}
64	}
		
14 行目

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

22 行目

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

24~26 行目

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

28~32,38~42,49~53 行目

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

34~36 行目

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

44~47 行目

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

56~63 行目

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

20.2.3 ダブルバッファリング

  アニメーションは,一般的に,一定時間間隔で異なる画像を描画することによって作成します.AWT を使用してアニメーションを作成する場合,簡単なアニメーションの場合は問題になりませんが,一度に描く量が多く,かつ,描画速度が速くなると画面のちらつきが発生するようになります.そのため,ダブルバッファリングを行う必要が生じます.しかし,Swing の場合は,ダブルバッファリングをデフォルトでサポートしているためそのような処理を行う必要がありません.以下に示す例において確認して下さい.

(プログラム例 20.19 ) ダブルバッファリング

  この例においては,朝顔の背景の上に,2 つの文字列と,直径 10 ピクセルの円をランダムな位置に 10 ms 毎に描いています.ここをクリックして表示してみてください.時々画面が崩れ,ちらつくのが分かると思います.
01	import java.awt.*;
02	import java.applet.*;
03	import java.util.Random;
04	
05	public class Test extends Applet implements Runnable {
06	
07		int w, h, x, y;
08		boolean win_state = true;
09		Image hana;
10		Thread th = null;
11	
12		public void init()
13		{
14						// イメージの読み込み
15			hana = getImage(getCodeBase(), "hana.gif");
16						// スクリーンのサイズ
17			w    = getSize().width;
18			h    = getSize().height;
19		}
20	
21		public void start()
22		{
23			th = new Thread(this);
24			th.start();
25		}
26	
27		public void run()
28		{
29						// ランダム変数の初期化
30			Random rand = new Random();
31						// 円の位置の決定と再描画
32			while (win_state) {
33				try {
34					th.sleep(10);
35				}
36				catch (InterruptedException e) {}
37				x = (int)(rand.nextDouble() * w);
38				y = (int)(rand.nextDouble() * h);
39				repaint();
40			}
41		}
42	
43		public void paint (Graphics g)
44		{
45			g.setColor(new Color(0, 255, 255));
46			g.drawImage(hana, 0, 0, this);
47			Font f = new Font("TimesRoman", Font.BOLD, 20);
48			g.setFont(f);
49			g.drawString("背景", 140, 50);
50			g.drawString("朝顔", 140, 80);
51			g.fillOval(x, y, 10, 10);
52		}
53	}
		
5 行目

  マルチスレッドを実現するため,Runnable インタフェースを継承しています.

15 行目

  外部画像を読み込んでいます.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

17 行目~ 18 行目

  スクリーンのサイズ(アプレットの大きさ)を取り込んでいます.

21 行目~ 25 行目

  アプレットクラスのメソッド start をオーバーライドしています.23 行目において,Thread クラスのオブジェクトを生成し,24 行目において,そのスレッドを開始しています.

27 行目 ~ 41 行目

  Thread クラスのメソッド run をオーバーライドしています.34 行目の記述により,37 ~ 38 行目(円の位置の決定)及び 39 行目(再描画)の処理が 10 ms 毎に実行されます.

43 行目 ~ 52 行目

  アプレットクラスのメソッド paint をオーバーライドしています.背景画像( 46 行目),文字列( 49,50 行目),及び,円( 51 行目)が 10 ms 毎に描画されることになります.

  次に示す例には,上と全く同じアニメーションですが,ダブルバッファリングを行っています.ここをクリックして表示してみてください.正常に表示されるのが分かると思います.
01	import java.awt.*;
02	import java.applet.*;
03	import java.util.Random;
04	
05	public class Test extends Applet implements Runnable {
06	
07		int w, h;
08		boolean win_state = true;
09		Image hana, Buf;
10		Graphics g_b;
11		Thread th = null;
12	
13		public void init()
14		{
15						// イメージの読み込み
16			hana = getImage(getCodeBase(), "hana.gif");
17			MediaTracker trk = new MediaTracker(this);
18			trk.addImage(hana, 0);
19			try {
20				trk.waitForID(0);
21			}
22			catch (InterruptedException e) {}
23						// スクリーンバッファの生成
24			w   = getSize().width;
25			h   = getSize().height;
26			Buf = createImage(w, h);     //バッファ生成
27			g_b = Buf.getGraphics();    //グラフィックコンテキスト取得
28		}
29	
30		public void start()
31		{
32			th = new Thread(this);
33			th.start();
34		}
35	
36		public void run()
37		{
38						// ランダム変数の初期化と色の設定
39			Random rand = new Random();
40			g_b.setColor(new Color(0, 255, 255));
41						// 背景画像の生成
42			Image Back = createImage(w, h);
43			Graphics g = Back.getGraphics();
44			g.drawImage(hana, 0, 0, this);
45			Font f = new Font("TimesRoman", Font.BOLD, 20);
46			g.setFont(f);
47			g.drawString("背景", 140, 50);
48			g.drawString("朝顔", 140, 80);
49			g.dispose();   //グラフィックコンテキスト破棄
50	
51			while (win_state) {
52				try {
53					th.sleep(10);
54				}
55				catch (InterruptedException e) {}
56				g_b.drawImage(Back, 0, 0, this);  //背景画像をバッファに描画
57				int x = (int)(rand.nextDouble() * w);
58				int y = (int)(rand.nextDouble() * h);
59				g_b.fillOval(x, y, 10, 10); // 円をバッファに描画
60				repaint();
61			}
62		}
63	
64		public void paint (Graphics g)
65		{
66			g.drawImage(Buf, 0, 0, this);      //バッファを画面に描画
67		}
68	
69		public void update(Graphics g)    //オーバーライドして最低限のことだけをする
70		{
71			paint(g);
72		}
73	}
		
16 行目~ 22 行目

  外部画像を読み込んでいます.MediaTracker クラスを使用して,すべての画像を完全に読み込むまで次に進まないようにしています.この例においては,上で述べた例と同じ方法で構いませんが,大量の画像を読み込む場合は,このような方法を採る必要があります.

24 行目~ 27 行目

  画面と同じサイズのバッファを生成し,そのグラフィックコンテキストを取得しています.

42 行目~ 49 行目

  画面と同じサイズのイメージ(背景画像) Back を生成し,そこに花と文字列を描画しています.

56 行目~ 59 行目

  背景画像 Back 及び円をバッファに描画しています.

64 行目~ 67 行目

  10 ms 毎に描くのは,バッファだけになります.

  Swing においては,ダブルバッファリングをデフォルトでサポートしています.従って,ダブルバッファリング機能を off にしない限り,バッファリングを利用しない AWT による記述と同じような方法で,問題なく表示されます(Swing を使用した場合).なお,JComponent クラスには,ダブルバッファリングの状態を調べたり,または,設定するため,
public boolean isDoubleBuffered()
public void setDoubleBuffered(boolean sw)
		
のようなメソッドが用意されています.
import java.awt.*;
import javax.swing.*;
import java.util.*;
					// 初期設定
public class Test extends JApplet
{
	public void init()
	{
							// イメージの読み込み
		Image hana = getImage(getDocumentBase(), "hana.gif");
							// スクリーンのサイズ
		int w = getSize().width;
		int h = getSize().height;
							// パネルの生成
		Ani_7_Panel pn = new Ani_7_Panel(hana, w, h);
		getContentPane().add(pn);
	}
}
					// 描画パネル
class Ani_7_Panel extends JPanel implements Runnable
{
	int w, h, x, y;
	boolean win_state = true;
	Image hana;
	Thread th = null;
							// コンストラクタ
	Ani_7_Panel(Image hana1, int w1, int h1)
	{
		hana = hana1;
		w    = w1;
		h    = h1;
		th = new Thread(this);
		th.start();
	}
							// スレッドの実行
	public void run()
	{
									// ランダム変数の初期化
		Random rand = new Random();
									// 点の生成と描画
		while (win_state) {
			try {
				th.sleep(10);
			}
			catch (InterruptedException e) {}
			x = (int)(rand.nextDouble() * w);
			y = (int)(rand.nextDouble() * h);
			repaint();
		}
	}
							// 描画
	public void paintComponent (Graphics g)
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
		g.setColor(new Color(0, 255, 255));
		g.drawImage(hana, 0, 0, this);
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		g.setFont(f);
		g.drawString("背景", 140, 50);
		g.drawString("朝顔", 140, 80);
		g.fillOval(x, y, 10, 10);
	}
}
		

20.3 アニメーション

  アニメーションの基本は,Thread クラスを使用し,sleep メソッドによって一定時間毎に何らかの処理を行うことです.ただし,17.3 節で述べたように,多重継承ができないため,Runnable インタフェースを利用しています.

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

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

  この例においては,100 ms 毎に半径の異なる円を描画し,アニメーションを作成しています.また,ボタンをクリックすることによって,スレッドの停止・開始の制御を行うことができます.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.

AWT を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04	
05	/********************/
06	/* クラスTestの定義 */
07	/********************/
08	public class Test extends Applet implements Runnable, ActionListener
09	{
10		boolean state;
11		Button b_start, b_stop;
12		D_Panel dp;
13		Thread th;
14	
15		public void init() {
16						// レイアウト,背景色,フォント
17			setLayout(new BorderLayout(5, 10));
18			setBackground(new Color(225, 255, 225));
19			setFont(new Font("TimesRoman", Font.BOLD, 20));
20						// 上のパネル(ボタンの設定)
21			Panel pn1 = new Panel();
22			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
23			add(pn1, BorderLayout.NORTH);
24			b_start = new Button("開始");
25			b_start.addActionListener(this);
26			pn1.add(b_start);
27			b_stop = new Button("停止");
28			b_stop.addActionListener(this);
29			pn1.add(b_stop);
30						// 中央のパネル(描画領域)
31			dp = new D_Panel();
32			add(dp, BorderLayout.CENTER);
33						// スレッドの生成
34			th = new Thread(this);
35		}
36	
37		public Insets getInsets()   // 余白の設定
38		{
39			return new Insets(10, 10, 10, 10);
40		}
41	
42		public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
43		{
44			state = false;
45		}
46	
47		public void run()   // Runnable インタフェースのメソッド(必須)
48		{
49			while (state) {
50				dp.count++;
51				if (dp.count > 10)
52					dp.count = 0;
53				dp.repaint();   // 再描画
54				try {
55					th.sleep(100);   // 100 ms 毎の描画
56				}
57				catch (InterruptedException e) {}
58			}
59		}
60	
61		public void actionPerformed(ActionEvent e)
62		{
63			if (e.getSource() == b_start) {   // 開始
64				state = true;
65				th.start();
66			}
67			else {   // 停止
68				state    = false;
69				th       = new Thread(this);
70				dp.count = 0;
71				dp.repaint();
72			}
73		}
74	}
75	
76	class D_Panel extends Panel
77	{
78		int count;
79		D_Panel()
80		{
81			setBackground(Color.white);
82			count = 0;
83		}
84		public void paint (Graphics g)   // 描画
85		{
86			int i1, r;
87			r = 10;
88			for (i1 = 0; i1 < count; i1++) {
89				g.drawOval(0, 0, 2*r, 2*r);
90				r = (int)(1.5 * r);
91			}
92		}
93	}
		
08 行目

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

17 行目~ 19 行目

  レイアウトマネージャを BorderLayout に変更すると共に,背景色及びフォント(Font クラスのオブジェクト)を設定しています.

21 行目~ 29 行目

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

31 行目~ 32 行目

  アニメーションを描画するためのパネルとして,D_Panel クラス( 76 行目~ 93 行目)のオブジェクトを BorderLayout の中央に追加しています.

34 行目

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

37 行目~ 40 行目

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

42 行目~ 45 行目

  Applet クラスのメソッドに対するオーバーライドであり,他のページへ移動するときなどに自動的に呼び出されます.この場合は,スレッドを停止しています.

47 行目~ 59 行目

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

63 行目~ 66 行目

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

67 行目~ 72 行目

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

76 行目~ 93 行目

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

Swing を利用した場合

001	import java.awt.*;
002	import java.awt.event.*;
003	import javax.swing.*;
004	
005	/********************/
006	/* クラスTestの定義 */
007	/********************/
008	public class Test extends JApplet implements Runnable, ActionListener
009	{
010		boolean state;
011		JButton b_start, b_stop;
012		Test_Panel dp;
013		Thread th;
014	
015		public void init() {
016			Container cP = getContentPane();
017						// レイアウト,背景色,フォント
018			JPanel pn = new JPanel();
019			pn.setLayout(new BorderLayout(5, 10));
020			pn.setBackground(new Color(225, 255, 225));
021			cP.add(pn);
022			Font f = new Font("TimesRoman", Font.BOLD, 20);
023						// 上のパネル(ボタンの設定)
024			JPanel pn1 = new JPanel();
025			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
026			pn1.setBackground(new Color(225, 255, 225));
027			pn.add(pn1, BorderLayout.NORTH);
028			b_start = new JButton("開始");
029			b_start.addActionListener(this);
030			b_start.setFont(f);
031			pn1.add(b_start);
032			b_stop = new JButton("停止");
033			b_stop.addActionListener(this);
034			b_stop.setFont(f);
035			pn1.add(b_stop);
036						// 中央のパネル(描画領域)
037			JPanel pn2 = new JPanel();
038			pn2.setBackground(new Color(225, 255, 225));
039			pn2.setLayout(null);
040			pn.add(pn2, BorderLayout.CENTER);
041			dp = new Test_Panel();
042			dp.setSize(380, 175);
043			dp.setLocation(10, 10);
044			pn2.add(dp);
045						// スレッドの生成
046			th = new Thread(this);
047		}
048	
049		public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
050		{
051			state = false;
052		}
053	
054		public void run()   // Runnable インタフェースのメソッド(必須)
055		{
056			while (state) {
057				dp.count++;
058				if (dp.count > 10)
059					dp.count = 0;
060				dp.repaint();   // 再描画
061				try {
062					th.sleep(100);   // 100 ms 毎の描画
063				}
064				catch (InterruptedException e) {}
065			}
066		}
067	
068		public void actionPerformed(ActionEvent e)
069		{
070			if (e.getSource() == b_start) {   // 開始
071				state = true;
072				th.start();
073			}
074			else {   // 停止
075				state    = false;
076				th       = new Thread(this);
077				dp.count = 0;
078				dp.repaint();
079			}
080		}
081	}
082	
083	class Test_Panel extends JPanel
084	{
085		int count;
086		Test_Panel()
087		{
088			setBackground(Color.white);
089			count = 0;
090		}
091		public void paintComponent (Graphics g)   // 描画
092		{
093			super.paintComponent(g);   // 親クラスの描画(必ず必要)
094			int i1, r;
095			r = 10;
096			for (i1 = 0; i1 < count; i1++) {
097				g.drawOval(0, 0, 2*r, 2*r);
098				r = (int)(1.5 * r);
099			}
100		}
101	}
		
008 行目

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

018 行目~ 022 行目

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

024 行目~ 035 行目

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

037 行目~ 044 行目

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

046 行目

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

049 行目~ 052 行目

  JApplet クラスのメソッドに対するオーバーライドであり,他のページへ移動するとき自動的に呼び出されます.この場合は,スレッドを停止しています.

054 行目~ 066 行目

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

070 行目~ 073 行目

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

074 行目~ 079 行目

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

083 行目~ 101 行目

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

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

(プログラム例 20.16 ) ボールの運動(描画)

  先に述べたように,一定時間毎に何らかの処理を行うことによってアニメーションを作成することができます.プログラム例 20.15 では,「半径の異なる円を順に描く」という方法で作成しました.以下に示す例も同様の方法で作成してあります.ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.

AWT を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04	
05	public class Test extends Applet implements Runnable
06	{
07		private boolean state = true;
08		private double g = 9.8;   // y 軸方向の加速度
09		private double v0 = 0;   // y 軸方向の初期速度
10		private double v = 0;   // y 軸方向の現在速度
11		private double t = 0;   // 時間の初期値
12		private double h0;   // ボールの初期位置の y 座標(上が正,初期高さ)
13		private double x, y;   // ボールの現在位置の x 座標,y 座標(上が正)
14		private int sw = 1;   // ボールの状態( 0:停止している,1:動いている)
15		private Dimension d;
16		private Thread th;
17	
18		public void init() {
19						// 背景色
20			setBackground(new Color(238, 255, 238));
21						// 初期設定
22			d  = getSize();
23			h0 = d.height + 40;
24			x  = -40;
25			y  = -40;
26			addMouseListener(new Mouse());
27						// スレッドの生成
28			th = new Thread(this);
29			th.start();
30		}
31	
32		public Insets getInsets()
33		{
34			return new Insets(0, 0, 0, 0);
35		}
36	
37		public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
38		{
39			state = false;
40		}
41	
42		public void run()
43		{
44			while (state) {
45				try {
46					th.sleep(33);
47				}
48				catch (InterruptedException e) {}
49				if (x < d.width + 80 && sw > 0) {
50					x += 1.5;
51					t += 0.1;
52					v  = -g * t + v0;
53					y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
54					if (y >= d.height - 80 && v < 0) {
55						y  = d.height - 80;
56						v0 = -0.8 * v;
57						h0 = 80;
58						t  = 0;
59					}
60					System.out.println("position " + x + " " + y);
61					repaint();
62				}
63			}
64		}
65	
66		public void paint (Graphics g)
67		{
68			g.setColor(Color.green);
69			g.fillOval((int)x, (int)y, 80, 80);
70		}
71	
72		class Mouse extends MouseAdapter {
73			public void mouseClicked(MouseEvent e)
74			{
75				int mx = e.getX();
76				int my = e.getY();
77				double x1 = x + 40 - mx;
78				double y1 = y + 40 - my;
79				double r  = Math.sqrt(x1 * x1 + y1 * y1);
80				if (r < 40) {
81					if (sw > 0)
82						sw = 0;
83					else
84						sw = 1;
85				}
86			}
87		}
88	}
		
22 行目~ 26 行目

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

32 行目~ 35 行目

  アプレットと実際に描画される領域間の余白を設定しています(この例の場合は,余白 0 ).

49 行目~ 62 行目

  ボールが動いており( sw > 0 ),かつ,画面内に存在したときの処理です.
  • 50 行目: ボールの x 座標の変更( 1.5 ピクセル / 33 ms )
  • 51 行目: 時間の変更( 0.1 秒 / 33 ms )
  • 52 行目: ボールの速度の変更
  • 53 行目: ボールの y 座標の変更.画面座標に合うように座標変換も行っています.
  • 54 行目~ 59 行目: ボールが地面に落ちたときの跳ね返り処理を行っています.その際,速度が 0.8 倍されます.
  • 60 行目: ボールの位置を標準出力( Java コンソール)に出力しています.
  • 61 行目: 再描画

73 行目~ 86 行目

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

Swing を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import javax.swing.*;
04	
05	public class Test extends JApplet
06	{
07		Ball_Panel pn;
08		public void init() {
09			Dimension d = getSize();
10			pn = new Ball_Panel(d);
11			getContentPane().add(pn);
12		}
13	
14		public void stop()   // 他ページへ移動の際,一時的にスレッドを停止
15		{
16			pn.state = false;
17		}
18	}
19	
20	class Ball_Panel extends JPanel implements Runnable
21	{
22		boolean state = true;
23		private double g = 9.8;   // y 軸方向の加速度
24		private double v0 = 0;   // y 軸方向の初期速度
25		private double v = 0;   // y 軸方向の現在速度
26		private double t = 0;   // 時間の初期値
27		private double h0;   // ボールの初期位置の y 座標(上が正,初期高さ)
28		private double x, y;   // ボールの現在位置の x 座標,y 座標(上が正)
29		private int sw = 1;   // ボールの状態( 0:停止している,1:動いている)
30		private Dimension d;
31		private Thread th;
32	
33		Ball_Panel(Dimension d1)
34		{
35			d = d1;
36						// 背景色
37			setBackground(new Color(238, 255, 238));
38						// 初期設定
39			h0 = d.height + 40;
40			x  = -40;
41			y  = -40;
42			addMouseListener(new Mouse());
43						// スレッドの生成
44			th = new Thread(this);
45			th.start();
46		}
47	
48		public void run()
49		{
50			while (state) {
51				try {
52					th.sleep(33);
53				}
54				catch (InterruptedException e) {}
55				if (x < d.width + 80 && sw > 0) {
56					x += 1.5;
57					t += 0.1;
58					v  = -g * t + v0;
59					y  = d.height - (-0.5 * g * t * t + v0 * t + h0);
60					if (y >= d.height - 80 && v < 0) {
61						y  = d.height - 80;
62						v0 = -0.8 * v;
63						h0 = 80;
64						t  = 0;
65					}
66					System.out.println("position " + x + " " + y);
67					repaint();
68				}
69			}
70		}
71	
72		public void paintComponent (Graphics g)   // 描画
73		{
74			super.paintComponent(g);   // 親クラスの描画(必ず必要)
75			g.setColor(Color.green);
76			g.fillOval((int)x, (int)y, 80, 80);
77		}
78	
79		class Mouse extends MouseAdapter {
80			public void mouseClicked(MouseEvent e)
81			{
82				int mx = e.getX();
83				int my = e.getY();
84				double x1 = x + 40 - mx;
85				double y1 = y + 40 - my;
86				double r  = Math.sqrt(x1 * x1 + y1 * y1);
87				if (r < 40) {
88					if (sw > 0)
89						sw = 0;
90					else
91						sw = 1;
92				}
93			}
94		}
95	}
		
39 行目~ 42 行目

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

55 行目~ 68 行目

  ボールが動いており( sw > 0 ),かつ,画面内に存在したときの処理です.
  • 56 行目: ボールの x 座標の変更( 1.5 ピクセル / 33 ms )
  • 57 行目: 時間の変更( 0.1 秒 / 33 ms )
  • 58 行目: ボールの速度の変更
  • 59 行目: ボールの y 座標の変更.画面座標に合うように座標変換も行っています.
  • 60 行目~ 65 行目: ボールが地面に落ちたときの跳ね返り処理を行っています.その際,速度が 0.8 倍されます.
  • 66 行目: ボールの位置を標準出力( Java コンソール)に出力しています.
  • 67 行目: 再描画

80 行目~ 93 行目

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

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

  複雑な画像を描きたいような場合は,上に示したような方法では難しくなります.この例では,円に対応する外部画像( ball.gif )を,Image クラスのオブジェクトとして読み込み,それを画面に表示しています.上の例と同様,ボールをクッリクすると停止し,もう一度クリックすると再び動き出します.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.外部画像を読み込み表示する以外,上で述べたプログラムとほとんど同じですので説明は省略します.

AWT を利用した場合

import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class Test extends Applet implements Runnable
{
	private boolean state = true;
	private double g = 9.8;   // y 軸方向の加速度
	private double v0 = 0;   // y 軸方向の初期速度
	private double v = 0;   // y 軸方向の現在速度
	private double t = 0;   // 時間の初期値
	private double h0;   // ボールの初期位置の y 座標(上が正,初期高さ)
	private double x, y;   // ボールの現在位置の x 座標,y 座標(上が正)
	private int sw = 1;   // ボールの状態( 0:停止している,1:動いている)
	private Dimension d;
	private Thread th;
	private Image im;

	public void init() {
					// 背景色
		setBackground(new Color(238, 255, 238));
					// 初期設定
		d  = getSize();
		h0 = d.height + 40;
		x  = -40;
		y  = -40;
		addMouseListener(new Mouse());
					// 画像の読み込み
		im = getImage(getDocumentBase(), "ball.gif");
					// スレッドの生成
		th = new Thread(this);
		th.start();
	}

	public Insets getInsets()
	{
		return new Insets(0, 0, 0, 0);
	}

	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 - 80 && v < 0) {
					y  = d.height - 80;
					v0 = -0.8 * v;
					h0 = 80;
					t  = 0;
				}
				System.out.println("position " + x + " " + y);
				repaint();
			}
		}
	}

	public void paint (Graphics g)
	{
		g.drawImage(im, (int)x, (int)y, this);
	}

	class Mouse extends MouseAdapter {
		public void mouseClicked(MouseEvent e)
		{
			int mx = e.getX();
			int my = e.getY();
			double x1 = x + 40 - mx;
			double y1 = y + 40 - my;
			double r  = Math.sqrt(x1 * x1 + y1 * y1);
			if (r < 40) {
				if (sw > 0)
					sw = 0;
				else
					sw = 1;
			}
		}
	}
}
		

Swing を利用した場合

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Test extends JApplet
{
	Ball_Panel pn;
	public void init() {
		Dimension d = getSize();
		Image im = getImage(getDocumentBase(), "ball.gif");   // 画像の読み込み
		pn = new Ball_Panel(d, im);
		getContentPane().add(pn);
	}

	public void stop()
	{
		pn.state = false;
	}
}

class Ball_Panel extends JPanel implements Runnable
{
	boolean state = true;
	private double g = 9.8;   // y 軸方向の加速度
	private double v0 = 0;   // y 軸方向の初期速度
	private double v = 0;   // y 軸方向の現在速度
	private double t = 0;   // 時間の初期値
	private double h0;   // ボールの初期位置の y 座標(上が正,初期高さ)
	private double x, y;   // ボールの現在位置の x 座標,y 座標(上が正)
	private int sw = 1;   // ボールの状態( 0:停止している,1:動いている)
	private Dimension d;
	private Thread th;
	private Image im;

	Ball_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 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 - 80 && v < 0) {
					y  = d.height - 80;
					v0 = -0.8 * v;
					h0 = 80;
					t  = 0;
				}
				System.out.println("position " + x + " " + y);
				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 = e.getX();
			int my = e.getY();
			double x1 = x + 40 - mx;
			double y1 = y + 40 - my;
			double r  = Math.sqrt(x1 * x1 + y1 * y1);
			if (r < 40) {
				if (sw > 0)
					sw = 0;
				else
					sw = 1;
			}
		}
	}
}
		

(プログラム例 20.26 ) ランニング(外部の複数画像)

  プログラム例 20.17 と全く同じ方法ですが,複数の画像を利用しています.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.

AWT を利用した場合

001	/****************************/
002	/* ランニング               */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.awt.*;
006	import java.applet.*;
007	
008	/********************/
009	/* クラスTestの定義 */
010	/********************/
011	public class Test extends Applet implements Runnable
012	{
013	
014		boolean win_state = true;
015		Image a[] = new Image [6];
016		Image now_im;
017		int now_id = 0, x, y;
018		Thread load;
019	
020		/**********************/
021		/* イメージの読み込み */
022		/**********************/
023		public void init() {
024			setBackground(Color.white);
025						// getDocumentBase() : アプレットが置かれているディレクトリの取得得
026			a[0] = getImage(getDocumentBase(), "fig0.gif");
027			a[1] = getImage(getDocumentBase(), "fig1.gif");
028			a[2] = getImage(getDocumentBase(), "fig2.gif");
029			a[3] = getImage(getDocumentBase(), "fig3.gif");
030			a[4] = getImage(getDocumentBase(), "fig4.gif");
031			a[5] = getImage(getDocumentBase(), "fig5.gif");
032		}
033	
034		/**********************/
035		/* スレッドのスタート */
036		/**********************/
037		public void start()
038		{
039			load = new Thread(this);
040			load.start();
041		}
042	
043		/******************/
044		/* スレッドの停止 */
045		/******************/
046		public void stop()
047		{
048			win_state = false;
049		}
050	
051		/******************/
052		/* スレッドの実行 */
053		/******************/
054		public void run()
055		{
056			while (win_state) {
057				now_im = a[now_id%6];
058				if (now_id == 0) {
059					x = -20;
060					y = 200 - 238 / 2;
061				}
062				else if (now_id == 1) {
063					x = 5;
064					y = 200 - 249 / 2;
065				}
066				else if (now_id == 2) {
067					x = 70;
068					y = 200 - 258 / 2;
069				}
070				else if (now_id == 3) {
071					x = 140;
072					y = 200 - 258 / 2;
073				}
074				else if (now_id == 4) {
075					x = 150;
076					y = 200 - 258 / 2;
077				}
078				else if (now_id == 5) {
079					x = 160;
080					y = 200 - 247 / 2;
081				}
082				else if (now_id == 6) {
083					x = 200;
084					y = 200 - 238 / 2;
085				}
086				else if (now_id == 7) {
087					x = 225;
088					y = 200 - 249 / 2;
089				}
090				else if (now_id == 8) {
091					x = 290;
092					y = 200 - 258 / 2;
093				}
094				else if (now_id == 9) {
095					x = 360;
096					y = 200 - 258 / 2;
097				}
098				else if (now_id == 10) {
099					x = 370;
100					y = 200 - 258 / 2;
101				}
102				else if (now_id == 11) {
103					x = 380;
104					y = 200 - 247 / 2;
105				}
106				else if (now_id == 12) {
107					x = 420;
108					y = 200 - 238 / 2;
109				}
110				else if (now_id == 13) {
111					x = 445;
112					y = 200 - 249 / 2;
113				}
114				else if (now_id == 14) {
115					x = 510;
116					y = 200 - 258 / 2;
117				}
118				else if (now_id == 15) {
119					x = 580;
120					y = 200 - 258 / 2;
121				}
122				else if (now_id == 16) {
123					x = 590;
124					y = 200 - 258 / 2;
125				}
126				else {
127					x = 600;
128					y = 200 - 247 / 2;
129				}
130				repaint();
131				try {
132					load.sleep(100);
133				}
134				catch (InterruptedException e) {}
135				now_id++;
136				if (now_id > 17)
137					now_id = 0;
138			}
139		}
140	
141		/********/
142		/* 描画 */
143		/********/
144		public void paint (Graphics g)
145		{
146			if (now_im != null)
147				g.drawImage(now_im, x, y, this);   // 位置(左上)
148		}
149	}
		
026 行目~ 031 行目

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

037 行目~ 041 行目

  Applet クラスのメソッドに対するオーバーライドであり,ブラウザが init メソッドを呼び出したとき,または,他のページからアプレットのあるページへ戻ったときに自動的に呼び出されます.この場合は,Thread クラスのオブジェクトを生成し,それをスタートさせています.

046 行目~ 049 行目

  Applet クラスのメソッドに対するオーバーライドであり,他のページへ移動するとき自動的に呼び出されます.この場合は,スレッドを停止しています.

057 行目

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

058 行目~ 129 行目

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

135 行目~ 137 行目

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

147 行目

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

Swing を利用した場合

001	/****************************/
002	/* ランニング               */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.awt.*;
006	import javax.swing.*;
007	
008	public class Test extends JApplet
009	{
010		Ani_7_Panel pn;
011		public void init() {
012			Image a[] = new Image [6];
013			a[0] = getImage(getDocumentBase(), "fig0.gif");
014			a[1] = getImage(getDocumentBase(), "fig1.gif");
015			a[2] = getImage(getDocumentBase(), "fig2.gif");
016			a[3] = getImage(getDocumentBase(), "fig3.gif");
017			a[4] = getImage(getDocumentBase(), "fig4.gif");
018			a[5] = getImage(getDocumentBase(), "fig5.gif");
019			pn = new Ani_7_Panel(a);
020			getContentPane().add(pn);
021		}
022	
023		public void stop()
024		{
025			pn.win_state = false;
026		}
027	}
028	
029	class Ani_7_Panel extends JPanel implements Runnable
030	{
031		boolean win_state = true;
032		Image a[] = new Image [6];
033		Image now_im;
034		int now_id = 0, x, y;
035		Thread load;
036	
037		Ani_7_Panel(Image a1[])
038		{
039			a = a1;
040			setBackground(Color.white);
041			load = new Thread(this);
042			load.start();
043		}
044	
045		public void run()
046		{
047			while (win_state) {
048				now_im = a[now_id%6];
049				if (now_id == 0) {
050					x = -20;
051					y = 200 - 238 / 2;
052				}
053				else if (now_id == 1) {
054					x = 5;
055					y = 200 - 249 / 2;
056				}
057				else if (now_id == 2) {
058					x = 70;
059					y = 200 - 258 / 2;
060				}
061				else if (now_id == 3) {
062					x = 140;
063					y = 200 - 258 / 2;
064				}
065				else if (now_id == 4) {
066					x = 150;
067					y = 200 - 258 / 2;
068				}
069				else if (now_id == 5) {
070					x = 160;
071					y = 200 - 247 / 2;
072				}
073				else if (now_id == 6) {
074					x = 200;
075					y = 200 - 238 / 2;
076				}
077				else if (now_id == 7) {
078					x = 225;
079					y = 200 - 249 / 2;
080				}
081				else if (now_id == 8) {
082					x = 290;
083					y = 200 - 258 / 2;
084				}
085				else if (now_id == 9) {
086					x = 360;
087					y = 200 - 258 / 2;
088				}
089				else if (now_id == 10) {
090					x = 370;
091					y = 200 - 258 / 2;
092				}
093				else if (now_id == 11) {
094					x = 380;
095					y = 200 - 247 / 2;
096				}
097				else if (now_id == 12) {
098					x = 420;
099					y = 200 - 238 / 2;
100				}
101				else if (now_id == 13) {
102					x = 445;
103					y = 200 - 249 / 2;
104				}
105				else if (now_id == 14) {
106					x = 510;
107					y = 200 - 258 / 2;
108				}
109				else if (now_id == 15) {
110					x = 580;
111					y = 200 - 258 / 2;
112				}
113				else if (now_id == 16) {
114					x = 590;
115					y = 200 - 258 / 2;
116				}
117				else {
118					x = 600;
119					y = 200 - 247 / 2;
120				}
121				repaint();
122				try {
123					load.sleep(100);
124				}
125				catch (InterruptedException e) {}
126				now_id++;
127				if (now_id > 17)
128					now_id = 0;
129			}
130		}
131	
132		public void paintComponent (Graphics g)   // 描画
133		{
134			super.paintComponent(g);   // 親クラスの描画(必ず必要)
135			if (now_im != null)
136				g.drawImage(now_im, x, y, this);   // 位置(左上)
137		}
138	}
		
013 行目~ 018 行目

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

023 行目~ 026 行目

  JApplet クラスのメソッドに対するオーバーライドであり,他のページへ移動するとき自動的に呼び出されます.この場合は,スレッドを停止しています.

041 行目~ 042 行目

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

048 行目

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

049 行目~ 120 行目

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

126 行目~ 128 行目

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

136 行目

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

(プログラム例 20.18 ) 花火(ピクセル値の操作)

  アニメーションを作成する方法としては,今まで述べた図形を描画する方法や外部から読み込んだ図形を順に表示する方法の他に,MemoryImageSource クラスを利用して,イメージのピクセル値を直接編集する方法が考えられます(もちろん,これらの方法を同時に使用する方法も考えられます).この例は,ピクセル値を直接編集する方法によって作成しています.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.

AWT を利用した場合

001	import java.awt.*;
002	import java.awt.image.*;
003	import java.applet.*;
004	import java.util.Random;
005	
006	public class Test extends Applet implements Runnable
007	{
008		private int max = 20;   // 花火の数
009		private int m_pr = 7;   // 打ち上げ間隔の最大値
010		private int m_cl = 10;   // 花火の色の最大値
011		private int f_l = 300;   // 花火の直径
012		private int count = 0;   // カウンタ
013		private int next = 0;   // 次の打ち上げ時期
014		private int x[];   // 花火のx座標
015		private int y[];   // 花火のy座標
016		private int cl[];   // 花火の色
017		private int k[];   // 花火の状態
018						   // (-1:打ち上げ前,0:打ち上げ当初,>0:描いた輪の数)
019		private int height, width, size, f_size, pixels[][], color[];
020		private boolean state = true;
021		private Thread th;
022		private MemoryImageSource mis[];
023		private Image im[];
024		private Random rn;
025	
026		public void init() {
027			int i1;
028						// 背景色
029			setBackground(new Color(0, 0, 0));
030						// 初期設定
031			width  = getSize().width;
032			height = getSize().height;
033			size   = width * height;
034			f_size = f_l * f_l;
035			pixels = new int [max][f_size];
036			k      = new int [max];
037			x      = new int [max];
038			y      = new int [max];
039			cl     = new int [max];
040			mis    = new MemoryImageSource [max];
041			im     = new Image [max];
042			for (i1 = 0; i1 < max; i1++)
043				k[i1] = -1;
044			rn    = new Random();
045			color = new int [m_cl];
046			color[0] = 0xffff0000;
047			color[1] = 0xff00ff00;
048			color[2] = 0xff0000ff;
049			color[3] = 0xffffff00;
050			color[4] = 0xffff00ff;
051			color[5] = 0xff00ffff;
052			color[6] = 0xffeeffee;
053			color[7] = 0xffffaaaa;
054			color[8] = 0xffaaffaa;
055			color[9] = 0xffaaaaff;
056						// スレッドの生成
057			th = new Thread(this);
058			th.start();
059		}
060	
061		public Insets getInsets()
062		{
063			return new Insets(0, 0, 0, 0);
064		}
065	
066		public void stop()
067		{
068			state = false;
069		}
070	
071		public void run()
072		{
073			double ang, s;
074			int i0, i1, i2, i3, kx, ky, kxy, sw;
075	
076			while (state) {
077				try {
078					th.sleep(200);
079				}
080				catch (InterruptedException e) {}
081	
082				sw = 0;
083				for (i0 = 0; i0 < max; i0++) {
084					if (k[i0] < 0) {
085						if (count >= next && sw == 0) {
086							sw     = 1;
087							count  = 0;
088							cl[i0] = (int)(m_cl * rn.nextDouble());
089							if (cl[i0] >= m_cl)
090								cl[i0] = m_cl - 1;
091							for (i1 = 0; i1 < f_size; i1++)
092								pixels[i0][i1] = 0x00000000;
093							x[i0]   = (int)(width * rn.nextDouble()) - f_l / 2;
094							y[i0]   = (int)(height * rn.nextDouble()) - f_l / 2;
095							k[i0]   = 0;
096							mis[i0] = new MemoryImageSource(f_l, f_l, pixels[i0], 0, f_l);
097							mis[i0].setAnimated(true);
098							im[i0] = createImage(mis[i0]);
099							next   = (int)(m_pr * rn.nextDouble());
100							if (next <= 0)
101								next = 1;
102						}
103					}
104					else {
105						k[i0]++;
106						if (k[i0] > m_pr)
107							k[i0] = -1;
108						else {
109							s   = Math.PI / 6;
110							ang = 0;
111							for (i1 = 0; i1 < 12; i1++) {
112								kx = f_l / 2 + (int)(20 * k[i0] * Math.cos(ang));
113								ky = f_l / 2 + (int)(20 * k[i0] * Math.sin(ang));
114								for (i2 = kx-5; i2 < kx+5; i2++) {
115									for (i3 = ky-5; i3 < ky+5; i3++) {
116										kxy = f_l * i2 + i3;
117										if (kxy >= 0 && kxy < f_size)
118											pixels[i0][kxy] = color[cl[i0]];
119									}
120								}
121										// 以下は,四角を丸に近くするため行っている
122								pixels[i0][f_l*(kx-1)+ky-6] = color[cl[i0]];
123								pixels[i0][f_l*kx+ky-6] = color[cl[i0]];
124								pixels[i0][f_l*(kx+1)+ky-6] = color[cl[i0]];
125								pixels[i0][f_l*(kx-1)+ky+5] = color[cl[i0]];
126								pixels[i0][f_l*kx+ky+5] = color[cl[i0]];
127								pixels[i0][f_l*(kx+1)+ky+5] = color[cl[i0]];
128								pixels[i0][f_l*(kx-6)+ky-1] = color[cl[i0]];
129								pixels[i0][f_l*(kx-6)+ky] = color[cl[i0]];
130								pixels[i0][f_l*(kx-6)+ky+1] = color[cl[i0]];
131								pixels[i0][f_l*(kx+5)+ky-1] = color[cl[i0]];
132								pixels[i0][f_l*(kx+5)+ky] = color[cl[i0]];
133								pixels[i0][f_l*(kx+5)+ky+1] = color[cl[i0]];
134								ang += s;
135							}
136							im[i0] = createImage(mis[i0]);
137						}
138					}
139				}
140				count++;
141				repaint();
142			}
143		}
144	
145		public void paint (Graphics g)
146		{
147			int i0;
148			for (i0 = 0; i0 < max; i0++) {
149				if (k[i0] >= 0)
150					g.drawImage(im[i0], x[i0], y[i0], this);
151			}
152		}
153	}
		
031 行目~ 035 行目

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

040 行目

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

041 行目

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

042 行目~ 043 行目

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

044 行目

  乱数の初期設定です.

045 行目~ 055 行目

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

084 行目~ 103 行目

  花火が打ち上げ前の状態( k[i0] < 0 )であるときの処理です.カウンタ count の値が 次の打ち上げ時刻 next 以降であり,かつ,次の花火の打ち上げ時刻が設定されていない場合( sw == 0 )は,新しい花火を打ち上げるために,以下の処理が行われます.
  • 086 行目: 次の花火の打ち上げ時刻設定済み
  • 087 行目: カウンタの初期設定
  • 088 行目~ 090 行目: 花火の色をランダムに選択
  • 091 行目~ 092 行目: 画面の初期設定
  • 093 行目~ 094 行目: 花火の位置をランダムに選択
  • 095 行目: 花火を打ち上げた状態に設定
  • 096 行目: 設定されたピクセル値に基づき,MemoryImageSource クラスのオブジェクトを生成
  • 097 行目: アニメーションを実行するために必要
  • 098 行目: MemoryImageSource クラスのオブジェクトを,Image クラスのオブジェクトに変換
  • 099 行目~ 101 行目: 次の打ち上げ時刻をランダムに選択

105 行目~ 107 行目

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

109 行目~ 135 行目

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

136 行目

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

140 行目

  カウンタ(時間)の増加処理を行っています.

141 行目

  再描画しています.

Swing を利用した場合

001	import java.awt.*;
002	import java.awt.image.*;
003	import javax.swing.*;
004	import java.util.Random;
005	
006	public class Test extends JApplet
007	{
008		Fire_Panel pn;
009		public void init() {
010			Dimension d = getSize();
011			pn = new Fire_Panel(d);
012			getContentPane().add(pn);
013		}
014	
015		public void stop()
016		{
017			pn.state = false;
018		}
019	}
020	
021	class Fire_Panel extends JPanel implements Runnable
022	{
023		private int max = 20;   // 花火の数
024		private int m_pr = 7;   // 打ち上げ間隔の最大値
025		private int m_cl = 10;   // 花火の色の最大値
026		private int f_l = 300;   // 花火の直径
027		private int count = 0;   // カウンタ
028		private int next = 0;   // 次の打ち上げ時期
029		private int x[];   // 花火のx座標
030		private int y[];   // 花火のy座標
031		private int cl[];   // 花火の色
032		private int k[];   // 花火の状態
033						   // (-1:打ち上げ前,0:打ち上げ当初,>0:描いた輪の数)
034		private int height, width, size, f_size, pixels[][], color[];
035		boolean state = true;
036		private Thread th;
037		private MemoryImageSource mis[];
038		private Image im[];
039		private Random rn;
040	
041		Fire_Panel(Dimension d)
042		{
043			int i1;
044						// 背景色
045			setBackground(new Color(0, 0, 0));
046						// 初期設定
047			width  = d.width;
048			height = d.height;
049			size   = width * height;
050			f_size = f_l * f_l;
051			pixels = new int [max][f_size];
052			k      = new int [max];
053			x      = new int [max];
054			y      = new int [max];
055			cl     = new int [max];
056			mis    = new MemoryImageSource [max];
057			im     = new Image [max];
058			for (i1 = 0; i1 < max; i1++)
059				k[i1] = -1;
060			rn    = new Random();
061			color = new int [m_cl];
062			color[0] = 0xffff0000;
063			color[1] = 0xff00ff00;
064			color[2] = 0xff0000ff;
065			color[3] = 0xffffff00;
066			color[4] = 0xffff00ff;
067			color[5] = 0xff00ffff;
068			color[6] = 0xffeeffee;
069			color[7] = 0xffffaaaa;
070			color[8] = 0xffaaffaa;
071			color[9] = 0xffaaaaff;
072						// スレッドの生成
073			th = new Thread(this);
074			th.start();
075		}
076	
077		public void run()
078		{
079			double ang, s;
080			int i0, i1, i2, i3, kx, ky, kxy, sw;
081	
082			while (state) {
083				try {
084					th.sleep(200);
085				}
086				catch (InterruptedException e) {}
087	
088				sw = 0;
089				for (i0 = 0; i0 < max; i0++) {
090					if (k[i0] < 0) {
091						if (count >= next && sw == 0) {
092							sw     = 1;
093							count  = 0;
094							cl[i0] = (int)(m_cl * rn.nextDouble());
095							if (cl[i0] >= m_cl)
096								cl[i0] = m_cl - 1;
097							for (i1 = 0; i1 < f_size; i1++)
098								pixels[i0][i1] = 0x00000000;
099							x[i0]   = (int)(width * rn.nextDouble()) - f_l / 2;
100							y[i0]   = (int)(height * rn.nextDouble()) - f_l / 2;
101							k[i0]   = 0;
102							mis[i0] = new MemoryImageSource(f_l, f_l, pixels[i0], 0, f_l);
103							mis[i0].setAnimated(true);
104							im[i0] = createImage(mis[i0]);
105							next   = (int)(m_pr * rn.nextDouble());
106							if (next <= 0)
107								next = 1;
108						}
109					}
110					else {
111						k[i0]++;
112						if (k[i0] > m_pr)
113							k[i0] = -1;
114						else {
115							s   = Math.PI / 6;
116							ang = 0;
117							for (i1 = 0; i1 < 12; i1++) {
118								kx = f_l / 2 + (int)(20 * k[i0] * Math.cos(ang));
119								ky = f_l / 2 + (int)(20 * k[i0] * Math.sin(ang));
120								for (i2 = kx-5; i2 < kx+5; i2++) {
121									for (i3 = ky-5; i3 < ky+5; i3++) {
122										kxy = f_l * i2 + i3;
123										if (kxy >= 0 && kxy < f_size)
124											pixels[i0][kxy] = color[cl[i0]];
125									}
126								}
127								pixels[i0][f_l*(kx-1)+ky-6] = color[cl[i0]];
128								pixels[i0][f_l*kx+ky-6] = color[cl[i0]];
129								pixels[i0][f_l*(kx+1)+ky-6] = color[cl[i0]];
130								pixels[i0][f_l*(kx-1)+ky+5] = color[cl[i0]];
131								pixels[i0][f_l*kx+ky+5] = color[cl[i0]];
132								pixels[i0][f_l*(kx+1)+ky+5] = color[cl[i0]];
133								pixels[i0][f_l*(kx-6)+ky-1] = color[cl[i0]];
134								pixels[i0][f_l*(kx-6)+ky] = color[cl[i0]];
135								pixels[i0][f_l*(kx-6)+ky+1] = color[cl[i0]];
136								pixels[i0][f_l*(kx+5)+ky-1] = color[cl[i0]];
137								pixels[i0][f_l*(kx+5)+ky] = color[cl[i0]];
138								pixels[i0][f_l*(kx+5)+ky+1] = color[cl[i0]];
139								ang += s;
140							}
141							im[i0] = createImage(mis[i0]);
142						}
143					}
144				}
145				count++;
146				repaint();
147			}
148		}
149	
150		public void paintComponent (Graphics g)   // 描画
151		{
152			super.paintComponent(g);   // 親クラスの描画(必ず必要)
153			int i0;
154			for (i0 = 0; i0 < max; i0++) {
155				if (k[i0] >= 0)
156					g.drawImage(im[i0], x[i0], y[i0], this);
157			}
158		}
159	}
		
047 行目~ 051 行目

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

056 行目

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

057 行目

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

058 行目~ 059 行目

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

060 行目

  乱数の初期設定です.

061 行目~ 071 行目

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

090 行目~ 109 行目

  花火が打ち上げ前の状態( k[i0] < 0 )であるときの処理です.カウンタ count の値が 次の打ち上げ時刻 next 以降であり,かつ,次の花火の打ち上げ時刻が設定されていない場合( sw == 0 )は,新しい花火を打ち上げるために,以下の処理が行われます.
  • 092 行目: 次の花火の打ち上げ時刻設定済み
  • 093 行目: カウンタの初期設定
  • 094 行目~ 096 行目: 花火の色をランダムに選択
  • 097 行目~ 098 行目: 画面の初期設定
  • 099 行目~ 100 行目: 花火の位置をランダムに選択
  • 101 行目: 花火を打ち上げた状態に設定
  • 102 行目: 設定されたピクセル値に基づき,MemoryImageSource クラスのオブジェクトを生成
  • 103 行目: アニメーションを実行するために必要
  • 104 行目: MemoryImageSource クラスのオブジェクトを,Image クラスのオブジェクトに変換
  • 105 行目~ 107 行目: 次の打ち上げ時刻をランダムに選択

111 行目~ 113 行目

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

115 行目~ 140 行目

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

141 行目

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

145 行目

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

146 行目

  再描画しています.

20.4 ネットワーク

  ここでは,Web ページにおいてネットワークを利用する場合について説明します.なお,ソケットを使用する方法に関しては,19 章を参照してください.

(プログラム例 20.20 ) URL へリンク

  他のホームページへ移動するのは簡単です.この例では,プログラム例 20.3 と同様,HTML ファイルから PARAM 要素を通してリンク先に関する情報を Java プログラムに渡し,Java では,URL クラスApplet クラスJApplet クラス)の getAppletContext().showDocument() というメソッドを利用して,目的のページへ移動しています.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.なお,HTML ファイル内のアプレットを起動する部分は,以下のように記述してあります.
<object type="application/x-java-applet" width="350" height="90">
	<param name="code" value="Test.class">
	<param name="URL1_name" value="静岡理工科大学のホームページ">
	<param name="URL1" value="http://www.sist.ac.jp/">
	<param name="URL2_name" value="菅沼のホームページ">
	<param name="URL2" value="http://www.sist.ac.jp/~suganuma/">
</object>
		

AWT を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04	import java.net.*;
05	
06	public class Test extends Applet implements ActionListener
07	{
08		Button bt[] = new Button [2];
09		URL url[] = new URL [2];
10	
11		public void init() {
12			Font f = new Font("TimesRoman", Font.BOLD, 20);
13			setFont(f);
14						// ボタンの設定
15			bt[0] = new Button(getParameter("URL1_name"));
16			bt[0].addActionListener(this);
17			add(bt[0]);
18			url[0] = URL_List(getParameter("URL1"));
19	
20			bt[1] = new Button(getParameter("URL2_name"));
21			bt[1].addActionListener(this);
22			add(bt[1]);
23			url[1] = URL_List(getParameter("URL2"));
24		}
25						// URL の設定
26		public URL URL_List(String name)
27		{
28			URL ul = null;
29			try {
30				ul = new URL(name);
31			}
32			catch (MalformedURLException e)
33			{
34				System.out.println("Bad URL: " + name);
35			}
36			return ul;
37		}
38							// ボタンがクリックされたときの処理
39		public void actionPerformed(ActionEvent e)
40		{
41			URL link = null;
42			for (int i1 = 0; i1 < bt.length && link == null; i1++) {
43				if (e.getSource() == bt[i1])
44					link = url[i1];
45			}
46			if (link != null) {
47				System.out.println(link + " に接続中\n");
48				getAppletContext().showDocument(link);   // リンク
49			}
50		}
51	}
		
15 行目~ 23 行目

  HTML ファイルから受け取った URL 名を 2 つのボタンに設定し,また,配列 url には,そのアドレスから得た URL クラスのオブジェクトを記憶しています.

26 行目~ 37 行目

  URL アドレスから URL オブジェクトを生成するためのメソッドです.

39 行目~ 50 行目

  ボタンがクリックされたときの処理であり,クリックされたボタンを特定し,指定された URL を表示しています.

Swing を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import javax.swing.*;
04	import java.net.*;
05	
06	public class Test extends JApplet implements ActionListener
07	{
08		JButton bt[] = new JButton [2];
09		URL url[] = new URL [2];
10	
11		public void init() {
12			Font f = new Font("TimesRoman", Font.BOLD, 20);
13			Container cp = getContentPane();
14			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
15						// ボタンの設定
16			bt[0] = new JButton(getParameter("URL1_name"));
17			bt[0].addActionListener(this);
18			bt[0].setFont(f);
19			cp.add(bt[0]);
20			url[0] = URL_List(getParameter("URL1"));
21	
22			bt[1] = new JButton(getParameter("URL2_name"));
23			bt[1].addActionListener(this);
24			bt[1].setFont(f);
25			cp.add(bt[1]);
26			url[1] = URL_List(getParameter("URL2"));
27		}
28						// URL の設定
29		public URL URL_List(String name)
30		{
31			URL ul = null;
32			try {
33				ul = new URL(name);
34			}
35			catch (MalformedURLException e)
36			{
37				System.out.println("Bad URL: " + name);
38			}
39			return ul;
40		}
41							// ボタンがクリックされたときの処理
42		public void actionPerformed(ActionEvent e)
43		{
44			URL link = null;
45			for (int i1 = 0; i1 < bt.length && link == null; i1++) {
46				if (e.getSource() == bt[i1])
47					link = url[i1];
48			}
49			if (link != null) {
50				System.out.println(link + " に接続中\n");
51				getAppletContext().showDocument(link);   // リンク
52			}
53		}
54	}
		
16 行目~ 26 行目

  HTML ファイルから受け取った URL 名を 2 つのボタンに設定し,また,配列 url には,そのアドレスから得た URL クラスのオブジェクトを記憶しています.

29 行目~ 40 行目

  URL アドレスから URL オブジェクトを生成するためのメソッドです.

42 行目~ 53 行目

  ボタンがクリックされたときの処理であり,クリックされたボタンを特定し,指定された URL を表示しています.

(プログラム例 20.21 ) URL データの読み込み

  この例では,PARAM 要素を通して指定された URL にあるテキストデータを,URL クラス,または,URLConnection クラスのメソッドを使用して,アプレットのテキストエリアに読み込みます.この例のように,単にテキストデータを読むだけなら,Java だけで記述可能です.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.

  なお,この例においてはスレッドを利用していますが,必ずしもその必要はありません.しかし,一般に,ネットワークへ接続しデータを読み込むには,待ち時間も含め時間が掛かります.そのためだけに CPU を占有してしまうことは好ましくありません.そこで,この例に示すように,Thread クラスの sleep メソッドを利用して CPU を解放する方法の方が良いと思います.また,このプログラムにおいて,HTML ファイル内のアプレットを起動する部分は,以下のように記述してあります.
<object type="application/x-java-applet" width="650" height="430">
	<param name="code" value="Test.class">
	<param name="FILE" value="Test.java">
</object>
		

AWT を利用した場合

01	import java.awt.*;
02	import java.applet.*;
03	import java.net.*;
04	import java.io.*;
05	
06	public class Test extends Applet implements Runnable
07	{
08		URL url;
09		Thread th;
10		TextArea ta;
11		boolean state = true;
12	
13		public void init() {
14						// 読み込むファイル名
15			String name = getParameter("FILE");
16						// 読み込むファイルの設定
17			try {
18				url = new URL(getCodeBase(), name);
19			}
20			catch (MalformedURLException e)
21			{
22				System.out.println("Bad URL: " + name);
23			}
24						// テキストエリアの追加
25			Font f = new Font("TimesRoman", Font.BOLD, 20);
26			setFont(f);
27			ta = new TextArea(15, 50);
28			add(ta);
29		}
30						// 周囲の余白
31		public Insets getInsets()
32		{
33			return new Insets(10, 10, 10, 10);
34		}
35						// スレッドのスタート
36		public void start()
37		{
38			if (th == null) {
39				th = new Thread(this);
40				th.start();
41			}
42		}
43						// スレッドの停止
44		public void stop()
45		{
46			state = false;
47		}
48	
49		public void run()
50		{
51			InputStream stm = null;
52	//          		URLConnectionクラスを使用する場合
53	//		URLConnection c_inURL = null;
54			BufferedReader in = null;
55			String line;
56			StringBuffer buf = new StringBuffer();
57	
58			while (state) {
59	
60				try {
61					Thread.currentThread().sleep(100);	// 100msスリープ
62				}
63				catch (InterruptedException e) {};
64	
65				try {
66								// URL への接続
67					stm = this.url.openStream();
68	//          			URLConnectionクラスを使用する場合(上の1行は削除)
69	//				c_inURL = url.openConnection();
70	//				c_inURL.setDoInput(true);
71	//				c_inURL.setDoOutput(false);
72	//				c_inURL.setUseCaches(false);
73	//				c_inURL.connect();
74	//				stm = c_inURL.getInputStream();
75								// ファイルから 1 行ずつ読み込み
76					in = new BufferedReader(new InputStreamReader(stm));
77					while ((line = in.readLine()) != null)
78						buf.append(line + "\n");
79								// テキストエリアに出力
80					ta.setText(buf.toString());
81					state = false;
82					in.close();
83				}
84				catch (IOException e) {
85					System.out.println("IO Error:" + e.getMessage());
86				}
87			}
88		}
89	}
		
15 行目

  HTML ファイルから,読み込むファイル名を取得しています.

17 行目~ 23 行目

  ファイル名から URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

31 行目~ 34 行目

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

67 行目,76 行目~ 78 行目

  ファイルから 1 行ずつ読み込み,可変長の文字列を扱う StringBuffer クラスのオブジェクト buf に記憶しています.

80 行目

  buf を文字列( String クラスのオブジェクト)に変換し,テキストエリアに表示しています.

Swing を利用した場合

01	import java.awt.*;
02	import javax.swing.*;
03	import java.net.*;
04	import java.io.*;
05	
06	public class Test extends JApplet implements Runnable
07	{
08		URL url;
09		Thread th;
10		JTextArea ta;
11		boolean state = true;
12	
13		public void init() {
14						// 読み込むファイル名
15			String name = getParameter("FILE");
16						// 読み込むファイルの設定
17			try {
18				url = new URL(getCodeBase(), name);
19			}
20			catch (MalformedURLException e)
21			{
22				System.out.println("Bad URL: " + name);
23			}
24						// テキストエリアの追加
25			Font f = new Font("TimesRoman", Font.BOLD, 20);
26			Container cp = getContentPane();
27			ta = new JTextArea(15, 35);
28			ta.setFont(f);
29			JScrollPane sp = new JScrollPane(ta);
30			cp.add(sp);
31		}
32						// 周囲の余白
33		public Insets getInsets()
34		{
35			return new Insets(10, 10, 10, 10);
36		}
37						// スレッドのスタート
38		public void start()
39		{
40			if (th == null) {
41				th = new Thread(this);
42				th.start();
43			}
44		}
45						// スレッドの停止
46		public void stop()
47		{
48			state = false;
49		}
50	
51		public void run()
52		{
53			InputStream stm = null;
54	//          		URLConnectionクラスを使用する場合
55	//		URLConnection c_inURL = null;
56			BufferedReader in = null;
57			String line;
58			StringBuffer buf = new StringBuffer();
59	
60			while (state) {
61	
62				try {
63					Thread.currentThread().sleep(100);	// 100msスリープ
64				}
65				catch (InterruptedException e) {};
66	
67				try {
68								// URL への接続
69					stm = this.url.openStream();
70	//          			URLConnectionクラスを使用する場合(上の1行は削除)
71	//				c_inURL = url.openConnection();
72	//				c_inURL.setDoInput(true);
73	//				c_inURL.setDoOutput(false);
74	//				c_inURL.setUseCaches(false);
75	//				c_inURL.connect();
76	//				stm = c_inURL.getInputStream();
77								// ファイルから 1 行ずつ読み込み
78					in = new BufferedReader(new InputStreamReader(stm));
79					while ((line = in.readLine()) != null)
80						buf.append(line + "\n");
81								// テキストエリアに出力
82					ta.setText(buf.toString());
83					state = false;
84					in.close();
85				}
86				catch (IOException e) {
87					System.out.println("IO Error:" + e.getMessage());
88				}
89			}
90		}
91	}
		
15 行目

  HTML ファイルから,読み込むファイル名を取得しています.

17 行目~ 23 行目

  ファイル名から URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

69 行目,78 行目~ 80 行目

  ファイルから 1 行ずつ読み込み,可変長の文字列を扱う StringBuffer クラスのオブジェクト buf に記憶しています.

82 行目

  buf を文字列( String クラスのオブジェクト)に変換し,テキストエリアに表示しています.

(プログラム例 20.22 ) データの転送とサーバへの保存

  URL(サーバ)にデータを送り,そのデータをサーバ側にファイルとして保存するなど,サーバ側で何らかの処理を行いたいような場合は,すべてのプログラムをクライアント側の Java だけで記述することは不可能です.CGI( Common Gateway Interface ) が必要になります.CGI は,外部のプログラム(フォームを含んだホームページやサーバにデータを送信するアプレットなど)が,Web サーバとの間で情報を授受するための標準的な方法を規定するものです.この方法を実現するプログラムのことを,CGI プログラムと呼び,Web サーバに準備しておく必要があります.

  ここでは,テキストエリアに入力されたデータをサーバーに転送し,ファイルとして保存したい場合について説明します.なお,サーバ側の処理が終了すると,サーバはメッセージをクライアント側に送信しますが,そのメッセージも同じテキストエリアに表示します.(「AWT の利用」,「Swing の利用」)をクリックして表示してみてください.ただし,ここには CGI プログラムが準備されていないため,送信するとエラーになってしまいます.

AWT を利用した場合

001	import java.awt.*;
002	import java.awt.event.*;
003	import java.applet.*;
004	import java.net.*;
005	import java.io.*;
006	
007	public class Test extends Applet implements Runnable, ActionListener
008	{
009		URL url;
010		Thread th;
011		TextArea ta = new TextArea(15, 50);
012		boolean state = true;
013		boolean submit_sw = false;
014		Button soshin;
015	
016		public void init() {
017						// CGIの設定
018			String name = "test.cgi";   // PHPの場合: String name = "test.php";
019			try {
020				url = new URL(getCodeBase(), name);
021			}
022			catch (MalformedURLException e)
023			{
024				System.out.println("Bad URL: " + name);
025			}
026						// テキストエリアの追加
027			Font f = new Font("TimesRoman", Font.BOLD, 20);
028			setFont(f);
029			add(ta);
030						// ボタンの追加
031			soshin = new Button("送信");
032			add(soshin);
033			soshin.addActionListener(this);
034		}
035						// 周囲の余白
036		public Insets getInsets()
037		{
038			return new Insets(10, 10, 10, 10);
039		}
040						// スレッドのスタート
041		public void start()
042		{
043			if (th == null) {
044				th = new Thread(this);
045				th.start();
046			}
047		}
048						// スレッドの停止
049		public void stop()
050		{
051			state = false;
052		}
053						// ボタンが押されたときの処理
054		public void actionPerformed(ActionEvent e)
055		{
056			if (e.getSource() == soshin)
057				submit_sw = true;
058		}
059	
060		public void run()
061		{
062			URLConnection c_URL;
063			OutputStream o_stm = null;
064			PrintStream out = null;
065			InputStream i_stm = null;
066			BufferedReader in = null;
067			String line;
068			StringBuffer buf = new StringBuffer();
069	
070			while (state) {
071	
072				try {
073					Thread.currentThread().sleep(100);	// 100msスリープ
074				}
075				catch (InterruptedException e) {};
076	
077				if (submit_sw) {
078					try {
079						line = ta.getText();
080									// データが入力されたことの確認
081						if (line.equals("")) {
082							ta.setText("データを入れて下さい\n");
083							try {
084								Thread.currentThread().sleep(1000);
085							}
086							catch (InterruptedException e) {};
087							ta.setText("");
088						}
089						else {
090									// CGIへ接続
091							c_URL = url.openConnection();
092							c_URL.setDoInput(true);
093							c_URL.setDoOutput(true);
094							c_URL.setUseCaches(false);
095							c_URL.connect();
096									// データをCGIへ送信
097							o_stm = c_URL.getOutputStream();
098							out   = new PrintStream(o_stm);
099	//						line  = "trans=" + line;   PHP の場合はこの行が必要
100							out.print(line);
101							out.close();
102									// CGIからのメッセージを受け取る
103							i_stm = c_URL.getInputStream();
104							in    = new BufferedReader(new InputStreamReader(i_stm));
105							while ((line = in.readLine()) != null)
106								buf.append(line + "\n");
107									// メッセージをテキストエリアに出力
108							ta.setText(buf.toString());
109							in.close();
110	
111							state = false;
112						}
113						submit_sw = false;
114					}
115	
116					catch (UnknownServiceException e) {
117						ta.setText("Unknown Service: " + e.getMessage());
118					}
119					catch (IOException e) {
120						ta.setText("IO Error: " + e.getMessage());
121					}
122				}
123			}
124		}
125	}
		
018 行目~ 025 行目

  CGI 名から URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

054 行目~ 058 行目

  送信ボタンがクリックされたときの処理です.submit_sw を true に設定し,送信が可能なようにします.

079 行目

  テキストエリアに入力されたデータを String クラスのオブジェクト line に記憶しています.

091 行目~ 095 行目

  CGI に接続しています.

100 行目

  line に記憶されたデータを CGI に送信しています.

105 行目~ 106 行目

  CGI から送られてきたデータを受け取り,StringBuffer クラスのオブジェクト buf に記憶しています.

108 行目

  buf を文字列( String クラスのオブジェクト)に変換し,テキストエリアに表示しています.

Swing を利用した場合

001	import java.awt.*;
002	import java.awt.event.*;
003	import javax.swing.*;
004	import java.net.*;
005	import java.io.*;
006	
007	public class Test extends JApplet implements Runnable, ActionListener
008	{
009		URL url;
010		Thread th;
011		JTextArea ta = new JTextArea(15, 30);
012		boolean state = true;
013		boolean submit_sw = false;
014		JButton soshin;
015	
016		public void init() {
017						// CGIの設定
018			String name = "test.cgi";   // PHPの場合: String name = "test.php";
019			try {
020				url = new URL(getCodeBase(), name);
021			}
022			catch (MalformedURLException e)
023			{
024				System.out.println("Bad URL: " + name);
025			}
026						// テキストエリアの追加
027			Font f = new Font("TimesRoman", Font.BOLD, 20);
028			Container cp = getContentPane();
029			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
030			ta.setFont(f);
031			JScrollPane sp = new JScrollPane(ta);
032			cp.add(sp);
033						// ボタンの追加
034			soshin = new JButton("送信");
035			soshin.setFont(f);
036			cp.add(soshin);
037			soshin.addActionListener(this);
038		}
039						// 周囲の余白
040		public Insets getInsets()
041		{
042			return new Insets(10, 10, 10, 10);
043		}
044						// スレッドのスタート
045		public void start()
046		{
047			if (th == null) {
048				th = new Thread(this);
049				th.start();
050			}
051		}
052						// スレッドの停止
053		public void stop()
054		{
055			state = false;
056		}
057						// ボタンが押されたときの処理
058		public void actionPerformed(ActionEvent e)
059		{
060			if (e.getSource() == soshin)
061				submit_sw = true;
062		}
063	
064		public void run()
065		{
066			URLConnection c_URL;
067			OutputStream o_stm = null;
068			PrintStream out = null;
069			InputStream i_stm = null;
070			BufferedReader in = null;
071			String line;
072			StringBuffer buf = new StringBuffer();
073	
074			while (state) {
075	
076				try {
077					Thread.currentThread().sleep(100);	// 100msスリープ
078				}
079				catch (InterruptedException e) {};
080	
081				if (submit_sw) {
082					try {
083						line = ta.getText();
084									// データが入力されたことの確認
085						if (line.equals("")) {
086							ta.setText("データを入れて下さい\n");
087							try {
088								Thread.currentThread().sleep(1000);
089							}
090							catch (InterruptedException e) {};
091							ta.setText("");
092						}
093						else {
094									// CGIへ接続
095							c_URL = url.openConnection();
096							c_URL.setDoInput(true);
097							c_URL.setDoOutput(true);
098							c_URL.setUseCaches(false);
099							c_URL.connect();
100									// データをCGIへ送信
101							o_stm = c_URL.getOutputStream();
102							out   = new PrintStream(o_stm);
103	//						line  = "trans=" + line;   PHP の場合はこの行が必要
104							out.print(line);
105							out.close();
106									// CGIからのメッセージを受け取る
107							i_stm = c_URL.getInputStream();
108							in    = new BufferedReader(new InputStreamReader(i_stm));
109							while ((line = in.readLine()) != null)
110								buf.append(line + "\n");
111									// メッセージをテキストエリアに出力
112							ta.setText(buf.toString());
113							in.close();
114	
115							state = false;
116						}
117						submit_sw = false;
118					}
119	
120					catch (UnknownServiceException e) {
121						ta.setText("Unknown Service: " + e.getMessage());
122					}
123					catch (IOException e) {
124						ta.setText("IO Error: " + e.getMessage());
125					}
126				}
127			}
128		}
129	}
		
018 行目~ 025 行目

  CGI 名から URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

058 行目~ 062 行目

  送信ボタンがクリックされたときの処理です.submit_sw を true に設定し,送信が可能なようにします.

083 行目

  テキストエリアに入力されたデータを String クラスのオブジェクト line に記憶しています.

095 行目~ 099 行目

  CGI に接続しています.

104 行目

  line に記憶されたデータを CGI に送信しています.

109 行目~ 110 行目

  CGI から送られてきたデータを受け取り,StringBuffer クラスのオブジェクト buf に記憶しています.

112 行目

  buf を文字列( String クラスのオブジェクト)に変換し,テキストエリアに表示しています.

  上の例で見るように,クライアント側は,CGI プログラムを一種のファイルとみなして入出力を行っています.また,CGI プログラムは,クライアントから送られてきたデータを標準入力として読み込み,また,クライアント側へのメッセージは標準出力として出力します.CGI プログラムは,どのようなプログラミング言語でも書くことが出来ます.次に示すのは,シェルスクリプトで書いた例です.見ても明らかなように,非常に簡単なプログラムです.なお,いずれの CGI プログラムでも同様ですが,プログラムの最初に,CGI プログラムが出力する情報の種類をクライアント側のプログラム(ブラウザ)に送ってやる必要があります.それを,HTTP ヘッダと呼びます.シェルスクリプトの場合は,2 行目及び 3 行目がこの情報に相当します.
#!/bin/sh
echo Content-type: text/plain  # HTTP header
echo                           # HTTP header
umask 000
cat > data
umask 022
echo End of Submit
		

  このシェルスクリプトを CGI として動作させるためには,上に示した Java プログラムをコンパイルしたクラスファイルと同じディレクトリに test.cgi という名前で保存し,test.cgi のファイルモードを実行可能に設定します.さらに,データを保存するため,以下のような手続きが必要です.この例においては,クライアントから送られてきたデータを,CGI と同じディレクトリにある data というファイルに保存しています.そのため,すべての人が読み書き可能なファイルモードを持つ data を前もって準備しておくか,又は,CGI が存在するディレクトリ自体をすべての人が読み書き可能なように設定しておかなければなりません.このファイル data に関する処置は,どの言語で CGI を記述しても同じです.

  次は,Perl で CGI を記述した例です.この場合も,ファイル名を test.cgi とし,かつ,ファイルモードを実行可能に変更する必要があります.
#!/usr/local/bin/perl
#

#
# 出力データファイルを指定
#

$datafile = "data";

#
# 標準入力データ読込
#
read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});

#
# ファイルのオープン
#
if (!open(OUT, ">$datafile")) {
    print "Content-type: text/plain\n\n";	# オープンに失敗した場合
    print "Writing Error\n";				# 書込失敗メッセージ送出
    exit(0);
}

#
# データファイル書込
#
flock(OUT, 2);					# ファイルをロック
print OUT "$buffer\n";				# データを書き出す
print OUT "\n";					# データを書き出す
flock(OUT, 8);
close(OUT);					# ファイルをクローズ

print "Content-type: text/plain\n\n";		# HTTPヘッダの送信
                                     		# (これから送る情報の種類)
print "データの送信を終了しました\n";       # 書込完了メッセージ送出
exit(0);
		

  次は,C/C++ で書いた例です.上で述べた 2 つの例との違いは,コンパイルしなければならない点です.つまり,以下のプログラムをサーバ側でコンパイルし,実行可能プログラムの名前を test.cgi にする必要があります.
#include <stdio.h>
#include <stdlib.h>

int main()
{
	char c;
	FILE *out;

	out = fopen("data", "w");

	while ((c = getchar()) != EOF)
		fputc(c, out);

	fclose(out);

	printf("Content-type: text/plain\n\n");   // HTTPヘッダの送信
	printf("データの送信を終了しました\n");

	return 0;
}
		

  最後は,PHP で書いた例です.この場合も,Perl などと同様,コンパイルする必要はありませんが,Java のプログラムにおいて 2 行ほど修正してあります.PHP の場合は,「変数名=内容」の形で送信された「内容」が,配列 $_POST['変数名'] の中身になりますので,Java プログラムにおいて,テキストエリアに入力されたデータの前に「 trans= 」を付け加えています.PHP のプログラムでは,配列 $_POST['trans'] の中身を改行で区切り,それぞれを配列 $x に保存しています.

<?php
					// 送信されてきたデータを受け取る(「\n」で分離)
	$k = 0;
	$x = strtok($_POST['trans'], "\n");
	while ($x) {
		$p[$k] = $x;
		$x     = strtok("\n");
		$k++;
	}
					// ファイルへ出力
	$out = fopen("data", "wb");
	for ($i1 = 0; $i1 < $k; $i1++)
		fwrite($out, $p[$i1]);
	fclose($out);
					// メッセージの送信
	printf("データの送信を終了しました\n");
?>
		

なお,CGI の名称は test.cgi のままで構いませんが,その場合は,PHP のプログラムの 1 行目に次の行を入れ,かつ,実行可能なモードに変更する必要があります.
	#!/usr/local/bin/php
		

(プログラム例 20.29 ) 演習問題への解答

  このアプレット(「AWT の利用」,「Swing の利用」)では,サーバによって実行された正誤の判定結果をテキストエリアに表示するようにしています.以下に示すのは,Java のソースプログラム( Problem.java )です.なお,CGI としては,PHP のプログラム( check.php )を使用しています.

AWT を利用した場合

001	import java.applet.*;
002	import java.util.*;
003	import java.awt.*;
004	import java.awt.event.*;
005	import java.net.*;
006	import java.io.*;
007	public class Problem extends Applet implements ActionListener
008	{
009		int a = 3, b = 4, c;
010		TextField ans, name;
011		TextArea ta;
012		Button bt;
013		Label shiki;
014		Random rn;
015		URL url;
016	
017		/************/
018		/* 初期設定 */
019		/************/
020		public void init()
021		{
022						// CGI ファイルの設定
023			try {
024				url = new URL(getCodeBase(), "check.php");
025			}
026			catch (MalformedURLException e) {
027				ta.setForeground(Color.red);
028				ta.setText("Error: " + e.getMessage());
029			}
030						// レイアウト,背景色,フォントなど
031			setLayout(new BorderLayout(5, 10));
032			setBackground(new Color(225, 255, 225));
033			setFont(new Font("TimesRoman", Font.BOLD, 20));
034			rn = new Random();
035						// 上のパネル
036			Panel pn1 = new Panel();
037			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
038			add(pn1, BorderLayout.NORTH);
039			pn1.add(new Label("次の問題の答えは?"));
040						// 中央のパネル
041			Panel pn2 = new Panel();
042			pn2.setLayout(new GridLayout(2, 1, 10, 10));
043			add(pn2, BorderLayout.CENTER);
044			Panel pn21 = new Panel();
045			pn21.setLayout(new FlowLayout(FlowLayout.CENTER));
046			pn2.add(pn21);
047			shiki = new Label("    3 + 4 = ");
048			pn21.add(shiki);
049			ans = new TextField("", 3);
050			pn21.add(ans);
051			Panel pn22 = new Panel();
052			pn22.setLayout(new FlowLayout(FlowLayout.CENTER));
053			pn2.add(pn22);
054			pn22.add(new Label("名前:"));
055			name = new TextField("", 10);
056			pn22.add(name);
057			bt = new Button("送信");
058			bt.addActionListener(this);
059			pn22.add(bt);
060						// 下のパネル
061			Panel pn3 = new Panel();
062			add(pn3, BorderLayout.SOUTH);
063			ta = new TextArea(2, 30);
064			pn3.add(ta);
065		}
066	
067		/******************************/
068		/* 上,左,下,右の余白の設定 */
069		/******************************/
070		public Insets getInsets()
071		{
072			return new Insets(10, 10, 10, 10);
073		}
074	
075		/******************************/
076		/* ボタンが押されたときの処理 */
077		/******************************/
078		public void actionPerformed(ActionEvent e)
079		{
080			int sw = 0;
081			Dialogbox db = null;
082			URLConnection c_URL;
083			OutputStream o_stm = null;
084			PrintStream out = null;
085			InputStream i_stm = null;
086			BufferedReader in = null;
087			String str;
088			if (ans.getText().length() <= 0) {
089				db = new Dialogbox(" 答えを入力してください");
090				sw = 1;
091			}
092			else if (name.getText().length() <= 0) {
093				db = new Dialogbox(" 名前を入力してください");
094				sw = 1;
095			}
096			if (sw > 0) {
097				db.setSize(300,130);
098				Toolkit tool = getToolkit();
099				Dimension d = tool.getScreenSize();
100				db.setLocation(d.width / 2 - 150, d.height / 2 - 65);
101				db.setVisible(true);
102			}
103			else {
104				try {
105					c_URL = url.openConnection();
106					c_URL.setDoInput(true);
107					c_URL.setDoOutput(true);
108					c_URL.setUseCaches(false);
109					c_URL.connect();
110						// 出力
111					o_stm = c_URL.getOutputStream();
112					out   = new PrintStream(o_stm);
113					c     = Integer.parseInt(ans.getText());
114					out.println("ans=" + a + "," + b + "," + c);
115					out.close();
116						// 入力
117					i_stm = c_URL.getInputStream();
118					in    = new BufferedReader(new InputStreamReader(i_stm));
119					str   = in.readLine();
120					in.close();
121					if (Integer.parseInt(str) > 0) {
122						ta.setForeground(Color.black);
123						ta.setText(" " + name.getText() + "さん,正解です\n");
124						ta.append(" 続いて,次の問題の答えは?");
125						a = (int)(100.0 * rn.nextDouble());
126						b = (int)(100.0 * rn.nextDouble());
127						ans.setText("");
128						shiki.setText(Integer.toString(a) + " + " + Integer.toString(b) + " = ");
129					}
130					else {
131						ta.setForeground(Color.red);
132						ta.setText(" " + name.getText() + "さん,間違っています\n");
133						ta.append(" もう一度計算してみて下さい");
134					}
135				}
136				catch (IOException e1) {
137					ta.setForeground(Color.red);
138					ta.setText("IO Error: " + e1.getMessage());
139				}
140			}
141		}
142	}
143	
144	/*******************/
145	/* Dialogboxクラス */
146	/*******************/
147	class Dialogbox extends Frame {
148		Dialogbox(String message)
149		{
150			super("Error");
151			setFont(new Font("TimesRoman", Font.BOLD, 20));
152			add(new Label(message));
153			addWindowListener(new WinEnd());
154		}
155		class WinEnd extends WindowAdapter
156		{
157			public void windowClosing(WindowEvent e) {
158				setVisible(false);
159			}
160		}
161	}
		
023 行目~ 029 行目

  CGI ファイル check.php に対する URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

031 行目

  レイアウトマネージャを BorderLayout に変更しています.

032 行目

  背景色の設定をしています.

033 行目

  Font クラスのオブジェクトを生成し,設定しています.

034 行目

  Random クラスのオブジェクトを生成をしています(新しい乱数の初期化).

036 行目~ 038 行目

  BorderLayout の上部( BorderLayout.NORTH )に Panel クラスのオブジェクト pn1 を追加し,そのレイアウトマネージャとして FlowLayout を設定しています.

039 行目

  Panel クラスのオブジェクト pn1 に,「次の問題の答えは?」というメッセージを表示するため,Label クラスのオブジェクトを追加しています.

041 行目~ 059 行目

  BorderLayout の中央( BorderLayout.CENTER )に Panel クラスのオブジェクト pn2 を追加し,そこに,問題を表示するための Label クラスのオブジェクト,答を入力するための TextField クラスのオブジェクト,"名前:"というタイトルの Label クラスのオブジェクト,名前を入力するための TextField クラスのオブジェクト,及び,答を送信するための Button クラスのオブジェクトを貼り付けています.なお,Button クラスのオブジェクトには,ボタンがクリックされたときの処理を行うため,ActionListener を追加しています.

061 行目~ 064 行目

  BorderLayout の下部( BorderLayout.SOUTH )に Panel クラスのオブジェクト pn3 を追加し,そこに,サーバから送られてきたメッセージを表示するため,TextArea クラスのオブジェクトを追加しています.

070 行目~ 073 行目

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

078 行目~ 141 行目

  「送信」ボタンがクリックされたときの処理です.
  • 088 行目~ 095 行目: 答えや名前が入力されていない場合,メッセージを出力する Dialogbox クラス(後述)のオブジェクトを生成しています.
  • 097 行目~ 101 行目: 答えや名前が入力されていない場合の処理であり,スクリーンの指定された位置に,Dialogbox クラスのオブジェクト(ダイアログボックス)を表示しています.
  • 113 行目: 答え領域に入力されたデータを Integer クラスのスタティックメソッド parseInt を利用して整数に変換しています.
  • 114 行目: 問題と答をサーバに送信しています.
  • 119 行目: サーバからの返信を受け取っています.
  • 122 行目~ 128 行目: 答えが正しかった場合の処理であり,メッセージをテキストエリアに表示すると共に,次の問題を Label クラスのオブジェクト shiki に設定しています.
  • 131 行目~ 133 行目: 答えが間違っていた場合の処理であり,メッセージをテキストエリアに表示しています.

147 行目~ 161 行目

  Dialogbox クラスの定義です.
  • 147 行目: 新しい Window を生成するため,Frame クラスを継承しています.
  • 150 行目: Window のタイトルとなる文字列を引数として,親クラス( Frame クラス)のコンストラクタを呼び出しています .
  • 153 行目: Window の「×」ボタンをクリックしたとき,Window を終了させるために WindowListener を追加しています(内部クラス WinEnd の利用).
  • 155 行目~ 160 行目: Window の「×」ボタンがクリックされたときの処理であり,Window を終了させています.

Swing を利用した場合

001	import javax.swing.*;
002	import java.util.*;
003	import java.awt.*;
004	import java.awt.event.*;
005	import java.net.*;
006	import java.io.*;
007	public class Problem extends JApplet implements ActionListener
008	{
009		int a = 3, b = 4, c;
010		JTextField ans, name;
011		JTextArea ta;
012		JButton bt;
013		JLabel shiki;
014		Random rn;
015		URL url;
016	
017		/************/
018		/* 初期設定 */
019		/************/
020		public void init()
021		{
022						// CGI ファイルの設定
023			try {
024				url = new URL(getCodeBase(), "check.php");
025			}
026			catch (MalformedURLException e) {
027				ta.setForeground(Color.red);
028				ta.setText("Error: " + e.getMessage());
029			}
030						// レイアウト,背景色,フォントなど
031			Container cp = getContentPane();
032			cp.setBackground(new Color(225, 255, 225));
033			Font f = new Font("TimesRoman", Font.BOLD, 20);
034			rn = new Random();
035						// 上のパネル
036			JPanel pn1 = new JPanel();
037			pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
038			pn1.setBackground(new Color(225, 255, 225));
039			cp.add(pn1, BorderLayout.NORTH);
040			JLabel lb1 = new JLabel("次の問題の答えは?");
041			lb1.setFont(f);
042			pn1.add(lb1);
043						// 中央のパネル
044			JPanel pn2 = new JPanel();
045			pn2.setLayout(new GridLayout(2, 1, 10, 10));
046			pn2.setBackground(new Color(225, 255, 225));
047			cp.add(pn2, BorderLayout.CENTER);
048			JPanel pn21 = new JPanel();
049			pn21.setLayout(new FlowLayout(FlowLayout.CENTER));
050			pn21.setBackground(new Color(225, 255, 225));
051			pn2.add(pn21);
052			shiki = new JLabel("    3 + 4 = ");
053			shiki.setFont(f);
054			pn21.add(shiki);
055			ans = new JTextField("", 3);
056			ans.setBackground(Color.white);
057			ans.setFont(f);
058			ans.setHorizontalAlignment(JTextField.RIGHT);
059			pn21.add(ans);
060			JPanel pn22 = new JPanel();
061			pn22.setLayout(new FlowLayout(FlowLayout.CENTER));
062			pn22.setBackground(new Color(225, 255, 225));
063			pn2.add(pn22);
064			JLabel lb2 = new JLabel("名前:");
065			lb2.setFont(f);
066			pn22.add(lb2);
067			name = new JTextField("", 10);
068			name.setBackground(Color.white);
069			name.setFont(f);
070			pn22.add(name);
071			bt = new JButton("送信");
072			bt.setFont(f);
073			bt.addActionListener(this);
074			pn22.add(bt);
075						// 下のパネル
076			JPanel pn3 = new JPanel();
077			pn3.setBackground(new Color(225, 255, 225));
078			cp.add(pn3, BorderLayout.SOUTH);
079			ta = new JTextArea(2, 20);
080			ta.setFont(f);
081			ta.setBackground(Color.white);
082			JScrollPane sp = new JScrollPane(ta);
083			pn3.add(sp);
084		}
085	
086		/******************************/
087		/* 上,左,下,右の余白の設定 */
088		/******************************/
089		public Insets getInsets()
090		{
091			return new Insets(10, 10, 10, 10);
092		}
093	
094		/******************************/
095		/* ボタンが押されたときの処理 */
096		/******************************/
097		public void actionPerformed(ActionEvent e)
098		{
099			int sw = 0;
100			Dialogbox db = null;
101			URLConnection c_URL;
102			OutputStream o_stm = null;
103			PrintStream out = null;
104			InputStream i_stm = null;
105			BufferedReader in = null;
106			String str;
107			if (ans.getText().length() <= 0) {
108				db = new Dialogbox(" 答えを入力してください");
109				sw = 1;
110			}
111			else if (name.getText().length() <= 0) {
112				db = new Dialogbox(" 名前を入力してください");
113				sw = 1;
114			}
115			if (sw > 0) {
116				db.setSize(300,130);
117				Toolkit tool = getToolkit();
118				Dimension d = tool.getScreenSize();
119				db.setLocation(d.width / 2 - 150, d.height / 2 - 65);
120				db.setVisible(true);
121			}
122			else {
123				try {
124					c_URL = url.openConnection();
125					c_URL.setDoInput(true);
126					c_URL.setDoOutput(true);
127					c_URL.setUseCaches(false);
128					c_URL.connect();
129						// 出力
130					o_stm = c_URL.getOutputStream();
131					out   = new PrintStream(o_stm);
132					c     = Integer.parseInt(ans.getText());
133					out.println("ans=" + a + "," + b + "," + c);
134					out.close();
135						// 入力
136					i_stm = c_URL.getInputStream();
137					in    = new BufferedReader(new InputStreamReader(i_stm));
138					str   = in.readLine();
139					in.close();
140					if (Integer.parseInt(str) > 0) {
141						ta.setForeground(Color.black);
142						ta.setText(" " + name.getText() + "さん,正解です\n");
143						ta.append(" 続いて,次の問題の答えは?");
144						a = (int)(100.0 * rn.nextDouble());
145						b = (int)(100.0 * rn.nextDouble());
146						ans.setText("");
147						shiki.setText(Integer.toString(a) + " + " + Integer.toString(b) + " = ");
148					}
149					else {
150						ta.setForeground(Color.red);
151						ta.setText(" " + name.getText() + "さん,間違っています\n");
152						ta.append(" もう一度計算してみて下さい");
153					}
154				}
155				catch (IOException e1) {
156					ta.setForeground(Color.red);
157					ta.setText("IO Error: " + e1.getMessage());
158				}
159			}
160		}
161	}
162	
163	/*******************/
164	/* Dialogboxクラス */
165	/*******************/
166	class Dialogbox extends JFrame {
167		Dialogbox(String message)
168		{
169			super("Error");
170			Container cp = getContentPane();
171			cp.setBackground(Color.white);
172			JLabel lb = new JLabel(message);
173			lb.setFont(new Font("TimesRoman", Font.BOLD, 20));
174			cp.add(lb);
175			addWindowListener(new WinEnd());
176		}
177		class WinEnd extends WindowAdapter
178		{
179			public void windowClosing(WindowEvent e) {
180				setVisible(false);
181			}
182		}
183	}
		
023 行目~ 029 行目

  CGI ファイル check.php に対する URL クラスのオブジェクトを生成しています.getCodeBase() は,アプレットのコードが存在する URL を返す Applet クラスのメソッドです.

031 行目

  ContentPane を取得し,変数 cp に設定しています.なお,contentPane には,BorderLayout マネージャーがデフォルトで設定されています.

032 行目

  背景色の設定をしています.

033 行目

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

034 行目

  Random クラスのオブジェクトを生成をしています(新しい乱数の初期化).

036 行目~ 039 行目

  BorderLayout の上部( BorderLayout.NORTH )に JPanel クラスのオブジェクト pn1 を追加し,そのレイアウトマネージャとして FlowLayout を設定しています.

040 行目~ 042 行目

  JPanel クラスのオブジェクト pn1 に,「次の問題の答えは?」というメッセージを表示するため,JLabel クラスのオブジェクトを追加しています.

044 行目~ 074 行目

  BorderLayout の中央( BorderLayout.CENTER )に JPanel クラスのオブジェクト pn2 を追加し,そこに,問題を表示するための JLabel クラスのオブジェクト,答を入力するための JTextField クラスのオブジェクト,"名前:"というタイトルの JLabel クラスのオブジェクト,名前を入力するための JTextField クラスのオブジェクト,及び,答を送信するための JButton クラスのオブジェクトを貼り付けています.なお,JButton クラスのオブジェクトには,ボタンがクリックされたときの処理を行うため,ActionListener を追加しています.

076 行目~ 083 行目

  BorderLayout の下部( BorderLayout.SOUTH )に JPanel クラスのオブジェクト pn3 を追加し,そこに,サーバから送られてきたメッセージを表示するため,JTextArea クラスのオブジェクトを追加しています.

089 行目~ 092 行目

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

097 行目~ 160 行目

  「送信」ボタンがクリックされたときの処理です.
  • 107 行目~ 114 行目: 答えや名前が入力されていない場合,メッセージを出力する Dialogbox クラス(後述)のオブジェクトを生成しています.
  • 116 行目~ 120 行目: 答えや名前が入力されていない場合の処理であり,スクリーンの指定された位置に,Dialogbox クラスのオブジェクト(ダイアログボックス)を表示しています.
  • 132 行目: 答え領域に入力されたデータを Integer クラスのスタティックメソッド parseInt を利用して整数に変換しています.
  • 133 行目: 問題と答をサーバに送信しています.
  • 138 行目: サーバからの返信を受け取っています.
  • 140 行目~ 148 行目: 答えが正しかった場合の処理であり,メッセージをテキストエリアに表示すると共に,次の問題を JLabel クラスのオブジェクト shiki に設定しています.
  • 149 行目~ 153 行目: 答えが間違っていた場合の処理であり,メッセージをテキストエリアに表示しています.

166 行目~ 183 行目

  Dialogbox クラスの定義です.
  • 166 行目: 新しい Window を生成するため,JFrame クラスを継承しています.
  • 169 行目: Window のタイトルとなる文字列を引数として,親クラス( JFrame クラス)のコンストラクタを呼び出しています .
  • 175 行目: Window の「×」ボタンをクリックしたとき,Window を終了させるために WindowListener を追加しています(内部クラス WinEnd の利用).
  • 177 行目~ 182 行目: Window の「×」ボタンがクリックされたときの処理であり,Window を終了させています.

  答や名前が入力されていないときの処理として,JOptionPane クラスのメソッド showMessageDialog を使用すれば,Dialogbox クラスを定義すること無しに,もっと簡単に記述できます.この点は,AWT を使用した場合も同様です.
import javax.swing.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;

public class Problem extends JApplet implements ActionListener
{
	int a = 3, b = 4, c;
	JTextField ans, name;
	JTextArea ta;
	JButton bt;
	JLabel shiki;
	Random rn;
	URL url;

	/************/
	/* 初期設定 */
	/************/
	public void init()
	{
					// CGI ファイルの設定
		try {
			url = new URL(getCodeBase(), "check.php");
		}
		catch (MalformedURLException e) {
			ta.setForeground(Color.red);
			ta.setText("Error: " + e.getMessage());
		}
					// レイアウト,背景色,フォントなど
		Container cp = getContentPane();
		cp.setBackground(new Color(225, 255, 225));
		Font f = new Font("TimesRoman", Font.BOLD, 20);
		rn = new Random();
					// 上のパネル
		JPanel pn1 = new JPanel();
		pn1.setLayout(new FlowLayout(FlowLayout.CENTER));
		pn1.setBackground(new Color(225, 255, 225));
		cp.add(pn1, BorderLayout.NORTH);
		JLabel lb1 = new JLabel("次の問題の答えは?");
		lb1.setFont(f);
		pn1.add(lb1);
					// 中央のパネル
		JPanel pn2 = new JPanel();
		pn2.setLayout(new GridLayout(2, 1, 10, 10));
		pn2.setBackground(new Color(225, 255, 225));
		cp.add(pn2, BorderLayout.CENTER);
		JPanel pn21 = new JPanel();
		pn21.setLayout(new FlowLayout(FlowLayout.CENTER));
		pn21.setBackground(new Color(225, 255, 225));
		pn2.add(pn21);
		shiki = new JLabel("    3 + 4 = ");
		shiki.setFont(f);
		pn21.add(shiki);
		ans = new JTextField("", 3);
		ans.setBackground(Color.white);
		ans.setFont(f);
		ans.setHorizontalAlignment(JTextField.RIGHT);
		pn21.add(ans);
		JPanel pn22 = new JPanel();
		pn22.setLayout(new FlowLayout(FlowLayout.CENTER));
		pn22.setBackground(new Color(225, 255, 225));
		pn2.add(pn22);
		JLabel lb2 = new JLabel("名前:");
		lb2.setFont(f);
		pn22.add(lb2);
		name = new JTextField("", 10);
		name.setBackground(Color.white);
		name.setFont(f);
		pn22.add(name);
		bt = new JButton("送信");
		bt.setFont(f);
		bt.addActionListener(this);
		pn22.add(bt);
					// 下のパネル
		JPanel pn3 = new JPanel();
		pn3.setBackground(new Color(225, 255, 225));
		cp.add(pn3, BorderLayout.SOUTH);
		ta = new JTextArea(2, 20);
		ta.setFont(f);
		ta.setBackground(Color.white);
		JScrollPane sp = new JScrollPane(ta);
		pn3.add(sp);
	}

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

	/******************************/
	/* ボタンが押されたときの処理 */
	/******************************/
	public void actionPerformed(ActionEvent e)
	{
		URLConnection c_URL;
		OutputStream o_stm = null;
		PrintStream out = null;
		InputStream i_stm = null;
		BufferedReader in = null;
		String str;
		if (ans.getText().length() <= 0)
			JOptionPane.showMessageDialog(this, "答えを入力してください");
		else if (name.getText().length() <= 0)
			JOptionPane.showMessageDialog(this, "名前を入力してください");
		else {
			try {
				c_URL = url.openConnection();
				c_URL.setDoInput(true);
				c_URL.setDoOutput(true);
				c_URL.setUseCaches(false);
				c_URL.connect();
					// 出力
				o_stm = c_URL.getOutputStream();
				out   = new PrintStream(o_stm);
				c     = Integer.parseInt(ans.getText());
				out.println("ans=" + a + "," + b + "," + c);
				out.close();
					// 入力
				i_stm = c_URL.getInputStream();
				in    = new BufferedReader(new InputStreamReader(i_stm));
				str   = in.readLine();
				in.close();
				if (Integer.parseInt(str) > 0) {
					ta.setForeground(Color.black);
					ta.setText(" " + name.getText() + "さん,正解です\n");
					ta.append(" 続いて,次の問題の答えは?");
					a = (int)(100.0 * rn.nextDouble());
					b = (int)(100.0 * rn.nextDouble());
					ans.setText("");
					shiki.setText(Integer.toString(a) + " + " + Integer.toString(b) + " = ");
				}
				else {
					ta.setForeground(Color.red);
					ta.setText(" " + name.getText() + "さん,間違っています\n");
					ta.append(" もう一度計算してみて下さい");
				}
			}
			catch (IOException e1) {
				ta.setForeground(Color.red);
				ta.setText("IO Error: " + e1.getMessage());
			}
		}
	}
}
		

  以下に示すのは,サーバ側のプログラム check.php のソースです.式と計算結果を受け取り,結果が正しければ 1,誤っていれば 0 を返しています.もちろん,この例の場合は,結果を保存するようなことを行っていませんので,CGI を使用せず,クライアント側だけで処理が可能です.

<?php
	$k = 0;
	$p = array();
	$x = strtok($_POST['ans'], ",");
	while ($x !== FALSE) {
		$p[$k] = $x;
		$x     = strtok(",");
		$k++;
	}
	$a = intval($p[0]);
	$b = intval($p[1]);
	$c = intval($p[2]);
	if (($a + $b) == $c)
		printf("1\n");
	else
		printf("0\n");
?>
		

(プログラム例 20.28 ) Web ページ

  ここでは,Java によってホームページにおけるトップページを作成した例(「AWT の利用」,「Swing の利用」)を示します.最初に表示された状態では,すべての画像が停止していますが,徐々に各画像が動き出します.まず,上部にある文字の部分をクリックすると,

加速: 動きが少しずつ速くなります
加速: 動きが少しずつ遅くなります
情報学部: 情報学部への入り口に移動します

といった動作を行います.

  画像部分をクリックすると,動きが止まり,対応する説明が表示されます.説明を閉じると再び動き出し,説明を閉じずに文字や画像以外の部分をクリックすると説明に対応する画像が削除された後,他の画像は再び動き出します.また,文字や画像以外の部分をクリックすると,動きが停止します.停止した状態で,文字や画像以外の部分をクリックすると,再び動き出します.最初に設定されている速度では,各画像をクリックすることは難しいかと思いますので,減速又は停止した後にクリックしてみてください.

AWT を利用した場合

Java のソース( Space.java )
import java.applet.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class Space extends Applet implements Runnable
{
	int p[], a[], b[];   // 周期,楕円の長径と短径
	int x[], y[], z[];   // 画像の位置
	int period = 100;   // 表示間隔
	int w, h;   // 画面の大きさ
	int t_x1, t_x2, t_y1, t_y2;   // タイトルの位置
	int d_x1, d_x2, d_y1, d_y2;   // 減速の位置
	int u_x1, u_x2, u_y1, u_y2;   // 加速の位置
	int i_w[], i_wt[], i_h[], i_ht[];   // 画像の幅と高さ
	String tt[];
	Image Back, Buf, back, bun[], bun_t[];
	int state = 0;
	Graphics g_b;
	Thread th;
	Random rand;
	Space sp = this;

	/************/
	/* 初期設定 */
	/************/
	public void init()
	{
		int i1;
					// イメージの読み込み
		bun   = new Image [9];
		bun_t = new Image [9];
		MediaTracker trk = new MediaTracker(this);
		back = getImage(getCodeBase(), "space.jpg");
		trk.addImage(back, 0);
		bun[0]   = getImage(getCodeBase(), "computer.gif");
		bun[1]   = getImage(getCodeBase(), "architecture.gif");
		bun[2]   = getImage(getCodeBase(), "AI.gif");
		bun[3]   = getImage(getCodeBase(), "life.gif");
		bun[4]   = getImage(getCodeBase(), "education.gif");
		bun[5]   = getImage(getCodeBase(), "media.gif");
		bun[6]   = getImage(getCodeBase(), "social.gif");
		bun[7]   = getImage(getCodeBase(), "business.gif");
		bun[8]   = getImage(getCodeBase(), "communication.gif");
		bun_t[0] = getImage(getCodeBase(), "computer_txt.gif");
		bun_t[1] = getImage(getCodeBase(), "architecture_txt.gif");
		bun_t[2] = getImage(getCodeBase(), "AI_txt.gif");
		bun_t[3] = getImage(getCodeBase(), "life_txt.gif");
		bun_t[4] = getImage(getCodeBase(), "education_txt.gif");
		bun_t[5] = getImage(getCodeBase(), "media_txt.gif");
		bun_t[6] = getImage(getCodeBase(), "social_txt.gif");
		bun_t[7] = getImage(getCodeBase(), "business_txt.gif");
		bun_t[8] = getImage(getCodeBase(), "communication_txt.gif");
		for (i1 = 0; i1 < 9; i1++) {
			trk.addImage(bun[i1], 2*i1+1);
			trk.addImage(bun_t[i1], 2*i1+2);
		}
		try {
			trk.waitForAll();
		}
		catch (InterruptedException e1) {}

		tt    = new String [9];
		tt[0] = "コンピュータ";
		tt[1] = "情報基礎";
		tt[2] = "人工知能・認知科学";
		tt[3] = "生命情報";
		tt[4] = "教育情報";
		tt[5] = "メディア";
		tt[6] = "社会情報";
		tt[7] = "経営・ビジネス";
		tt[8] = "コミュニケーション";

		i_w = new int [9];
		i_h = new int [9];
		for (i1 = 0; i1 < 9; i1++) {
			i_w[i1] = bun[i1].getWidth(this);
			i_h[i1] = bun[i1].getHeight(this);
		}
		i_wt = new int [9];
		i_ht = new int [9];
		for (i1 = 0; i1 < 9; i1++) {
			i_wt[i1] = bun_t[i1].getWidth(this);
			i_ht[i1] = bun_t[i1].getHeight(this);
		}
					// スクリーンバッファの生成
		w   = getSize().width;
		h   = getSize().height;
		Buf = createImage(w, h);     //バッファ生成
		g_b = Buf.getGraphics();    //グラフィックコンテキスト取得
					// 背景画像の生成
		Back = createImage(w, h);
		Graphics g = Back.getGraphics();
		g.drawImage(back, 0, 0, w, h, this);
		g.setColor(Color.cyan);
		Font f = new Font("TimesRoman", Font.BOLD, 25);
		g.setFont(f);
		FontMetrics fm = g.getFontMetrics(f);
		int c_w = fm.stringWidth("情報学部");
		int c_h = fm.getHeight();
		Insets insets = getInsets();
		t_x1 = w / 2 - c_w / 2;
		t_x2 = t_x1 + c_w;
		t_y1 = insets.top;
		t_y2 = t_y1 + c_h;
		g.drawString("情報学部", t_x1, t_y2);
		g.setColor(Color.yellow);
		f = new Font("TimesRoman", Font.BOLD, 15);
		g.setFont(f);
		fm   = g.getFontMetrics(f);
		c_w  = fm.stringWidth("<< 減速");
		c_h  = fm.getHeight();
		d_x1 = t_x1 - c_w - 10;
		d_x2 = d_x1 + c_w;
		d_y1 = t_y2 - c_h;
		d_y2 = t_y2;
		g.drawString("<< 減速", d_x1, d_y2);
		u_x1 = t_x2 + 10;
		u_x2 = u_x1 + c_w;
		u_y1 = t_y2 - c_h;
		u_y2 = t_y2;
		g.drawString("加速 >>", u_x1, u_y2);
		g.dispose();
					// マウスリスナーの追加
        addMouseListener(new Mouse());
					// ランダム変数の初期化
		rand = new Random();
					// 周期,楕円の長径と短径
		p = new int [9];
		a = new int [9];
		b = new int [9];
		x = new int [9];
		y = new int [9];
		z = new int [9];
		for (i1 = 0; i1 < 9; i1++) {
			p[i1] = (int)(20 + 20 * rand.nextDouble());
			a[i1] = (int)(3 * w / 8 + (w / 8 + 50) * rand.nextDouble());
			b[i1] = (int)(3 * h / 8 + (h / 8 + 50) * rand.nextDouble());
		}
					// スレッドの定義と開始
		state = 1;
		th    = new Thread(this);
		th.start();
	}

	/******************/
	/* スレッドの実行 */
	/******************/
	public void run()
	{
		int i1, i2, m = 0, c[], now[], s[], sw, sh;
		double r, x1, x2, y1, y2, z1, z2, a45 = Math.PI / 4.0;
					// 初期位置
		now = new int [9];
		s   = new int [9];
		c   = new int [9];
		for (i1 = 0; i1 < 9; i1++)
			now[i1] = 0;
		for (i1 = 1; i1 < 9; i1++)
			c[i1] = (int)(100 * rand.nextDouble());
		x[0] = w / 2;
		y[0] = h / 2;
		z[0] = 0;
					// 描画
		while (state > 0) {
			try {
				th.sleep(period);
			}
			catch (InterruptedException e) {}
			if (state == 1) {
				g_b.drawImage(Back, 0, 0, this);  //背景画像の描画
						// アーキテクチャ
				x1   = a[1] * Math.cos(2 * Math.PI / p[1] * now[1]);
				y[1] = h / 2;
				z1   = b[1] * Math.sin(2 * Math.PI / p[1] * now[1]);
				x[1] = w / 2 + (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
				z[1] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
						// 人工知能
				x1   = a[2] * Math.cos(-Math.PI + 2 * Math.PI / p[2] * now[2]);
				y[2] = h / 2;
				z1   = b[2] * Math.sin(-Math.PI + 2 * Math.PI / p[2] * now[2]);
				x[2] = w / 2 + (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
				z[2] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
						// 生命
				x[3] = w / 2;
				y1   = a[3] * Math.cos(2 * Math.PI / p[3] * now[3]);
				z1   = b[3] * Math.sin(2 * Math.PI / p[3] * now[3]);
				y[3] = h / 2 + (int)(Math.cos(a45) * y1 - Math.sin(a45) * z1);
				z[3] = (int)(Math.sin(a45) * y1 + Math.cos(a45) * z1);
						// 教育
				x[4] = w / 2;
				y1   = a[4] * Math.cos(-Math.PI + 2 * Math.PI / p[4] * now[4]);
				z1   = b[4] * Math.sin(-Math.PI + 2 * Math.PI / p[4] * now[4]);
				y[4] = h / 2 + (int)(Math.cos(-a45) * y1 - Math.sin(-a45) * z1);
				z[4] = (int)(Math.sin(-a45) * y1 + Math.cos(-a45) * z1);
						// メディア
				x1   = a[5] * Math.cos(2 * Math.PI / p[5] * now[5]);
				y1   = 0;
				z1   = b[5] * Math.sin(2 * Math.PI / p[5] * now[5]);
				x2   = (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
				z[5] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
				x[5] = w / 2 + (int)(Math.cos(a45) * x2 - Math.sin(a45) * y1);
				y[5] = h / 2 + (int)(Math.sin(a45) * x2 + Math.cos(a45) * y1);
						// 社会
				x1   = a[6] * Math.cos(2 * Math.PI / p[6] * now[6]);
				y1   = 0;
				z1   = b[6] * Math.sin(2 * Math.PI / p[6] * now[6]);
				x2   = (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
				z[6] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
				x[6] = w / 2 + (int)(Math.cos(-a45) * x2 - Math.sin(-a45) * y1);
				y[6] = h / 2 + (int)(Math.sin(-a45) * x2 + Math.cos(-a45) * y1);
						// ビジネス
				x1   = a[7] * Math.cos(Math.PI + 2 * Math.PI / p[7] * now[7]);
				y1   = 0;
				z1   = b[7] * Math.sin(Math.PI + 2 * Math.PI / p[7] * now[7]);
				x2   = (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
				z[7] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
				x[7] = w / 2 + (int)(Math.cos(a45) * x2 - Math.sin(a45) * y1);
				y[7] = h / 2 + (int)(Math.sin(a45) * x2 + Math.cos(a45) * y1);
						// コミュニケーション
				x1   = a[8] * Math.cos(Math.PI + 2 * Math.PI / p[8] * now[8]);
				y1   = 0;
				z1   = b[8] * Math.sin(Math.PI + 2 * Math.PI / p[8] * now[8]);
				x2   = (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
				z[8] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
				x[8] = w / 2 + (int)(Math.cos(-a45) * x2 - Math.sin(-a45) * y1);
				y[8] = h / 2 + (int)(Math.sin(-a45) * x2 + Math.cos(-a45) * y1);
						// 表示
				for (i1 = 0; i1 < 9; i1++)
					s[i1] = 0;
				for (i1 = 0; i1 < 9; i1++) {
					m = -1;
					for (i2 = 0; i2 < 9; i2++) {
						if (s[i2] == 0 && (m < 0 || z[m] > z[i2]))
							m = i2;
					}
					s[m] = 1;
					if  (m == 0)
						g_b.drawImage(bun[0], x[0]-i_w[0]/2, y[0]-i_h[0]/2, this);
					else {
						r  = (w / 2.0 + z[m]) / w;
						sw = (int)(r * i_w[m]);
						sh = (int)(r * i_h[m]);
						g_b.drawImage(bun[m], x[m]-sw/2, y[m]-sh/2, sw, sh, this);
						if (c[m] > 0)
							c[m]--;
						else {
							now[m]++;
							if (now[m] >= p[m])
								now[m] = 0;
						}
					}
				}
				repaint();
			}
		}
	}

	/********/
	/* 描画 */
	/********/
	public void paint (Graphics g)
	{
		g.drawImage(Buf, 0, 0, this);      //バッファを画面に描画
	}

	public void update(Graphics g)    //オーバーライドして最低限のことだけをする
	{
		paint(g);
	}

	/************************/
	/* マウスイベントの処理 */
	/************************/
    class Mouse extends MouseAdapter {
        public void mouseClicked(MouseEvent e)
        {
			int i1, m, r, x1, x2, y1, y2;
			Explanation exp;

            x1 = e.getX();
            y1 = e.getY();

			if (x1 > d_x1 && x1 < d_x2 && y1 > d_y1 && y1 < d_y2) {
				period += 10;
			}
			else if (x1 > u_x1 && x1 < u_x2 && y1 > u_y1 && y1 < u_y2) {
				if (period > 10)
					period -= 10;
			}
			else if (x1 > t_x1 && x1 < t_x2 && y1 > t_y1 && y1 < t_y2) {
				URL url = null;
				try {
					url = new URL("http://ex-cs.sist.ac.jp/~suganuma/dep/index.html");
				}
				catch (MalformedURLException e1) {}
				getAppletContext().showDocument(url, "_blank");
			}
			else {
				m = -1;
				for (i1 = 0; i1 < 9; i1++) {
					x2 = x1 - x[i1];
					y2 = y1 - y[i1];
					r  = (int)Math.sqrt(x2 * x2 + y2 * y2);
					if (r < 50 && (m < 0 || z[m] < z[i1]))
						m = i1;
				}
				if (m >= 0)
					exp = new Explanation(tt[m], i_wt[m], i_ht[m], bun_t[m], x1, y1, sp);
				else {
					if (state == 1)
						state = 2;
					else
						state = 1;
				}
			}
        }
    }
}
		
Java のソース( Explanation.java )
/****************************/
/* 各分野の説明             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;
import java.awt.*;
import java.awt.event.*;

public class Explanation extends Frame implements Runnable
{

	int width, height;   // 画像の大きさ
	int r_width, r_height;   // Windowの大きさ
	int cx, cy, top, left;
	int n_sp = 1, sp = 10;
	Image img;
	Thread th;
	boolean state;
	Insets insets;
	Dimension scr;
	Space space;

	/****************************************/
	/* コンストラクタ                       */
	/*      title : タイトル                */
	/*      width_i,height_i : 画像の大きさ */
	/*      img_i : 画像                    */
	/*      cx_i, cy_i : クリックした位置   */
	/****************************************/
	Explanation(String title, int width_i, int height_i, Image img_i, int cx_i, int cy_i, Space space_i)
	{
					// Frameクラスのコンストラクタの呼び出し
		super(title);
					// データの保存
		width  = width_i;
		height = height_i;
		img    = img_i;
		cx     = cx_i;
		cy     = cy_i;
		space  = space_i;
		space.state = 2;
					// Windowサイズの初期設定
		int w, h;
		setSize(0, 0);
					// ウィンドウを表示
		setVisible(true);
		w            = getSize().width;
		h            = getSize().height;
		Toolkit tool = getToolkit();
		scr          = tool.getScreenSize();
		left         = (scr.width - space.w) / 2 - 10;
		top          = scr.height - space.h - 30;
		setLocation(left + cx - w / 2, top + cy - h / 2);
		insets = getInsets();
					// イベントアダプタ
		addWindowListener(new WinEnd());
		addComponentListener(new ComponentResize());
					// スレッドの定義と開始
		state = true;
		th    = new Thread(this);
		th.start();
	}

	/******************/
	/* スレッドの実行 */
	/******************/
	public void run()
	{
		int w, h, x, y;

		while (state) {
			try {
				th.sleep(50);
			}
			catch (InterruptedException e) {}
			w = n_sp * width / sp + insets.left + insets.right;
			h = n_sp * height / sp + insets.top + insets.bottom;
			setSize(w, h);
			w = getSize().width;
			h = getSize().height;
			if (cx > space.w/2)
				x = left - n_sp * (cx - space.w / 2) / sp + cx - w / 2;
			else
				x = left + n_sp * (space.w / 2 - cx) / sp + cx - w / 2;
			if (cy > space.h/2)
				y = top - n_sp * (cy - space.h / 2) / sp + cy - h / 2;
			else
				y = top + n_sp * (space.h / 2 - cy) / sp + cy - h / 2;
			setLocation(x, y);
			n_sp++;
			if (n_sp > sp)
				state = false;
		}
	}

	/********/
	/* 描画 */
	/********/
	public void paint (Graphics g)
	{
		int w, h;

		Dimension d = getSize();
		h = d.height - (insets.top + insets.bottom);
		w = (int)(width * (double)h / height);
		g.drawImage(img, insets.left, insets.top, w, h, this);
	}

	/**********************/
	/* Windowのサイズ変化 */
	/**********************/
	class ComponentResize extends ComponentAdapter
	{
		public void componentResized(ComponentEvent e)
		{
			repaint();
		}
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			space.state = 1;
			setVisible(false);
		}
	}
}
		

Swing を利用した場合

Java のソース( Space.java )
import javax.swing.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;

public class Space extends JApplet
{
	Draw_m dm;

	/************/
	/* 初期設定 */
	/************/
	public void init()
	{
		int i1;
					// イメージの読み込み
		Image bun[]   = new Image [9];
		Image bun_t[] = new Image [9];
		MediaTracker trk = new MediaTracker(this);
		Image back = getImage(getDocumentBase(), "space.jpg");
		trk.addImage(back, 0);
		bun[0]   = getImage(getDocumentBase(), "computer.gif");
		bun[1]   = getImage(getDocumentBase(), "architecture.gif");
		bun[2]   = getImage(getDocumentBase(), "AI.gif");
		bun[3]   = getImage(getDocumentBase(), "life.gif");
		bun[4]   = getImage(getDocumentBase(), "education.gif");
		bun[5]   = getImage(getDocumentBase(), "media.gif");
		bun[6]   = getImage(getDocumentBase(), "social.gif");
		bun[7]   = getImage(getDocumentBase(), "business.gif");
		bun[8]   = getImage(getDocumentBase(), "communication.gif");
		bun_t[0] = getImage(getDocumentBase(), "computer_txt.gif");
		bun_t[1] = getImage(getDocumentBase(), "architecture_txt.gif");
		bun_t[2] = getImage(getDocumentBase(), "AI_txt.gif");
		bun_t[3] = getImage(getDocumentBase(), "life_txt.gif");
		bun_t[4] = getImage(getDocumentBase(), "education_txt.gif");
		bun_t[5] = getImage(getDocumentBase(), "media_txt.gif");
		bun_t[6] = getImage(getDocumentBase(), "social_txt.gif");
		bun_t[7] = getImage(getDocumentBase(), "business_txt.gif");
		bun_t[8] = getImage(getDocumentBase(), "communication_txt.gif");
		for (i1 = 0; i1 < 9; i1++) {
			trk.addImage(bun[i1], 2*i1+1);
			trk.addImage(bun_t[i1], 2*i1+2);
		}
		try {
			trk.waitForAll();
		}
		catch (InterruptedException e1) {}
					// 描画パネル
		Container cp = getContentPane();
		dm = new Draw_m(bun, bun_t, back, this);
		cp.add(dm);
	}
}

class Draw_m extends JPanel implements Runnable {

	int p[], a[], b[];   // 周期,楕円の長径と短径
	int x[], y[], z[];   // 画像の位置
	int draw[][];   // 描く図形とその位置
	int nd;   // 描く図形の数
	int period = 100;   // 表示間隔
	int w, h;   // 画面の大きさ
	int t_x1, t_x2, t_y1, t_y2;   // タイトルの位置
	int d_x1, d_x2, d_y1, d_y2;   // 減速の位置
	int u_x1, u_x2, u_y1, u_y2;   // 加速の位置
	int i_w[], i_wt[], i_h[], i_ht[];   // 画像の幅と高さ
	boolean dsp[];   // 画像の表示
	String tt[];
	Image back, bun[], bun_t[];
	int state = 0;
	Thread th;
	Random rand;
	Space sp;
	Draw_m dm = this;

	/******************/
	/* コンストラクタ */
	/******************/
	Draw_m(Image bun1[], Image bun_t1[], Image back1, Space sp1)
	{
		bun   = bun1;
		bun_t = bun_t1;
		back  = back1;
		sp    = sp1;
		w     = sp.getSize().width;
		h     = sp.getSize().height;

		tt    = new String [9];
		tt[0] = "コンピュータ";
		tt[1] = "情報基礎";
		tt[2] = "人工知能・認知科学";
		tt[3] = "生命情報";
		tt[4] = "教育情報";
		tt[5] = "メディア";
		tt[6] = "社会情報";
		tt[7] = "経営・ビジネス";
		tt[8] = "コミュニケーション";

		i_w  = new int [9];
		i_h  = new int [9];
		i_wt = new int [9];
		i_ht = new int [9];
		dsp  = new boolean [9];
		for (int i1 = 0; i1 < 9; i1++) {
			i_w[i1]  = bun[i1].getWidth(this);
			i_h[i1]  = bun[i1].getHeight(this);
			i_wt[i1] = bun_t[i1].getWidth(this);
			i_ht[i1] = bun_t[i1].getHeight(this);
			dsp[i1]  = true;
		}
					// マウスリスナーの追加
        addMouseListener(new Mouse());
					// ランダム変数の初期化
		rand = new Random();
					// 周期,楕円の長径と短径
		p = new int [9];
		a = new int [9];
		b = new int [9];
		x = new int [9];
		y = new int [9];
		z = new int [9];
		draw = new int [9][6];
		for (int i1 = 0; i1 < 9; i1++) {
			p[i1] = (int)(10 + 70 * rand.nextDouble());
			if (i1 == 3 || i1 == 4)
				a[i1] = (int)(3 * h / 8 + (w / 8 + 50) * rand.nextDouble());
			else
				a[i1] = (int)(3 * w / 8 + (w / 8 + 50) * rand.nextDouble());
			b[i1] = (int)(3 * h / 8 + (h / 8 + 50) * rand.nextDouble());
		}
					// スレッドの定義と開始
		state = 1;
		th    = new Thread(this);
		th.start();
	}

	/******************/
	/* スレッドの実行 */
	/******************/
	public void run()
	{
		int i1, i2, m = 0, c[], now[], s[], sw, sh;
		double r, x1, x2, y1, y2, z1, z2, a45 = Math.PI / 4.0;
					// 初期位置
		now = new int [9];
		s   = new int [9];
		c   = new int [9];
		for (i1 = 0; i1 < 9; i1++)
			now[i1] = 0;
		for (i1 = 1; i1 < 9; i1++)
			c[i1] = (int)(100 * rand.nextDouble());
		x[0] = w / 2;
		y[0] = h / 2;
		z[0] = 0;
					// 描画
		while (state > 0) {
			try {
				th.sleep(period);
			}
			catch (InterruptedException e) {}
			if (state == 1) {
						// アーキテクチャ
				if (dsp[1]) {
					x1   = a[1] * Math.cos(2 * Math.PI / p[1] * now[1]);
					y[1] = h / 2;
					z1   = b[1] * Math.sin(2 * Math.PI / p[1] * now[1]);
					x[1] = w / 2 + (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
					z[1] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
				}
						// 人工知能
				if (dsp[2]) {
					x1   = a[2] * Math.cos(-Math.PI + 2 * Math.PI / p[2] * now[2]);
					y[2] = h / 2;
					z1   = b[2] * Math.sin(-Math.PI + 2 * Math.PI / p[2] * now[2]);
					x[2] = w / 2 + (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
					z[2] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
				}
						// 生命
				if (dsp[3]) {
					x[3] = w / 2;
					y1   = a[3] * Math.cos(2 * Math.PI / p[3] * now[3]);
					z1   = b[3] * Math.sin(2 * Math.PI / p[3] * now[3]);
					y[3] = h / 2 + (int)(Math.cos(a45) * y1 - Math.sin(a45) * z1);
					z[3] = (int)(Math.sin(a45) * y1 + Math.cos(a45) * z1);
				}
						// 教育
				if (dsp[4]) {
					x[4] = w / 2;
					y1   = a[4] * Math.cos(-Math.PI + 2 * Math.PI / p[4] * now[4]);
					z1   = b[4] * Math.sin(-Math.PI + 2 * Math.PI / p[4] * now[4]);
					y[4] = h / 2 + (int)(Math.cos(-a45) * y1 - Math.sin(-a45) * z1);
					z[4] = (int)(Math.sin(-a45) * y1 + Math.cos(-a45) * z1);
				}
						// メディア
				if (dsp[5]) {
					x1   = a[5] * Math.cos(2 * Math.PI / p[5] * now[5]);
					y1   = 0;
					z1   = b[5] * Math.sin(2 * Math.PI / p[5] * now[5]);
					x2   = (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
					z[5] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
					x[5] = w / 2 + (int)(Math.cos(a45) * x2 - Math.sin(a45) * y1);
					y[5] = h / 2 + (int)(Math.sin(a45) * x2 + Math.cos(a45) * y1);
				}
						// 社会
				if (dsp[6]) {
					x1   = a[6] * Math.cos(2 * Math.PI / p[6] * now[6]);
					y1   = 0;
					z1   = b[6] * Math.sin(2 * Math.PI / p[6] * now[6]);
					x2   = (int)(Math.cos(a45) * x1 - Math.sin(a45) * z1);
					z[6] = (int)(Math.sin(a45) * x1 + Math.cos(a45) * z1);
					x[6] = w / 2 + (int)(Math.cos(-a45) * x2 - Math.sin(-a45) * y1);
					y[6] = h / 2 + (int)(Math.sin(-a45) * x2 + Math.cos(-a45) * y1);
				}
						// ビジネス
				if (dsp[7]) {
					x1   = a[7] * Math.cos(Math.PI + 2 * Math.PI / p[7] * now[7]);
					y1   = 0;
					z1   = b[7] * Math.sin(Math.PI + 2 * Math.PI / p[7] * now[7]);
					x2   = (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
					z[7] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
					x[7] = w / 2 + (int)(Math.cos(a45) * x2 - Math.sin(a45) * y1);
					y[7] = h / 2 + (int)(Math.sin(a45) * x2 + Math.cos(a45) * y1);
				}
						// コミュニケーション
				if (dsp[8]) {
					x1   = a[8] * Math.cos(Math.PI + 2 * Math.PI / p[8] * now[8]);
					y1   = 0;
					z1   = b[8] * Math.sin(Math.PI + 2 * Math.PI / p[8] * now[8]);
					x2   = (int)(Math.cos(-a45) * x1 - Math.sin(-a45) * z1);
					z[8] = (int)(Math.sin(-a45) * x1 + Math.cos(-a45) * z1);
					x[8] = w / 2 + (int)(Math.cos(-a45) * x2 - Math.sin(-a45) * y1);
					y[8] = h / 2 + (int)(Math.sin(-a45) * x2 + Math.cos(-a45) * y1);
				}
						// 表示
				nd = 0;
				for (i1 = 0; i1 < 9; i1++)
					s[i1] = 0;
				for (i1 = 0; i1 < 9; i1++) {
					m = -1;
					for (i2 = 0; i2 < 9; i2++) {
						if (s[i2] == 0 && (m < 0 || z[m] > z[i2]))
							m = i2;
					}
					s[m] = 1;
					if (dsp[m]) {
						if (m == 0) {
							draw[nd][0] = 0;
							draw[nd][1] = 0;
							draw[nd][2] = x[0] - i_w[0] / 2;
							draw[nd][3] = y[0] - i_h[0] / 2;
						}
						else {
							r  = (w / 2.0 + z[m]) / w;
							sw = (int)(r * i_w[m]);
							sh = (int)(r * i_h[m]);
							draw[nd][0] = 1;
							draw[nd][1] = m;
							draw[nd][2] = x[m] - sw / 2;
							draw[nd][3] = y[m] - sh / 2;
							draw[nd][4] = sw;
							draw[nd][5] = sh;
							if (c[m] > 0)
								c[m]--;
							else {
								now[m]++;
								if (now[m] >= p[m])
									now[m] = 0;
							}
						}
						nd++;
					}
				}
				repaint();
			}
		}
	}

	/************************/
	/* マウスイベントの処理 */
	/************************/
    class Mouse extends MouseAdapter {
        public void mouseClicked(MouseEvent e)
        {
			int i1, m, r, x1, x2, y1, y2;
			Explanation exp;

            x1 = e.getX();
            y1 = e.getY();

			if (x1 > d_x1 && x1 < d_x2 && y1 > d_y1 && y1 < d_y2) {
				period += 10;
			}
			else if (x1 > u_x1 && x1 < u_x2 && y1 > u_y1 && y1 < u_y2) {
				if (period > 10)
					period -= 10;
			}
			else if (x1 > t_x1 && x1 < t_x2 && y1 > t_y1 && y1 < t_y2) {
				URL url = null;
				try {
					url = new URL("http://ex-cs.sist.ac.jp/~suganuma/dep/index.html");
				}
				catch (MalformedURLException e1) {}
				sp.getAppletContext().showDocument(url);
			}
			else {
				m = -1;
				for (i1 = 0; i1 < 9; i1++) {
					if (dsp[i1]) {
						x2 = x1 - x[i1];
						y2 = y1 - y[i1];
						r  = (int)Math.sqrt(x2 * x2 + y2 * y2);
						if (r < 50 && (m < 0 || z[m] < z[i1]))
							m = i1;
					}
				}
				if (m >= 0) {
					dsp[m] = false;
					exp    = new Explanation(tt[m], m, i_wt[m], i_ht[m], bun_t[m], x1-w/2, y1-h/2, dm);
				}
				else {
					if (state == 1)
						state = 2;
					else
						state = 1;
				}
			}
        }
    }

	/********/
	/* 描画 */
	/********/
	public void paintComponent (Graphics g)
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
					// 背景画像
		g.drawImage(back, 0, 0, w, h, this);
		g.setColor(Color.cyan);
		Font f = new Font("TimesRoman", Font.BOLD, 25);
		g.setFont(f);
		FontMetrics fm = g.getFontMetrics(f);
		int c_w = fm.stringWidth("情報学部");
		int c_h = fm.getHeight();
		Insets insets = getInsets();
		t_x1 = w / 2 - c_w / 2;
		t_x2 = t_x1 + c_w;
		t_y1 = insets.top;
		t_y2 = t_y1 + c_h;
		g.drawString("情報学部", t_x1, t_y2);
		g.setColor(Color.yellow);
		f = new Font("TimesRoman", Font.BOLD, 15);
		g.setFont(f);
		fm   = g.getFontMetrics(f);
		c_w  = fm.stringWidth("<< 減速");
		c_h  = fm.getHeight();
		d_x1 = t_x1 - c_w - 10;
		d_x2 = d_x1 + c_w;
		d_y1 = t_y2 - c_h;
		d_y2 = t_y2;
		g.drawString("<< 減速", d_x1, d_y2);
		u_x1 = t_x2 + 10;
		u_x2 = u_x1 + c_w;
		u_y1 = t_y2 - c_h;
		u_y2 = t_y2;
		g.drawString("加速 >>", u_x1, u_y2);
					// 対象物
		for (int i1 = 0; i1 < nd; i1++) {
			if (draw[i1][0] == 0)
				g.drawImage(bun[draw[i1][1]], draw[i1][2], draw[i1][3], this);
			else
				g.drawImage(bun[draw[i1][1]], draw[i1][2], draw[i1][3], draw[i1][4], draw[i1][5], this);
		}
	}
}
		
Java のソース( Explanation.java )
/****************************/
/* 各分野の説明             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

class Explanation extends JFrame implements Runnable
{

	int width, height;   // 画像の大きさ
	int r_width, r_height;   // Windowの大きさ
	int cx, cy, s_x = 0, s_y = 0;
	int m, n_sp = 1, sp = 10;
	Image img;
	Thread th;
	boolean state;
	Insets insets;
	Dimension scr;
	Draw_m dm;
	Draw_e de;

	/****************************************/
	/* コンストラクタ                       */
	/*      title : タイトル                */
	/*      m_i : 画像番号                  */
	/*      width_i,height_i : 画像の大きさ */
	/*      img_i : 画像                    */
	/*      cx_i, cy_i : クリックした位置   */
	/****************************************/
	Explanation(String title, int m_i, int width_i, int height_i, Image img_i, int cx_i, int cy_i, Draw_m dm_i)
	{
					// Frameクラスのコンストラクタの呼び出し
		super(title);
					// データの保存
		m      = m_i;
		width  = width_i;
		height = height_i;
		img    = img_i;
		cx     = cx_i;
		cy     = cy_i;
		dm     = dm_i;
		dm.state = 2;
					// Windowサイズの初期設定
		int w, h;
		setSize(0, 0);
					// ウィンドウを表示
		setVisible(true);
		w            = getSize().width;
		h            = getSize().height;
		Toolkit tool = getToolkit();
		scr          = tool.getScreenSize();
		if (cx < -100) {
			if (cy < -100) {
				s_x = 0;
				s_y = 100;
			}
			else if (cy > 100) {
				s_x = 0;
				s_y = scr.height - 100;
			}
			else {
				s_x = 0;
				s_y = scr.height / 2;
			}
		}
		else if (cx > 100) {
			if (cy < -100) {
				s_x = scr.width;
				s_y = 100;
			}
			else if (cy > 100) {
				s_x = scr.width;
				s_y = scr.height - 100;
			}
			else {
				s_x = scr.width;
				s_y = scr.height / 2;
			}
		}
		else {
			if (cy < -100) {
				s_x = scr.width / 2;
				s_y = 100;
			}
			else if (cy > 100) {
				s_x = scr.width / 2;
				s_y = scr.height - 100;
			}
			else {
				s_x = scr.width / 2;
				s_y = scr.height / 2;
			}
		}
		setLocation(s_x - w / 2, s_y - h / 2);
		insets = getInsets();
					// 描画パネル
		Container cp = getContentPane();
		de = new Draw_e(this);
		cp.add(de);
					// イベントアダプタ
		addWindowListener(new WinEnd());
		addComponentListener(new ComponentResize());
					// スレッドの定義と開始
		state = true;
		th    = new Thread(this);
		th.start();
	}

	/******************/
	/* スレッドの実行 */
	/******************/
	public void run()
	{
		int w, h, x, y;

		while (state) {
			try {
				th.sleep(50);
			}
			catch (InterruptedException e) {}
			w = n_sp * width / sp + insets.left + insets.right;
			h = n_sp * height / sp + insets.top + insets.bottom;
			setSize(w, h);
			w = getSize().width;
			h = getSize().height;
			if (cx > scr.width/2)
				x = s_x - n_sp * (scr.width / 2 - s_x) / sp - w / 2;
			else
				x = s_x + n_sp * (scr.width / 2 - s_x) / sp - w / 2;
			if (cy > scr.height/2)
				y = s_y - n_sp * (scr.height / 2 - s_y) / sp - h / 2;
			else
				y = s_y + n_sp * (scr.height / 2 - s_y) / sp - h / 2;
			setLocation(x, y);
			n_sp++;
			if (n_sp > sp)
				state = false;
		}
	}

	/**********************/
	/* Windowのサイズ変化 */
	/**********************/
	class ComponentResize extends ComponentAdapter
	{
		public void componentResized(ComponentEvent e)
		{
			de.repaint();
		}
	}

	/************/
	/* 終了処理 */
	/************/
	class WinEnd extends WindowAdapter
	{
		public void windowClosing(WindowEvent e) {
			dm.state  = 1;
			dm.dsp[m] = true;
			setVisible(false);
		}
	}
}

class Draw_e extends JPanel {

	Explanation ex;

	/******************/
	/* コンストラクタ */
	/******************/
	Draw_e(Explanation ex_i)
	{
		ex = ex_i;
		setBackground(Color.white);
	}

	/********/
	/* 描画 */
	/********/
	public void paintComponent (Graphics g)
	{
		super.paintComponent(g);   // 親クラスの描画(必ず必要)
		int w, h;

		Dimension d = ex.getSize();
		h = d.height - (ex.insets.top + ex.insets.bottom);
		w = (int)(ex.width * (double)h / ex.height);
		g.drawImage(ex.img, ex.insets.left, ex.insets.top/3, w, h, this);
	}
}
		

20.5 様々な例題

20.5.1 パズル & ゲーム

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

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

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

   

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

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

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


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

  (「AWT の利用」,「Swing の利用)は,同じプログラム例をアプレット用に書き換えた例です.クリックすれば,Web 上で実行可能です.

  例え 8 パズルであっても,問題によっては解くためにかなりの時間がかかります.そこで,コンピュータによって解くことを考えてみます.8 パズルの解法(「AWT の利用」,「Swing の利用」)を開き,表示されているアプレットに適当な値を設定した後「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を与える事によって得られる得点

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

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

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

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

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

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

  (「AWT の利用」,「Swing の利用」)は,同じプログラム例をアプレット用に書き換えた例です.クリックすれば,Web 上で実行できます.

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

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

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

  1. ターゲットと砲台の表示(「AWT の利用」,「Swing の利用」):  どのような位置に表示しても構いませんが,後から,これらの位置を変更する必要がありますので,表示位置は変数((xt, yt) と (x, y))で与えておいた方がよいと思います.また,このプログラムでは,変数 game を false に設定することによって,ゲームオーバー画面も確認することができます.

    AWT を利用した場合

    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import java.applet.*;
    06	
    07	public class Game1 extends Applet
    08	{
    09		boolean game = true;   // ゲーム実行中か否か
    10		int xt = 20, yt = 50;   // ターゲットの位置
    11		int x, y;   // 砲台の位置
    12		int r = 25;   // ターゲットの半径
    13		Dimension d;
    14	
    15		public void init() {
    16						// 背景色
    17			setBackground(new Color(238, 255, 238));
    18						// 初期設定
    19			d  = getSize();
    20			x  = d.width / 2 - 10;
    21			y  = d.height - 20;
    22		}
    23	
    24		public Insets getInsets()
    25		{
    26			return new Insets(0, 0, 0, 0);
    27		}
    28	
    29		public void paint (Graphics g)
    30		{
    31							// ゲーム中
    32			if (game) {
    33									// 砲台の表示
    34				g.fill3DRect(x, y, 20, 20, true);
    35									// ターゲットの表示
    36				g.setColor(Color.green);
    37				g.fillOval(xt, yt, 2*r, 2*r);
    38			}
    39							// ゲームオーバ
    40			else {
    41				Font f = new Font("TimesRoman", Font.BOLD, 50);
    42				g.setFont(f);
    43				g.drawString("Game Over", d.width/2-130, d.height/2);
    44			}
    45		}
    46	}
    			
    34 行目

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

    36 行目~ 37 行目

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

    41 行目~ 43 行目

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

    Swing を利用した場合

    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import javax.swing.*;
    06	
    07	public class Game1 extends JApplet
    08	{
    09		public void init() {
    10						// 背景色
    11			setBackground(new Color(238, 255, 238));
    12						// 初期設定
    13			Container cp = getContentPane();
    14			cp.setBackground(Color.white);
    15			Draw dp = new Draw(this);
    16			dp.setBackground(new Color(238, 255, 238));
    17			cp.add(dp, BorderLayout.CENTER);
    18		}
    19	}
    20	
    21	class Draw extends JPanel
    22	{
    23		boolean game = true;   // ゲーム実行中か否か
    24		int xt = 20, yt = 50;   // ターゲットの位置
    25		int x, y;   // 砲台の位置
    26		int r = 25;   // ターゲットの半径
    27		Dimension d;
    28		Insets in;
    29		Game1 gm;
    30	
    31		Draw(Game1 gm1) {
    32			gm = gm1;
    33						// 初期設定
    34			in = gm.getInsets();
    35			d  = gm.getSize();
    36			d.width  = d.width - in.left - in.right;
    37			d.height = d.height - in.top - in.bottom;
    38			x  = d.width / 2 - 10;
    39			y  = d.height - 20;
    40		}
    41	
    42		public void paintComponent (Graphics g)
    43		{
    44			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    45	
    46							// ゲーム中
    47			if (game) {
    48									// 砲台の表示
    49				g.fill3DRect(x, y, 20, 20, true);
    50									// ターゲットの表示
    51				g.setColor(Color.green);
    52				g.fillOval(xt, yt, 2*r, 2*r);
    53			}
    54							// ゲームオーバ
    55			else {
    56				Font f = new Font("TimesRoman", Font.BOLD, 50);
    57				g.setFont(f);
    58				g.drawString("Game Over", d.width/2-130, d.height/2);
    59			}
    60		}
    61	}
    			
    49 行目

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

    51 行目~ 52 行目

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

    56 行目~ 58 行目

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

  2. ターゲットの移動(「AWT の利用」,「Swing の利用」):  スレッドを利用して,33 ms 毎に,ターゲットを下方向へ移動してみます.Runnable インターフェースを継承し,run メソッドの中でターゲットの y 座標を変数 sp に設定されている値だけ増加していくことになります.前のステップで作成したプログラムを少し変更するだけですが,スレッドを利用したアニーションの機能を確認することができます.

    AWT を利用した場合

    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import java.applet.*;
    06	
    07	public class Game1 extends Applet implements Runnable
    08	{
    09		boolean state = true;   // スレッドが動作中か否か
    10		boolean game = true;   // ゲーム実行中か否か
    11		int xt = 20, yt = 50;   // ターゲットの位置
    12		int x, y;   // 砲台の位置
    13		int r = 25;   // ターゲットの半径
    14		int sp = 5;   // ターゲットの速さ
    15		Thread th;
    16		Dimension d;
    17	
    18		public void init() {
    19						// 背景色
    20			setBackground(new Color(238, 255, 238));
    21						// 初期設定
    22			d  = getSize();
    23			x  = d.width / 2 - 10;
    24			y  = d.height - 20;
    25						// スレッドの生成
    26			th = new Thread(this);
    27			th.start();
    28		}
    29	
    30		public Insets getInsets()
    31		{
    32			return new Insets(0, 0, 0, 0);
    33		}
    34	
    35		public void stop()
    36		{
    37			state = false;
    38		}
    39	
    40		public void run()
    41		{
    42			while (state) {
    43				try {
    44					th.sleep(33);
    45				}
    46				catch (InterruptedException e) {}
    47							// ターゲットの移動
    48				yt += sp;
    49							// 再描画
    50				repaint();
    51			}
    52		}
    53	
    54		public void paint (Graphics g)
    55		{
    56							// ゲーム中
    57			if (game) {
    58									// 砲台の表示
    59				g.fill3DRect(x, y, 20, 20, true);
    60									// ターゲットの表示
    61				g.setColor(Color.green);
    62				g.fillOval(xt, yt, 2*r, 2*r);
    63			}
    64							// ゲームオーバ
    65			else {
    66				Font f = new Font("TimesRoman", Font.BOLD, 50);
    67				g.setFont(f);
    68				g.drawString("Game Over", d.width/2-130, d.height/2);
    69			}
    70		}
    71	}
    			
    07 行目

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

    26 行目~ 27 行目

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

    40 行目~ 52 行目

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

    Swing を利用した場合

    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import javax.swing.*;
    06	
    07	public class Game1 extends JApplet
    08	{
    09		Draw dp;
    10		public void init() {
    11						// 背景色
    12			setBackground(new Color(238, 255, 238));
    13						// 初期設定
    14			Container cp = getContentPane();
    15			cp.setBackground(Color.white);
    16			dp = new Draw(this);
    17			dp.setBackground(new Color(238, 255, 238));
    18			cp.add(dp, BorderLayout.CENTER);
    19		}
    20	
    21		public void stop()
    22		{
    23			dp.state = false;
    24		}
    25	}
    26	
    27	class Draw extends JPanel implements Runnable
    28	{
    29		boolean state = true;   // スレッドが動作中か否か
    30		boolean game = true;   // ゲーム実行中か否か
    31		int xt = 20, yt = 50;   // ターゲットの位置
    32		int x, y;   // 砲台の位置
    33		int r = 25;   // ターゲットの半径
    34		int sp = 5;   // ターゲットの速さ
    35		Thread th;
    36		Dimension d;
    37		Insets in;
    38		Game1 gm;
    39	
    40		Draw(Game1 gm1) {
    41			gm = gm1;
    42						// 初期設定
    43			in = gm.getInsets();
    44			d  = gm.getSize();
    45			d.width  = d.width - in.left - in.right;
    46			d.height = d.height - in.top - in.bottom;
    47			x  = d.width / 2 - 10;
    48			y  = d.height - 20;
    49						// スレッドの生成
    50			th = new Thread(this);
    51			th.start();
    52		}
    53	
    54		public void run()
    55		{
    56			while (state) {
    57				try {
    58					th.sleep(33);
    59				}
    60				catch (InterruptedException e) {}
    61							// ターゲットの移動
    62				yt += sp;
    63							// 再描画
    64				repaint();
    65			}
    66		}
    67	
    68		public void paintComponent (Graphics g)
    69		{
    70			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    71	
    72							// ゲーム中
    73			if (game) {
    74									// 砲台の表示
    75				g.fill3DRect(x, y, 20, 20, true);
    76									// ターゲットの表示
    77				g.setColor(Color.green);
    78				g.fillOval(xt, yt, 2*r, 2*r);
    79			}
    80							// ゲームオーバ
    81			else {
    82				Font f = new Font("TimesRoman", Font.BOLD, 50);
    83				g.setFont(f);
    84				g.drawString("Game Over", d.width/2-130, d.height/2);
    85			}
    86		}
    87	}
    			
    27 行目

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

    50 行目~ 51 行目

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

    54 行目~ 66 行目

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

  3. ターゲットの生成・移動・消滅(「AWT の利用」,「Swing の利用」):  ターゲットは,

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

    という 3 つの状態のいずれかになります.その状態を記憶しているのが変数 target です.ここでは,ターゲットを画面上部の任意の場所に生成し,画面の外に出たら消滅するように変更します.消滅すると,再び,新しいターゲットが生成されます.また,移動方向は,π/4 ~ 3π/4 の間の値からランダムに選択します.なお,位置や方向をランダムにするために,Random クラスを使用しています.

    AWT を利用した場合

    01	/************************/
    02	/* シューティングゲーム */
    03	/************************/
    04	import java.awt.*;
    05	import java.applet.*;
    06	import java.util.*;
    07	
    08	public class Game1 extends Applet implements Runnable
    09	{
    10		boolean state = true;   // スレッドが動作中か否か
    11		boolean game = true;   // ゲーム実行中か否か
    12		int xt = 20, yt = 50;   // ターゲットの位置
    13		int x, y;   // 砲台の位置
    14		int r = 25;   // ターゲットの半径
    15		int sp = 5;   // ターゲットの速さ
    16		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    17		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    18		Thread th;
    19		Dimension d;
    20		Random rn;
    21	
    22		public void init() {
    23						// 背景色
    24			setBackground(new Color(238, 255, 238));
    25						// 初期設定
    26			d  = getSize();
    27			x  = d.width / 2 - 10;
    28			y  = d.height - 20;
    29			rn = new Random();
    30						// スレッドの生成
    31			th = new Thread(this);
    32			th.start();
    33		}
    34	
    35		public Insets getInsets()
    36		{
    37			return new Insets(0, 0, 0, 0);
    38		}
    39	
    40		public void stop()
    41		{
    42			state = false;
    43		}
    44	
    45		public void run()
    46		{
    47			double ang;
    48	
    49			while (state) {
    50				try {
    51					th.sleep(33);
    52				}
    53				catch (InterruptedException e) {}
    54							// ターゲットの移動と消滅
    55				if (target == 1) {
    56					xt += vx;
    57					yt += vy;
    58					if (xt < -2*r || xt > d.width || yt > d.height)
    59						target = 0;
    60				}
    61							// ターゲットの生成
    62				else if (target == 0){
    63					target = 1;
    64					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    65					yt     = -r;
    66					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    67					vx     = (int)(sp * Math.cos(ang));
    68					vy     = (int)(sp * Math.sin(ang));
    69				}
    70							// 再描画
    71				repaint();
    72			}
    73		}
    74	
    75		public void paint (Graphics g)
    76		{
    77							// ゲーム中
    78			if (game) {
    79									// 砲台の表示
    80				g.fill3DRect(x, y, 20, 20, true);
    81									// ターゲットの表示
    82				g.setColor(Color.green);
    83				g.fillOval(xt, yt, 2*r, 2*r);
    84			}
    85							// ゲームオーバ
    86			else {
    87				Font f = new Font("TimesRoman", Font.BOLD, 50);
    88				g.setFont(f);
    89				g.drawString("Game Over", d.width/2-130, d.height/2);
    90			}
    91		}
    92	}
    			
    56 行目~ 59 行目

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

    62 行目~ 69 行目

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

    Swing を利用した場合

    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import javax.swing.*;
    006	import java.util.*;
    007	
    008	public class Game1 extends JApplet
    009	{
    010		Draw dp;
    011		public void init() {
    012						// 背景色
    013			setBackground(new Color(238, 255, 238));
    014						// 初期設定
    015			Container cp = getContentPane();
    016			cp.setBackground(Color.white);
    017			dp = new Draw(this);
    018			dp.setBackground(new Color(238, 255, 238));
    019			cp.add(dp, BorderLayout.CENTER);
    020		}
    021	
    022		public void stop()
    023		{
    024			dp.state = false;
    025		}
    026	}
    027	
    028	class Draw extends JPanel implements Runnable
    029	{
    030		boolean state = true;   // スレッドが動作中か否か
    031		boolean game = true;   // ゲーム実行中か否か
    032		int xt = 20, yt = 50;   // ターゲットの位置
    033		int x, y;   // 砲台の位置
    034		int r = 25;   // ターゲットの半径
    035		int sp = 5;   // ターゲットの速さ
    036		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    037		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    038		Thread th;
    039		Dimension d;
    040		Insets in;
    041		Random rn;
    042		Game1 gm;
    043	
    044		Draw(Game1 gm1) {
    045			gm = gm1;
    046						// 初期設定
    047			in = gm.getInsets();
    048			d  = gm.getSize();
    049			d.width  = d.width - in.left - in.right;
    050			d.height = d.height - in.top - in.bottom;
    051			x  = d.width / 2 - 10;
    052			y  = d.height - 20;
    053			rn = new Random();
    054						// スレッドの生成
    055			th = new Thread(this);
    056			th.start();
    057		}
    058	
    059		public void run()
    060		{
    061			double ang;
    062	
    063			while (state) {
    064				try {
    065					th.sleep(33);
    066				}
    067				catch (InterruptedException e) {}
    068							// ターゲットの移動と消滅
    069				if (target == 1) {
    070					xt += vx;
    071					yt += vy;
    072					if (xt < -2*r || xt > d.width || yt > d.height)
    073						target = 0;
    074				}
    075							// ターゲットの生成
    076				else if (target == 0){
    077					target = 1;
    078					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    079					yt     = -r;
    080					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    081					vx     = (int)(sp * Math.cos(ang));
    082					vy     = (int)(sp * Math.sin(ang));
    083				}
    084							// 再描画
    085				repaint();
    086			}
    087		}
    088	
    089		public void paintComponent (Graphics g)
    090		{
    091			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    092	
    093							// ゲーム中
    094			if (game) {
    095									// 砲台の表示
    096				g.fill3DRect(x, y, 20, 20, true);
    097									// ターゲットの表示
    098				g.setColor(Color.green);
    099				g.fillOval(xt, yt, 2*r, 2*r);
    100			}
    101							// ゲームオーバ
    102			else {
    103				Font f = new Font("TimesRoman", Font.BOLD, 50);
    104				g.setFont(f);
    105				g.drawString("Game Over", d.width/2-130, d.height/2);
    106			}
    107		}
    108	}
    			
    069 行目~ 074 行目

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

    076 行目~ 083 行目

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

  4. ゲームオーバー(「AWT の利用」,「Swing の利用」):  このゲームでは,ターゲットと砲台が衝突するとゲームオーバーとなります.ここでは,その判定を追加します.まだ,砲台を移動できませんので,砲台に衝突するようなターゲットが現れるまでしばらく待ち,機能を確認します.

    AWT を利用した場合

    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.applet.*;
    006	import java.util.*;
    007	
    008	public class Game1 extends Applet implements Runnable
    009	{
    010		boolean state = true;   // スレッドが動作中か否か
    011		boolean game = true;   // ゲーム実行中か否か
    012		int xt = 20, yt = 50;   // ターゲットの位置
    013		int x, y;   // 砲台の位置
    014		int r = 25;   // ターゲットの半径
    015		int sp = 5;   // ターゲットの速さ
    016		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    017		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    018		Thread th;
    019		Dimension d;
    020		Random rn;
    021	
    022		public void init() {
    023						// 背景色
    024			setBackground(new Color(238, 255, 238));
    025						// 初期設定
    026			d  = getSize();
    027			x  = d.width / 2 - 10;
    028			y  = d.height - 20;
    029			rn = new Random();
    030						// スレッドの生成
    031			th = new Thread(this);
    032			th.start();
    033		}
    034	
    035		public Insets getInsets()
    036		{
    037			return new Insets(0, 0, 0, 0);
    038		}
    039	
    040		public void stop()
    041		{
    042			state = false;
    043		}
    044	
    045		public void run()
    046		{
    047			double ang, x1, y1;
    048	
    049			while (state) {
    050				try {
    051					th.sleep(33);
    052				}
    053				catch (InterruptedException e) {}
    054							// ターゲットの移動と消滅
    055				if (target == 1) {
    056									// ゲームオーバ
    057					x1 = xt + r - (x + 10);
    058					y1 = yt + r - (y + 10);
    059					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    060						game = false;
    061									// ゲームオーバでない
    062					else {
    063						xt += vx;
    064						yt += vy;
    065						if (xt < -2*r || xt > d.width || yt > d.height)
    066							target = 0;
    067					}
    068				}
    069							// ターゲットの生成
    070				else if (target == 0){
    071					target = 1;
    072					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    073					yt     = -r;
    074					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    075					vx     = (int)(sp * Math.cos(ang));
    076					vy     = (int)(sp * Math.sin(ang));
    077				}
    078							// 再描画
    079				repaint();
    080			}
    081		}
    082	
    083		public void paint (Graphics g)
    084		{
    085							// ゲーム中
    086			if (game) {
    087									// 砲台の表示
    088				g.fill3DRect(x, y, 20, 20, true);
    089									// ターゲットの表示
    090				g.setColor(Color.green);
    091				g.fillOval(xt, yt, 2*r, 2*r);
    092			}
    093							// ゲームオーバ
    094			else {
    095				Font f = new Font("TimesRoman", Font.BOLD, 50);
    096				g.setFont(f);
    097				g.drawString("Game Over", d.width/2-130, d.height/2);
    098				state = false;
    099			}
    100		}
    101	}
    			
    057 行目~ 060 行目

      ターゲットが砲台に衝突したか否かの判定を行い,衝突した場合は,ゲームオーバーにしています.

    098 行目

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

    Swing を利用した場合

    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import javax.swing.*;
    006	import java.util.*;
    007	
    008	public class Game1 extends JApplet
    009	{
    010		Draw dp;
    011		public void init() {
    012						// 背景色
    013			setBackground(new Color(238, 255, 238));
    014						// 初期設定
    015			Container cp = getContentPane();
    016			cp.setBackground(Color.white);
    017			dp = new Draw(this);
    018			dp.setBackground(new Color(238, 255, 238));
    019			cp.add(dp, BorderLayout.CENTER);
    020		}
    021	
    022		public void stop()
    023		{
    024			dp.state = false;
    025		}
    026	}
    027	
    028	class Draw extends JPanel implements Runnable
    029	{
    030		boolean state = true, game = true;
    031		int xt, yt, vx, vy, x, y, r = 25, sp = 5, target = 0;
    032		Thread th;
    033		Dimension d;
    034		Insets in;
    035		Random rn;
    036		Game1 gm;
    037	
    038		Draw(Game1 gm1) {
    039			gm = gm1;
    040						// 初期設定
    041			in = gm.getInsets();
    042			d  = gm.getSize();
    043			d.width  = d.width - in.left - in.right;
    044			d.height = d.height - in.top - in.bottom;
    045			x  = d.width / 2 - 10;
    046			y  = d.height - 20;
    047			rn = new Random();
    048						// スレッドの生成
    049			th = new Thread(this);
    050			th.start();
    051		}
    052	
    053		public void run()
    054		{
    055			double ang, x1, y1;
    056	
    057			while (state) {
    058				try {
    059					th.sleep(33);
    060				}
    061				catch (InterruptedException e) {}
    062							// ターゲットの移動と消滅
    063				if (target == 1) {
    064									// ゲームオーバ
    065					x1 = xt + r - (x + 10);
    066					y1 = yt + r - (y + 10);
    067					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    068						game = false;
    069									// ゲームオーバでない
    070					else {
    071						xt += vx;
    072						yt += vy;
    073						if (xt < -2*r || xt > d.width || yt > d.height)
    074							target = 0;
    075					}
    076				}
    077							// ターゲットの生成
    078				else if (target == 0){
    079					target = 1;
    080					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    081					yt     = -r;
    082					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    083					vx     = (int)(sp * Math.cos(ang));
    084					vy     = (int)(sp * Math.sin(ang));
    085				}
    086							// 再描画
    087				repaint();
    088			}
    089		}
    090	
    091		public void paintComponent (Graphics g)
    092		{
    093			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    094	
    095							// ゲーム中
    096			if (game) {
    097									// 砲台の表示
    098				g.fill3DRect(x, y, 20, 20, true);
    099									// ターゲットの表示
    100				g.setColor(Color.green);
    101				g.fillOval(xt, yt, 50, 50);
    102			}
    103							// ゲームオーバ
    104			else {
    105				Font f = new Font("TimesRoman", Font.BOLD, 50);
    106				g.setFont(f);
    107				g.drawString("Game Over", d.width/2-130, d.height/2);
    108				state = false;
    109			}
    110		}
    111	}
    			
    065 行目~ 068 行目

      ターゲットが砲台に衝突したか否かの判定を行い,衝突した場合は,ゲームオーバーにしています.

    108 行目

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

  5. キーイベント(「AWT の利用」,「Swing の利用」):  最後に,キーイベントに対する処理を追加します.左矢印または右矢印キーを押すと,砲台が左または右に 20 ピクセルだけ移動します.また,Shift キーを押すと,レーザ光が発射されます(変数 fire を true にする).

    AWT を利用した場合

    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import java.applet.*;
    007	import java.util.*;
    008	
    009	public class Game1 extends Applet implements Runnable
    010	{
    011		boolean fire = false;   // レーザ砲が発射されているか否か
    012		boolean state = true;   // スレッドが動作中か否か
    013		boolean game = true;   // ゲーム実行中か否か
    014		int xt = 20, yt = 50;   // ターゲットの位置
    015		int x, y;   // 砲台の位置
    016		int r = 25;   // ターゲットの半径
    017		int sp = 5;   // ターゲットの速さ
    018		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    019		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    020		Dimension d;
    021		Thread th;
    022		Random rn;
    023	
    024		public void init() {
    025						// 背景色
    026			setBackground(new Color(238, 255, 238));
    027						// 初期設定
    028			d  = getSize();
    029			x  = d.width / 2 - 10;
    030			y  = d.height - 20;
    031			rn = new Random();
    032						// キーリスナの付加
    033			addKeyListener(new Key_e());
    034						// スレッドの生成
    035			th = new Thread(this);
    036			th.start();
    037		}
    038	
    039		public Insets getInsets()
    040		{
    041			return new Insets(0, 0, 0, 0);
    042		}
    043	
    044		public void stop()
    045		{
    046			state = false;
    047		}
    048	
    049		public void run()
    050		{
    051			double ang, x1, y1;
    052	
    053			while (state) {
    054				try {
    055					th.sleep(33);
    056				}
    057				catch (InterruptedException e) {}
    058							// ターゲットの移動と消滅
    059				if (target == 1) {
    060									// ゲームオーバ
    061					x1 = xt + r - (x + 10);
    062					y1 = yt + r - (y + 10);
    063					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    064						game = false;
    065									// ゲームオーバでない
    066											// 命中
    067					else if (fire && xt <= x+10 && xt > x+10-2*r)
    068						target = 2;
    069											// 命中しない
    070					else {
    071						xt += vx;
    072						yt += vy;
    073						if (xt < -2*r || xt > d.width || yt > d.height)
    074							target = 0;
    075					}
    076				}
    077							// ターゲットの生成
    078				else if (target == 0){
    079					target = 1;
    080					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    081					yt     = -r;
    082					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    083					vx     = (int)(sp * Math.cos(ang));
    084					vy     = (int)(sp * Math.sin(ang));
    085				}
    086							// 再描画
    087				repaint();
    088			}
    089		}
    090	
    091		public void paint (Graphics g)
    092		{
    093							// ゲーム中
    094			if (game) {
    095									// 砲台の表示
    096				g.fill3DRect(x, y, 20, 20, true);
    097									// ターゲットの表示
    098				if (target > 0) {
    099					if (target == 1)
    100						g.setColor(Color.green);
    101					else {
    102						g.setColor(Color.pink);
    103						target = 0;
    104					}
    105					g.fillOval(xt, yt, 2*r, 2*r);
    106				}
    107									// レーザ砲の発射
    108				if (fire){
    109					g.setColor(Color.red);
    110					g.drawLine(x+9, 0, x+9, d.height-10);
    111					g.drawLine(x+10, 0, x+10, d.height-10);
    112					g.drawLine(x+11, 0, x+11, d.height-10);
    113					fire = false;
    114				}
    115			}
    116							// ゲームオーバ
    117			else {
    118				Font f = new Font("TimesRoman", Font.BOLD, 50);
    119				g.setFont(f);
    120				g.drawString("Game Over", d.width/2-130, d.height/2);
    121				state = false;
    122			}
    123		}
    124	
    125		public boolean isFocusable() { return true; }
    126	
    127		class Key_e extends KeyAdapter {
    128			public void keyPressed(KeyEvent e)
    129			{
    130				if (e.getKeyCode() == 37)  // 左矢印
    131					x -= 20;
    132				else if (e.getKeyCode() == 39)   // 右矢印
    133					x += 20;
    134				if (e.isShiftDown())   // Shift キー
    135					fire = true;
    136			}
    137		}
    138	}
    			
    033 行目

      キーイベントを受け付けるために,KeyListener を追加しています.

    067 行目~ 068 行目

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

    098 行目~ 106 行目

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

    108 行目~ 114 行目

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

    128 行目~ 136 行目

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

    Swing を利用した場合

    001	/************************/
    002	/* シューティングゲーム */
    003	/************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	import java.util.*;
    008	
    009	public class Game1 extends JApplet
    010	{
    011		Draw dp;
    012		public void init() {
    013						// 背景色
    014			setBackground(new Color(238, 255, 238));
    015						// 初期設定
    016			Container cp = getContentPane();
    017			cp.setBackground(Color.white);
    018			dp = new Draw(this);
    019			dp.setBackground(new Color(238, 255, 238));
    020			cp.add(dp, BorderLayout.CENTER);
    021		}
    022	
    023		public void stop()
    024		{
    025			dp.state = false;
    026		}
    027	}
    028	
    029	class Draw extends JPanel implements Runnable
    030	{
    031		boolean fire = false;   // レーザ砲が発射されているか否か
    032		boolean state = true;   // スレッドが動作中か否か
    033		boolean game = true;   // ゲーム実行中か否か
    034		int xt = 20, yt = 50;   // ターゲットの位置
    035		int x, y;   // 砲台の位置
    036		int r = 25;   // ターゲットの半径
    037		int sp = 5;   // ターゲットの速さ
    038		int vx, vy;   // ターゲットの x 軸,及び,y 軸方向の速さ
    039		int target = 0;   // ターゲットの状態(0:存在しない,1:移動中,2:命中)
    040		Dimension d;
    041		Insets in;
    042		Thread th;
    043		Random rn;
    044		Game1 gm;
    045	
    046		Draw(Game1 gm1) {
    047			gm = gm1;
    048						// 初期設定
    049			in = gm.getInsets();
    050			d  = gm.getSize();
    051			d.width  = d.width - in.left - in.right;
    052			d.height = d.height - in.top - in.bottom;
    053			x  = d.width / 2 - 10;
    054			y  = d.height - 20;
    055			rn = new Random();
    056						// キーリスナの付加
    057			addKeyListener(new Key_e());
    058						// スレッドの生成
    059			th = new Thread(this);
    060			th.start();
    061		}
    062	
    063		public void run()
    064		{
    065			double ang, x1, y1;
    066	
    067			while (state) {
    068				try {
    069					th.sleep(33);
    070				}
    071				catch (InterruptedException e) {}
    072							// ターゲットの移動と消滅
    073				if (target == 1) {
    074									// ゲームオーバ
    075					x1 = xt + r - (x + 10);
    076					y1 = yt + r - (y + 10);
    077					if (Math.sqrt(x1*x1+y1*y1) < r+10)
    078						game = false;
    079									// ゲームオーバでない
    080											// 命中
    081					else if (fire && xt <= x+10 && xt > x+10-2*r)
    082						target = 2;
    083											// 命中しない
    084					else {
    085						xt += vx;
    086						yt += vy;
    087						if (xt < -2*r || xt > d.width || yt > d.height)
    088							target = 0;
    089					}
    090				}
    091							// ターゲットの生成
    092				else if (target == 0){
    093					target = 1;
    094					xt     = (int)((d.width - 2 * r) * rn.nextDouble());
    095					yt     = -r;
    096					ang    = 0.5 * Math.PI * rn.nextDouble() + 0.25 * Math.PI;
    097					vx     = (int)(sp * Math.cos(ang));
    098					vy     = (int)(sp * Math.sin(ang));
    099				}
    100							// 再描画
    101				repaint();
    102			}
    103		}
    104	
    105		public void paintComponent (Graphics g)
    106		{
    107			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    108	
    109							// ゲーム中
    110			if (game) {
    111									// 砲台の表示
    112				g.fill3DRect(x, y, 20, 20, true);
    113									// ターゲットの表示
    114				if (target > 0) {
    115					if (target == 1)
    116						g.setColor(Color.green);
    117					else {
    118						g.setColor(Color.pink);
    119						target = 0;
    120					}
    121					g.fillOval(xt, yt, 2*r, 2*r);
    122				}
    123									// レーザ砲の発射
    124				if (fire){
    125					g.setColor(Color.red);
    126					g.drawLine(x+9, 0, x+9, d.height-10);
    127					g.drawLine(x+10, 0, x+10, d.height-10);
    128					g.drawLine(x+11, 0, x+11, d.height-10);
    129					fire = false;
    130				}
    131			}
    132							// ゲームオーバ
    133			else {
    134				Font f = new Font("TimesRoman", Font.BOLD, 50);
    135				g.setFont(f);
    136				g.drawString("Game Over", d.width/2-130, d.height/2);
    137				state = false;
    138			}
    139		}
    140	
    141		public boolean isFocusable() { return true; }
    142	
    143		class Key_e extends KeyAdapter {
    144			public void keyPressed(KeyEvent e)
    145			{
    146				if (e.getKeyCode() == 37)  // 左矢印
    147					x -= 20;
    148				else if (e.getKeyCode() == 39)   // 右矢印
    149					x += 20;
    150				if (e.isShiftDown())   // Shift キー
    151					fire = true;
    152			}
    153		}
    154	}
    			
    057 行目

      キーイベントを受け付けるために,KeyListener を追加しています.

    081 行目~ 082 行目

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

    114 行目~ 122 行目

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

    124 行目~ 130 行目

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

    144 行目~ 152 行目

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

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

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

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

  1. ピースの表示(「AWT の利用」,「Swing の利用」):  このプログラムでは,画面を格子状に区切り,この格子を配列に対応させ,各要素の値によってピースの色を制御しています( 0 の場合は,ピースが存在しない).どのような位置に,どのような色で表示しても構いませんが,後からのことを考えると,位置や色に対する一般的な処理を行っておいた方がよいと思います.なお,変数 p_x 及び p_y はピースの位置を表すための変数です.また,このプログラムでは,変数 game を false に設定することによって,ゲームオーバー画面も確認することができます.

    AWT を利用した場合

    01	/********************************/
    02	/* ぷよぷよ(ゲーム全体の制御) */
    03	/********************************/
    04	import java.awt.*;
    05	import java.applet.*;
    06	
    07	public class Game2 extends Applet
    08	{
    09		Puyo py;
    10		boolean game = true;   // ゲーム中か否か
    11		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    12		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    13		int p[][] = new int [row][col];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    14	
    15		/************/
    16		/* 初期設定 */
    17		/************/
    18		public void init() {
    19						// 背景色
    20			setBackground(new Color(238, 255, 238));
    21						// ゲームパネルの配置
    22			p[p_y][p_x]   = 2;
    23			p[p_y][p_x+1] = 3;
    24			setLayout(null);
    25			py = new Puyo(this);
    26			add(py);
    27			py.setSize(30*col, 30*row);
    28			py.setLocation(10, 10);
    29			py.setBackground(Color.white);
    30		}
    31	
    32		/******************/
    33		/* 左右上下の余裕 */
    34		/******************/
    35		public Insets getInsets()
    36		{
    37			return new Insets(10, 10, 10, 10);
    38		}
    39	}
    40	
    41	/****************/
    42	/* ゲームパネル */
    43	/****************/
    44	class Puyo extends Panel
    45	{
    46		Game2 gm;
    47	
    48		/******************/
    49		/* コンストラクタ */
    50		/******************/
    51		Puyo (Game2 gm_t)
    52		{
    53			gm = gm_t;
    54		}
    55	
    56		/********/
    57		/* 描画 */
    58		/********/
    59		public void paint (Graphics g)
    60		{
    61			int i1, i2;
    62								// ゲーム中
    63			if (gm.game) {
    64				for (i1 = 0; i1 < gm.row; i1++) {
    65					for (i2 = 0; i2 < gm.col; i2++) {
    66						if (gm.p[i1][i2] > 0) {
    67							switch (gm.p[i1][i2]) {
    68								case 1:
    69									g.setColor(Color.red);
    70									break;
    71								case 2:
    72									g.setColor(Color.pink);
    73									break;
    74								case 3:
    75									g.setColor(Color.green);
    76									break;
    77								case 4:
    78									g.setColor(Color.blue);
    79									break;
    80							}
    81							g.fillRect(i2*30, i1*30, 30, 30);
    82						}
    83					}
    84				}
    85			}
    86								// ゲームオーバー
    87			else {
    88				Font f = new Font("TimesRoman", Font.BOLD, 40);
    89				g.setFont(f);
    90				g.drawString("Game", 20, 200);
    91				g.drawString("Over!", 25, 250);
    92			}
    93		}
    94	}
    			
    22 行目~ 23 行目

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

    24 行目

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

    25 行目~ 29 行目

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

    64 行目~ 84 行目

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

    88 行目~ 91 行目

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

    Swing を利用した場合

    01	/********************************/
    02	/* ぷよぷよ(ゲーム全体の制御) */
    03	/********************************/
    04	import java.awt.*;
    05	import java.awt.event.*;
    06	import javax.swing.*;
    07	import java.util.*;
    08	
    09	public class Game2 extends JApplet
    10	{
    11		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    12	
    13		/************/
    14		/* 初期設定 */
    15		/************/
    16		public void init() {
    17						// 背景色
    18			setBackground(new Color(238, 255, 238));
    19						// ゲームパネルの配置
    20			Container cp = getContentPane();
    21			cp.setBackground(new Color(238, 255, 238));
    22			cp.setLayout(null);
    23			Puyo py = new Puyo(this);
    24			cp.add(py);
    25			py.setSize(30*col, 30*row);
    26			py.setLocation(10, 10);
    27			py.setBackground(Color.white);
    28		}
    29	}
    30	
    31	/****************/
    32	/* ゲームパネル */
    33	/****************/
    34	class Puyo extends JPanel
    35	{
    36		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    37		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    38		boolean game = true;   // ゲーム中か否か
    39		Game2 gm;
    40	
    41		/******************/
    42		/* コンストラクタ */
    43		/******************/
    44		Puyo (Game2 gm_t)
    45		{
    46			gm = gm_t;
    47			p  = new int [gm.row][gm.col];
    48			p[p_y][p_x]   = 2;
    49			p[p_y][p_x+1] = 3;
    50		}
    51	
    52		/********/
    53		/* 描画 */
    54		/********/
    55		public void paintComponent (Graphics g)
    56		{
    57			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    58	
    59			int i1, i2;
    60								// ゲーム中
    61			if (game) {
    62				for (i1 = 0; i1 < gm.row; i1++) {
    63					for (i2 = 0; i2 < gm.col; i2++) {
    64						if (p[i1][i2] > 0) {
    65							switch (p[i1][i2]) {
    66								case 1:
    67									g.setColor(Color.red);
    68									break;
    69								case 2:
    70									g.setColor(Color.pink);
    71									break;
    72								case 3:
    73									g.setColor(Color.green);
    74									break;
    75								case 4:
    76									g.setColor(Color.blue);
    77									break;
    78							}
    79							g.fillRect(i2*30, i1*30, 30, 30);
    80						}
    81					}
    82				}
    83			}
    84								// ゲームオーバー
    85			else {
    86				Font f = new Font("TimesRoman", Font.BOLD, 40);
    87				g.setFont(f);
    88				g.drawString("Game", 20, 200);
    89				g.drawString("Over!", 25, 250);
    90			}
    91		}
    92	}
    			
    22 行目

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

    23 行目~ 27 行目

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

    48 行目~ 49 行目

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

    62 行目~ 82 行目

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

    86 行目~ 89 行目

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

  2. ピースの落下(「AWT の利用」,「Swing の利用」):  先ほど表示したピースを,一定時間毎( この例では,500 ms )に落下させてみます.Runnable インターフェースを継承し,run メソッドの中でピースの位置を配列の 1 要素分だけ下に移動させています(配列の外に出ないように注意すること).前のステップで作成したプログラムを少し変更するだけですが,スレッドを利用したアニーションの機能を確認することができます.

    AWT を利用した場合

    001	/********************************/
    002	/* ぷよぷよ(ゲーム全体の制御) */
    003	/********************************/
    004	import java.awt.*;
    005	import java.applet.*;
    006	
    007	public class Game2 extends Applet implements Runnable
    008	{
    009		Puyo py;
    010		boolean game = true;   // ゲーム中か否か
    011		boolean state = true;   // スレッドが動作中か否か
    012		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    013		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    014		int p[][] = new int [row][col];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    015		Thread th;
    016	
    017		/************/
    018		/* 初期設定 */
    019		/************/
    020		public void init() {
    021						// 背景色
    022			setBackground(new Color(238, 255, 238));
    023						// ゲームパネルの配置
    024			p[p_y][p_x]   = 2;
    025			p[p_y][p_x+1] = 3;
    026			setLayout(null);
    027			py = new Puyo(this);
    028			add(py);
    029			py.setSize(30*col, 30*row);
    030			py.setLocation(10, 10);
    031			py.setBackground(Color.white);
    032						// スレッドの生成
    033			th = new Thread(this);
    034			th.start();
    035		}
    036	
    037		/******************/
    038		/* 左右上下の余裕 */
    039		/******************/
    040		public Insets getInsets()
    041		{
    042			return new Insets(10, 10, 10, 10);
    043		}
    044	
    045		/******************/
    046		/* スレッドの停止 */
    047		/******************/
    048		public void stop()
    049		{
    050			state = false;
    051		}
    052	
    053		/******************/
    054		/* スレッドの実行 */
    055		/******************/
    056		public void run()
    057		{
    058			while (state) {
    059				try {
    060					th.sleep(500);
    061				}
    062				catch (InterruptedException e) {}
    063							// ピースの落下
    064				if (p_y < row-1) {
    065					p[p_y+1][p_x]   = p[p_y][p_x];
    066					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    067					p[p_y][p_x]     = 0;
    068					p[p_y][p_x+1]   = 0;
    069					p_y++;
    070				}
    071							// 再描画
    072				py.repaint();
    073			}
    074		}
    075	}
    076	
    077	/****************/
    078	/* ゲームパネル */
    079	/****************/
    080	class Puyo extends Panel
    081	{
    082		Game2 gm;
    083	
    084		/******************/
    085		/* コンストラクタ */
    086		/******************/
    087		Puyo (Game2 gm_t)
    088		{
    089			gm = gm_t;
    090		}
    091	
    092		/********/
    093		/* 描画 */
    094		/********/
    095		public void paint (Graphics g)
    096		{
    097			int i1, i2;
    098								// ゲーム中
    099			if (gm.game) {
    100				for (i1 = 0; i1 < gm.row; i1++) {
    101					for (i2 = 0; i2 < gm.col; i2++) {
    102						if (gm.p[i1][i2] > 0) {
    103							switch (gm.p[i1][i2]) {
    104								case 1:
    105									g.setColor(Color.red);
    106									break;
    107								case 2:
    108									g.setColor(Color.pink);
    109									break;
    110								case 3:
    111									g.setColor(Color.green);
    112									break;
    113								case 4:
    114									g.setColor(Color.blue);
    115									break;
    116							}
    117							g.fillRect(i2*30, i1*30, 30, 30);
    118						}
    119					}
    120				}
    121			}
    122								// ゲームオーバー
    123			else {
    124				Font f = new Font("TimesRoman", Font.BOLD, 40);
    125				g.setFont(f);
    126				g.drawString("Game", 20, 200);
    127				g.drawString("Over!", 25, 250);
    128				gm.state = false;
    129			}
    130		}
    131	}
    			
    007 行目

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

    033 行目~ 034 行目

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

    056 行目~ 074 行目

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

    128 行目

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

    Swing を利用した場合

    001	/********************************/
    002	/* ぷよぷよ(ゲーム全体の制御) */
    003	/********************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	import java.util.*;
    008	
    009	public class Game2 extends JApplet
    010	{
    011		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    012		Puyo py;
    013	
    014		/************/
    015		/* 初期設定 */
    016		/************/
    017		public void init() {
    018						// 背景色
    019			setBackground(new Color(238, 255, 238));
    020						// ゲームパネルの配置
    021			Container cp = getContentPane();
    022			cp.setBackground(new Color(238, 255, 238));
    023			cp.setLayout(null);
    024			py = new Puyo(this);
    025			cp.add(py);
    026			py.setSize(30*col, 30*row);
    027			py.setLocation(10, 10);
    028			py.setBackground(Color.white);
    029		}
    030	
    031		/******************/
    032		/* スレッドの停止 */
    033		/******************/
    034		public void stop()
    035		{
    036			py.state = false;
    037		}
    038	}
    039	
    040	/****************/
    041	/* ゲームパネル */
    042	/****************/
    043	class Puyo extends JPanel implements Runnable
    044	{
    045		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    046		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    047		boolean game = true;   // ゲーム中か否か
    048		boolean state = true;   // スレッドが動作中か否か
    049		Thread th;
    050		Game2 gm;
    051	
    052		/******************/
    053		/* コンストラクタ */
    054		/******************/
    055		Puyo (Game2 gm_t)
    056		{
    057			gm = gm_t;
    058			p  = new int [gm.row][gm.col];
    059			p[p_y][p_x]   = 2;
    060			p[p_y][p_x+1] = 3;
    061						// スレッドの生成
    062			th = new Thread(this);
    063			th.start();
    064		}
    065	
    066		/******************/
    067		/* スレッドの実行 */
    068		/******************/
    069		public void run()
    070		{
    071			while (state) {
    072				try {
    073					th.sleep(500);
    074				}
    075				catch (InterruptedException e) {}
    076							// ピースの落下
    077				if (p_y < gm.row-1) {
    078					p[p_y+1][p_x]   = p[p_y][p_x];
    079					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    080					p[p_y][p_x]     = 0;
    081					p[p_y][p_x+1]   = 0;
    082					p_y++;
    083				}
    084							// 再描画
    085				repaint();
    086			}
    087		}
    088	
    089		/********/
    090		/* 描画 */
    091		/********/
    092		public void paintComponent (Graphics g)
    093		{
    094			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    095	
    096			int i1, i2;
    097								// ゲーム中
    098			if (game) {
    099				for (i1 = 0; i1 < gm.row; i1++) {
    100					for (i2 = 0; i2 < gm.col; i2++) {
    101						if (p[i1][i2] > 0) {
    102							switch (p[i1][i2]) {
    103								case 1:
    104									g.setColor(Color.red);
    105									break;
    106								case 2:
    107									g.setColor(Color.pink);
    108									break;
    109								case 3:
    110									g.setColor(Color.green);
    111									break;
    112								case 4:
    113									g.setColor(Color.blue);
    114									break;
    115							}
    116							g.fillRect(i2*30, i1*30, 30, 30);
    117						}
    118					}
    119				}
    120			}
    121								// ゲームオーバー
    122			else {
    123				Font f = new Font("TimesRoman", Font.BOLD, 40);
    124				g.setFont(f);
    125				g.drawString("Game", 20, 200);
    126				g.drawString("Over!", 25, 250);
    127				state = false;
    128			}
    129		}
    130	}
    			
    043 行目

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

    062 行目~ 063 行目

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

    069 行目~ 087 行目

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

    127 行目

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

  3. ピースの生成と落下(「AWT の利用」,「Swing の利用」):  ピースを画面上部(水平位置はランダム)に発生させ,落下させます.ランダムな値を設定するために,Random クラスを使用しています.

    AWT を利用した場合

    001	/********************************/
    002	/* ぷよぷよ(ゲーム全体の制御) */
    003	/********************************/
    004	import java.awt.*;
    005	import java.applet.*;
    006	import java.util.*;
    007	
    008	public class Game2 extends Applet implements Runnable
    009	{
    010		Puyo py;
    011		boolean game = true;   // ゲーム中か否か
    012		boolean state = true;   // スレッドが動作中か否か
    013		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    014		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    015		int p[][] = new int [row][col];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    016		Random rn;
    017		Thread th;
    018	
    019		/************/
    020		/* 初期設定 */
    021		/************/
    022		public void init() {
    023						// 背景色
    024			setBackground(new Color(238, 255, 238));
    025						// ゲームパネルの配置
    026			setLayout(null);
    027			py = new Puyo(this);
    028			add(py);
    029			py.setSize(30*col, 30*row);
    030			py.setLocation(10, 10);
    031			py.setBackground(Color.white);
    032			rn = new Random();
    033						// ピースの選択
    034			select();
    035						// スレッドの生成
    036			th = new Thread(this);
    037			th.start();
    038		}
    039	
    040		/******************/
    041		/* 左右上下の余裕 */
    042		/******************/
    043		public Insets getInsets()
    044		{
    045			return new Insets(10, 10, 10, 10);
    046		}
    047	
    048		/******************/
    049		/* スレッドの停止 */
    050		/******************/
    051		public void stop()
    052		{
    053			state = false;
    054		}
    055	
    056		/******************/
    057		/* スレッドの実行 */
    058		/******************/
    059		public void run()
    060		{
    061			while (state) {
    062				try {
    063					th.sleep(500);
    064				}
    065				catch (InterruptedException e) {}
    066							// ピースの落下
    067				if (p_y < row-1) {
    068					p[p_y+1][p_x]   = p[p_y][p_x];
    069					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    070					p[p_y][p_x]     = 0;
    071					p[p_y][p_x+1]   = 0;
    072					p_y++;
    073				}
    074				else
    075					select();
    076							// 再描画
    077				py.repaint();
    078			}
    079		}
    080	
    081		/****************/
    082		/* ピースの選択 */
    083		/****************/
    084		void select()
    085		{
    086			int color;
    087	
    088			p_y = 0;
    089			p_x = (int)(rn.nextDouble() * (col - 1));
    090			if (p_x > col-2)
    091				p_x = col - 2;
    092	
    093			color = (int)(rn.nextDouble() * 4) + 1;
    094			if (color > 4)
    095				color = 4;
    096			p[0][p_x] = color;
    097			color = (int)(rn.nextDouble() * 4) + 1;
    098			if (color > 4)
    099				color = 4;
    100			p[0][p_x+1] = color;
    101		}
    102	}
    103	
    104	/****************/
    105	/* ゲームパネル */
    106	/****************/
    107	class Puyo extends Panel
    108	{
    109		Game2 gm;
    110	
    111		/******************/
    112		/* コンストラクタ */
    113		/******************/
    114		Puyo (Game2 gm_t)
    115		{
    116			gm = gm_t;
    117		}
    118	
    119		/********/
    120		/* 描画 */
    121		/********/
    122		public void paint (Graphics g)
    123		{
    124			int i1, i2;
    125								// ゲーム中
    126			if (gm.game) {
    127				for (i1 = 0; i1 < gm.row; i1++) {
    128					for (i2 = 0; i2 < gm.col; i2++) {
    129						if (gm.p[i1][i2] > 0) {
    130							switch (gm.p[i1][i2]) {
    131								case 1:
    132									g.setColor(Color.red);
    133									break;
    134								case 2:
    135									g.setColor(Color.pink);
    136									break;
    137								case 3:
    138									g.setColor(Color.green);
    139									break;
    140								case 4:
    141									g.setColor(Color.blue);
    142									break;
    143							}
    144							g.fillRect(i2*30, i1*30, 30, 30);
    145						}
    146					}
    147				}
    148			}
    149								// ゲームオーバー
    150			else {
    151				Font f = new Font("TimesRoman", Font.BOLD, 40);
    152				g.setFont(f);
    153				g.drawString("Game", 20, 200);
    154				g.drawString("Over!", 25, 250);
    155				gm.state = false;
    156			}
    157		}
    158	}
    			
    032 行目

      乱数の初期化.

    034 行目

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

    074 行目~ 075 行目

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

    089 行目~ 091 行目

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

    093 行目~ 100 行目

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

    Swing を利用した場合

    001	/********************************/
    002	/* ぷよぷよ(ゲーム全体の制御) */
    003	/********************************/
    004	import java.awt.*;
    005	import java.awt.event.*;
    006	import javax.swing.*;
    007	import java.util.*;
    008	
    009	public class Game2 extends JApplet
    010	{
    011		int row = 12, col = 5;   // ブロックの行数と列数(ブロックの大きさは 30 ピクセル)
    012		Puyo py;
    013	
    014		/************/
    015		/* 初期設定 */
    016		/************/
    017		public void init() {
    018						// 背景色
    019			setBackground(new Color(238, 255, 238));
    020						// ゲームパネルの配置
    021			Container cp = getContentPane();
    022			cp.setBackground(new Color(238, 255, 238));
    023			cp.setLayout(null);
    024			py = new Puyo(this);
    025			cp.add(py);
    026			py.setSize(30*col, 30*row);
    027			py.setLocation(10, 10);
    028			py.setBackground(Color.white);
    029		}
    030	
    031		/******************/
    032		/* スレッドの停止 */
    033		/******************/
    034		public void stop()
    035		{
    036			py.state = false;
    037		}
    038	}
    039	
    040	/****************/
    041	/* ゲームパネル */
    042	/****************/
    043	class Puyo extends JPanel implements Runnable
    044	{
    045		int p_x = 1, p_y = 3;   // 動いているピースの位置(列と行)
    046		int p[][];   // 各ブロックに存在するピースの色( 0 の時は存在しない)
    047		boolean game = true;   // ゲーム中か否か
    048		boolean state = true;   // スレッドが動作中か否か
    049		Thread th;
    050		Random rn;
    051		Game2 gm;
    052	
    053		/******************/
    054		/* コンストラクタ */
    055		/******************/
    056		Puyo (Game2 gm_t)
    057		{
    058			gm = gm_t;
    059			p  = new int [gm.row][gm.col];
    060			rn = new Random();
    061						// ピースの選択
    062			select();
    063						// スレッドの生成
    064			th = new Thread(this);
    065			th.start();
    066		}
    067	
    068		/******************/
    069		/* スレッドの実行 */
    070		/******************/
    071		public void run()
    072		{
    073			while (state) {
    074				try {
    075					th.sleep(500);
    076				}
    077				catch (InterruptedException e) {}
    078							// ピースの落下
    079				if (p_y < gm.row-1) {
    080					p[p_y+1][p_x]   = p[p_y][p_x];
    081					p[p_y+1][p_x+1] = p[p_y][p_x+1];
    082					p[p_y][p_x]     = 0;
    083					p[p_y][p_x+1]   = 0;
    084					p_y++;
    085				}
    086				else
    087					select();
    088							// 再描画
    089				repaint();
    090			}
    091		}
    092	
    093		/****************/
    094		/* ピースの選択 */
    095		/****************/
    096		void select()
    097		{
    098			int color;
    099	
    100			p_y = 0;
    101			p_x = (int)(rn.nextDouble() * (gm.col - 1));
    102			if (p_x > gm.col-2)
    103				p_x = gm.col - 2;
    104	
    105			color = (int)(rn.nextDouble() * 4) + 1;
    106			if (color > 4)
    107				color = 4;
    108			p[0][p_x] = color;
    109			color = (int)(rn.nextDouble() * 4) + 1;
    110			if (color > 4)
    111				color = 4;
    112			p[0][p_x+1] = color;
    113		}
    114	
    115		/********/
    116		/* 描画 */
    117		/********/
    118		public void paintComponent (Graphics g)
    119		{
    120			super.paintComponent(g);   // 親クラスの描画(必ず必要)
    121	
    122			int i1, i2;
    123								// ゲーム中
    124			if (game) {
    125				for (i1 = 0; i1 < gm.row; i1++) {
    126					for (i2 = 0; i2 < gm.col; i2++) {
    127						if (p[i1][i2] > 0) {
    128							switch (p[i1][i2]) {
    129								case 1:
    130									g.setColor(Color.red);
    131									break;
    132								case 2:
    133									g.setColor(Color.pink);
    134									break;
    135								case 3:
    136									g.setColor(Color.green);
    137									break;
    138								case 4:
    139									g.setColor(Color.blue);
    140									break;
    141							}
    142							g.fillRect(i2*30, i1*30, 30, 30);
    143						}
    144					}
    145				}
    146			}
    147								// ゲームオーバー
    148			else {
    149				Font f = new Font("TimesRoman", Font.BOLD, 40);
    150				g.setFont(f);
    151				g.drawString("Game", 20, 200);
    152				g.drawString("Over!", 25, 250);
    153				state = false;
    154			}
    155		}
    156	}
    			
    060 行目

      乱数の初期化.

    062 行目

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

    086 行目~ 087 行目

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

    101 行目~ 103 行目

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

    105 行目~ 112 行目

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

  4. キーイベント(「AWT の利用」,「Swing の利用」):  キーイベントに対する処理を追加します.移動や回転を行う場合は,移動や回転が可能か否かのチェックが必要となります.変数 rot の値が 0 の時は横並び,1 の時は縦並びであることを意味します.当然,rot の値によって,一番下に到着したか否かのチェック方法も異なってきますので,ピースの落下を制御する部分も変更する必要があります.また,一番下に到着すると,変数 ok を false に設定し,キーイベントを受け付けないようにしています.

    AWT を利用した場合

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

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

    142 行目

      キーイベントを受け付けるために,KeyListener を追加しています.

    188 行目

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

    201 行目~ 205 行目

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

    208 行目~ 213 行目

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

    218 行目~ 236 行目

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

    239 行目~ 249 行目

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

    252 行目~ 267 行目

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

    Swing を利用した場合

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

      キーイベントを受け付けるために,KeyListener を追加しています.

    098 行目~ 106 行目

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

    185 行目

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

    198 行目~ 202 行目

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

    205 行目~ 210 行目

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

    215 行目~ 233 行目

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

    236 行目~ 246 行目

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

    249 行目~ 264 行目

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

  5. ピースの積み上げ(「AWT の利用」,「Swing の利用」):  このままでは,ピースが積み上がっていきません.落下しているピースが,他のピースの上に乗ったらそこで止めるための処理が必要になります.特に,横並びの場合,一つのピースだけが他のピースの上に乗った場合,キーイベントの処理を受け付けないようにした後(変数 ok を false ),乗っていないピースだけを落下させる処理が必要になる点に注意して下さい.また,上まで積み上がるとゲームオーバーになる処理も追加しています.

    AWT を利用した場合

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

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

    086 行目

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

    087 行目~ 092 行目

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

    093 行目~ 099 行目

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

    103 行目~ 108 行目

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

    113 行目~ 121 行目

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

    145 行目,155 行目~ 156 行目

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

    Swing を利用した場合

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

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

    097 行目

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

    098 行目~ 103 行目

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

    104 行目~ 110 行目

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

    114 行目~ 119 行目

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

    124 行目~ 132 行目

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

    156 行目,166 行目~ 167 行目

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

  6. ピースの削除(「AWT の利用」,「Swing の利用」):  最後に行わなければならないのがピースの削除です.同じ色のピースが縦横 4 個以上並んだ場合,それらを削除し,それらの上に乗っていたピースを可能なところまで落下させます.メソッド search で同じ色のピースを数え,メソッド delete で削除しています.

    AWT を利用した場合

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

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

    125 行目~ 143 行目

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

    185 行目~ 207 行目

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

    218 行目~ 223 行目

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

    225 行目~ 248 行目

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

    Swing を利用した場合

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

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

    137 行目~ 155 行目

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

    197 行目~ 219 行目

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

    230 行目~ 235 行目

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

    237 行目~ 260 行目

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

20.5.2 グラフの表示

(プログラム例 20.9 ) グラフの表示

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

(プログラム例 20.27 ) データベースとグラフ

  上の例では,プログラム内で適当に作成したデータを,棒グラフを描画するクラス,BarGraph クラスなどに渡して,グラフを描画しています.しかし,一般には,どこからか得られたデータに基づき,動的にグラフを描画したいような場合が多くあります.例えば,データベースなどから引き出したデータを,単に数値として示すだけではなく,グラフとして表示したいような場合です.そのためには,BarGraph クラスなど,各グラフを描画するクラスに,グラフを表示するためのデータを渡してやる必要があります.様々な方法が考えられますが,一つの方法は,
<object type="application/x-java-applet" width="600" height="400">
	<param name="code" value="Graph.class">
	<param name="data" value="1,2,・・・" >
</object>
		
のように,OBJECT 要素の PARAM 要素を利用し,描画したいグラフの種類とそのグラフを描画するために必要なデータをカンマで区切って渡してやる方法です.プログラム Graph.java では,渡されたデータをカンマを利用して分割し,グラフを描画することになります.このようにすれば,サーバ上のデータベースから得られた結果を,ネットワークを介して即座にグラフとして表示することも可能になります.

  なお,本コンテンツ内では,JavaScript を使用してグラフを描画する例をいくつか示しました.JavaScript においては,「~.html?parameter 」という形式で,paraneter 部分にグラフ描画に必要なデータを記述し,グラフを描画しています.そのため,サーバ側の設定にもよりますが,データ量が多いと描画できないことがあります.

  この例は,PHP のプログラム(ソースプログラムは以下に示すとおり)によって,データベースに保存されている内容を取り出し,その結果を表とグラフで表現しようとするものです.jar ファイル内には,上で述べて Grapha.java が含まれています.このプログラムでは,データベースのテーブルを作成し,データを挿入する操作を行っていますが,これは,あくまでテスト用であり,一般的には必要ありません.

001	<!DOCTYPE HTML>
002	<HTML>
003	<HEAD>
004		<TITLE>表&グラフ</TITLE>
005		<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
006		<LINK REL="stylesheet" TYPE="text/css" HREF="master.css">
007	</HEAD>
008	<BODY CLASS="white" STYLE="text-align: center">
009		<H1 CLASS="center">表&グラフ</H3>
010	<?php
011						// データベースへの追加
012		$db = mysql_connect("*****", "*****", "*****");
013		if (!$db)
014			exit("<h1>データベースへ接続できません</h1>\n");
015						// テーブルの削除
016		mysql_select_db("WEB_SHOP", $db);
017		$result = mysql_query("SHOW TABLES", $db);
018		if (mysql_num_rows($result) > 0) {
019			while ($row = mysql_fetch_row($result)) {
020				if (strcmp($row[0], "commodity") == 0) {
021					mysql_query("DROP TABLE commodity", $db);
022					break;
023				}
024			}
025			mysql_free_result($result);
026		}
027						// テーブルの生成とデータの追加
028		$str = "(id INT AUTO_INCREMENT PRIMARY KEY,itemname varchar(100) not null,stock INT NOT NULL)";
029		mysql_query("CREATE TABLE commodity ".$str, $db);
030		if (mysql_errno($db) != 0)
031			exit("<h1>テーブル commodity を作成できません</h1>\n");
032		mysql_query("INSERT INTO commodity VALUES(NULL,'商品1',10)", $db);
033		mysql_query("INSERT INTO commodity VALUES(NULL,'商品2',20)", $db);
034		mysql_query("INSERT INTO commodity VALUES(NULL,'商品3',4)", $db);
035		mysql_query("INSERT INTO commodity VALUES(NULL,'商品4',40)", $db);
036		mysql_query("INSERT INTO commodity VALUES(NULL,'商品5',35)", $db);
037		mysql_query("INSERT INTO commodity VALUES(NULL,'商品6',4)", $db);
038		mysql_query("INSERT INTO commodity VALUES(NULL,'商品7',3)", $db);
039		mysql_query("INSERT INTO commodity VALUES(NULL,'商品8',2)", $db);
040		mysql_query("INSERT INTO commodity VALUES(NULL,'商品9',15)", $db);
041		mysql_query("INSERT INTO commodity VALUES(NULL,'商品10',20)", $db);
042						// データの削除とすべてのデータの出力
043		mysql_query("DELETE FROM commodity WHERE id IN (1,8)", $db);
044		$result = mysql_query("SELECT * FROM commodity", $db);
045		echo "id = 1, 8 を削除\n";
046		echo "	<DL>\n";
047		while ($row = mysql_fetch_row($result))
048			echo "<DT>".$row[0]." ".$row[1]." ".$row[2]."<BR>\n";
049		echo "	</DL><P>\n";
050						// データの追加とすべてのデータの出力
051		mysql_query("INSERT INTO commodity VALUES(NULL,'商品1',10)", $db);
052		$result = mysql_query("SELECT * FROM commodity", $db);
053		echo "商品 1 を追加\n";
054		echo "	<DL>\n";
055		while ($row = mysql_fetch_row($result))
056			echo "<DT>".$row[0]." ".$row[1]." ".$row[2]."<BR>\n";
057		echo "	</DL><P>\n";
058						// データの修正とすべてのデータの出力
059		mysql_query("UPDATE commodity SET stock = 50 WHERE id = 11", $db);
060		$result = mysql_query("SELECT * FROM commodity", $db);
061		echo "商品 1 のデータを修正\n";
062		echo "	<DL>\n";
063		while ($row = mysql_fetch_row($result))
064			echo "<DT>".$row[0]." ".$row[1]." ".$row[2]."<BR>\n";
065		echo "	</DL><P>\n";
066						// データの出力
067								// グラフ表示の準備
068		$gp      = "0,在庫の多い商品,商品名,商品在庫,1,在庫";
069		$x_title = array();
070		$data    = array();
071		$n       = 0;
072		$max     = 0;
073								// 表の作成
074		echo "	<TABLE BORDER='1' STYLE='margin-right: auto; margin-left: auto'>\n";
075		echo "		<TR>\n";
076		echo "			<TH>商品名</TH>\n";
077		echo "			<TH>商品在庫</TH>\n";
078		echo "		</TR>\n";
079		$result = mysql_query("SELECT * FROM commodity WHERE stock >= 10 ORDER BY stock", $db);
080		while ($row = mysql_fetch_row($result)) {
081			echo "		<TR>\n";
082			echo "			<TD>".$row[1]."</TD>\n";
083			echo "			<TD>".$row[2]."</TD>\n";
084			echo "		</TR>\n";
085			$x_title[$n] = $row[1];
086			$data[$n] = $row[2];
087			if ($row[2] > $max)
088				$max = $row[2];
089			$n++;
090		}
091		echo "	</TABLE>\n";
092	
093		mysql_free_result($result);
094								// グラフ表示データの作成
095		$gp = $gp.",".$n;   // データの数
096		for ($i1 = 0; $i1 < $n; $i1++)
097			$gp = $gp.",".$x_title[$i1];   // x軸タイトル
098		if ($max > 50) {
099			$step = 20;
100			$max  = 100;
101		}
102		else {
103			$step = 10;
104			$max  = 50;
105		}
106		$gp = $gp.",0,".$max.",".$step.",0";  // 最小値,最大値,刻み幅,小数点以下桁数
107		for ($i1 = 0; $i1 < $n; $i1++)
108			$gp = $gp.",".$data[$i1];   // データ
109		$gp = $gp.",1,0";   // グラフタイトルと凡例の表示
110						// データベースへの接続を切る
111		mysql_close ($db);
112								// 描画
113		printf("	<div class=\"center\">\n");
114		printf("		<object type=\"application/x-java-applet\" width=\"0\" height=\"0\">\n");
115		printf("			<param name=\"archive\" value=\"Graph.jar\">\n");
116		printf("			<param name=\"code\" value=\"Graph\">\n");
117		printf("			<param name=\"data\" value=\"%s\">\n", $gp);
118		printf("		</object>\n");
119		printf("	</div>\n");
120	?>
121	</BODY>
122	</HTML>
		
012 行目

  MySQL と接続しています.

016 行目~ 026 行目

  データベース WEB_SHOP に,テーブル commodity が存在した場合は,それを削除しています.このプログラムで,テーブルの生成やデータの追加方法を示したいために行っている処理であり,一般的には必要ありません.

028 行目~ 029 行目

  テーブル commodity を作成しています.この処理も,一般的には必要ありません.

032 行目~ 041 行目

  テーブル commodity にデータを追加しています.この処理も,一般的には必要ありません.

043 行目~ 049 行目

  id が 1 と 8 であるデータを削除し,その結果を表示しています.他のデータの id は変化していないことに注意してください.この処理も,一般的には必要ありません.

051 行目~ 057 行目

  上で削除した商品1に対するデータを追加し,その結果を表示しています.追加されたデータの id に注意してください.この処理も,一般的には必要ありません.

059 行目~ 065 行目

  商品1に対するデータを修正し,その結果を表示しています.この処理も,一般的には必要ありません.

068 行目

  114 行目~ 118 行目に記述してある Java アプレットは,グラフを描画するために必要な情報をカンマ「 , 」で区切った文字列として受け取り( 117 行目),その内容に従って様々なグラフを描画します.変数 $gp には,グラフを描画するために必要な情報が設定されています.ここでは,「グラフの種類( 0 は棒グラフを示す),グラフタイトル, x 軸タイトル, y 軸タイトル,グラフの数,各グラフのタイトル(グラフの数だけ入力)」を設定しています.

069 行目~ 072 行目

  このプログラムでは,在庫数が多い商品に対して,商品毎の在庫数を棒グラフで表すことを目的としています.配列変数 $x_title には各商品の商品名,配列変数 $data にはその在庫数,変数 $n には商品の数,及び,変数 $max には最大在庫数が入ります.

074 行目~ 091 行目

  079 行目の SQL 文によって,在庫数が 10 以上の商品を選び,上で述べた変数に必要なデータを設定すると共に,商品名とその在庫数を表で表しています.

095 行目

  データ数(商品数)を変数 $gp に追加しています.

096 行目~ 097 行目

  各商品の商品名を変数 $gp に追加しています.

098 行目~ 105 行目

  $max の値に基づき,縦軸に対する刻み幅と最大値を設定しています.

106 行目

  変数 $gp に,「縦軸の最小値,縦軸の最大値,縦軸の刻み幅,縦軸に表示する数値の小数点以下桁数」を追加しています.

107 行目~ 108 行目

  各商品に対する在庫数を変数 $gp に追加しています.グラフの数が複数の場合は,その数だけ,これらの行を繰り返すことになります.

109 行目

  変数 $gp に,「グラフタイトルを表示するか否か( 1:表示,0:表示しない),凡例を表示するか否か( 1:表示,0:表示しない)」を追加しています.

114 行目~ 118 行目

  変数 $gp をパラメータとして,Java アプレットを起動しています.なお,Graph.jar には,Graph.java と共に,上の例で示したすべてのグラフを描くためのクラスが含まれています.

20.5.3 遺伝的アルゴリズム

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

(プログラム例 20.11 ) 1の数

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

  なお,親や交叉位置の選択は,人間が行うことになりますので,できるだけ無作為に選択してみてください.

(プログラム例 20.12 ) 関数の最大値

  このアプレット(「AWT の利用」,「Swing の利用」)は,関数,

y = -x2 + 2x + 1 = -(x - 1)2 + 2

y = sin(3x) + 0.5sin(9x) + sin(15x + 50)

の最大値を GA を用いて求めるためのものです.関数を選択して,「Start」ボタンを押すと,1 世代目の結果が表示されます.次に,「Next」ボタンを押す毎に,2 世代目以降の結果が順に表示されます.

20.5.4 お絵かき

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

  この例(「AWT の利用」,「Swing の利用」)は,マウスのドラッグを利用した簡単なお絵かきツールです.アプレットの画面上でマウスをドラッグすると,その動きに沿った自由曲線が描画されます.

AWT を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import java.applet.*;
04	import java.util.*;
05	
06	public class Test extends Applet implements MouseMotionListener {
07		Vector <Point> v = new Vector <Point> ();
08						// 初期設定
09		public void init()
10		{
11			setBackground(Color.white);
12			addMouseMotionListener(this);
13		}
14						// マウスイベントの処理
15		public void mouseDragged(MouseEvent e)
16		{
17			v.add(new Point(e.getX(), e.getY()));
18			repaint();
19		}
20		public void mouseMoved(MouseEvent e)
21		{
22			if (v.size() > 0) {
23				Point p = (Point)v.get(v.size()-1);
24				if (p.x >= 0)
25					v.add(new Point(-1, -1));
26			}
27		}
28						// 描画
29		public void paint (Graphics g)
30		{
31			int i1;
32			Point p1, p2;
33			if (v.size() > 0) {
34				p1 = (Point)v.get(0);
35				for (i1 = 1; i1 < v.size(); i1++) {
36					p2 = (Point)v.get(i1);
37					if (p1.x >= 0 && p2.x >= 0)
38						g.drawLine(p1.x, p1.y, p2.x, p2.y);
39					p1 = p2;
40				}
41			}
42		}
43	}
		
06 行目

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

07 行目

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

11 行目~ 12 行目

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

15 行目~ 19 行目

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

20 行目~ 27 行目

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

29 行目~ 42 行目

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

Swing を利用した場合

01	import java.awt.*;
02	import java.awt.event.*;
03	import javax.swing.*;
04	import java.util.*;
05						// 初期設定
06	public class Test extends JApplet
07	{
08		public void init()
09		{
10			Test_Panel pn = new Test_Panel();
11			getContentPane().add(pn);
12		}
13	}
14						// 描画パネル
15	class Test_Panel extends JPanel implements MouseMotionListener
16	{
17		ArrayList <Point> v = new ArrayList <Point> ();
18								// コンストラクタ
19		Test_Panel()
20		{
21			addMouseMotionListener(this);
22			setBackground(Color.white);
23		}
24								// マウスイベントの処理
25		public void mouseDragged(MouseEvent e)
26		{
27			v.add(new Point(e.getX(), e.getY()));
28			repaint();
29		}
30		public void mouseMoved(MouseEvent e)
31		{
32			if (v.size() > 0) {
33				Point p = (Point)v.get(v.size()-1);
34				if (p.x >= 0)
35					v.add(new Point(-1, -1));
36			}
37		}
38								// 描画
39		public void paintComponent (Graphics g)
40		{
41			super.paintComponent(g);   // 親クラスの描画(必ず必要)
42			int i1;
43			Point p1, p2;
44			if (v.size() > 0) {
45				p1 = (Point)v.get(0);
46				for (i1 = 1; i1 < v.size(); i1++) {
47					p2 = (Point)v.get(i1);
48					if (p1.x >= 0 && p2.x >= 0)
49						g.drawLine(p1.x, p1.y, p2.x, p2.y);
50					p1 = p2;
51				}
52			}
53		}
54	}
		
10 行目~ 11 行目

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

15 行目

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

17 行目

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

21 行目~ 22 行目

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

25 行目~ 29 行目

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

30 行目~ 37 行目

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

39 行目~ 53 行目

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

  以下に示す例は,上で示した例にメニューを付加し,多少お絵かきソフトらしく修正したものです.始点と終点をクリックすることによって直線を描くことができます.また,線の太さや色の変更も可能です.ここをクリックするとアプレット版が表示されますので,適当な図を描いてみてください.ここでは,アプリケーション版,アプレット版,いずれも Swing を利用しています.

アプリケーション版

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

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

031 行目~ 032 行目

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

035 行目

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

040 行目~ 045 行目

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

062 行目

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

064 行目~ 067 行目

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

069 行目~ 073 行目

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

075 行目~ 078 行目

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

111 行目~ 119 行目

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

120 行目~ 131 行目

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

133 行目~ 150 行目

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

151 行目~ 154 行目

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

160 行目

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

162 行目~ 175 行目

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

181 行目~ 188 行目

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

191 行目~ 197 行目

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

198 行目~ 203 行目

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

228 行目~ 254 行目

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

257 行目~ 273 行目

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

280 行目~ 298 行目

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

アプレット版( Swing )

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

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

033 行目

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

035 行目~ 038 行目

  アプレットの大きさ( 035 行目)から,余白のサイズ( 036 行目)を引き,実際の表示領域を Dimension クラスのオブジェクト size に設定しています.

040 行目~ 044 行目

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

046 行目~ 049 行目

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

082 行目~ 090 行目

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

091 行目~ 102 行目

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

104 行目~ 121 行目

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

122 行目~ 125 行目

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

131 行目

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

133 行目~ 146 行目

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

152 行目~ 159 行目

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

162 行目~ 168 行目

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

169 行目~ 174 行目

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

199 行目~ 225 行目

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

228 行目~ 244 行目

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

251 行目~ 269 行目

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

20.5.5 その他

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

  このアプレット(「AWT の利用」,「Swing の利用」)は,2 次方程式,

   ax2 + bx + c = 0

の根を求めるためのものです.適当な数値を入力して,結果を確認してみてください.

  2 次方程式の根を求める程度のことであっても,コンピュータが勝手にやってくれるわけではありません.その計算手順を記述したものをプログラムとしてコンピュータに教えてやる必要があります(「2次方程式を解く」以下の部分を参照).

AWT を利用した場合

001	/********************************************/
002	/* クラスEquationの定義(二次方程式を解く) */
003	/*      coded by Y.Suganuma                 */
004	/********************************************/
005	import java.io.*;
006	import java.awt.*;
007	import java.awt.event.*;
008	import java.applet.*;
009	
010	public class Equation extends Applet {
011	
012		double a, b, c;   // 2次方程式の係数
013	
014		private TextField a1, b1, c1;
015		private Equation dt = this;
016	
017		/************/
018		/* 初期設定 */
019		/************/
020		public void init()
021		{
022			Font f = new Font("TimesRoman", Font.BOLD, 20);
023							// レイアウトの変更
024			setLayout(new GridLayout(4, 2, 5, 10));
025							// ラベル & テキストフィールド
026			Label fill1 = new Label("2次の係数");
027			fill1.setFont(f);
028			add(fill1);
029			a1 = new TextField(10);
030			a1.setFont(f);
031			add(a1);
032	
033			Label fill2 = new Label("1次の係数");
034			fill2.setFont(f);
035			add(fill2);
036			b1 = new TextField(10);
037			b1.setFont(f);
038			add(b1);
039	
040			Label fill3 = new Label("定数項");
041			fill3.setFont(f);
042			add(fill3);
043			c1 = new TextField(10);
044			c1.setFont(f);
045			add(c1);
046							// OK ボタンの設定
047			Label fill4 = new Label("  実行?");
048			fill4.setFont(f);
049			add(fill4);
050			Button bt = new Button("OK");
051			bt.setFont(f);
052			bt.addMouseListener(new ClickMouse());
053			add(bt);
054		}
055	
056		/******************************/
057		/* 上,左,下,右の余白の設定 */
058		/******************************/
059		public Insets getInsets()
060		{
061			return new Insets(40, 10, 10, 10);
062		}
063	
064		/********************************/
065		/* OKボタンが押されたときの処理 */
066		/********************************/
067		class ClickMouse extends MouseAdapter
068		{
069			/************************************/
070			/* マウスがクリックされたときの処理 */
071			/************************************/
072			public void mouseClicked(MouseEvent e)
073			{
074								// データの設定
075				if ((a1.getText()).length() <= 0)
076					a = 0.0;
077				else
078					a = (Double.valueOf(a1.getText())).doubleValue();
079	
080				if ((b1.getText()).length() <= 0)
081					b = 0.0;
082				else
083					b = (Double.valueOf(b1.getText())).doubleValue();
084	
085				if ((c1.getText()).length() <= 0)
086					c = 0.0;
087				else
088					c = (Double.valueOf(c1.getText())).doubleValue();
089								// 実行
090				Run run = new Run(dt);
091			}
092		}
093	}
094	
095	/************************/
096	/* クラスRunの定義      */
097	/* (2次方程式を解く) */
098	/************************/
099	import java.io.*;
100	import java.awt.*;
101	import java.awt.event.*;
102	
103	class Run extends Frame {
104	
105		private Equation dt;
106		private Run rn = this;
107		private TextArea text;
108	
109		/********************************************/
110		/* コンストラクタ                           */
111		/*      dt_i : Equationクラスのオブジェクト */
112		/********************************************/
113		Run(Equation dt_i)
114		{
115							// Frameクラスのコンストラクタの呼び出し
116			super("2次方程式");
117							// レイアウトの変更
118			setLayout(new GridLayout(1, 1));
119							// テキストフィールドの設定
120			setFont(new Font("TimesRoman", Font.PLAIN, 20));
121			dt   = dt_i;
122			text = new TextArea("", 5, 50);
123			add(text);
124							// Windowサイズを設定
125			setSize(450, 250);
126			setLocation(100, 150);
127							// ウィンドウを表示
128			setVisible(true);
129							// イベントアダプタ
130			addWindowListener(new WinEnd());
131							// 実行
132			solve();
133		}
134	
135		/******************************/
136		/* 上,左,下,右の余白の設定 */
137		/******************************/
138		public Insets getInsets()
139		{
140			return new Insets(70, 20, 70, 20);
141		}
142	
143		/********************/
144		/* 2次方程式を解く */
145		/********************/
146		public void solve()
147		{
148			double a = dt.a, b = dt.b, c = dt.c, D, x, x1, x2;
149			String str;
150		/*
151		          一次方程式 or 解なし
152		*/
153			if (Math.abs(a) <= 1.0e-10) {
154				if (Math.abs(b) <= 1.0e-10)
155					text.insert("解を求めることができません!\n", 0);
156				else {
157					x = -c / b;
158					str = "x = " + Double.toString(x) + "\n";
159					text.insert(str, 0);
160				}
161			}
162		/*
163		         二次方程式
164		*/
165			else {
166	
167				D = b * b - 4.0 * a * c;
168							// 実根
169				if (D >= 0.0) {
170					D  = Math.sqrt(D);
171					x1 = 0.5 * (-b - D) / a;
172					x2 = 0.5 * (-b + D) / a;
173					str = "x = " + Double.toString(x1) + ", " + Double.toString(x2) + "\n";
174					text.insert(str, 0);
175				}
176							// 虚根
177				else {
178					D  = Math.sqrt(-D);
179					x1 = -0.5 * b / a;
180					x2 = 0.5 * D / a;
181					str = "x = " + Double.toString(x1) + " ± i " + Double.toString(x2) + "\n";
182					text.insert(str, 0);
183				}
184			}
185		}
186	
187		/************/
188		/* 終了処理 */
189		/************/
190		class WinEnd extends WindowAdapter
191		{
192			public void windowClosing(WindowEvent e) {
193				rn.setVisible(false);
194			}
195		}
196	}
		
024 行目

  レイアウトマネージャを,4 行 2 列の GridLayout に変更しています.

026 行目~ 053 行目

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

059 行目~ 062 行目

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

072 行目~ 091 行目

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

116 行目~ 130 行目

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

132 行目

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

155 行目

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

157 行目~ 159 行目

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

167 行目

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

170 行目~ 174 行目

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

178 行目~ 182 行目

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

Swing を利用した場合

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

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

031 行目~ 068 行目

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

074 行目~ 077 行目

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

087 行目~ 106 行目

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

132 行目~ 156 行目

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

158 行目

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

173 行目

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

175 行目~ 177 行目

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

185 行目

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

188 行目~ 192 行目

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

196 行目~ 200 行目

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

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

       

  表示される 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:フィードバックゲイン

とします. → プログラム:「AWT の利用」,「Swing の利用

  (「AWT の利用」,「Swing の利用」)は,同じプログラム例をアプレット用に書き換えた例です.

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

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

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

  まず,Java のプログラム(「AWT の利用」,または,「Swing の利用」のいずれでも良い)と制御用の 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 つのデータを出力しています.

AWT を利用した場合( Java )

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

  各リンクの長さです.

035 行目~ 059 行目

  Window を生成すると共に,リセットボタンを追加し,スレッドを開始しています.

100 行目~ 114 行目

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

120 行目~ 155 行目

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

159 行目~ 175 行目

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

182 行目~ 223 行目

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

Swing を利用した場合( 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 のプログラム(「AWT の利用」,または,「Swing の利用」のいずれでも良い)と C/C++ のプログラムから構成されており,LED.txt というファイルを通して制御が実行されます.もちろん,この場合においても,制御部分のプログラムを Java で記述することが可能です.

  まず,Java のプログラム(「AWT の利用」,または,「Swing の利用」のいずれでも良い)と制御用の 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 だけ処理を待っています.

AWT を利用した場合

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

  Window を生成すると共に,リセットボタンを追加し,スレッドを開始しています.

076 行目~ 079 行目

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

080 行目

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

081 行目

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

083 行目~ 086 行目

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

087 行目~ 089 行目

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

105 行目~ 122 行目

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

Swing を利用した場合

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 ボタンを描画しています.

静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引