ハッカーになろうや模造クリスタル『ゲーム部』2で言及されてたライフゲームが面白そうだったので書いてみた。ライフゲームはセル平面上(要は方眼紙のこと)で生命をシミュレートするモデルです。
ライフゲームのルール
- セルには生と死の二つの状態がある。
- 時間(世代)が経過すると、あるルールに従ってセルの状態が変わる。
- あるルールとは次の4つ。
- 過疎による死: 自身が生で、周りの8セルのうち1セル以下が生のとき、次の世代で死ぬ。
- 生存: 自身が生で、周りの8セルのうち2〜3セルが生のとき、次の世代で生き続ける。
- 過密による死: 自身が生で、周りの8セルのうち4セル以上が生のとき、次の世代で死ぬ。
- 誕生: 自身が死で、周りの8セルのうち3セルが生のとき、次の世代で生になる。
コードで表現しやすそうなルールだ。疑似コードで書いてみるとこんな感じ?
class Cell
def next_alive?
alive_count = 周囲.count { |item| item.alive? }
# 過疎
return false if self.alive? && alive_count <= 1
# 生存
return true if self.alive? && 2 <= alive_count && alive_count <= 3
# 過密
return false if self.alive? && 4 <= alive_count
# 誕生
return false if self.dead? && alive_count == 3
false
end
end
で、これを1世代ごとに全てのセルに対してチェックすればいけそう。
Ruby で実装
https://github.com/oieioi/lifegame.rb
こんな感じに動きます。JoyDivisionっぽい見た目になった。
上に示した次世代の生死判定はこんな感じのコードになりました。
lib/lifegame/game.rb#L47-L78
# あるセルが次のターンに生きてるか確認する
def next_alive?(x, y)
target = self[x, y]
# 隣接セルの座標
adjoining = [
[x - 1, y - 1],
[x , y - 1],
[x + 1, y - 1],
[x - 1, y],
# [x , y],
[x + 1, y],
[x - 1, y + 1],
[x , y + 1],
[x + 1, y + 1],
]
dead_or_live = adjoining.map { |point|
n_x, n_y = point
self[n_x, n_y]&.alive?
}.compact
live_count = dead_or_live.count { |live| live }
if target.dead?
# 3の時のみ誕生
live_count == 3
else
# 2,3の時のみ生き残る
(2..3).include?(live_count)
end
end
JavaScript (with React)で実装
CLI だとインタラクティブなのがやりづらいのでJSでも書いてみた。
https://github.com/oieioi/lifegame.js
デモはこちら: https://dreamy-lumiere-0f384d.netlify.com/
こちらは全てのセルを二次元配列で受け取って新しい生死の二次元配列を返すようにした。
src/lib/LifegameLogic.js#L1-L18
function nextCells(cells) {
return cells.map((line, x)=> {
return line.map((alive, y) => {
// 周囲を調べる
const aliveCount = getAdojoiningPositions(x, y).filter((position) => {
const [x,y] = position;
if (!cells[x]) return false;
return cells[x][y]
}).length
if (alive) {
// 周囲の生き残りが2,3のとき生存
return aliveCount === 2 || aliveCount === 3;
} else {
return aliveCount === 3
}
});
});
}