# 1D and 2D Cellular Automata Simulators
# David S. Touretzky, October 2012


# One-dimensional binary cellular automata

def ca1d(rule,width,generations,initial_positions=[width/2],scale=10)
  # Rule must be an integer from 0 to 255
  # Example:  ca1d(18, 61, 36)
  emptyrow = [false]*width
  this_gen = emptyrow.clone
  initial_positions.each{|i| this_gen[i] = true}   # set up generation 0
  history = [this_gen]
  for i in 1..generations-1
    this_gen = apply_rule(rule,this_gen)
    history[i] = this_gen
    show1d(history,scale)
    sleep(0.15)
  end
  return nil
end

def apply_rule(rule, gen)
  next_gen = gen.clone
  bits = if gen[1] then 1 else 0 end
  for i in 1..gen.length-2
    # Pipeline the bit calculation: at each step, we
    # AND out the left bit (&3), shift left (*2), and OR in the right bit
    bits = (bits&3)*2 | if gen[i+1] then 1 else 0 end
    next_gen[i] = (rule[bits] == 1)    # rule[i] yields the ith bit
  end
  return next_gen
end

def show1d(history,scale)
  pixels = history.collect{ |row|
    row.collect{ |pixel|
      if pixel then [0,0,0] else [255,255,255] end
    }
  }
  plot(pixels,scale)
end

################################################################

# John Conway's Game of Life

$glider = [[25,25],[25,26],[25,27],[24,27],[23,26]]

$row = [[25,22],[25,23],[25,24],[25,25],[25,26],[25,27],[25,28]]

$complex = [[30,30],[30,31],[30,32],[31,32],[29,31]]

def life(width=50, height=50, inits=[],generations=50)
  # Example: life(60,60,$glider,50)
  board = (1..height).collect{|i| [0]*width}
  inits.each{|p| board[p.first][p.last] = 1}
  for gen in 1..generations do
    show2d(board)
    sleep(0.2)
    counts = (1..height).collect {|i| [0]*width}
    for i in 1..height-2 do
      for j in 1..width-2 do
	counts[i][j] = board[i-1][j-1] + board[i-1][j] + board[i-1][j+1] +
	               board[i][j-1]   +                 board[i][j+1] +
	               board[i+1][j-1] + board[i+1][j] + board[i+1][j+1]
      end
    end
    for i in 1..height-2 do
      for j in 1..width-2 do
	c = counts[i][j]
	board[i][j] = 
	  if board[i][j]==0 then       # empty square may have a birth
	    if c==3 then 1 else 0 end
	  else                         # occupied square may live or die
	    if c==2 or c==3 then 1 else 0 end
	  end
      end
    end
  end
  show2d(board)
end

def show2d(board)
  pixels = board.collect{ |row|
    row.collect{ |pixel|
      if pixel==1 then [0,0,0] else [255,255,255] end
    }
  }
  plot(pixels,10)
end


# This plot function comes from Jeff VonRonne's ppm.rb
def plot(matrix,mag=1)
  height = matrix.length
  width = matrix.first.length 
  Canvas.init(mag*width+2,mag*height+2, "Plot")
  Canvas::Rectangle.new(1,1,mag*(width+1),mag*(height+1),:width=>0, :fill=>"black")
  color = "white"
  for y in 0..height-1 do
    for x in 0..width-1 do
      rgb_tuple = matrix[y][x]
      color = "#%02X%02X%02X" % [rgb_tuple[0], rgb_tuple[1], rgb_tuple[2]]
      Canvas::Rectangle.new(1+mag*x,1+mag*y,1+mag*(x+1),1+mag*(y+1), :width=>0, :fill=>color)
    end
  end                                         
  return nil
end

