Android始めました

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


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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