[Next] [Prev] [Back] [Home]

■ UpperHand Gameを作る


コンピュータが考えている間に割り込めるようにする

前回までに先読みが行なえるようにしましたが、 コンピュータが考えている間はボタンの入力をいっさい受け付けませんでした。 今回は、スレッドを使ってコンピュータが考えている間でも ボタンの入力を受け付けるように変更します。

スレッドの基本操作

スレッドは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インタフェースを 実装するように修正します。 スレッドの生成はゲーム開始時に行います。 生成されたスレッドでは、

という処理を行うことにします。

//=============================================================================
//  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に対応していません。

Javaソースコード (Ver. 1.1a13)



[Next] [Prev] [Back] [Home]
Satoshi Kobayashi (koba@yk.rim.or.jp)