pythonでライフゲームの結果をgifで保存する 

##この記事で扱うこと
　$n$番煎じのネタですが、この記事ではライフゲームの簡単な説明をし、pythonでシミュレートしてみます。そして各時代の状態を図として保存し、それをgifとして組み合わせて簡単なアニメーションを作ります。

##ライフゲームとは
　ライフゲームとは、簡単に言えば生物の生存状況をシミュレートするものです。
　平面上を格子で区切り、各マスは生物がいるか、いないのか、2通りの状態を保持しています。単位時間ごとに生物の分布に対し以下の法則で状態が遷移します。

・あるマスの自分を除く周囲の8マスに
　　もし　生物が1匹なら→死亡(過疎)
　　もし　2匹なら→そのまま(生存)
　　もし　3匹なら→そのマスに生物が生まれる(誕生)
　　もし　4匹以上なら→死亡(過密)　
　
　このような単純なルールなのにも関わらず、全体として見ると非常に面白い模様が生まれます、まず、本記事のプログラムから出力されたgifを見てみましょう。

![simulation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651939/99ca1267-22da-3f24-60fe-a322b2ac66ef.gif)
　　
 　生物が増えたり減ったりするだけでなく、特殊な模様を作ったり、移動したりします。みてるだけでなんか面白いです。
 個々の生物は単純な法則に従っているだけにも関わらず、全体としては個々の単純な動きのみには還元できないような複雑な動きをする…このようなシステムは複雑系と呼ばれています。

## プログラム
### モジュールのインポートと結果の出力先
```python:1.py
#モジュール類
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
```

### 結果を図に起こし、保存する
```python:2.py
#結果を図に起こし、保存
#引数はt時代の平面の状態とそれが何世代目なのかを示す値
def make_fig(world,t):
    picsfname = pics + str(t) + ".png"
    pictitle = "Generation No." + str(t)
    plt.figure()
    plt.title(pictitle)
#2次元平面上にプロット
    plt.imshow(world)
    plt.savefig(picsfname)
#図を表示させない
    plt.close()
```

### 周囲のマスを探索する

```python:3.py
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
```

### 初期状態のフィールド生成
```python:4.py
#生物の初期位置は乱数で決める
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)
```

### パラメータの受け取り
```python:5.py
#パラメータ入力
#枠の多さと繰り返す世代の数
d = int(input("枠の大きさ"))
n = int(input("何世代?"))
world = []
```

### メイン部
```python:4.py
#初期状態を記録
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)
```

### 画像をまとめてgifアニメーションを生成する
```python:6.py
#./picturesに保存した図を繋ぎ合わせてgifにする
frames = []
#./simulation_results/ に出力
giffname = gif + "simulation.gif"

# ./pictures/ の画像の読み込み
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)

#参考：https://blog.fantom.co.jp/2019/12/28/make-gif-animation-from-multiple-images/
```

###全体

```python:7.py
#モジュール類
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")
```