生命の誕生、進化、淘汰などのプロセスを簡易的なモデルで再現したシミュレーションゲーム。

#!usr/bin/env ruby

require 'sdl'
require 'cairo'

class Sprite
  attr_accessor :x, :y, :alive, :enabled, :idx, :info
  attr_writer :draw, :matrix

  def initialize
    @x, @y = 0, 0
    @alive = true
    @enabled = true
    @idx = 0
    @info = {}
    @matrix = Cairo::Matrix.identity
  end

  def renew(cr)
    return if !@enabled
    cr.matrix = @matrix * Cairo::Matrix.translate(@x, @y)
    @draw[cr]
  end
end

class Phase
  attr_accessor :handles, :task

  def initialize(w, h, info=nil)
    @image = Cairo::ImageSurface.new(w, h)
    @info = info
    @sprites = []
    @handles = {}
  end

  def addSprite
    @sprites << Sprite.new
    yield(@sprites.last)
    @sprites.last
  end

  def renew(drawFlag=true)
    @task[] if @task
    if drawFlag
      cr = Cairo::Context.new(@image)
      @sprites.reject!{|obj| !obj.alive}
      @sprites.each {|obj| obj.renew(cr)}
      @image
    else
      nil
    end
  end

  def layerSort
    @sprites.sort!{|a, b| a.idx <=> b.idx}
  end
end

SDL.init(SDL::INIT_VIDEO)
SDL::WM.set_caption("Life Game","")
cw, ch = 8, 8
cx, cy = 60, 60
w, h = cw * cx, ch * cy
screen = SDL::Screen.open(w, h, 32, 0)
phase = Phase.new(w, h)
phase.addSprite do |obj|
  obj.draw = proc do |cr|
    cr.set_source_color(:black)
    cr.rectangle(0, 0, w, h)
    cr.fill
  end
end
items = []
cx.times do |i|
  cy.times do |j|
    items << phase.addSprite do |obj|
      obj.x, obj.y =  i * cw + 1, j * ch + 1
      obj.info[:px] = i
      obj.info[:py] = j
      obj.draw = proc do |cr|
        cr.set_source_color(:green)
        cr.rectangle(0, 0, cw - 1, ch - 1)
        cr.fill
      end
    end
  end
end
phase.addSprite do |obj|
  obj.draw = proc do |cr|
    cr.set_source_color(:gray)
    (1..cx-1).each do |i|
      cr.move_to(i * cw, 0)
      cr.line_to(i * cw, h)
      cr.stroke
    end
    (1..cy-1).each do |i|
      cr.move_to(0, i * ch)
      cr.line_to(w, i * ch)
      cr.stroke
    end
  end
end
glider_gun = [
  [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,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,1,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
  [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1],
  [1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,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,0,0,1,0,0,0,0,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,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,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
]
galaxy = [
  [1,1,1,1,1,1,0,1,1],
  [1,1,1,1,1,1,0,1,1],
  [0,0,0,0,0,0,0,1,1],
  [1,1,0,0,0,0,0,1,1],
  [1,1,0,0,0,0,0,1,1],
  [1,1,0,0,0,0,0,1,1],
  [1,1,0,0,0,0,0,0,0],
  [1,1,0,1,1,1,1,1,1],
  [1,1,0,1,1,1,1,1,1]
]
traffic_light = [
  [0,0,0,0,1,0,0,0,0],
  [0,0,0,0,1,0,0,0,0],
  [0,0,0,0,1,0,0,0,0],
  [0,0,0,0,0,0,0,0,0],
  [1,1,1,0,0,0,1,1,1],
  [0,0,0,0,0,0,0,0,0],
  [0,0,0,0,1,0,0,0,0],
  [0,0,0,0,1,0,0,0,0],
  [0,0,0,0,1,0,0,0,0]
]
pentadecathlon = [
  [0,1,0,0,0,0,1,0,],
  [1,1,0,0,0,0,1,1,],
  [0,1,0,0,0,0,1,0,],
]
eight = [
  [0,0,0,1,1,1],
  [0,0,0,1,1,1],
  [0,0,0,1,1,1],
  [1,1,1,0,0,0],
  [1,1,1,0,0,0],
  [1,1,1,0,0,0]
]
eater = [
  [1,1,0,0],
  [1,0,0,0],
  [0,1,1,1],
  [0,0,0,1]
]
clock = [
  [0,1,0,0],
  [0,0,1,1],
  [1,1,0,0],
  [0,0,1,0]
]
dots = []
cx.times do |i|
  dots << []
  cy.times do |j|
    dots[i] << false
  end
end
[
  [glider_gun,3,3],
  [galaxy,10,40],
  [pentadecathlon,10,25],
  [traffic_light,35,45],
  [eight,45,15],
  [eater,50,37],
  [clock,30,30]
].each do |data, x, y|
  data.size.times do |i|
    data[i].size.times do |j|
    if data[i][j] == 1
      dots[j + x][i + y] = true 
      end
    end
  end
end
phase.task = proc do
  items.each do |obj|
    obj.enabled = dots[obj.info[:px]][obj.info[:py]]
  end
  copy = []
  cx.times do |i|
    copy << []
    cy.times do |j|
      copy[i] << dots[i][j]
    end
  end
  cx.times do |i|
    cy.times do |j|
      count = 0
      (-1..1).each do |x|
        next if !(0..cx-1).include?(i + x)
        (-1..1).each do |y|
          next if !(0..cy-1).include?(j + y)
          count += 1 if copy[i + x][j + y]
        end
      end
      if dots[i][j]
        dots[i][j] = false if !(3..4).include?(count)
      else
        dots[i][j] = true if count == 3
      end
    end
  end
end
phase.handles[SDL::Event::Quit] = proc{exit}
phase.handles[SDL::Event::KeyDown] = proc{|e| exit if e.sym == SDL::Key::ESCAPE}

loop do
  while e = SDL::Event.poll
    if handle = phase.handles[e.class]
      handle[e]
    end
  end
  image = phase.renew
  surface = SDL::Surface.new_from(image.data, w, h, 32, image.stride, 0, 0, 0, 0)
  screen.put(surface,0,0)
  screen.flip
  SDL.delay(50)
end