# -*- coding: utf-8 -*-
# An interactive visualization of the Wireworld cellular 
# automaton. No persistence yet.

class Wireworld < Processing::App
  def setup
    color_mode RGB, 1.0
    no_stroke
    smooth
    background 0.45
    
    @cellsize = 10              # pixels
    @nx = width / @cellsize
    @ny = height / @cellsize

    @cells = fresh_cells
    @dirty = []                 # coordinates of dirty cells

    mark_all_cells_dirty
  end
  
  def draw
    @dirty.each { |item| draw_cell item.first, item.last }
    @dirty = []
    run_wireworld_rule
  end

  def mouse_clicked
    x = mouse_x / @cellsize
    y = mouse_y / @cellsize

    if @cells[x][y] == :empty
      set x, y, :wire
    elsif mouse_button == LEFT
      set x, y, :empty
    else
      set x, y, :electron_head
    end
  end

  def draw_cell(x, y)
    fill *color_from(@cells[x][y])
    rect(x * @cellsize, y * @cellsize, 
         @cellsize - 2, @cellsize - 2)
  end
  
  def color_from(state)
    case state
    when :empty
      return 0.5, 0.5, 0.5, 1
    when :wire
      return 0.75, 0.75, 0.5, 1
    when :electron_head
      return 1, 1, 1, 1
    when :electron_tail
      return 0.75, 0.75, 0.75, 1
    end
  end

  def run_wireworld_rule
    old_cells = @cells
    @cells = fresh_cells

    each_coord do |x,y|
      # This could be optimized somewhat by only recalculating
      # the neighbors of dirty cells.
      case old_cells[x][y]
      when :electron_head
        set x, y, :electron_tail
      when :electron_tail
        set x, y, :wire
      when :empty
        # do nothing; fresh_cells are all :empty
      when :wire
        case electron_head_count old_cells, x, y
        when 1, 2
          set x, y, :electron_head
        else
          # Don’t call `set` in this case so as not to mark 
          # the cell dirty for redrawing.
          @cells[x][y] = :wire
        end
      end
    end
  end

  # This could perhaps be optimized somewhat with a sum table.
  def electron_head_count(cells, base_x, base_y)
    count = 0
    ([0, base_x-1].max..[base_x+1, @nx-1].min).each do |x|
      ([0, base_y-1].max..[base_y+1, @ny-1].min).each do |y|
        count += 1 if cells[x][y] == :electron_head
      end
    end
    return count
  end

  def each_coord
    (0..@nx-1).each do |x|
      (0..@ny-1).each do |y|
        yield x, y
      end
    end
  end      

  def mark_all_cells_dirty
    each_coord do |x, y|
      @dirty << [x, y]
    end
  end

  def set(x, y, value)
    @cells[x][y] = value
    @dirty << [x, y]
  end

  def fresh_cells
    Array.new(@nx) { Array.new(@ny, :empty) }
  end
end

Wireworld.new(:title => "Wireworld",
              :width => 1024, :height => 600, 
              :full_screen => true)

# Local Variables:
# compile-command: "./rp5 run wireworld.rb"
# End:

