コンウェイのライフゲームを Ruby で実装してみた
有名な「コンウェイのライフゲーム」を素朴に実装してみました。一種の生態系シミュレーションですね。ルールは簡単です。「セル」が集まって長方形領域を形成しているとき、それぞれの「セル」はまわりの 8つの「セル」の状態によって「生きる」か「死ぬ」かします。
- 「セル」がないとき
- まわりにちょうど 3つの「セル」があれば「誕生」します。
- 「セル」があるとき
- まわりに 0 または 1個の「セル」があれば過疎で「死に」ます。
- まわりに 2 または 3個の「セル」があればそのまま「生き」ます。
- まわりに 4個以上の「セル」があれば、過密で「死に」ます。
このパターンを繰り返します。
パターンはランダムに与えられ、毎回自動的に lifemap.txt に(上書き)記録されます。-r オプションをつけて実行すると、開始パターンを lifemap.txt からロードして実行します。描画には GTK+ を使っています。
ソースは以下です。Gist。
require 'gtk2' Width = 30 Height = 30 class Component def initialize f = Field.new(Width, Height) f.generate(150) #ランダムに150個セットする if ARGV[0] == "-r" File.open("lifemap.txt") {|io| f = Marshal.load(io)} else File.open("lifemap.txt", "w") {|io| Marshal.dump(f, io)} end @window = Gui.new(f) @disp = FieldDisplay.new(f) end def main @disp.show end end class Field def initialize(width, height) @width = width @height = height @field = Array.new(@width + 2) {Array.new(@height + 2, 0)} end attr_reader :width, :height def set(x, y) @field[x][y] = 1 end def reset(x, y) @field[x][y] = 0 end def get(x, y) @field[x][y] end def generate(n) n.times {set(rand(@width) + 1, rand(@height) + 1)} end #次の世代を得る def next nxf = Array.new(@width + 2) {Array.new(@height + 2, 0)} @width.times do |x| @height.times do |y| x1, y1 = x + 1, y + 1 n = alive_cells(x1, y1) if get(x1, y1).zero? nxf[x1][y1] = 1 if n == 3 else nxf[x1][y1] = 1 if n == 2 or n == 3 end end end @field = nxf end def alive_cells(x, y) get(x - 1, y - 1) + get(x, y - 1) + get(x + 1, y - 1) + get(x - 1, y) + get(x + 1, y) + get(x - 1, y + 1) + get(x, y + 1) + get(x + 1, y + 1) end end class Gui CellWidth = 10 Space = 3 Margin = 8 def initialize(f) @f = f @w = Gtk::Window.new @width = CellWidth * @f.width + Space * (@f.width - 1) + 2 * Margin @height = CellWidth * @f.height + Space * (@f.height - 1) + 2 * Margin @w.set_size_request(@width, @height) @w.set_app_paintable(true) @w.realize @drawable = @w.window @gc = Gdk::GC.new(@drawable) colormap = Gdk::Colormap.system @white = Gdk::Color.new(65535, 65535, 65535) @black = Gdk::Color.new(0, 0, 0) colormap.alloc_color(@white, false, true) colormap.alloc_color(@black, false, true) end end class FieldDisplay < Gui def initialize(f) super(f) end def show @w.signal_connect("expose_event") do @gc.set_foreground(@black) @drawable.draw_rectangle(@gc, true, 0, 0, @width, @height) end Gtk.timeout_add(160) do @f.width.times do |x| @f.height.times do |y| if @f.get(x + 1, y + 1).zero? reset(x + 1, y + 1) else set(x + 1, y + 1) end end end @f.next end @w.signal_connect("destroy") {Gtk.main_quit} @w.show Gtk.main end def set(x, y) @gc.set_foreground(@white) draw_cell(x, y) end def reset(x, y) @gc.set_foreground(@black) draw_cell(x, y) end def draw_cell(x, y) x1 = Margin + (x - 1) * CellWidth + (x - 1) * Space y1 = Margin + (y - 1) * CellWidth + (y - 1) * Space @drawable.draw_rectangle(@gc, true, x1, y1, CellWidth, CellWidth) end end Component.new.main
※追記(2018/1/18)
コンソール版も作ってみました。
※追記(2018/4/12)
フィールド・エディタ付きのを作ってみました。