ライフゲームとは
ライフゲームというものをご存知でしょうか。
ライフゲームとは、生物の栄枯盛衰をシミュレートするモデルです。まずはどのようなものか、完成品を見てみましょう。
黄色いマスが生物がいるところ、紫の部分はなにもないところです。シミュレートしてみると、黄色い部分がウヨウヨと動いていることがわかります。移動したり、その場でとどまったり、特徴的な模様を創り出す場所もあります。
ライフゲームでは、生物は次のようなルールで動いています。
あるマスに生物がいるとき
→もしそのマスの周囲に生物が2匹居れば生存
→3匹ならそのまま
→1匹なら死亡
→4匹以上でも死亡
あるマスに生物がいないとき
→もしそのマスの周囲に生物が2匹以下か4匹以上ならそのまま
→3匹いればそのマスに生物が誕生
このような単純なルールに従っているにも関わらず、全体としては非常に複雑な動きが見られます。このような、全体の動きが個々の単純な動きにとどまらないようなシステムを「複雑系」と呼びます。
プログラミング
この記事ではPythonを使ってライフゲームを作っていきます。
ライフゲームを作るに当たり、次のような部品が必要になります。
①シミュレートするフィールドを作る
②シミュレーションを行う
③結果を保存し、gifにする
さて、まずは全体がどのようになっているのか見てみましょう。
#モジュール類
from random import randint
import matplotlib.pyplot as plt
import numpy as np
import os
from PIL import Image
#シミュレーション結果の出力先
di = "./"
pics = di + "pictures/"
gif = di + "simulation_results/"
#のフォルダ作成
try:
os.mkdir(pics)
os.mkdir(gif)
except:
pass
#結果を図に起こし、保存
def make_fig(world,t):
picsfname = pics + str(t) + ".png"
pictitle = "Generation No." + str(t)
plt.figure()
plt.title(pictitle)
plt.imshow(world)
plt.savefig(picsfname)
plt.close()
#自分の周囲8マスを探し、生物の有無を確かめる
def serch(world,x,y):
#カウンタ
c = 0
#探索
for i in range(x-1,x+2):
for j in range(y-1,y+2):
c += world[i][j]
c -= world[x][y]
#周囲に3匹入れば生存、2匹ならそのまま、それ以外は死亡
if c == 3:
return 1
elif c == 2:
return world[x][y]
else:
return 0
#パラメータ入力
d = int(input("枠の大きさ"))
n = int(input("何世代?"))
world = []
#フィールドの生成
#生物の初期位置は乱数で決める
for i in range(d):
tmp = []
for j in range(d):
if randint(0,100)%7 == 0:
tmp.append(1)
else:
tmp.append(0)
world.append(tmp)
#初期状態を記録
make_fig(world,0)
#メインループ
for t in range(1,n):
newworld = []
for i in range(d):
tmp = []
for j in range(d):
tmp.append(0)
newworld.append(tmp)
for i in range(1,d-1):
for j in range(1,d-1):
newworld[i][j] = serch(world,i,j)
for i in range(d):
for j in range(d):
world[i][j] = newworld[i][j]
make_fig(world,t)
#./picturesに保存した図を繋ぎ合わせてgifにする
frames = []
giffname = gif + "simulation.gif"
#画像の読み込み
for i in range(n):
picname = pics + str(i) + ".png"
new_frame = Image.open(picname)
frames.append(new_frame)
#gif化
frames[0].save(giffname,
format='GIF',
append_images=frames[1:],
save_all=True,
duration=400,
loop=0)
print("finished")
そこそこ長いプログラムですが、一つ一つは難しくありません。
以下では詳細を説明します。
モジュール類
from random import randint
import matplotlib.pyplot as plt
import os
from PIL import Image
乱数生成のためのrandom、
図表作成のためのmatplotlib、
ディレクトリ操作のためのos、
gifを作るためのPILをインポートします。
①シミュレートするフィールドを作る
まずどのようなフィールドを作るのか標準入力から受け取ります。
パラメータは枠の大きさと何世代シミュレートするのかです。
#パラメータ入力
d = int(input("枠の大きさ"))
n = int(input("何世代?"))
world = []
フィールドを生成します。
初期状態の生物の分布は乱数を使って適当に定めます。
乱数を生物の分布を示す0/1に落とし込むため、
横枠の長さ分だけ乱数を発生させ、
次にその乱数が7の倍数なら1を
それ以外なら0を一時変数tmpに代入し、
フィールドを示すリストworldにまとめて格納します。
#フィールドの生成
#生物の初期位置は乱数で決める
for i in range(d):
tmp = []
for j in range(d):
if randint(0,100)%7 == 0:
tmp.append(1)
else:
tmp.append(0)
world.append(tmp)
後ほど作る、状態記録のための関数make_figで初期状態を記録します。
#初期状態を記録
make_fig(world,0)
最後にシミュレーション結果の出力もここで設定しておきましょう。
try文で./pictures/というディレクトリがあればそこに画像を保存し、なければ作成します。シミュレーション結果は./simulation_results/に保存します。
#シミュレーション結果の出力先
di = "./"
pics = di + "pictures/"
gif = di + "simulation_results/"
#のフォルダ作成
try:
os.mkdir(pics)
os.mkdir(gif)
except:
pass
②シミュレーションを行う
ライフゲームのキモはここです。
まずは
あるマスに生物がいるとき
→もしそのマスの周囲に生物が2匹居れば生存
→3匹ならそのまま
→1匹なら死亡
→4匹以上でも死亡
あるマスに生物がいないとき
→もしそのマスの周囲に生物が2匹以下か4匹以上ならそのまま
→3匹いればそのマスに生物が誕生
というルールを実現するためにマスの周囲を確認し、そのマスの状態をどう遷移させるのか定める関数を作ります。
#自分の周囲8マスを探し、生物の有無を確かめる
def serch(world,x,y):
#カウンタ
c = 0
#探索 forの範囲をこのように定めることで、角に来ても
#枠外を探索することがない
for i in range(x-1,x+2):
for j in range(y-1,y+2):
c += world[i][j]
#自分自身は探索しない
c -= world[x][y]
#周囲に3匹入れば生存、2匹ならそのまま、それ以外は死亡
if c == 3:
return 1
elif c == 2:
return world[x][y]
else:
return 0
あとはこのルールを全マスに対し、設定した世代だけ回せば完了です。
一世代ごとに状態をmake_figで記録します。
#メインループ
for t in range(1,n):
newworld = []
for i in range(d):
tmp = []
for j in range(d):
tmp.append(0)
newworld.append(tmp)
for i in range(1,d-1):
for j in range(1,d-1):
newworld[i][j] = serch(world,i,j)
for i in range(d):
for j in range(d):
world[i][j] = newworld[i][j]
make_fig(world,t)
③結果を保存し、gifにする
まず結果をmatplotlibで図に起こします。
def make_fig(world,t):
#画像の名前を設定します。
#例えば、./pictures/1.png のような名前で保存されます。
#./pictures/はディレクトリ名なので自動的にこの画像は
#./pictures/に入ることになります。
#このように連番で名前をつけるのは、分かりやすくする以上に、
#結果が世代でソートされた状態で保存されるため、
#後にgif化しやすいからです。
picsfname = pics + str(t) + ".png"
pictitle = "Generation No." + str(t)
#枠を生成
plt.figure()
#図の上に表示するタイトル
plt.title(pictitle)
#2次元平面上にプロット
plt.imshow(world)
#保存
plt.savefig(picsfname)
plt.close()
最後に、./pictures/にある画像をまとめて拾ってgif化します。
#画像の在り処を格納するframesリストを作成し
frames = []
#シミュレーション結果は./simulation_results/simulation.gif
#で保存します。
giffname = gif + "simulation.gif"
#for文で画像を次々に読み込みます
for i in range(n):
picname = pics + str(i) + ".png"
new_frame = Image.open(picname)
frames.append(new_frame)
#それらをまとめてgif化します。
frames[0].save(giffname,
format='GIF',
append_images=frames[1:],
save_all=True,
duration=400,
loop=0)
#プログラムが終了したことを知らせる
print("finished")