Numpy에서 라이프 게임을 만들어 보았습니다.

Playground Advent Calendar를 계기로 기사 최초 투고!

라이프 게임이란?



이하, wikipedia 보다 인용. 아래 인용문을 읽는 것보다 링크로 날아가는 것이 더 이해하기 쉽다고 생각합니다.

라이프 게임에서는 초기 상태만으로 그 후의 상태가 결정된다. 바둑판과 같은 격자가 있고, 하나의 격자는 셀(cell)이라고 불린다. 각 셀에는 8개의 이웃 셀이 있다. 각 셀에는 "원시"와 "죽음"의 두 가지 상태가 있으며, 한 셀의 다음 단계 (세대)의 상태는 주변 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 정도까지 크게 하면 무거워져 버린다…)

인접한 "원시" 셀의 수를 계산합니다.


def sum_around(i, j):
    return 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 파이썬은 인덱스가 행렬의 크기를 오버해도 괜찮은 것 같았고, n행째(또는 n열째)일 때는 마찬가지로 할 필요는 없었다.

다음 세대로 업데이트


def update(old):

    @np.vectorize
    def sum_around(i, j):
        return cells[max(0, i-1):i+2, max(0, j-1):j+2].sum() - cells[i, j]

    around = np.fromfunction(sum_around, old.shape)
    new = np.where(old , ((2 <= around) & (around <= 3)), (around == 3))
    return new
np.fromfunction 함수의 제 1 인수에 sum_around 함수를 넣는 것으로, 각 요소의 값 = 인접하는 「원시」의 셀의 수가 되는 새로운 행렬 around 를 만든다.
각 요소가 True 의 때, 즉 「생」의 셀이었을 때는 생존・과소・과밀의 판정 결과를 돌려주고, False 의 때, 즉 「죽음」의 셀이었을 때는 탄생의 판정 결과 를 반환합니다. 반환값은 bool 형태로, 이것이 그대로 다음 세대의 셀이 된다.

np.vectorize 함수를 사용한 이유


np.fromfunction의 첫 번째 인수는 범용 함수 (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()        # これがないとどんどん重くなる

크리스마스 컬러


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) 로 변경하면 지정한 색으로 변경할 수 있다.

실행 결과





코드 전체


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):

    @np.vectorize
    def sum_around(i, j):
        return cells[max(0, i-1):i+2, max(0, j-1):j+2].sum() - cells[i, j]

    around = np.fromfunction(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()

감상



처음부터 혼자 만든 것이 움직인다고 해도 프로그래밍 시작하고 나서 처음일지도 모른다. 했어.

좋은 웹페이지 즐겨찾기