🦠

Wikipediaに載っているライフゲーム亜種ぜんぶ試す

2023/12/06に公開

はじめに

一般的に知られているライフゲームは B3/S23 と定義されている。これは周囲の生存点が 3 つで誕生し、2 つか 3 つのときに生存するというルールを簡潔に表記したものになる。

その表記を用いたいろいろなパターンが Wikipedia に載っているのだが、具体的なパターンについては文章で説明が書いてあるだけで視覚的にどのようなパターンになるのかよくわからない。そこで簡単に確認できるようにした。

説明文は翻訳したのをのっけているが意味がよくわからないのもある。

また説明内にあるパターンの特徴が見られないものは、おそらくそのパターンを意図した初期配置から始めないといけないのでないかと考えられる。(以下のデモはすべてランダムな配置から開始している)

Replicator (B1357/S1357)

すべてのパターンは最終的にそのパターン自体の複数のコピーに置き換えられる。

Seeds (B2/S)

すべてのパターンはフェニックスで、すべての生きたセルは直ちに死に、多くのパターンが爆発的なカオスの成長を引き起こす。しかし、複雑な振る舞いを持つ一部の設計されたパターンが知られている。

名称不明 (B25/S4)

このルールは小さな自己複製パターンをサポートしている。これと小さなグライダーパターンを組み合わせるとグライダーが疑似ランダムウォークで行ったり来たりする。

Life without Death (B3/S012345678)

インクスポットやフレークなどとも呼ばれる。生きているセルはけっして死なない。カオス的な成長と任意のブーリアン回路をシミュレートするために使用できるより構造化された階段状のパターンを組み合わせる。

Life (B3/S23)

これが本家。非常に複雑な振る舞いを持つ。

34 Life (B34/S34)

最初は Life の安定した代替品と考えられていたが、コンピュータシミュレーションによって、大きなパターンは爆発的に成長することが明らかになった。多くの小さなオシレーターと宇宙船を持つ。

Diamoeba (B35678/S5678)

大きなダイヤモンドを形成し、その境界線はカオス的に揺れ動く。最初に Dean Hickerson によって研究され、1993年に彼は生きているセルで空間を埋め尽くすパターンを見つけるために $50 の賞金を提供し、この賞金は1999年に David Bell によって獲得された。

2x2 (B36/S125)

パターンが 2x2 のブロックで構成されていれば、それは同じ形で進化を続ける。これらのブロックを 2 の大きなパワーにグループ化すると、同じ振る舞いが見られるが、進化が遅くなる。高周期の複雑なオシレーターと小さなグライダーを持つ。

HighLife (B36/S23)

Life と似ているが、小さな自己複製パターンがある。

Day & Night (B3678/S34678)

オンオフの反転に対して対称になる。非常に複雑な振る舞いを示す設計されたパターンを持つ。

Morley (B368/S245)

Stephen Morley の名にちなんで名付けられた。非常に高い周期と遅い宇宙船をサポートしている。

Anneal (B4678/S35678)

ねじれた多数決ルールとも呼ばれる。オンオフの反転に対して対称になる。生きたセルと死んだセルの境界線上で曲線短縮フローを近似する。

コード

開く
ca.rb
require "#{__dir__}/../../物理/ベクトル/base"
Base::Palette[:background] = nil

module CA
  class Cell
    NEIGHBORS = [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]]

    class << self
      def value_to_write
        true
      end
    end

    attr_accessor :state

    def initialize(v)
      @v = v
      @state = state_default(v)
    end

    def new_state_update
      c = neighbors.count(&:state)
      @new_state = @state ? (c == 2 || c == 3) : (c == 3)
    end

    def state_update
      @state = @new_state
    end

    def draw
      Gosu.draw_rect(@v.x, @v.y, 1, 1, color)
    end

    private

    def color
      Gosu::Color.from_hsv(0, 0, brightness)
    end

    def brightness
      if @state.kind_of?(Numeric)
        @state
      else
        @state ? 1.0 : 0.0
      end
    end

    def neighbors
      @neighbors ||= NEIGHBORS.collect { |e|
        pos = @v + V[*e]
        if toroidal
          pos = pos.modulo($app.world_wh)
        else
          if pos.min_element.negative?
            pos = nil
          end
        end
        if pos
          $app.field.dig(*pos)
        end
      }.compact
    end

    def state_default(v)
      [true, false].sample
    end

    def toroidal
      true
    end
  end

  class App < ::Base
    attr_accessor :field

    def show
      reset
      super
    end

    def world_wh
      @world_wh ||= window_wh / cell_wh
    end

    private

    def reset
      @field = Array.new(world_wh.x) do |x|
        Array.new(world_wh.y) do |y|
          cell_class.new(V[x, y])
        end
      end
    end

    def button_down(id)
      super

      if id == Gosu::KB_R
        reset
      end
    end

    def update
      super

      mouse_click_then_write

      @field.each { |e| e.each(&:new_state_update) }
      @field.each { |e| e.each(&:state_update) }
    end

    def mouse_click_then_write
      if button_down?(Gosu::MS_LEFT)
        v = (mouse_v / cell_wh).floor
        @field.dig(*v)&.state = cell_class.value_to_write
      end
    end

    def draw
      super

      Gosu.scale(*cell_wh) do
        @field.each { |e| e.each(&:draw) }
      end

      if ENV["FPS"]
        vputs Gosu.fps
      end
    end

    def cell_class
      Cell
    end

    def cell_wh
      @cell_wh ||= V.splat(cell_px)
    end

    def cell_px
      10
    end

    def window_size_default
      V[800, 600]
    end

    def fps_default
      30
    end
  end
end

if $0 == __FILE__
  CA::App.show
end
life_like.rb
require "#{__dir__}/../セルオートマトン関連まとめ/ca"

module LifeLike
  def self.show(rule)
    App.instance.tap { |e|
      e.rule = rule
    }.show
  end

  class Cell < CA::Cell
    def new_state_update
      c = neighbors.count(&:state)
      if !@state
        @new_state = $app.backet_b[c]
      else
        @new_state = $app.backet_s[c]
      end
    end
  end

  class App < CA::App
    attr_accessor :backet_b
    attr_accessor :backet_s

    def rule=(str)
      b, s = str.match(%r{B(\d*)/S(\d*)}).captures
      @backet_b = backet_build(b)
      @backet_s = backet_build(s)
    end

    def cell_class
      Cell
    end

    private

    def backet_build(s)
      if s.empty?
        return []
      end
      a = s.chars.collect(&:to_i)
      backet = Array.new(a.max.next, false)
      a.each { |e| backet[e] = true }
      backet
    end
  end
end

if $0 == __FILE__
  LifeLike.show("B3/S234")
end

参照

https://en.wikipedia.org/wiki/Life-like_cellular_automaton

Discussion