🔎

三次元のライフゲームを作った話

2022/02/18に公開

ライフゲームとは

Wikipediaによると、

ライフゲーム (Conway's Game of Life) は1970年にイギリスの数学者ジョン・ホートン・コンウェイ (John Horton Conway) が考案した生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲームである。単純なルールでその模様の変化を楽しめるため、パズルの要素を持っている。
生物集団においては、過疎でも過密でも個体の生存に適さないという個体群生態学的な側面を背景に持つ。セル・オートマトンのもっともよく知られた例でもある。

「ライフゲーム」(2022年1月8日 (土) 22:13  UTCの版)『ウィキペディア日本語版』。
https://ja.wikipedia.org/wiki/ライフゲーム

というもので、あるマスの周りにいる生命体の数に応じて、消えだり、そのままだったり、増えたりと変化するものである。そして、一般にこれは二次元で行われる。

しかし、三次元でもできそうじゃん。と思ったので、pythonで実装してみた。

実装について

基本的にはkerasの畳み込みを三次元リストに対して行って実装している。
これのいい点は

  • 必要になれば、周囲から受ける影響を自由に変えられる。
  • そんなに重くない。
    と思っている。
    まず、kerasのモデル作りをする。
input = Input((depth,height,width,1))
layer = Conv3D(1, kernel_size=(3,3,3), input_shape=(depth,height,width,1),use_bias=False,padding="same",data_format="channels_last") 
x = layer(input)
model = Model(input, x)

そして重さを設定する。
今回は周囲26マスから均等に影響を受けるとした。

weights=np.array([[[[[1.]],[[1.]],[[1.]]],
          [[[1.]],[[1.]],[[1.]]],
          [[[1.]],[[1.]],[[1.]]]],
         [[[[1.]],[[1.]],[[1.]]],
          [[[1.]],[[0.]],[[1.]]],
          [[[1.]],[[1.]],[[1.]]]],
         [[[[1.]],[[1.]],[[1.]]],
          [[[1.]],[[1.]],[[1.]]],
          [[[1.]],[[1.]],[[1.]]]]])
layer.set_weights([weights])

あとはモデルを動かすことで、周囲のマスの数の合計を入れた結果を得られるので、これを元に、次の状態を形成する。

def generation(first,model,under,over,count,list_):
  result=model.predict(first)
  buffer=first.copy()
  buffer=np.where((result>under)&(result<over), 1, buffer)
  buffer=np.where((result<=under)|(result>=over), 0, buffer)
  after=buffer.copy()
  list_.append(after.reshape(1,depth,height,width).tolist())
  for i in range(count-1):
    before=after.copy()
    result=model.predict(before)
    buffer=before.copy()
    buffer=np.where((result>under)&(result<over), 1, buffer)
    buffer=np.where((result<=under)|(result>=over), 0, buffer)
    after=buffer.copy()
    list_.append(after.reshape(1,depth,height,width).tolist())
  return after

最終的にアニメーションにする際に途中経過が欲しくなるのでlist_に収容している。
注意する点は途中結果をbufferに入れずに前のに反映させてしまうと、結果が変わってしまうことくらいだと思う。
ちなみに上の関数では最初の状態はlist_に収容されないので、実行時の最初に突っ込んでから回している。

あとはBlenderなどのお好みのソフトで可視化するだけなのだが、これが本当に大変だったのは別のお話。


https://zenn.dev/kaitoyama/articles/3fb92e58ef037c

Discussion