LoginSignup
20
18

More than 5 years have passed since last update.

Pythonでライフゲーム!(Conway's Game of Life)

Posted at

はじめに

セル・オートマトンとして有名なライフゲームが面白そうだったので、Pythonとnumpyでライフゲームのマップ配列を生成するコードを書いてみました。0(dead)と1(live)で書かれた初期世代のマップ配列を与えることで、iterativeに次世代のマップ配列を生成するgeneratorとして実装しています。マップは周期的境界条件(端と端がつながった状態)にも対応させてみました!

lifegame.py

以下のlifegame.pyをモジュールとしてimportして使用します。実際の使用例は下記のサンプルコードをご覧ください。進化のルールはWikipediaで紹介されている23/3に準拠してますが、コードの中をいじって容易に変えることができます。

usage

>>> lg_map = lifegame.MapGenerator(map_init, periodic=False)
>>> lg_map.evolve()
array([...]) # 第0世代のマップ配列 (= map_init)
>>> lg_map.evolve()
array([...]) # 第1世代のマップ配列
>>> ...
  • map_init : 初期世代のマップ配列 (list or numpy.ndarray)
  • periodic : マップに周期的境界条件を使用するかどうか (bool)

lifegame.py

lifegame.py
import numpy as np
from itertools import product

class MapGenerator(object):
    def __init__(self, map_init, periodic=False):
        self.map_init  = np.array(map_init, np.int64)
        self.periodic  = periodic
        self.generator = self.map_evolver()

    def map_evolver(self):
        map_now   = self.map_init
        map_shape = self.map_init.shape

        while True:
            map_next = np.zeros_like(map_now, np.int64)
            for (i,j) in product(range(map_shape[0]), range(map_shape[1])):
                islive = bool(map_now[i,j])
                nlive  = self.num_live_neighbours(map_now, i, j)
                # write the rules of lifegame here!
                # ........................................
                if nlive == 2:
                    if islive: map_next[i,j] = 1
                elif nlive == 3:
                    map_next[i,j] = 1
                # ........................................

            yield map_now
            map_now = map_next

    def num_live_neighbours(self, map_now, i, j):
        if self.periodic:
            neighbours = np.roll(np.roll(map_now, -i+1, 0), -j+1, 1)[:3,:3]
        else:
            sl_i = slice(0,i+2) if i == 0 else slice(i-1,i+2)
            sl_j = slice(0,j+2) if j == 0 else slice(j-1,j+2)
            neighbours = map_now[sl_i,sl_j]

        return neighbours.sum() - map_now[i,j]

    def evolve(self):
        return self.generator.next()

matplotlibを使ったサンプルコード

以下ではパルサーと呼ばれる振動子の一種が、15x15のマップ上でエンドレスに進化するサンプルコードです。
あくまでサンプルなので、配列の計算速度よりもmatplotlibの描画効率でリミットされていると思われます…

サンプルコード

pulsar.py
import time
import numpy as np
import matplotlib.pyplot as plt
import lifegame

pulsar = np.array([
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

lg_map = lifegame.MapGenerator(map_init=pulsar, periodic=False)
plt.ion()
while True:
    plt.pcolormesh(lg_map.evolve())
    plt.xlim([0, pulsar.shape[1]])
    plt.ylim([0, pulsar.shape[0]])
    plt.draw()
    plt.clf()
    time.sleep(0.05)

描画の様子

lifegame-pulsar.png

今後の予定?

現時点では初期世代のマップ配列をテキストで予め用意する必要があるので、matplotlib.widgetsを使って画面上からマップ配列をポチポチと入力できるようにもしたいなと思います。遊びなのでいつ作るかは未定ですが(笑)

20
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
18