Help us understand the problem. What is going on with this article?

Numpyでライフゲームを作ってみた

 Playground Advent Calendar を機に記事初投稿!

ライフゲームとは

以下、wikipediaより引用。下の引用を読むより、リンクに飛んだ方が分かりやすいと思うw

 ライフゲームでは初期状態のみでその後の状態が決定される。碁盤のような格子があり、一つの格子はセル(細胞)と呼ばれる。各セルには8つの近傍のセルがある。各セルには「生」と「死」の2つの状態があり、あるセルの次のステップ(世代)の状態は周囲の8つのセルの今の世代における状態により決定される。

 セルの生死は次のルールに従う。

誕生
 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
生存
 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
過疎
 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
過密
 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。
下に中央のセルにおける次のステップでの生死の例を示す。生きているセルは■、死んでいるセルは□で表す。

使用したパッケージ

import matplotlib.pyplot as plt
import numpy as np

初期状態

n = 50

# ランダムなセルを作成
cells = np.random.randint(0, 2, (n, n), dtype=bool)

 TrueFalseのどちらか一つをランダムに選んだものが要素のn×n行列を作る。(Trueなら「生」、Falseなら「死」)
 (n = 500くらいまで大きくすると重くなってしまう…)

隣接する「生」のセルの個数を数える

sum_around = lambda i, j : cells[max(0, i-1):i+2, max(0, j-1):j+2].sum() - cells[i, j]

 まず、行列のij列成分を中心とする3×3行列を抽出して、その行列の要素の和を求める。その結果から中心を引くと、ij列成分に隣接する「生」のセルの個数が求められる。(ちTrue = 1, False = 0として計算される)

max関数を使った理由

 0行目(または0列目)にあるセルの左(または上)のセルを指定したいときi - 1 = -1となってしまってもmax(0, -1) = 0となるから、壁の内側の範囲のみを参照できる。Pythonはインデックスが行列のサイズをオーバーしても大丈夫らしく、n行目(またはn列目)の時は同様にする必要はなかった。

次の世代に更新する

def update(old):

    sum_around = lambda i, j : cells[max(0, i-1):i+2, max(0, j-1):j+2].sum() - cells[i, j]

    around = np.fromfunction(np.vectorize(sum_around), old.shape)
    new = np.where(old , ((2 <= around) & (around <= 3)), (around == 3))
    return new

 np.fromfunction関数の第一引数にsum_around関数を入れることで、 各要素の値 = 隣接する「生」のセルの個数 となる新しい行列aroundを作る。
 各要素がTrueのとき、つまり「生」のセルだったときは生存・過疎・過密の判定結果を返し、Falseのとき、つまり「死」のセルだったときは誕生の判定結果を返す。返り値はbool型で、これがそのまま次の世代のセルになる。

np.vectorize関数を使った理由

 第一引数の関数はユニバーサル関数(ufunc)である必要があるらしい。

Parameters :

function : callable

The function is called with N parameters, where N is the rank of shape. Each parameter represents the coordinates of the array varying along a specific axis. For example, if shape were (2, 2), then the parameters would be array([[0, 0], [1, 1]]) and array([[0, 1], [0, 1]])
numpy.fromfunction

sum_around関数の引数はint型を想定しているので、ndarray型を受け取れるようにnp.vectorizeでufunc化した。

出力

while True:
# for _ in range(200):  # 回数指定したい場合

    cells = update(cells)

    plt.imshow(cells)
    plt.pause(.01)   # 0.01秒ごとに更新
    plt.cla()        # これがないとどんどん重くなる

:santa:クリスマスカラー:christmas_tree:

from matplotlib.colors import LinearSegmentedColormap

colors = ['green', 'red']
xmas = LinearSegmentedColormap.from_list('xmas', colors)
norm = plt.Normalize(0, 1)

plt.tick_params(labelbottom=False, labelleft=False,
                bottom=False, left=False)

while文の中をplt.imshow(cells, cmap=xmas, norm=norm)に変更すると指定した色に変更できる。

実行結果

ezgif-3-866aafd6a847.gif

コード全体

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap


n = 15

cells = np.zeros([n, n], dtype=bool)

# 銀河
cells[3:9, 3:5] = True
cells[3:5, 6:12] = True
cells[-5:-3, 3:9] = True
cells[6:12, -5:-3] = True

# ランダム
# cells = np.random.randint(0, 2, (n, n), dtype=bool)


def update(old):

    sum_around = lambda i, j : cells[max(0, i-1):i+2, max(0, j-1):j+2].sum() - cells[i, j]

    around = np.fromfunction(np.vectorize(sum_around), old.shape, dtype=int)
    new = np.where(old , ((2 <= around) & (around <= 3)), (around == 3))
    return new


colors = ['green', 'red']
xmas = LinearSegmentedColormap.from_list('xmas', colors)
norm = plt.Normalize(0, 1)

plt.tick_params(labelbottom=False, labelleft=False,
                bottom=False, left=False)

# for _ in range(200):
while True:

    cells = update(cells)

    plt.imshow(cells, cmap=xmas, norm=norm)
    plt.pause(.01)
    plt.cla()

:8ball:感想:8ball:

 ゼロから一人で作ったものが動いたのってもしかしてプログラミング始めてから初めてなのでは…
 やったあ(急募:喜びの言語化)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした