// pressureを Threadのサブクラスとして定義
// Threadによって動く実体とする
class pressure extends Thread{
// 圧力が安全圏内なら加える
static void RaisePressure(){
// 現在の圧力が制限値よりも15を超して少ないなら
int pval=p.pressureGauge;
if(pval < p.safetyLimit-15){
// わざと 100ms(0.1秒)待つ
try{sleep(100);} catch(Exception e){}
// 圧力を15加える
p.pressureGauge+=15;
System.out.println("pval="+pval+", p.pressureGauge="+p.pressureGauge);
}
else{
System.out.println("Failed adding pressure");
}
}
// pressureのインスタンスに対して startするとここが呼ばれる.
public void run(){
RaisePressure();
}
}
public class p {
// pクラスの静的変数なのでプログラム全体から共有される
// 圧力値
static int pressureGauge=0;
// 圧力の制限値
static final int safetyLimit=20;
public static void main(String[] args){
pressure [] p1=new pressure[10];
int i;
// 10個の Threadを作り,次々と作成
for(i=0;i<10;i++){
p1[i]=new pressure();
p1[i].start();
}
// すべてのスレッドが終了するのを待つ
try{
for(i=0;i<10;i++) p1[i].join();
}
catch (Exception e){}
System.out.println("gauge reads "+pressureGauge+", safe limit is 20");
}
}
これを実行すると,
pval=0, p.pressureGauge=15 pval=0, p.pressureGauge=30 pval=0, p.pressureGauge=45 pval=0, p.pressureGauge=60 pval=0, p.pressureGauge=75 pval=0, p.pressureGauge=90 pval=0, p.pressureGauge=105 pval=0, p.pressureGauge=120 pval=0, p.pressureGauge=135 pval=0, p.pressureGauge=150 gauge reads 150, safe limit is 20となり,範囲を超えてしまうだろう.これを防ぐために,Java言語では相互排 他(mutual exclusion)を実現するためのsynchronized というキーワードを用 意してある.この使い方は,メソッドにつける.ブロックにつけるなどいろい ろある.先ほどの例では,
// pressure1を Threadのサブクラスとして定義
// Threadによって動く実体とする
class pressure1 extends Thread{
// 圧力が安全圏内なら加える
static synchronized void RaisePressure(){
// 現在の圧力が制限値よりも15を超して少ないなら
int pval=p1.pressureGauge;
if(pval < p1.safetyLimit-15){
// わざと 100ms(0.1秒)待つ
try{sleep(100);} catch(Exception e){}
// 圧力を15加える
p1.pressureGauge+=15;
System.out.println("pval="+pval+", p1.pressureGauge="+p1.pressureGauge);
}
else{
System.out.println("Failed adding pressure");
}
}
// pressureのインスタンスに対して startするとここが呼ばれる.
public void run(){
RaisePressure();
}
}
public class p1 {
// p1クラスの静的変数なのでプログラム全体から共有される
// 圧力値
static int pressureGauge=0;
// 圧力の制限値
static final int safetyLimit=20;
public static void main(String[] args){
pressure1 [] p1=new pressure1[10];
int i;
// 10個の Threadを作り,次々と作成
for(i=0;i<10;i++){
p1[i]=new pressure1();
p1[i].start();
}
// すべてのスレッドが終了するのを待つ
try{
for(i=0;i<10;i++) p1[i].join();
}
catch (Exception e){}
System.out.println("gauge reads "+pressureGauge+", safe limit is 20");
}
}
のようにRaisePressureにsynchronized というキーワードを付けると,同じク
ラスのオブジェクトが,RaisePressureを同時に実行するのを防ぐことができ
る.
メソッドRaisePressureが static になっていないと,synchronizedにより, 「同時には実行しない」単位が,「同じインスタンスに対するメソッド RaisePressureの呼び出し」というものになるので,このケースではふさわしくない.
// < applet code="Test15" width=400 height=400 ></applet>
import java.applet.*;
import java.awt.*;
public class Test15 extends Applet implements Runnable{
// スレッドの宣言
public Thread th=null;
int x=50,y=20,dx=4,dy=0;
public void paint(Graphics g){
g.setColor(Color.white);
g.fillRect(0,0,200,200);
g.setColor(Color.black);
g.drawLine(0,150,200,150);
g.setColor(Color.red);
g.drawString("Click me", x, y);
}
// Runnable な Applet はまず, start メソッドが呼ばれる
public void start(){
// スレッドができていない時はここで作成する
if(th==null){ th=new Thread(this); th.start();}
}
// stop メソッドを作っておかないと, WWWブラウザで別のページに行っても動き続けてしまうことがある.
public void stop(){
if(th!=null){ th.stop(); th=null;}
}
// Runnable な Appletでは, run メソッドが実行の主体となる
public void run(){
while(th.isAlive()){
dy=dy+2;x=x+dx;y=y+dy;
if(x<10){ x=10+(10-x); dx= -dx; }
else if (x>150){ x=150-(x-150); dx= -dx; }
if(y>150){ y=150-(y-150); dy= -dy;}
// 画面の更新. これを忘れると変更の結果が表示されない
repaint();
// Threadクラス のsleep メソッドで ミリ秒単位の sleep(休止) を指定できる.
try { Thread.sleep(200); }
catch(InterruptedException e){}
}
}
}
アプレットの中でアニメーション(リアルタイムゲームを含む)をするのは結構
面倒である.まず,アプレットにはmainがない(書いても呼ばれない)ので,明
示的にスレッドを作る必要がある.アプレットは Applet クラスのサブクラス
で作らなくてはいけないので,Runnable インタフェースを継承することになる.
mainがないのでどこで,実行のためのスレッドを作るかということだが, initメソッドの中で作るのも可能だが,一度スレッドを作るとWWWブラウザで 他のページに移っても動き続けるというのはやっかいなので,アプレットのあ るページに移動した際に呼ばれる start メソッドの中で作って,アプレット のあるページから抜けた際に呼ばれる stop メソッドの中で終了させるのが一 般的である.BallGame.java をアプレットとして書き直したのが以下 のプログラムである.
// <applet code=BallGameApplet width=300 height=600></applet>
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
public class BallGameApplet extends Applet implements KeyListener, MouseListener, Runnable{
// ボールの座標,速度のx, y成分の宣言
int ball_x=100,ball_y=100,ball_vx=16,ball_vy=12;
// ボールの大きさ
int ball_size=20;
// 画面の幅,高さ
int width=300, height=600;
// バーの座標
int bar_x=0, bar_y=550;
// バーの速度
int bar_vx=0;
// バーの幅,高さ
int bar_width=60,bar_height=10;
// BallGameクラスのコンストラクタ
// アニメーションを行うためのスレッド
public Thread th=null;
// WWWブラウザがアプレットを含むページに来たときに呼ばれる.
public void init(){
System.out.println("init is called");
addKeyListener(this);
addMouseListener(this);
requestFocus();
}
public void start(){
System.out.println("start is called");
// スレッドができていない時はここで作成する
if(th==null){ th=new Thread(this); th.start();}
}
public void stop(){
System.out.println("stop is called");
// スレッドがある時はスレッドを止める
// なお, 最近のJavaではこの呼び出しは
// 推奨されない
if(th!=null){ th.stop(); th=null;}
}
// 上のstartメソッドの中で,th.start()が呼ばれるとスレッドからこの
// メソッドが呼ばれる.
public void run(){
for(;;){
// 0.1秒(100ミリ秒)スリープする
try { th.sleep(100); }
catch(Exception e){}
// ボール,バーの移動をおこなう
timeTick();
}
}
void timeTick(){
// バーの移動
bar_x=bar_x+bar_vx;
// 左端から飛び出そうになったら左端に合わせる
if(bar_x<0) bar_x=0;
// 右端から飛び出そうになったら右端に合わせる
else if(bar_x+bar_width >width) bar_x=width-bar_width;
// ボールの移動.古い座標を保存
int old_x=ball_x;
int old_y=ball_y;
// 速度に従って,次の座標を決定
ball_x=ball_x+ball_vx;
ball_y=ball_y+ball_vy;
// 左端から飛び出そうになったら,反射させる
if(ball_x<0){
ball_x= -ball_x;
ball_vx= -ball_vx;
}
// 右端から飛び出そうになったら,反射させる
else if(ball_x >width-ball_size){
ball_x=(width-ball_size)-(ball_x-(width-ball_size));
ball_vx= -ball_vx;
}
// 上端から飛び出そうになったら,反射させる
if(ball_y<0.0){
ball_y= -ball_y;
ball_vy= -ball_vy;
}
// バーのある線を通過しそうになったら,
else if(ball_y >bar_y && old_y<=bar_y){
// バーのある線を横切るX座標を計算
int x=old_x+(ball_x-old_x)*(bar_y-old_y)/(ball_y-old_y);
// バーと接触している場合は反射させる.
if(bar_x <= x && x<=bar_x+bar_width){
ball_y=bar_y-(ball_y-bar_y);
ball_vy= -ball_vy;
}
}
// 下端から飛び出そうになったら,反射させる
else if(ball_y > height){
ball_vy=-ball_vy;
}
// 再表示
repaint();
}
public void update(Graphics g){
paint(g);
}
public void paint(Graphics g){
// 描画色を黒にする.
g.setColor(Color.black);
// 全体を塗り潰す
g.fillRect(0,0,width,height);
// 描画色を白にする.
g.setColor(Color.white);
// 警告文字列を(0,200)の点を左下にして描く
g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
// 描画色を赤にする.
g.setColor(Color.red);
// ボールを描く
g.fillOval(ball_x,ball_y,ball_size,ball_size);
// オフスクリーンイメージへの描画色を青にする.
g.setColor(Color.blue);
// バーを描く
g.fillRect(bar_x,bar_y,bar_width,bar_height);
}
// キーが押された時にこのメソッドが呼ばれる.
public void keyReleased(KeyEvent e){}
public void keyTyped(KeyEvent e){}
public void keyPressed(KeyEvent e){
System.out.println("keyPressed("+e+")");
int key=e.getKeyChar();
// 'h' のキーが押された時は,バーの移動速度を -10に
if(key=='h'){
bar_vx= -10;
}
// 'l' のキーが押された時は,バーの移動速度を 10に
else if(key=='l'){
bar_vx=10;
}
// 'j' のキーが押された時は,バーの移動速度を 0に
else if(key=='j'){
bar_vx=0;
}
else if(key=='q'){
System.exit(0);
}
}
public void mouseReleased(MouseEvent e){}
public void mousePressed(MouseEvent e){
System.out.println(e);
}
public void mouseClicked(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
}
アプレット