週間プログラミング①:ライフゲーム

記事
IT・テクノロジー

ライフゲームとは

 ライフゲームというものをご存知でしょうか。
 ライフゲームとは、生物の栄枯盛衰をシミュレートするモデルです。まずはどのようなものか、完成品を見てみましょう。

simulation.gif


黄色いマスが生物がいるところ、紫の部分はなにもないところです。シミュレートしてみると、黄色い部分がウヨウヨと動いていることがわかります。移動したり、その場でとどまったり、特徴的な模様を創り出す場所もあります。
 ライフゲームでは、生物は次のようなルールで動いています。

あるマスに生物がいるとき
   →もしそのマスの周囲に生物が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")