ライフゲームエディタをJavaアプリケーションに移植してみます。今回は、アプリケーションの設計をデータとその操作を行うデータクラスと表示・入力を行うGUIクラスに分離して、GUI部分も「部品(コンポーネント)」化するなど今風(?)のプログラミング手法を試してみる事にしましょう。
ライフゲームは2次元セルオートマトンなので、データクラスはセルの状態を記録する2次元配列が基本になるでしょう。そして、その配列と時間的に発展していく時の遷移ルール、ライフゲームの状態を保持する変数をいくつか、というデータ構造になると思います。また、操作に関してはルールに基づいて1ステップ進める処理とセル配列や状態変数に対するアクセスメソッドを定義する事になりそうですね。
今回は、セル配列とセルの大きさを記憶し、それらのデータに対するアクセスメソッドも定義したデータクラスを作ってみました。大きさは、コンストラクタで指定します。
class CLdat1 { // データクラス private byte lifes[][]; // セル配列 private int width,height; // 大きさ保存用変数 CLdat1(int w,int h) { // コンストラクタ width=w; height=h; // 指定された大きさでセル配列確保 lifes=new byte[width][height]; } public int getWidth() { return width; } public int getHeight() { return height; } public byte getLife(int x,int y) { return lifes[x][y]; } public void setLife(int x,int y,byte val) { lifes[x][y]=val; } }
GUIの方は、まずセルの状態を表示するパネルを作ってみましょう。今回は、データクラスのインスタンスからセルの情報を取得してそれを自分のメンバであるImageに描画・表示するクラスを作ってみました。このクラスは、単純な描画やマウスイベントの取得が中心になるのでCanvasクラスから派生させます。クラス名はCLdisp1としました。
class CLdisp1 extends Canvasこのクラスには、コンストラクタで表示するデータ(データクラスCLdat1のインスタンス)を指定します。そして、渡されたデータオブジェクトのgetWidth()、getHieght()を呼び出して大きさを取得し保存。表示する大きさが決まったら、その大きさと表示するときの拡大率vrを元に自分自身の大きさをsetSize()で設定しましょう。
CLdisp1(CLdat1 dat) { ldat=dat; // 描画対象のデータクラスを保存 // データクラスの大きさ保存 lwid=ldat.getWidth(); lhei=ldat.getHeight(); setSize(lwid*vr+2,lhei*vr+2); setBackground(Color.white); }
セルを描画するImageオブジェクトimgとimgに描画するためのGraphicsオブジェクトg_imgは最初にpaint()が呼び出された時に生成します。
public void paint(Graphics g) { if (img==null) { // 最初の描画時にイメージ作成 img=createImage(lwid*vr,lhei*vr); g_img=img.getGraphics(); } drawDat(); // 現在の状況をイメージに描画 g.drawImage(img,1,1,this); }
描画のためのimgが出来たら、データオブジェクトldatからセルの状態をgetLife()で取得して描画します。
private void drawDat() { // 現在の状況を描画 Color cl; for (int i=0;i<lhei;i++) for (int j=0;j<lwid;j++) { // 生物の有無で色を設定 if (ldat.getLife(j,i)==0) cl=Color.black; else cl=Color.green; g_img.setColor(cl); // 設定した色で(j,i)を塗りつぶす g_img.fillRect(j*vr+1,i*vr+1,vr-1,vr-1); } }
今回は、セル配列のある要素が0なら生物なし、1なら生物あり、という事にして、生物がありなら緑なければ黒の正方形で塗りつぶして行きます。これをセルの大きさ分だけ繰り返せば、イメージに現在のセルの状態が描画されるわけです。
というわけで、セルを表示するCLdisp1クラスは以下のようになりました。
class CLdisp1 extends Canvas { // 表示パネルクラス private CLdat1 ldat; private Image img; private Graphics g_img; private int vr=5; private int lwid,lhei; CLdisp1(CLdat1 dat) { ldat=dat; // 描画対象のデータクラスを保存 // データクラスの大きさ保存 lwid=ldat.getWidth(); lhei=ldat.getHeight(); setSize(lwid*vr+2,lhei*vr+2); setBackground(Color.white); } private void drawDat() { // 現在の状況を描画 Color cl; for (int i=0;i<lhei;i++) for (int j=0;j<lwid;j++) { // 生物の有無で色を設定 if (ldat.getLife(j,i)==0) cl=Color.black; else cl=Color.green; g_img.setColor(cl); // 設定した色で(j,i)を塗りつぶす g_img.fillRect(j*vr+1,i*vr+1,vr-1,vr-1); } } public void paint(Graphics g) { if (img==null) { // 最初の描画時にイメージ作成 img=createImage(lwid*vr,lhei*vr); g_img=img.getGraphics(); } drawDat(); // 現在の状況をイメージに描画 g.drawImage(img,1,1,this); } }
以上でセルのデータを扱うデータクラスと表示用のクラスが出来たので、適当なセルを作って表示してみるアプリケーションを作りましょう。今回のアプリケーションでは、64×64のセルデータと表示パネル、それにアプリケーションの終了ボタンを作ってみました。
class CLife1 extends Frame implements ActionListener { CLdat1 ldat; Panel pl; Button bt; CLdisp1 disp; CLife1() { setLayout(new BorderLayout()); ldat=new CLdat1(64,64); disp=new CLdisp1(ldat); ldat.setLife(6,6,(byte)1); ldat.setLife(7,6,(byte)1); ldat.setLife(8,6,(byte)1); bt=new Button("Close"); // ボタン生成 bt.addActionListener(this); // ボタンのイベントリスナ設定 pl=new Panel(); // 部品配置用パネル生成 pl.setBackground(Color.green); pl.add(bt); // パネルにボタン配置 add(disp,"Center"); add(pl,"South"); // パネルをフレーム上に配置 } public static void main(String args[]) { CLife1 app=new CLife1(); // アプリケーション生成 app.pack(); app.show(); app.repaint(); } public void actionPerformed(ActionEvent e) { if (e.getSource()==bt) { // ボタンクリックイベント dispose(); // アプリケーションフレーム破棄 System.exit(0); // 終了 } } }
ソースファイルをCLife1.javaというファイル名にしてJDK1.1以上でコンパイルし、CLife1.classを実行してください。