携帯 Java アプリの具体的な開発手順 −携帯 Java 技術情報−
サンプルプログラム:ライフゲーム
ライフゲームは、生死を表すセルが置かれた 2 次元の盤面が疎密の条件により状態遷移するゲームです。
LifeGameApplication : 画面を管理するアプリクラス
Display.setCurrent メソッドに Frame クラスのインスタンスを渡すと、画面がその内容に切り替わります。
public class LifeGameApplication extends IApplication { public static final int STATE_TITLE = 0; public static final int STATE_GAMESTART = 1; public static final int STATE_EDIT = 2; public static final int STATE_SAVE = 3; public static final int STATE_LOAD = 4; public static final int STATE_CHANGESIZE = 5; private LifeGame lifeGame; private LifeGameThread thread; public LifeGameApplication() { lifeGame = new LifeGame(); } public void start() { Display.setCurrent(new LifeGameMainPanel(this)); } /** * 状態(画面)遷移は必ずこのメソッドを通して行われる. * * @param state int STATE_???? */ public void changeState(int state){ switch(state){ case STATE_GAMESTART: LifeGameMainCanvas canvas = new LifeGameMainCanvas(this,lifeGame); Display.setCurrent(canvas); thread = new LifeGameThread(canvas); thread.start(); break; case STATE_TITLE: if(thread != null){ thread.interrupt(); thread = null; } Display.setCurrent(new LifeGameMainPanel(this)); break; case STATE_EDIT: LifeGameEditCanvas edit = new LifeGameEditCanvas(this,lifeGame); Display.setCurrent(edit); edit.setCurrent(); break; case STATE_SAVE: Display.setCurrent(new SavePanel(this,lifeGame)); break; case STATE_LOAD: Display.setCurrent(new LoadPanel(this,lifeGame)); break; case STATE_CHANGESIZE: Display.setCurrent(new ChangeSizePanel(this,lifeGame)); break; } } }
LifeGame : ライフゲームの盤面
盤面の配置は matrix 変数に保存します。next メソッドで次の盤面に遷移し、paint メソッドで画面の描画をします。コンストラクタで最初の配置を設定します。
public class LifeGame { public static final int CELL_WIDTH = 4; public static final int CELL_HEIGHT = 4; private static final int MATRIX_WIDTH = 50; private static final int MATRIX_HEIGHT = 50; private int width = MATRIX_WIDTH; private int height = MATRIX_HEIGHT; private boolean[][][] matrix = new boolean[2][width+2][height+2]; private int current = 0; public LifeGame() { //初期配置設定. matrix[0][20][26] = true; matrix[0][21][26] = true; matrix[0][21][27] = true; matrix[0][25][27] = true; matrix[0][26][25] = true; matrix[0][26][27] = true; matrix[0][27][27] = true; } public boolean[][] getMatrix(){ return matrix[current]; } public int getWidth(){ return width; } public int getHeight(){ return height; } /** * 配置盤面の大きさをセットする. */ public void setSize(int width_, int height_){ boolean[][] m = new boolean[width_+2][height_+2]; int minw = (width < width_) ? width : width_; int minh = (height < height_) ? height : height_; width = width_; height = height_; for(int i=0;i<minw;i++){ for(int j=0;j<minh;j++){ m[i][j] = matrix[current][i][j]; } } matrix[current] = m; matrix[(current == 0) ? 1 : 0] = new boolean[width+2][height+2]; } /** * ライフゲームのルールは次のとおり * 生きているセルに対して,周りに生きているセルが 2,3 個の場合は生き. * それ以外は死ぬ. * 死んでいるセルに対して,周りに生きているセルが 3 個の場合は生き. * それ以外は死ぬ. */ public synchronized void next(){ int next = (current == 0) ? 1 : 0; int life; for(int i=1;i<width+1;i++){ for(int j=1;j<height+1;j++){ matrix[next][i][j] = false; life = 0; for(int a=i-1;a<=i+1;a++){ for (int b = j - 1; b <= j + 1; b++) { if(a == i && b == j){ continue; } if (matrix[current][a][b]) { life++; } } } if(matrix[current][i][j]){ if(1 < life && life <= 3){ matrix[next][i][j] = true; } } else{ if(life == 3){ matrix[next][i][j] = true; } } } } current = next; } /** * 盤面をセットする.盤面の大きさは引数のものと合わせる. */ public synchronized void setMatrix(boolean[][] copy){ matrix[current] = copy; width = copy.length-2; height = copy[0].length-2; matrix[(current == 0) ? 1 : 0] = new boolean[width+2][height+2]; } /** * 現在の盤面の描画 */ public synchronized void paint(Graphics g) { //現在の盤面をmとする. boolean[][] m = matrix[current]; //盤面の外枠を描画. g.setColor(g.getColorOfName(g.RED)); g.drawRect(CELL_WIDTH-1,CELL_HEIGHT-1,width*CELL_WIDTH,height*CELL_HEIGHT); //描画色を青色に設定. g.setColor(g.getColorOfName(g.BLUE)); for(int i=1;i<width+1;i++){ for(int j=1;j<height+1;j++){ //生きているセルの場所に対してのみ,青い四角を描画. if(m[i][j]){ g.fillRect(i * CELL_WIDTH, j * CELL_HEIGHT, CELL_WIDTH-1, CELL_HEIGHT-1); } } } } }
LifeGameMainPanel : メインメニュー画面
ライフゲームを始める、配置を Edit する、Save する、Load する、盤面の大きさを変えるなどの状態遷移のためのボタンを用意します。
public class LifeGameMainPanel extends Panel implements SoftKeyListener, ComponentListener{ private LifeGameApplication appli; private Button startButton; private Button editButton; private Button saveButton; private Button loadButton; private Button changeSizeButton; public LifeGameMainPanel(LifeGameApplication appli_) { appli = appli_; //UIの配置 add(new Label("Life Game")); startButton = new Button("Start"); add(startButton); editButton = new Button("Edit"); add(editButton); saveButton = new Button("Save"); add(saveButton); loadButton = new Button("Load"); add(loadButton); changeSizeButton = new Button("Change size"); add(changeSizeButton); //ソフトキーの設定 setSoftLabel(Frame.SOFT_KEY_2,"終了"); setSoftKeyListener(this); setComponentListener(this); } /** * ボタンが押された場合,それぞれのボタンの受け持つ画面遷移を行う. */ public void componentAction(Component source, int type, int param) { if(type == ComponentListener.BUTTON_PRESSED){ if(source == startButton){ appli.changeState(LifeGameApplication.STATE_GAMESTART); } else if(source == editButton){ appli.changeState(LifeGameApplication.STATE_EDIT); } else if(source == saveButton){ appli.changeState(LifeGameApplication.STATE_SAVE); } else if(source == loadButton){ appli.changeState(LifeGameApplication.STATE_LOAD); } else if(source == changeSizeButton){ appli.changeState(LifeGameApplication.STATE_CHANGESIZE); } } } /** * ソフトキーが押されたときの処理 */ public void softKeyPressed(int softKey){ return; } /** * ソフトキーが離されたときの処理 */ public void softKeyReleased(int softKey){ switch(softKey){ case Frame.SOFT_KEY_1: break; case Frame.SOFT_KEY_2: appli.terminate(); break; } } }
LifeGameCanvas : アプリ内の全ての Canvas クラスの抽象クラス
LifeGameApplication と LifeGame のインスタンスを保持します。
LifeGameMainCanvas : ライフゲームの開始画面
next メソッドが 1 秒毎に 呼ばれ、画面が再描画され、盤面の状態は遷移します。
LifeGameThread : 盤面の状態を遷移させるスレッド
LifeGameMainCanvas インスタンスの next メソッドを 1 秒ごとに呼び出します。
public class LifeGameThread extends Thread{ private LifeGameMainCanvas canvas; public LifeGameThread(LifeGameMainCanvas canvas_) { canvas = canvas_; } public void run(){ try{ while (true) { Thread.sleep(1000); canvas.next(); } } catch(InterruptedException ex){ //NOP } } }
LifeGameEditCanvas : 盤面の配置を編集するクラス
カーソルを保持する変数、カーソルの移動などの処理を用意します。
LifeGamePanel : ライフゲームで使われるPanelクラスの親クラス
LifeGameApplication、LifeGame のインスタンスを保持し、ソフトキーの描画、押されたときの処理も含みます。子クラスとして、スクラッチパッドを管理する MemoryPanel、それを継承する SavePanel、LoadPanel、盤面の大きさを変える ChangeSizePanel を定義します。
LifeGameData : 盤面を表すクラス
盤面をスクラッチパッドに保存し、読み込みます。
携帯 Java プログラミング
画面描画
携帯 Java には 2 つの画面描画の方法があります。
描画方法 | 使うクラス |
---|---|
コンポーネントの配置 | Panel ( DoJa ), Form ( MIDP ) |
座標の指定 | Canvas |
コンポーネントを配置する方法は、メニューなどの画面には有効ですが、プロファイル毎にプログラミングが異なります。
座標指定の方法では、Canvas を継承するサブクラスで Canvas の抽象メソッド paint を実装します。
LifeGame の画面描画の例
Canvas クラスの持つ repaint メソッドを呼べば画面は再描画されます。
public class LifeGameMainCanvas extends LifeGameCanvas{ public LifeGameMainCanvas(LifeGameApplication appli, LifeGame game) { super(appli, game); setSoftLabel(Frame.SOFT_KEY_1,"戻る"); setSoftLabel(Frame.SOFT_KEY_2,"終了"); } public synchronized void paint(Graphics g) { //画面のロック.ダブルバッファリングを行う. g.lock(); //白色で画面を塗りつぶす. g.setColor(g.getColorOfName(g.WHITE)); g.fillRect(0,0,getWidth(),getHeight()); //ライフゲームの画面を描画. game.paint(g); //画面のロックを外す. g.unlock(true); } } public class LifeGame { /** * 現在の盤面の描画 */ public synchronized void paint(Graphics g) { //現在の盤面をmとする. boolean[][] m = matrix[current]; //盤面の外枠を描画. g.setColor(g.getColorOfName(g.RED)); g.drawRect(CELL_WIDTH-1,CELL_HEIGHT-1,width*CELL_WIDTH,height*CELL_HEIGHT); //描画色を青色に設定. g.setColor(g.getColorOfName(g.BLUE)); for(int i=1;i<width+1;i++){ for(int j=1;j<height+1;j++){ //生きているセルの場所に対してのみ,青い四角を描画. if(m[i][j]){ g.fillRect(i * CELL_WIDTH, j * CELL_HEIGHT, CELL_WIDTH-1, CELL_HEIGHT-1); } } } } }
ボタンなどに関するイベントの処理
DoJa のイベント処理は、画面のインスタンスにあるイベント処理メソッドで行います。
public class ChangeSizePanel extends LifeGamePanel implements ComponentListener{ private TextBox widthTextBox; private TextBox heightTextBox; /** * ラベル,テキストボックスなどを配置したあと,ボタンを1つ配置 * コンポーネントリスナーとして自らを設定. */ public ChangeSizePanel(LifeGameApplication appli, LifeGame game) { super(appli, game); add(new Label("Width :")); widthTextBox = new TextBox(String.valueOf(game.getWidth()),20,1,TextBox.NUMBER); add(widthTextBox); add(new Label("Height :")); heightTextBox = new TextBox(String.valueOf(game.getHeight()),20,1,TextBox.NUMBER); add(heightTextBox); add(new Button("Set")); setComponentListener(this); } /** * ボタンがおされた場合の処理を書く */ public void componentAction(Component source, int type, int param) { if(type == ComponentListener.BUTTON_PRESSED){ //押されるボタンはSetボタンのみ. try{ int width = Integer.parseInt(widthTextBox.getText()); int height = Integer.parseInt(heightTextBox.getText()); if (2 < width && 2 < height) { game.setSize(width, height); } } catch(NumberFormatException ex){ new Dialog(Dialog.DIALOG_ERROR,"Number format is invalid.").show(); } appli.changeState(LifeGameApplication.STATE_TITLE); } } }
public class LifeGameMainCanvas extends LifeGameCanvas{ /** * イベント処理用のメソッド. */ public void processEvent(int type, int param){ if(type == Display.KEY_RELEASED_EVENT){ switch(param){ case Display.KEY_SOFT1: appli.changeState(LifeGameApplication.STATE_TITLE); break; case Display.KEY_SOFT2: appli.terminate(); break; } } } }
スクラッチパッドへの保存と読み込み
Connector クラスが提供するストリームを使って、アプリ終了後もデータが保存されるスクラッチパッドを読み書きします。
public class MemoryPanel extends LifeGamePanel implements SoftKeyListener{ protected LifeGameData[] data; protected int len; protected MemoryPanel(LifeGameApplication appli, LifeGame game) { super(appli, game); } protected void load(){ DataInputStream dis = null; try { //ストリームを開く dis = Connector.openDataInputStream("scratchpad:///0"); //データの読み込み len = dis.readInt(); data = new LifeGameData[len + 1]; for (int i = 0; i < len; i++) { data[i] = new LifeGameData(dis); } //ストリームを閉じる dis.close(); } catch (IOException ex) { //データ読み込み失敗時は何も読み込まなかったことにする. ex.printStackTrace(); if (data == null) { data = new LifeGameData[1]; } if (dis != null) { try { dis.close(); } catch (IOException ex2) { ex2.printStackTrace(); } } } } protected void save(){ DataOutputStream dos = null; try{ //ストリームを開く dos = Connector.openDataOutputStream("scratchpad:///0"); //データの書き込み dos.writeInt(len); for(int i=0;i<len;i++){ data[i].write(dos); } //ストリームを閉じる dos.close(); } catch(IOException ex){ //書き込み失敗時の処理 if(dos != null){ try{ dos.close(); } catch(IOException ex2){ ex2.printStackTrace(); } len--; Dialog dialog = new Dialog(Dialog.DIALOG_ERROR,"error occuerred while saving."); dialog.show(); try{ dos = Connector.openDataOutputStream("scratchpad:///0"); dos.writeInt(len); for(int i=0;i<len;i++){ data[i].write(dos); } dos.close(); } catch(IOException ex2){ ex2.printStackTrace(); if(dos != null){ try{ dos.close(); } catch(IOException ex3){ //NOP } } } } } } }