パソコン日記

気づいたこと まとめてみる

ライフゲームをPythonでつくってみる

ライフゲームとは

ライフゲーム(Conway's Game of Life)とはWikipediaによると「1970年ににイギリスの数学者ジョン・ホートン・コンウェイ(John Horton Conway)が考察した生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲームである。」とのことです。今回は難しい事はあまり考えず、ライフゲームを作っていきたいと思います。

ライフゲームのルール

まず、ライフゲームのルールを確認していきます。 ライフゲームは基本的に下の基盤のような格子の上で行われます。

f:id:lxxv:20170720233206j:plain

一つの格子はセル(細胞)と呼ばれており、それぞれのセルには「生」と「死」の状態があります。 あるセルの次のステップ(世代)の状態は注目するセルの周囲8つのセルの状態によって決まります。 今回プログラムで使うルールは以下にしめすwikipediaに乗っていたルールに従いたいと思います。

誕生 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。

生存 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。

過疎 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。

過密 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

Pythonでプログラムを作ってみる

では早速プログラムを考えていきます。 Pythonのバージョンは2.7を用いましたが、2.xでしたら動くと思います。 まず、基盤のような格子を作って生きます。 生物が生きている場と考えてフィールドと呼びましょう、 このフィールドは2次元のリストで表します。 とりあえず、テストなので二次元リストはそのままプログラム上に書いてしまいましょう。

field = [[0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,1,0,0,0,0,0],
         [0,0,0,0,1,1,0,0,0,0],
         [0,0,0,0,1,1,0,0,0,0],
         [0,0,0,0,0,1,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0]]

ここで、生きている状態、死んでいる状態を区別するのに

「1」を生きている状態

「0」を死んでいる状態

とします。 例で表したfieldのリストはいくつかが「1」、つまり生きている状態になっていますね。 ここで、リストの要素の参照の仕方ですがこのリストの座標は次のようになっています。

f:id:lxxv:20170720234244j:plain

例えばx=3,y=4の座標の要素を参照したいときは

field[4][3]

とすればいいですね。

基盤を表すことができたので、次は次の世代の状態を決めるプログラムを考えてみます。 プログラムは以下のようになります。

xsize = 10  #格子のサイズ x軸方向
ysize = 10  #格子のサイズ y軸方向
maxstep = 10
num = 0
xmax = xsize-1
ymax = ysize-1
 
while num < maxstep:
    newField = [[0 for i in range(xsize)] for j in range(ysize)] #新しい格子
    for y in range(ysize):
        for x in range(xsize):
            #注目するセルの周りで生きているセルを数える
            aliveCell = 0
            #左上
            if x != 0 and y != 0:
                if field[y-1][x-1] == 1:
                    aliveCell += 1
            #左
            if x != 0:
                if field[y][x-1] == 1:
                    aliveCell += 1
            #左下
            if x != 0 and y != ymax:
                if field[y+1][x-1] == 1:
                    aliveCell += 1
            #上
            if y != 0:
                if field[y-1][x] == 1:
                    aliveCell += 1
            #下
            if y != ymax:
                if field[y+1][x] == 1:
                   aliveCell += 1
            #右上
            if x != xmax and y != 0:
                if field[y-1][x+1] == 1:
                    aliveCell += 1
            #右
            if x != xmax:
                if field[y][x+1] == 1:
                    aliveCell += 1
            #右下
            if x != xmax and y != ymax:
                if field[y+1][x+1] == 1:
                    aliveCell += 1
 
            #注目するセルが死んでいる
            if field[y][x] == 0:
                #誕生
                if aliveCell == 3:
                    newField[y][x] = 1
            #注目するセルが生きている
            elif field[y][x] == 1:
                #生存1
                if aliveCell == 2:
                    newField[y][x] = 1
                #生存2
                elif field[y][x] == 1 and aliveCell == 3:
                    newField[y][x] = 1
                #過疎
                elif field[y][x] == 1 and aliveCell <= 1:
                    newField[y][x] = 0
                #過密
                elif field[y][x] == 1 and aliveCell >= 4:
                    newField[y][x] = 0
 
    field = list(newField)
    num += 1

まず、何世代シミュレーションを回すのかは

maxstep = 10

で定義し、while文を用いました。 1回シミュレーションを回すごとにnumを1ずつ増やしていき、maxstep回実行したら終了しましょう。

次にnewFieldというfieldと同じ大きさで0で初期化したリストを生成します。

[[0 for i in range(xsize)] for j in range(ysize)] #新しい格子

今回はリスト内包表記で生成しています。 これは次の世代のフィールドを一時的に格納するためのリストです。

次にfieldのリストをはじから参照していきます。

for y in range(ysize):
       for x in range(xsize):

for文を使って、その中で range(ysize)で[0,1,2,・・・,ysize-1]のリストを生成し、順番にyに代入していきます。 for文をxに対してもう一度使うことによってリストに端からアクセスすることができます。

注目するセルの回りの生きているセルを数えるために

aliveCell = 0

を定義します。

端っこのセルに対して周期境界条件を使うなどいろいろあると思いますが、 今回は端っこのセルの外側には何もないとします。 端のセルに対してさらに端のセルを参照しようとすると、リストの範囲の外を 参照することになってしますので、if文を使って次のようにしました。

#左上
if x != 0 and y != 0:
    if field[y-1][x-1] == 1:
        aliveCell += 1

これを周囲の8個のセルに対して行います。 これでシミュレーションは完了します。

シミュレーションができたら、どんな風になっているか表示したいですよね。 pygameOpenGLを使ってグラフィカルに表示する方法もありますが、 今回はシンプルにターミナル上に表示するだけにしましょう。 これは関数を定義しました。

 #格子を書き出す関数
def printField(field):
    for line in field:
        for n in line:
            if n == 1:
                print '■' + ' ',
            else:
                print '□' + ' ',
        print ''

関数内の■や□の文字を変えることによって、いろいろな記号で表すことができます。 これらをまとめたプログラム(lifegame.py)が次のようになります。

#-*-coding:utf-8-*-
 
#格子を書き出す関数
def printField(field):
    for line in field:
        for n in line:
            if n == 1:
                print '■' + ' ',
            else:
                print '□' + ' ',
        print ''
 
#格子の生成
#0が死んでいるセル
#1が生きているセル
#格子の初期座標
field = [[0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,1,0,0,0,0,0],
         [0,0,0,0,1,1,0,0,0,0],
         [0,0,0,0,1,1,0,0,0,0],
         [0,0,0,0,0,1,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0]]
 
print "============ Generation 0 ============"
printField(field)
 
xsize = 10  #格子のサイズ x軸方向
ysize = 10  #格子のサイズ y軸方向
maxstep = 10
num = 0
xmax = xsize-1
ymax = ysize-1
 
while num < maxstep:
    newField = [[0 for i in range(xsize)] for j in range(ysize)] #新しい格子
    for y in range(ysize):
        for x in range(xsize):
            #注目するセルの周りで生きているセルを数える
            aliveCell = 0
            #左上
            if x != 0 and y != 0:
                if field[y-1][x-1] == 1:
                    aliveCell += 1
            #左
            if x != 0:
                if field[y][x-1] == 1:
                    aliveCell += 1
            #左下
            if x != 0 and y != ymax:
                if field[y+1][x-1] == 1:
                    aliveCell += 1
            #上
            if y != 0:
                if field[y-1][x] == 1:
                    aliveCell += 1
            #下
            if y != ymax:
                if field[y+1][x] == 1:
                   aliveCell += 1
            #右上
            if x != xmax and y != 0:
                if field[y-1][x+1] == 1:
                    aliveCell += 1
            #右
            if x != xmax:
                if field[y][x+1] == 1:
                    aliveCell += 1
            #右下
            if x != xmax and y != ymax:
                if field[y+1][x+1] == 1:
                    aliveCell += 1
 
            #注目するセルが死んでいる
            if field[y][x] == 0:
                #誕生
                if aliveCell == 3:
                    newField[y][x] = 1
            #注目するセルが生きている
            elif field[y][x] == 1:
                #生存1
                if aliveCell == 2:
                    newField[y][x] = 1
                #生存2
                elif field[y][x] == 1 and aliveCell == 3:
                    newField[y][x] = 1
                #過疎
                elif field[y][x] == 1 and aliveCell <= 1:
                    newField[y][x] = 0
                #過密
                elif field[y][x] == 1 and aliveCell >= 4:
                    newField[y][x] = 0
 
    field = list(newField)
    num += 1
    print "============ Generation " + str(num) + "  ============"
    printField(newField)
    print ""

プログラムは

python lifegame.py

で実行できます。 試しにこれで実行してみると、次のようになります。

f:id:lxxv:20170720235130p:plain

見づらいですね。 表示する関数をこうしましょう。

 #格子を書き出す関数
def printField(field):
    for line in field:
        for n in line:
            if n == 1:
                print '■' + ' ',
            else:
                print ' ' + ' ',
        print ''

すると..

f:id:lxxv:20170720235236p:plain

こうなります! さっきよりも見やすいですね。 ちなみにこれは「ヒキガエル」と呼ばれるパターンで周期が2の振動子(2回ごとに同じ図形に戻る)です。 とりあえず、ライフゲームの基本はできたので色々試してみてください!