ライフゲーム(Game of Life)

ライフゲーム(Game of Life)は John Horton Conway が考案したシミュレーションゲーム(セル・オートマトン)である。Conway は2020年4月11日に COVID-19 で亡くなった。

ルールはウィキペディア等に書かれているのでここでは省略する。

次のプログラム例は生の Python または IPython 用である。Google Colab では動かないようだ。Jupyter Notebook では %matplotlib notebook を付ければ動いた(@CinderellaJapan 先生のご教示感謝!)。

#! /usr/bin/env python3

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

N = 100

x = np.arange(N * N) % N
y = np.arange(N * N) // N

# 初期状態(任意に与える)
a = np.zeros((N, N), dtype="int8")
a[N // 2, N // 2] = 1
a[N // 2 - 1, N // 2] = 1
a[N // 2 + 1, N // 2] = 1
a[N // 2, N // 2 - 1] = 1
a[N // 2 - 1, N // 2 + 1] = 1
a.shape = N * N

fig = plt.figure(figsize=(7, 7))
plt.axis([-1, N, -1, N])
line, = plt.plot(x[a != 0], y[a != 0], ".")

def update(_):
    line.set_data(x[a != 0], y[a != 0])
    b = a.copy()
    for i in range(N + 1, N * N - N - 1):
        n = (
            b[i - N - 1]
            + b[i - N]
            + b[i - N + 1]
            + b[i - 1]
            + b[i + 1]
            + b[i + N - 1]
            + b[i + N]
            + b[i + N + 1]
        )
        if n == 3:
            a[i] = 1
        elif n != 2:
            a[i] = 0

ani = FuncAnimation(fig, update, interval=100)
plt.show()

最後を例えば

ani = FuncAnimation(fig, update, interval=100, frames=1000)
ani.save("gameoflife.gif")

とすると1000世代までGIFで保存できる。

Game of Life

難しいところを解説すると,plt.plot() の戻り値は普通は使わないが,matplotlib.lines.Line2D というもののリストが返される。Google Colab などで plt.plot() すると [] のような表示が出るのはこのためである。これには図の座標の情報などが入っている。この情報を書き換えることにより図を更新している。

上のコードの line, = ... まで実行した時点で

line.get_data()

と打ち込むと

(array([50, 51, 49, 50, 50]), array([49, 49, 50, 50, 51]))

のようなグラフを構成する点の座標の配列のタプルが返る。この line, = ... は要素が一つだけのリストの要素を取り出すための常套句で,lines = ... としてから line = lines[0] とするのと同じである。このコンマの用法は

t, u = [1, 2]

とすると t に 1 が入り u に 2 が入ることを思い出せば理解しやすくなる。この要素が一つになれば

t, = [1]

とすると t に 1 が入る。

t = [1]

では t にリスト [1] が入ってしまう。

この line のデータを100ミリ秒ごとに更新するのが FuncAnimation(fig, update, interval=100) である。第2引数で指定した関数 update() は,この場合はシーケンス番号 0,1,2,… を引数として100ミリ秒ごとに呼び出される。この中で line.set_data(x[a != 0], y[a != 0]) によってデータだけ取り替え,配列 a を更新する。

上のコードは難しいと不評だったようだ。次のように毎回普通に描く方法のほうがわかりやすそうだ:

# 前半の準備は上と同じ

fig = plt.figure(figsize=(7, 7))
# plt.axis(...) ← 消す
# line, = ...   ← 消す

def update(_):
    plt.cla()  # 画面クリア (clear current axes)
    plt.axis([-1, N, -1, N])
    plt.plot(x[a != 0], y[a != 0], ".")
    b = a.copy()
    # 以下同じ

Last modified: