一番下にすぐに試せるエクセルファイル(無料)と全コードが載っています。
Cellular automaton
状態を持つセルによってセル・オートマトンは構成され、時間で個々のセルの状態が変化します。その中でもライフゲーム(Conway’s Game of Life)は代表格です。ライフゲームは以下の4つの規則(のみ)に従ってセルの生死が決まります。
- 誕生・・・死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
- 生存・・・生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
- 過疎・・・生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
- 過密・・・生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。
この4つをプログラミングで表現し、世代を重ねていくごとにセルの生死が遷移していきます。人工生命Alifeともいわれるコンピューター上で生命のシュミレーションです。単純な規則ですが意外とドラマチックな生命の一生を見ることができます。
動機
昨年、感染爆発の不安の中一時帰国した際、隔離中のホテルでyoutubeでたまたま流れてきたやくしまるえつこ「わたしは人類」。どうなるかわからない時勢もあり「わたしは人類。滅んじゃった」の歌詞で刺さり、画面を見たらなんとライフゲームでした。「あっ!」と、進化論に傾倒していた大学1年生のときにライフゲームに強い感銘を受けたのが思い出されました。進化論の研究者になるぞと意気込んでいたあの頃…と比べる今の姿。努力したのに期待に沿えず申し訳ないが、ともあれ、ライフゲームは作れるようになったぞ!
エクセルで実装
エクセルで生と死を表現するために、生死は1(生)か0(死)として、セルの条件付き書式で1はセルの色を黒くしました。100列100行のマス上で計算し、100世代まで遷移するようにしました。
ポイント
コードは長いので一番下に置いておきます。
- データフレーム(table、table2)を2つ作成。tableは現在の状態を保存し、table2は現在の状態から計算した次の世代の状態を保存する。次世代の計算が終わったら、現在の状態を次世代の状態にアップデート(table=table2)して、それをベースに更に次の世代を計算する。
- generationを1世代から100世代までfor loopで計算していく。
- データフレームのrow(1~100行)とcolumn(1~100列)を1つづつずらして次の世代を計算する。端っこだけ計算方法が異なるので条件分岐を行う。具体的には、4つ角のセルは隣接するセルが3つ。4つ角以外の端のセルは隣接セル5つ。その他のセルは、隣接するセルは8つ。
- 世代ごとにデータフレームをセルに書き込み、sleepを10マイクロ秒することでその世代を描写する
コアなアルゴリズム
条件1・・・現在セルが生きている状態なら
隣接するセルが3つor2つなら次の世代で生存。それ以外は次の世代で死亡。
条件2・・・現在セルが死んでいる状態なら
隣接するセルが3つなら次の世代で誕生。それ以外は次の世代で死亡。
If currentState = 1 Then '条件1
If numberOfNeighbor = 3 Or numberOfNeighbor = 2 Then
table2(row, col) = 1
Else
table2(row, col) = 0
End If
ElseIf currentState = 0 Then '条件2
If numberOfNeighbor = 3 Then
table2(row, col) = 1
Else
table2(row, col) = 0
End If
End If
シュミレーションのやり方
最初に初期状態をあなたが書き込みます。つまり、適当に1(生存)のセルを作り、その後、上記のアルゴリズムを走らせてその遷移を観察します。初期状態によって、さまざまなドラマを見ることができます。
無限にある初期状態のうち、有名な初期状態があります。その状態から開始すると興味深いパターンを見ることができます。
たった7つの生命から始まるドラマ
途中で花みたいな形を見せますが、結局は絶滅してしまいます。
つぎは直線の形をした生命からスタートします。
一匹の巨大な昆虫(蝶?)が変態を繰り返し、左下に子供みたいなものが生まれて移動していきます。その後も、小さい個体となり活動を続けます。
無限に動き続ける形も見つかっています。
上記に似ているのですが、無限に動き続けかつ子供を産み続ける形が発見されています。
また、生命の誕生の条件を変更することでパターンが大きく変わります。
条件2・・・現在セルが死んでいる状態なら
隣接するセルが1つなら次の世代で誕生。それ以外は次の世代で死亡。
街のような構造が生まれました。子育てをしなくても子供がどんどん繁殖するような生物でしょうか。
条件2・・・現在セルが死んでいる状態なら
隣接するセルが2つなら次の世代で誕生。それ以外は次の世代で死亡。
群れがでてきて、それぞれが生息地を拡大していきます。途中、合流したあとはまじりあってそのまま繁栄を続けます。
条件2・・・現在セルが死んでいる状態なら
隣接するセルが3つなら次の世代で誕生。それ以外は次の世代で死亡。(基本型)
ある程度、地域ができる。地域で活動をつづけるが時々、近くの地域と反応を起こして別の形になる。
条件2・・・現在セルが死んでいる状態なら
隣接するセルが4つなら次の世代で誕生。それ以外は次の世代で死亡。
すぐに過疎的な分布になり、地域で細々と生存を続けていく。周りの地域と交流はない。
全コード
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
Sub lifegame()
Dim col As Integer
Dim row As Integer
col = 1
row = 1
Dim table() As Variant
ReDim table(1 To 100) As Variant
table = Range(Cells(1, 1), Cells(100, 100)).Value
Dim table2(1 To 100, 1 To 100) As Variant
Dim currentState As Integer
Dim numberOfNeighbor As Integer
Dim generation As Integer
generation = 1
For generation = 1 To 100
For col = 1 To 100
For row = 1 To 100
currentState = table(row, col)
If col = 1 Then
If row = 1 Then
numberOfNeighbor = table(row + 1, col) + table(row, col + 1) + table(row + 1, col + 1)
ElseIf row >= 2 And row <= 99 Then
numberOfNeighbor = table(row - 1, col) + table(row - 1, col + 1) + table(row, col + 1) + table(row + 1, col + 1) + table(row + 1, col)
ElseIf row = 100 Then
numberOfNeighbor = table(row - 1, col) + table(row - 1, col + 1) + table(row, col + 1)
End If
ElseIf col >= 2 And col <= 99 Then
If row = 1 Then
numberOfNeighbor = table(row, col - 1) + table(row, col + 1) + table(row + 1, col - 1) + table(row + 1, col) + table(row + 1, col + 1)
ElseIf row >= 2 And row <= 99 Then
numberOfNeighbor = table(row - 1, col - 1) + table(row - 1, col) + table(row - 1, col + 1) + table(row, col - 1) + table(row, col + 1) + table(row + 1, col - 1) + table(row + 1, col) + table(row + 1, col + 1)
ElseIf row = 100 Then
numberOfNeighbor = table(row, col - 1) + table(row, col + 1) + table(row - 1, col - 1) + table(row - 1, col) + table(row - 1, col + 1)
End If
ElseIf col = 100 Then
If row = 1 Then
numberOfNeighbor = table(row, col - 1) + table(row + 1, col - 1) + table(row + 1, col)
ElseIf row >= 2 And row <= 99 Then
numberOfNeighbor = table(row - 1, col) + table(row - 1, col - 1) + table(row, col - 1) + table(row + 1, col - 1) + table(row + 1, col)
ElseIf row = 100 Then
numberOfNeighbor = table(row - 1, col) + table(row - 1, col - 1) + table(row, col - 1)
End If
End If
If currentState = 1 Then
If numberOfNeighbor = 3 Or numberOfNeighbor = 2 Then
table2(row, col) = 1
Else
table2(row, col) = 0
End If
ElseIf currentState = 0 Then
If numberOfNeighbor = 3 Then
table2(row, col) = 1
Else
table2(row, col) = 0
End If
End If
Next row
Next col
Range(Cells(1, 1), Cells(100, 100)).Value = table
Sleep 10
table = table2
Next generation
End Sub