コンピュータやソフトウェアのあれこれ@道民(&元道民)
Archive for 1月, 2012
ライブ壁紙でライフゲーム
1月 9th
前回はぶっきらぼうにライフゲームのコードだけ載せたので、
今回はライブ壁紙のコードも晒します。
ポイントは以下の通り
- LifeGameWallpaperEngineのコンストラクタで_lifeGameを初期化
- onSurfaceChangedで描画設定を初期化
- drawMainでイロイロしてる
あと、端末の向きが変わるとonSurfaceChangedが呼ばれるので、
ライフゲームの計算結果は引き継いだまま続行されるのがミソです。
ちなみに、このソースコードはgoogleの公式サイトからパクってます。
サンプルコード一覧のここ。
で、サンプルコードにあったタッチイベントを利用して、
そのタッチした場所に生命を誕生させるなんていうのは、
touchProcessに実装しています。
package your.domain.LifeGameWallpaper;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
//import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
public class LifeGameWallpaper extends WallpaperService {
private final Handler _handler = new Handler();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public Engine onCreateEngine() {
// Log.d( "lifegame", "onCreateEngine" );
return new LifeGameWallpaperEngine();
}
class LifeGameWallpaperEngine extends Engine {
private final long DRAW_INTERVAL = 1000 / 8; // 描画間隔
private int _screenW, _screenH;
private int _nx, _ny;
private int _x0, _y0;
private int _size;
private Point _offset = new Point( 0, 0 );
private Paint _paintBorn, _paintStay;
private LifeGame _lifeGame;
private byte[] _dst;
private float _touchX, _touchY;
private final Runnable _drawLifeGame = new Runnable() {
public void run() {
drawMain();
}
};
private boolean _visible;
public LifeGameWallpaperEngine() {
// ライフゲームの計算量に直結するので、動作を見ながらもっさりしないように決める
_lifeGame = new LifeGame( 80, 80 );
_dst = new byte[ _lifeGame.getWidth() * _lifeGame.getHeight() ];
{
// 初期パターンのコピー
final byte[][][] patterns = {
{ // ドングリ
{ 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0 },
{ 1, 1, 0, 0, 1, 1, 1 }
},
{ // ダイハード
{ 0, 0, 0, 0, 0, 0, 1, 0 },
{ 1, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 1, 1 }
}
};
byte[][] pattern = patterns[0];
int offsetX = (_lifeGame.getWidth() - pattern[0].length) / 2;
int offsetY = (_lifeGame.getHeight() - pattern.length) / 2;
_lifeGame.setPattern( pattern, offsetX, offsetY );
}
_lifeGame.exec( _dst );
_paintBorn = new Paint();
_paintBorn.setColor( Color.RED );
_paintStay = new Paint();
_paintStay.setColor( Color.YELLOW );
// 無効な座標で初期化
_touchX = _touchY = -1f;
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate( surfaceHolder );
setTouchEventsEnabled( true );
}
@Override
public void onDestroy() {
super.onDestroy();
_handler.removeCallbacks( _drawLifeGame );
}
@Override
public void onVisibilityChanged(boolean visible) {
_visible = visible;
if ( _visible ) {
drawMain();
}
else {
_handler.removeCallbacks( _drawLifeGame );
}
}
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
super.onSurfaceChanged( holder, format, width, height );
_screenW = width;
_screenH = height;
final int SIZE_MIN = 8;
{
// 端数切り上げしないと、
// 描画可能な要素数の計算がライフゲームの幅、もしくは高さを越えてしまう
int w = (int)Math.ceil( (float)_screenW / _lifeGame.getWidth() );
int h = (int)Math.ceil( (float)_screenH / _lifeGame.getHeight() );
// 長い方を正方形の高さとして採用する
_size = ( w < h ) ? h : w;
// 高さのクリップ
if ( _size < SIZE_MIN ) {
_size = SIZE_MIN;
}
}
// ライフゲーム空間のどの範囲を描画するか確定する
_nx = _screenW / _size;
_ny = _screenH / _size;
_x0 = (_lifeGame.getWidth() - _nx) / 2;
_y0 = (_lifeGame.getHeight() - _ny) / 2;
// 描画対象の中心に描画するための描画開始座標を計算
_offset = new Point(
(_screenW - (_nx * _size)) / 2,
(_screenH - (_ny * _size)) / 2
);
/*
Log.d( "onSurfaceChanged", "_screenW = " + _screenW );
Log.d( "onSurfaceChanged", "_screenH = " + _screenH );
Log.d( "onSurfaceChanged", "_size = " + _size );
Log.d( "onSurfaceChanged", "_nx = " + _nx );
Log.d( "onSurfaceChanged", "_ny = " + _ny );
Log.d( "onSurfaceChanged", "_x0 = " + _x0 );
Log.d( "onSurfaceChanged", "_y0 = " + _y0 );
Log.d( "onSurfaceChanged", "_offset.x = " + _offset.x );
Log.d( "onSurfaceChanged", "_offset.y = " + _offset.y );
*/
drawMain();
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
super.onSurfaceCreated( holder );
}
@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed( holder );
_visible = false;
_handler.removeCallbacks( _drawLifeGame );
}
@Override
public void onOffsetsChanged(
float xOffset, float yOffset,
float xOffsetStep, float yOffsetStep,
int xPixelOffset, int yPixelOffset) {
}
@Override
public void onTouchEvent(MotionEvent event) {
if ( event.getAction() == MotionEvent.ACTION_DOWN ) {
_touchX = event.getX();
_touchY = event.getY();
// Log.d( "touch", "X = " + _touchX + ", Y = " + _touchY );
}
super.onTouchEvent( event );
}
private void drawMain() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
long begin = 0, endDraw = 0, endLifeGame = 0;
try {
touchProcess();
c = holder.lockCanvas();
if (c != null) {
begin = System.currentTimeMillis();
_lifeGame.exec( _dst );
endLifeGame = System.currentTimeMillis();
drawRectangles( c );
endDraw = System.currentTimeMillis();
}
} finally {
if ( c != null ) {
holder.unlockCanvasAndPost( c );
}
}
// Log.d( "lifegame", "becnch = " + (endLifeGame - begin) );
// Log.d( "draw", "becnch = " + (endDraw - begin) );
// Reschedule the next redraw
_handler.removeCallbacks( _drawLifeGame );
if ( _visible ) {
long delay = DRAW_INTERVAL - (endDraw - begin);
if ( delay < 0 ) {
delay = 0; // clip
}
_handler.postDelayed( _drawLifeGame, delay );
}
}
private void drawRectangles(Canvas c) {
c.drawColor( Color.BLACK ); // Clear
for (int iy=0; iy<_ny; iy++) {
final float n = _size;
float offsetY = (float)( _offset.y + (iy * _size) );
float offsetX = (float)( _offset.x );
int lineOffset = _lifeGame.getWidth() * (_y0 + iy);
for (int ix=0; ix<_nx; ix++) {
int status = _dst[ lineOffset + _x0 + ix ];
if ( status != 0 ) {
final Paint paint = ( status == 1 ) ? _paintStay : _paintBorn;
c.drawRect(
offsetX, offsetY,
(offsetX + n - 1), (offsetY + n - 1),
paint
);
}
offsetX += n;
}
}
}
private void touchProcess() {
if ( _touchX < 0f || _touchY < 0f ) {
return;
}
final byte[][] pattern = {
{ 1, 1, 1 },
{ 1, 1, 1 },
{ 1, 1, 1 }
};
float x = (_touchX - (float)_offset.x) / (float)_size;
float y = (_touchY - (float)_offset.y) / (float)_size;
int offsetX = _x0 + (int)x - (pattern[0].length / 2);
int offsetY = _y0 + (int)y - (pattern.length / 2);
_lifeGame.setPattern( pattern, offsetX, offsetY );
// タッチした座標の破棄
_touchX = _touchY = -1f;
}
}
}
こんな時間なので、まだ呑んでないよ。
おしまい。
Android始めました
1月 9th
と言っても、何かアプリをリリースした訳じゃないですが、
宿題を出されたので、ライブ壁紙を一生懸命作ってみました。
ライブ壁紙でライフゲーム
最初は「日経ソフトウェア」の記事を写経してたんですが、
まぁ、それでうまくいったんですが、例外が起きてちょっとアレでして。
そこで、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
php5.4 RC5 を手元の Mac に入れてみた
1月 6th
そろそろ時代が php5.4 になるということで、さすがにちょっと触っておくかと、とりあえずインストールをしてみました。 build-in-server があるので、とりあえずは apache とか無視してホームディレクトリに置きました。
mkdir -p local/src
cd local/src
curl -LO http://downloads.php.net/stas/php-5.4.0RC5.tar.bz2
tar xf php-5.4.0RC5.tar.bz2
cd php-5.4.0RC5
./configure \
--prefix=$HOME/local \
--disable-cgi \
--enable-mbstring \
--without-pear
make && make test && make install
と、この状態で だけ書いた test.php を用意して php -S 0.0.0.0:8080 test.php を実行し、ブラウザで http://127.0.0.1:8080/ にアクセスして phpinfo が表示されたのでとりあえずこれで良いかなあ。いろいろ遊んでみたい。
Debian にも入れようと思って、弊社 CTO のパッケージを使おうと思ったんですが、なんか Apache 系のなにかに依存しているエラーでちゃったので、とりあえず放置。 Apache 入れたほうがいいのかな。
http://php.marvel.strk.jp/deb/
wget -nd -r -A .deb http://php.marvel.strk.jp/deb
dpkg -i *.deb
[perl][perl+web]Test::LeakTrace についてメモ書き
1月 5th
ハマったので未来の自分のためにメモ書きを残しつつ、今年もよろしくお願い致しますm(_ _)m。
Test::LeakTraceの特性
Test::LeakTrace で、以下のテストは通る。
my %x; no_leaks_ok { $x{x} = undef };
しかし以下のテストは通らない。
my @x; no_leaks_ok { push @x, undef };
これは、@x にエントリが増えた分がリークとしてカウントされるから。じゃあ前者はなぜ大丈夫なのかというと、no_leaks_ok はブロックを2回実行して2回目でリークをカウントするせい。1回目の実行で%xのバケットが1つ増えるが、2回目以降は再利用するのでリークとしてカウントされない。
あと、次のコードは "not ok 1 - leaks 1 <= 0" と言ってる癖にダンプを表示しない。
my %x; my $k = 0; no_leaks_ok { $k = ($k + 1) % 2; $x{$k} = undef; };
これはダンプを作る際にさらに改めて2回ブロックを実行する仕組みになっているため。この現象は2回目の呼び出しまではメモリが増加するけど3回目以降は増加しないような場合に起こる。
Test::LeakTrace はこのように複数回ブロックを呼び出してリークを判定・調査する特性があるので、メモリの使い方が安定した状態で呼んだ方が懸命である。
Test::LeakTrace と AnyEvent
AnyEvent のコードはコールバックを中心に書くため、リークしやすい。なので、 Test::LeakTrace でリークしてないことを確かめるのはよいプラクティスだと思う。
しかし AnyEvent は実際の初期化処理を任意のメソッドの使用時にまで遅らせている。そのため、任意のタイミングで初期化処理が走る可能性があり、それが Test::LeakTrace の結果に影響を及ぼすかもしれない。よって、 AnyEvent::detect を先に呼んで初期化を終わらせておいた方が変にハマらずに済む(気がする)。
クエ鍋のこと
1月 3rd
クエ鍋のこと
元旦になぜか宅急便が届き誰かなーと思ったら、差出人は弟でした。実家に帰ってきている弟からなぜ届け物が...?
さっそく弟に聞いてみたら、クエを食べてみたかったので、一本釣りしたものを買ってみたとのことでした。お値段はなんと2万円...、弟はずいぶん羽振りがいいなw
そんなこんなで晩ご飯は「クエ鍋」にしてみたところ、なんと全員があまり口に合いませんでしたw。買ってきた弟すら「駄目だ...」と言ってたしね。なんというか独特の生臭さがあり僕ら家族は苦手でした。(実はおかんの料理方法が悪かっただけなのかなw)
弟の最後の捨て台詞は
「もう二度と買わない!! ソイの鍋のほうが旨いわ!」
でした。
そんな正月二日目でした。
明日はリベンジの「黒ソイ鍋」にする予定です。
2012年目標
1月 1st
2012年目標
よい初日の出とおみくじに恵まれた一年の始まりとなりました。 今年はどんな年になるか楽しみです。
昨年は心・技・体の内、心と体を鍛えることにしていたので、今年は「技」の 配分を多めにしていやっていこくと思います!
「技」に関する目標
- お仕事
情報処理技術者試験の「プロジェクトマネージャ試験」か「情報セキュリティスペシャリスト試験」の 勉強に取り組んでみる。(あわよくば合格したいぜ!)
- アジャイル札幌の運営と参加をする。そして、よく勉強してよく飲む!
- Ruby札幌勉強会の初皆勤賞に挑戦する
- 英語の勉強を強化する(既に高校3年生くらいになっているような)。できたらRspecの読書会も再会させたいところ。
「体」に関する目標
- 筋トレ
筋トレを継続すること。今年は体脂肪率を14%と上半身のビルドアップを目指す! ちなみに体を動かすとストレス発散にもなるので「心」もすっきりするのでいいこと だらけ。
- フットサルを定期的にやる
- ボディスコップ(冬の日課)
- キャンプ部を発足させたい
「心」に関する目標
- 友人とよく遊びよく飲む!ここ数年でいろんなことを話せる友達と仲間が増えたので、このつながりを大切にしたい。
こんな私ですがみなさま今年もどうぞよろしくお願いします。
そして、みなさまにとりよい一年でありますように!
2011年ふりかえり
1月 1st
2011年ふりかえり
1月
- 「平鍋さんとアツク語る会 2011 札幌」を開催。鈴木さんや合田さん、東京エレクトロンチームのみなさんとの出会い。今年は平鍋さんに始めてもらって、平鍋さんに締めてもらった1年だった感じなんだなあ。20110129#p01
2月
- 名古屋Ruby会議02に参加。ここで貰ったものも大きいなあ。宿題の一つは、何とか来年のはやいうちに形にできるようにがんばろう。20110226#p01
3月
- 2年間続けてきた東京との行き来が終わって、そして、えにしテックを今のフォーメーションにすることを決めた月。大きな大きな区切りの月。いろんなことを考えた月。
4月
- 新しく社会にでた数十人の新人さんと過ごす日々。たくさん色んなことを勉強させてもらった大切な時間でした。みんな元気でやっているかなあ。 20110502#p01
5月
- 入籍。ひとつずつ、2人のペースで進んでいきたいと思っています。
6月
- アジャイル札幌発進。平鍋さんとアツク語る会から始まった、人と人のつながりや気持ちの積み重ねが"かたち"を作っていって、そして「アジャイル札幌」という"なまえ"を持ったのでした。
7月
- The Final RubyKaigi 20110718
8月
- このあたりからは、ほとんどずっと会社のことについて考え続ける日々だったような気がする。
9月
- 独立して以来の少し長い休みを貰ってフランス旅行へ。とても良い時間だったので、来年もまたどこかに旅行にいけると良いな。
10月
- 引き続き会社のことについて考え続ける日々。「全文検索エンジン groonga を囲む昼下がり@札幌」を開催できたこと、soupcurry.info をリニューアルできたこと、4年前の続きを4年前のメンバーでできたこと、なんかは今に繋がっているなあ。
11月
- ここから年末にかけては、公私でいろいろとあり本当に大変だったなあ...。
- 関西Ruby会議03 については、まだ日記をかけてない体たらくなんだけれど、とてもとても良い時間を過ごさせてもらったのでした。
- mrkn の壮行会。まるで札幌Ruby会議のような、札幌らしい壮行会だったと思います。
- アジャイル札幌特別編『平鍋さんを囲んで』を開催。ここまでで、丁度今年が1周する感じ。そして物語は2012年へつづく。
12月
- logaling-command のリリース。ひとつずつ。
- えにしテックのみんなで未来についての話をたくさん話をできたのがとても良かった。しっかりと考えて、みんなと良い時間を過ごしていきたいと思います。
今年は本当にいろんなことがあって、そんな中で何とか今日までやってこれたんだなあと思う年の瀬。そして、周りにいてくださる方々の力、助けてもらっている感覚を強く感じた年の瀬でした。来年、それからその先の未来で、そうして頂いている気持ちにしっかりとお返しをしていけるような、そんなぼくらでありたいなあ、というのが来年の目標です。2012年も引き続きどうぞよろしくお願いします。

