morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】matplotlibでライフゲームを作る

はじめに

皆さんはライフゲームとはご存じでしょうか?
生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲームのことです。
かなり単純なルールに基づいてセルの生死をシミュレーションします。
単純なルールにもかかわらず、複雑な動きをして面白いです。
今回はpythonで実際に作ってみようと思います。




ライフゲームのルール

以下を参考にしております。

ライフゲームではセルを格子状に配置します。
セルの状態は生か死かの2通りです。
各セルが以下のルールをベースに次の世代でのそのセルの状態が決まります。

  • 誕生:死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
  • 生存:生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
  • 過疎:生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
  • 過密:生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

上記のルールに従って世代を進めていき、どのように生命が誕生、死滅するのかシミュレーションします。

さっそく実装していきましょう。

ライフゲームの実装

まず初めに初期状態等を定義します。

初期状態の定義、ライブラリのインポート

今回、可視化はmatplotlibのアニメーションで行いますのでanimation保存用のライブラリ等も読み込んでおきます。

初期状態として、格子の高さ、幅及びアニメーションにする世代数を決めます。
全セルはboardという配列に保持しておき、配列の各要素は0(死)、1(生)とします。

初期状態の可視化までやってみます。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter

h = 25 # 格子の高さ
w = 25 # 格子の幅
gens = 300 # マックス世代数

board = np.random.randint(2, size =(h, w))

# 初期状態の可視化
fig, ax = plt.subplots()
im = ax.imshow(board)
plt.show()

結果は以下。

各セルの初期状態はとりあえずランダムに0と1を入力しておきます。
imshowで配列の可視化の方法については以前の記事で詳しく紹介しましたので、参照ください。

初期化と各種定義はこれで終了です。

盤面の評価用の関数定義

盤面評価用として以下3つの関数を定義します。

  • 対象のセルの周りの生きているセルの数をカウントする関数
  • 対象の周りの生きているセルの数をもとに、次世代での状態を決定する関数
  • 上記2つの関数を使って、盤面をアップデートする関数

一つ目から実装していきます。
探索範囲が8個と少ないので先に探索範囲を定義しておきます。
格子の範囲外の探索範囲は無視するようにしています。

# 探索範囲
area = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]


def count_cell(i, j):
    '''i列j行目のセルの周りの生きたセルの数をカウントする関数
    入力:
        i, j: int 対象セルの番地
    出力:
        cnt: int 対象セルの周りの生きたセルの数
    '''
    cnt = 0
    for ii, jj in area:
        indh = i + ii
        indw = j + jj
        if indh < 0 or indh >= board.shape[0]:
            continue
        elif indw < 0 or indw >= board.shape[1]:
            continue
        else:
            if board[indh, indw]:
                cnt += 1
            else:
                continue
    return cnt

次に、対象の周りの生きているセルの数をもとに、次世代での状態を決定する関数です。
これも最初に書いたルールをそのまま実装しているだけです。

def evaluate(cnt, now):
    '''セルの周りの生きているセルの数をもとに、次世代での状態を決定する関数
    入力:
        cnt: int 対象セルの周りの生きているセルの数
        now: int 対象セルの現世代での状態
    出力:
        new: int 対象セルの次世代での状態
    '''
    new = 0
    if now:
        if cnt == 2 or cnt == 3:
            new = 1
    else:
        if cnt == 3:
            new = 1
    return new

最後に盤面をアップデートする関数です。
boardをグローバル変数として更新します。

def update():
    '''盤面の世代を更新する関数
    '''
    global board
    new_board = np.zeros(board.shape)
    for i in range(board.shape[0]):
        for j in range(board.shape[1]):
            now = board[i, j]
            cnt = count_cell(i, j)
            new = evaluate(cnt, now)
            new_board[i, j] = new
    board = new_board
    return




ライフゲームの可視化

以前書いたmatplotlibでアニメーションを作成する方法の記事をベースにアニメーションを作成します。
詳細は省きますので、下記参照願います。


# アニメーションで可視化
fig, ax = plt.subplots()
im = ax.imshow(board)
text = ax.set_title('generation = {}'.format(0))
ax.set_aspect('equal')

# 初期化関数
def init():  # only required for blitting to give a clean slate.
    im.set_data(board)
    text.set_text('generation = {}'.format(0))
    return im,

# グラフ更新関数
def update_anim(dt):
    global board
    update()
    im.set_data(board)
    text.set_text('generation = {}'.format(dt))
    return im, text,

ani = FuncAnimation(
      fig,  # Figureオブジェクト
      update_anim,  # グラフ更新関数
      init_func=init,
      frames = np.arange(0, gens, 1),  # フレームを設定
      interval = 100,  # 更新間隔(ms)
      repeat = True,  # 描画を繰り返す
      blit = True,
      )

# アニメーションのgif形式での保存
ani.save("anim_test.gif", writer='pillow', dpi=200)

結果はこちら。

なんかみていておもしろいですね。
wiki等によるとおもしろい動きをする形みたいなのが発見されているようでそれが出ないかなーと何回か実行してみましたが駄目でした。

おわりに

今回はmatplotlibを用いてライフゲームを作成しました。
初期状態がランダムでやりましたが、ユーザがインタラクティブに決めれるようにしてみたいですね。