今回は、生物の生死を決めるルール設定用のパネルを追加し実際に1ステップづつ時間を進めて生物の生死を判定していきます。これで、ライフゲームとしては一通り完成ですね。
ライフゲームのルールは、あるセルの周囲8セル(8近傍)に注目してその8セルにある生物を合計した数によってセルの生物の生死を決める、というものでした。可能性としては、生物が生まれる、現状維持、生物を殺す、の3つがあり、それを8セルにいる生物の数0〜8に対応させるわけです。例えば、一般的なライフゲームでは2−3ルールと呼ばれる以下のようなルールになっています。
| 周辺の生物数 | 生物の状態 |
|---|---|
| 0 | 死 |
| 1 | 死 |
| 2 | 維持 |
| 3 | 生 |
| 4 | 死 |
| 5 | 死 |
| 6 | 死 |
| 7 | 死 |
| 8 | 死 |
このルールでは、あるセルの周辺8セルの生物数が2の時は現状維持で3なら新しくそのセルに生物が発生(すでに生物がいればそのまま)、そしてそれ以外は死(生物がいなければそのまま)というわけですね。このようなルールを設定するためには、0〜8の9通り分の生・死・維持3状態を指定するパネルがあれば良いでしょう。今回は、状態をチェックボックスで指定し、3×9=27個のチェックボックスを持つパネルCRule2クラスを定義しました。
class CRule2 extends Panel
CRule2クラスはPanelから派生させます。このクラスには、チェックボックスの二次元配列rchk[3][9]とチェックボックスを生物の数毎にグループ化するCheckboxGroup配列rchkb[9]を持たせました。そして、コンストラクタでチェックボックスを生成して生物の数毎にグループ化します。
CRule2() {
int i,j;
rchk=new Checkbox[3][9];
rchkb=new CheckboxGroup[9];
String str[]=new String[3];
str[0]="O";
str[1]="X";
str[2]="-";
lb=new Label[9];
setBackground(new Color(160,192,192));
setLayout(new GridLayout(9,4,1,2));
for (i=0;i<9;i++) { // チェックボックス作成
lb[i]=new Label(String.valueOf(i),Label.CENTER);
add(lb[i]);
rchkb[i]=new CheckboxGroup();
for (j=0;j<3;j++) {
rchk[j][i]=new Checkbox(str[j],false,rchkb[i]);
add(rchk[j][i]);
}
}
}
これで、0〜9の生物の数毎にラベルとグループ化されたチェックボックスが生成されました。チェックボックスはグループ化されているので、ある数の所で生・死・維持のいずれかを指定するとその状態のチェックボックスだけがチェックされます(画面上ではOが生、Xが死、-が維持に対応)。
では、ルールの設定に移りましょう。ルールは生・死・維持という3つの状態であらわされるので、int形1次元配列rules[9]であらわす事にします。今回は、−1が死、0が維持、1が生としました。パネルでルールを設定したら、チェックボックスの状態を読み取ってルールを設定するわけですね。このルール配列はセル(生物)を管理するデータクラスCLdat2に持たせ、パネルクラスにはその配列を渡すようにしましょう。パネルクラスでは、渡された配列にルールを設定するgetRules(int [])を定義します。
public void getRules(int rules[]) {
for (int i=0;i<9;i++)
if (rchk[0][i].getState())
rules[i]=1;
else if (rchk[1][i].getState())
rules[i]=-1;
else
rules[i]=0;
}
周辺の生物数がiの時には、rchk[0][i]が生、rchk[1][i]が死、rchk[2][i]が維持に対応するので、rules[i]に対応する数値を設定していきます。また、逆にrules[]からチェックボックスの状態を設定するsetRules()も定義しておきましょう。
public void setRules(int rules[]) {
for (int i=0;i<9;i++)
if (rules[i]==1)
rchk[0][i].setState(true);
else if (rules[i]==-1)
rchk[1][i].setState(true);
else
rchk[2][i].setState(true);
}
ルールが設定できたら、そのルールでセルを更新します。セルの更新は、データクラスで行うようにしましょう。処理としては、セルの周り8セルの生物数を合計し、その数に応じたrules[]配列を見てそのセルの内容を決定する、という感じになるでしょう。端のセルは、もう一方の端とつながっているものとします。
周辺の8セルの生物を合計する処理は、「自分を中心とする3×3=9セル−自分のセル」の生物数として求めました。また、処理の過程でセルの内容が変わってしまうので最初に処理前のセルを保存しておき、セルの生物数は保存したセルの方を参照して求める事にします。以上の事から、セルの更新処理は
public void next() { // 1ステップ進める
int i,j,k,l,n,x,y;
for (i=0;i<height;i++) // 処理前のセル配列を保存
for (j=0;j<width;j++)
blifes[j][i]=lifes[j][i];
for (i=0;i<height;i++) // 各セルをルールに基づいて更新
for (j=0;j<width;j++) {
n=0; // 生物数カウンタクリア
for (k=-1;k<2;k++) // セルを中心とする3×3の生物数合計
for (l=-1;l<2;l++) {
if (j+l==-1)
x=width-1;
else if (j+l==width)
x=0;
else
x=j+l;
if (i+k==-1)
y=height-1;
else if (i+k==width)
y=0;
else
y=i+k;
n+=blifes[x][y];
}
n-=blifes[j][i]; // 注目セル分を引く
if (rules[n]==-1) // ルールで生死判定
lifes[j][i]=0;
else if (rules[n]==1)
lifes[j][i]=1;
}
}
となりました。
マウスクリックで生物を配置・除去できるようにするためにセル表示パネルクラスCLdisp2クラスにMouseListenerインターフェースを追加し、マウスクリックを処理するmouseClicked()メソッドを実装します。処理としては、マウスがクリックされたら対応するセルの生物の状態を反転してセルを描画しなおすだけです。
public void mouseClicked(MouseEvent e) { // マウスクリック
int x=(int)((e.getX()-1)/vr);
int y=(int)((e.getY()-1)/vr);
if (ldat.getLife(x,y)==0)
ldat.setLife(x,y,(byte)1);
else
ldat.setLife(x,y,(byte)0);
repaint();
}
アプリケーションの本体には、セルの更新を行うボタンbtNextを追加しました。このボタンをクリックすると、ルール設定パネルで設定されたルールをデータクラスに渡して更新処理を行います。
if (e.getSource()==btNext) {
rl.getRules(ldat.getRules());
ldat.next();
disp.repaint();
}
ソースファイルをCLife2.javaというファイル名にしてJDK1.1以上でコンパイルし、CLife2.classを実行してください。セルにマウスクリックで生物を配置して、ルールをチェックボックスで設定したらNextボタンでセルを更新してみてください。