Android始めました

と言っても、何かアプリをリリースした訳じゃないですが、
宿題を出されたので、ライブ壁紙を一生懸命作ってみました。


ライブ壁紙でライフゲーム

最初は「日経ソフトウェア」の記事を写経してたんですが、
まぁ、それでうまくいったんですが、例外が起きてちょっとアレでして。
そこで、googleの公式サイトにもサンプルソースが上がってたので、
今度は、そっちからコードをパクって仕上げました。

まずは、ライフゲームのクラス

package your.domain.LifeGameWallpaper;

public class LifeGame {
	private final byte
		STATE_DEAD = 0,
		STATE_STAY = 1,
		STATE_BORN = 2;

	private int _width;
	private int _height;
	private byte[] _buf;

	public LifeGame(int w, int h) {
		_width = w;
		_height = h;
		_buf = new byte[ _width * _height ];
		
		for (int i=0; i<_buf.length; i++) {
			_buf[i] = STATE_DEAD;
		}
	}

	public int getWidth() {
		return _width;
	}

	public int getHeight() {
		return _height;
	}

	public void setPattern(byte[][] pattern, int offsetX, int offsetY) {
		int x0 = offsetX;
		int y0 = offsetY;
		int x1 = offsetX + pattern[0].length;
		int y1 = offsetY + pattern.length;

		// 上下左右の端の行と列は計算対象外なので、
		// データを書き込まないようにクリップ(LifeGameの仕様)
		if ( x0 <= 0 ) { x0 = 1; }
		if ( y0 <= 0 ) { y0 = 1; }
		if ( _width  <= x1 ) { x1 = _width  - 1; }
		if ( _height <= y1 ) { y1 = _height - 1; }

		// 上下左右の端の行と列にかぶらない部分を初期化
		for (int iy=y0; iy<y1; iy++) {
			for (int ix=x0; ix<x1; ix++) {
				int idx = (iy * _width) + ix;
				if ( pattern[(iy - offsetY)][(ix - offsetX)] != 0 ) {
					_buf[idx] = STATE_STAY;
				}
			}
		}	
	}

	public void exec(byte[] dst) {

		// dstに結果を格納
		execLifeGame( dst );

		// 次の準備
		for (int i=0; i<_buf.length; i++) {
			_buf[i] = dst[i];
		}
	}

	private int getAroundOf(int index) {
		int ret = 0;
		index -= _width; // 1つ前のラインから走査するためのデクリメント
		for (int i=0; i<3; i++) {
			if ( _buf[index - 1] != STATE_DEAD ) { ret++; }
			if ( _buf[index    ] != STATE_DEAD ) { ret++; }
			if ( _buf[index + 1] != STATE_DEAD ) { ret++; }
			index += _width;
		}
		
		return ret;
	}

	private void execLifeGame(byte[] dst) {
		int iy = 1;
		int lineOffset = _width * iy;
		for (; iy<(_height-1); iy++) {
			for (int ix=1; ix<(_width-1); ix++) {
				int idx = lineOffset + ix;
				byte state = _buf[idx];
				int cnt = getAroundOf( idx );

				if ( state == STATE_DEAD ) {
					state = ( cnt == 3 ) ? STATE_BORN : STATE_DEAD;
				}
				else {
					state = ( (cnt-1) <= 1 || 4 <= (cnt-1) ) ? STATE_DEAD : STATE_STAY;
				}

				dst[idx] = state;
			}

			lineOffset += _width;
		}
	}

}

そもそも、ライフゲームって何かというと、
自分の中心に3×3の計9マスを対象として、
その中心が死滅状態で周囲に3匹の生存していれば誕生、
もしくは中心に1匹と周辺に2〜3匹入れば生命を維持、
中心の他に生命が1匹以下、もしくは4匹以上居れば
過疎、もしくは過密により死滅するというプログラムです。
興味のあるヒトは、「ライフゲーム」検索してください。
例えば、こんな感じ。

で、最初は120×120くらいで計算してたのですが、
Androidシミュレータ上だと100ms以上時間が掛かってて、
描画処理も描画する長方形の数依存で気持ち悪いことになってて、
結果的に80×80を採用することにしました。
あと、初回execが時間掛かるので、
Engineのコンストラクタであらかじめ一回呼ぶようにしました。
一応、実測なんですが、実機にAcerのA500を使って計測したところ、
Androidシミュレータより画面(*1)が広くなって不安だったのですが、
結果的にはライフゲームが10ms以下で、描画処理は3〜5msくらい。
8fpsなら安定して動いてるようです。
あと、描画に必要な座標計算をonSurfaceChangedで行っていたので、
端末の向きが変わっても問題なく動作してました。

そんなこんなで、未だにソースコードを晒すときは
お酒を呑まないと恥ずかしくてやってられないネコでした。
おしまい。

(*1) シミュレータは320×240, A500は1280×800

Leave a Comment