ライフゲームを作る

Systemtapのプログラム例として、できるだけ役に立たなさそうなものを選んでみました。
今回取り上げるのはライフゲームです。非常に単純なルールなので、わかりやすく、かつ(実用上)役に立ちません。
ライフゲームの基本的なルールは、Wikipediaによる

碁盤のような格子があり、一つの格子はセルと呼ばれる。各セルは8つのセルと接している。各セルには「生」と「死」の2つの状態があり、あるセルの次のステップ(世代)の状態は周囲の8つのセルの今の世代における状態により決定される。

誕生
死んでいるセルの周囲に3つの生きているセルがあれば次の世代では生きる(誕生する)。
維持
生きているセルの周囲に2つか3つの生きているセルがあれば次の世代でも生き残る。
死亡
上以外の場合には次の世代では死ぬ。

素晴らしく単純です。

実行コード

では、実際にSystemtapで書いてみたコードを示します。

#!/usr/bin/stap
# lifegame
global X_MAX=15, Y_MAX=15
global map, tmp
global t

function showupdate()
{
  print("\033[1;1H\033[J") # clear screen
  delete map
  foreach([x,y] in tmp) {
    printf("\033[%d;%dH", y+1, x+1) # move cursor
    print("o")
    map[x,y] = 1
  }
  delete tmp
}

function lookaround(x,y)
{
  return map[x-1,y-1] + map[x,y-1] + map[x+1,y-1] +
         map[x-1,y  ]       +        map[x+1,y  ] +
         map[x-1,y+1] + map[x,y+1] + map[x+1,y+1]
}

probe timer.ms(10) {
  t ++
  if (t == Y_MAX) {
    showupdate()
    t = -1
    next # skip to the next timer.
  }
  # updating just 1 line
  y = t
  for (x=0; x < X_MAX; x++) {
    c = lookaround(x,y)
    if (c == 3) tmp[x,y] = 1
    else if (c == 2 && [x,y] in map) tmp[x,y] = map[x,y]
  }
}

probe begin {
   tmp[6,5] = 1; tmp[5,6] = 1; tmp[4,7] = 1; tmp[5,7] = 1
   tmp[6,7] = 1
   showupdate()
   t = -1
}

mapとtmpが二次元配列になっていて、この上でセルの状態を更新していきます。
これらは配列なのでglobalにしています。あと、Y_MAXとX_MAXでマップの大きさを示しています*1

showupdate()関数

この関数で、mapの内容を更新する(tmpの方が新しい)とともに、画面に書き出しています。

  • Systemtapの特徴であるforeachを使うと、生きているセル(存在しているセル)だけを列挙できますので、表示する手間が最小限ですみます。
  • delete mapは、map配列そのものを削除するのではなく、map配列を空にするだけです。
  • 画面への書き出しにはANSIエスケープシーケンスを使っています。例えば、"\033[1;1H\033[J"は、「カーソルを左上に持ってきて、そこから最終行までクリアする」という意味になります*2
lookaround()関数

この関数は、指定されたセルの周囲8セルのいくつが生きているかを計算して返します。

  • Systemtapの配列は、単なる連想配列です。存在しないインデックスを与えても、単に0が返るだけで副作用はありません。これを利用すれば、プログラムを単純化できます。
probe timer.ms(10)

このプローブは、10ミリ秒毎に呼び出されます。10ミリ秒ごとにY軸方向に1ラインずつ処理を行って、すべてのラインを処理したら、showupdate()関数を呼ぶようになっています。

  • [x,y] in map というのは、map[x,y]が存在するかどうかを確認する式になります。存在すれば真、しなければ偽が返ります。
  • 1ラインずつ処理しているのは、一回のプローブハンドラ内で実行できる処理の数が限られているからです*3。この上限は、無限ループや実質的に非常に時間のかかる処理を検出するために設けられています。
probe begin

このプローブは、スクリプトの実効が開始される直前に呼び出されます。一般的には変数などの初期化処理をここで行います。

まとめ

  • Systemtapの画面表示制御には、ANSIエスケープシーケンスが使える。
  • Systemtapは二次元配列も使うことが出来る。また、foreachを使うと、無駄な配列要素にアクセスしなくて済む。
  • [...] in XXX は、XXX[...]という要素があるかどうかを調べる式である。
  • Systemtapが一回のプローブハンドラで実行できる処理には上限がある。
  • ライフゲームは結果を見るだけなので少しつまらない。

*1:Systemtapには定数がないので、global変数を定数として使っています

*2:"\033[2J"を使うと、画面をスクロールして消すため、ターミナルバッファが無駄に汚れるので避けるようにしました

*3:とはいえ、生きているセルの数が増えすぎると、showupdate()関数が上限にひっかかるのですが