前回までで生物を配置し世代を進める、というライフゲームの基本はできました。ただ、まだ使いやすいとは言い難いので、今回からは機能を追加しながら完成度・実用性を高めていきましょう。今回は、世代を自動的に進める機能と世代数・生物数を表示するパネルを追加してみます。
世代の自動更新は、毎回Nextボタンをクリックしなくても世代の更新を自動的に行う機能です。アプリケーションにAutoボタンを追加し、このボタンをクリックすると更新用のスレッドを作成してこのスレッドでデータクラスのnext()メソッドを呼び出しセルの状態を再描画するようにしましょう。スレッドを追加するには、アプリケーションクラスにRunnableインターフェースを追加しrun()メソッドを実装します。
class CLife3 extends Frame implements ActionListener,Runnable
Autoボタンは、スレッドの起動・停止兼用でスレッド動作時にはスレッド停止ボタン(Stopボタン)として機能するようにしました。このボタンのクリック処理では、スレッドが起動されていなければ、スレッドを起動しスレッドが動作中ならスレッドを終了させる、という処理を行います。
if (e.getSource()==btAuto) { // Auto/Stopボタン if (running) { // 自動更新中 running=false; // 終了フラグリセット try { // スレッド終了を待つ runner.join(); } catch(InterruptedException ex) {} finally { btAuto.setLabel("Auto"); btClose.setEnabled(true); btNext.setEnabled(true); btClear.setEnabled(true); disp.setEnabled(true); runner=null; } } else { // 自動更新スレッド起動 rl.getRules(ldat.getRules()); running=true; runner=new Thread(this); runner.start(); btAuto.setLabel("Stop"); btClose.setEnabled(false); btNext.setEnabled(false); btClear.setEnabled(false); disp.setEnabled(false); } }
実際にスレッドが実行するrun()は、スレッド終了フラグrunningがfalseになるまでループ内で世代更新・描画処理を行います。ここの処理は、Nextボタンクリック時の1世代更新とほぼ同じですね。ただし、セルの表示を行うdispの描画処理ではrepaint()だとうまく描画されないので、dispのGraphicsを取得して直接paint()を呼び出す事にしました。
public void run() { // 自動更新スレッド while (running) { ldat.next(); // 1世代更新 // 状態表示 drawState(); Graphics g=disp.getGraphics(); disp.paint(g); g.dispose(); } }
自動更新できるようになると、どんどん世代を進めるうちに今何世代かわかりにくくなるので世代数と生物数の表示用ラベルもつけました。世代数と生物数に関しては、データクラスで管理してデータクラスから取得するようにすれば良いですね。今回は、世代数step、生物数nlifeというメンバ変数をデータクラスldat3に追加し、その変数へのアクセスメソッドで世代数・生物数を取得する形にしました。この2つの変数は世代更新時や生物が配置された時などに更新され、現在の世代数・生物数を保持するようにします。
また、その他ではデータクラスにセルと世代数のクリアメソッドを追加し、そのメソッドを呼び出してセルを初期化するClearボタンを追加しました。さらに、今回からセルのデータは2次元配列ではなく1次元配列(byte [width*height])としています。
ソースファイルをCLife3.javaというファイル名にしてJDK1.1以上でコンパイルし、CLife3.classを実行してください。セルにマウスクリックで生物を配置して、ルールをチェックボックスで設定したらAutoボタンでセルを自動更新してみてください。