前回までに先読みが行なえるようにしましたが、 コンピュータが考えている間はボタンの入力をいっさい受け付けませんでした。 今回は、スレッドを使ってコンピュータが考えている間でも ボタンの入力を受け付けるように変更します。
スレッドはJavaプログラムの実行単位です。 複数のスレッドを生成し、それぞれのスレッドに並列に処理を行わせることができます。
スレッドを生成する方法は2つあります。
1つはThread
クラスのサブクラスを定義し、
そのインスタンスを生成する方法です。
もう1つはRunnable
インタフェースを実装したクラスを
Thread
クラスのコンストラクタに渡す方法です。
最初の方法の場合、Thread
クラスのサブクラスの
run()
メソッドがスレッドが実行すべき処理となります。
2番目の方法の場合、Runnable
インタフェースを実装したクラスの
run()
メソッドがスレッドが実行すべき処理となります。
スレッドの実行を開始するにはThread
オブジェクトの
start()
メソッドを呼び出します。
すると、先ほど説明したrun()
メソッドの処理が開始されます。
run()
メソッドからリターンするか
Thread
オブジェクトのstop()
メソッドが呼び出されるまで
スレッドの処理が他のスレッドと並列に実行されます。
スレッド間の同期をとるときには
wait()
メソッドと
notify()
を使います。
run()
メソッド内でwait()
メソッドを呼び出すと
スレッドの実行が停止します。
run()
メソッドを持つオブジェクトの
notify()
メソッドが呼び出されるとスレッドは実行を再開します。
wait()
、notify()
を呼び出すときには スレッドにロックをかける (synchronized
ブロック内で実行する) 必要があります。
では、UpperHand Gameにスレッドの処理を組み込みましょう。
UpperHand
クラスをRunnable
インタフェースを
実装するように修正します。
スレッドの生成はゲーム開始時に行います。
生成されたスレッドでは、
wait()
を呼び出し人間の着手を待つ。
//============================================================================= // class UpperHand // Applet UpperHand を定義するクラス。 //============================================================================= public class UpperHand extends Applet implements Runnable { ... }
インスタンス変数に、スレッドを格納する変数gameCtrl
と
現在スレッドがwait()
を呼び出して待ち状態になっているかどうかを示す
変数isYourMove
を追加しました。
//----------------------------------------------------------------------------- // インスタンス変数定義 //----------------------------------------------------------------------------- ... // ゲーム制御用スレッド private Thread gameCtrl; // 人間の手番のときtrue private boolean isYourMove;
//----------------------------------------------------------------------------- // public void run() // ゲームを制御するスレッドの処理を行う。 //----------------------------------------------------------------------------- public void run() { try { startGame(); // ゲームを開始する。 while (!game.isFinish()) { int p = game.nextPlayer(); // 次の手番を得る。 isYourMove = (player[p] == null); // 人間の手番か判定する。 if (isYourMove) { // 人間の手番の場合 // 人間の着手を待つ。 synchronized(this) {wait();} } else { // コンピュータの手番の場合 // 0.5秒休止する。 try {Thread.sleep(500);} catch (InterruptedException e) {} // 次の一手をコンピュータに選択させ、着手する。 game.makeMove(player[p].selectMove(game)); if (!game.isFinish()) { // ゲームがまだ終了していない場合 playSound(PUT_BALL_SOUND); // 玉を置いた音を出す。 } } } finishGame(); // 終局時の処理を行う。 } catch (InterruptedException e) {} finally { stopGame(); // ゲームを終了状態にする。 } }
交互に着手する処理をrun()
メソッド内で行うようにしたため、
nextMove()
は不要となりました。
また、インスタンス変数isStopGame
の代わりに
isYourMove
を使用するようにしたので、
isStopGame
を削除しました。
これらの変更にともないstartGame()
、stopGame()
を修正しています。
//----------------------------------------------------------------------------- // public void startGame() // ゲームを開始する。 //----------------------------------------------------------------------------- public void startGame() { // ゲーム中の表示に切り替える。 for (int p = UpperHandGame.FIRST; p <= UpperHandGame.SECOND; p++) { Panel panel = playerPanel[p]; ((CardLayout)panel.getLayout()).show(panel, "VIEW"); // プレーヤー選択メニューを隠す。 } ((CardLayout)buttonPanel.getLayout()).show(buttonPanel, "STOP"); // ゲーム中断ボタンを表示する。 initPlayer(); // プレーヤーを初期化する。 game.init(); // ゲームを初期化する。 }
//----------------------------------------------------------------------------- // public void stopGame() // ゲームを終了状態にする。 //----------------------------------------------------------------------------- public void stopGame() { isYourMove = false; // どちらの手番でもないためfalseを設定する。 // ゲーム終了状態の表示に切り替える。 for (int p = UpperHandGame.FIRST; p <= UpperHandGame.SECOND; p++) { Panel panel = playerPanel[p]; ((CardLayout)panel.getLayout()).show(panel, "MENU"); // プレーヤー選択メニューを表示する。 } ((CardLayout)buttonPanel.getLayout()).show(buttonPanel, "START"); // ゲーム開始ボタンを表示する。 }
action()
を修正し、開始ボタンが押されたときスレッドを生成/開始し、
中断ボタンが押されたときスレッドを終了/破棄するようにしました。
//----------------------------------------------------------------------------- // public boolean action(Event evt, Object arg) // 他のボタンからのイベントを処理する。 //----------------------------------------------------------------------------- public boolean action(Event evt, Object arg) { if (evt.target == startButton) { // ゲーム開始ボタンが押されたとき playSound(START_GAME_SOUND); gameCtrl = new Thread(this); // ゲームを制御するスレッドを生成する。 gameCtrl.start(); // ゲームを開始する。 return true; } else if (evt.target == stopButton) { // ゲーム中断ボタンが押されたとき playSound(STOP_GAME_SOUND); if (isYourMove) { // 人間の手番の場合 // 着手を強制的に完了したことを通知する。 synchronized (this) {notify();} } gameCtrl.stop(); // ゲームを終了する。 gameCtrl = null; // スレッドを破棄する。 return true; } return super.action(evt, arg); // スーパークラスに処理を任せる。 }
makeMove()
を修正し、人間の着手が完了したとき
notify()
を呼び出すようにしました。
//----------------------------------------------------------------------------- // public void makeMove(int p) // 位置pに着手する。 //----------------------------------------------------------------------------- public void makeMove(int p) { if (!isYourMove) { // 人間の手番でない場合 playSound(BAD_MOVE_SOUND); // 不正な着手のときの音を出す。 return; // 着手は行わない。 } game.makeMove(p); // 着手する。 if (!game.isFinish()) { // ゲームがまだ終了していない場合 playSound(PUT_BALL_SOUND); // 玉を置いた音を出す。 } synchronized (this) {notify();} // 着手が完了したことを通知する。 }
最後にstop()
メソッドを修正しましょう。
今までは何もしていなかったのですが、Appletのページから離れるときに
スレッドを終了/破棄するようにしました。
//----------------------------------------------------------------------------- // public void stop() // Appletのページから離れるときの処理を行う。 //----------------------------------------------------------------------------- public void stop() { if (gameCtrl != null) { if (isYourMove) { // 人間の手番の場合 // 着手を強制的に完了したことを通知する。 synchronized (this) {notify();} } gameCtrl.stop(); // ゲームを終了する。 gameCtrl = null; // スレッドを破棄する。 } }
Javaソースコード (Ver. 1.1a13)