# Pen Pal Pixel Painter
# version 1
# for Shoes (tested on 0.r811 on WinXP)
# dRuby + pixel art = Pen Pal Pixel Painter
# written by chiisaitsu in July 2008 <chiisaitsu@gmail.com>

require 'drb'

SQUARE_SIZE      = 30
SQUARE_MARGIN    = 3
SQUARE_RADIUS    = 5
BACKGROUND_COLOR = '#333'
TEXT_COLOR       = '#eee'

PALETTE_WIDTH    = (2 * (SQUARE_SIZE + SQUARE_MARGIN)) + 15
CANVAS_HEIGHT    = (9 * SQUARE_SIZE) + (10 * SQUARE_MARGIN)
CANVAS_WIDTH     = (9 * SQUARE_SIZE) + (10 * SQUARE_MARGIN)

APP_NAME    = 'Pen Pal Pixel Painter'
APP_VERSION = '1'

class Server

  def initialize serving_app
    @apps= [serving_app]
  end

  def add_app app
    @apps << app
    @apps.first.instance_eval { @canvas }
  end

  def canvas_square calling_app, row, col, color
    @apps.each_with_index do |app, i|
      begin
        app.canvas_square(row, col, color) if app != calling_app
      rescue DRb::DRbError
        @apps.delete_at(i)
      end
    end
  end

end

Shoes.app(:title     => "#{APP_NAME} #{APP_VERSION}",
          :width     => CANVAS_WIDTH + PALETTE_WIDTH,
          :height    => CANVAS_HEIGHT + 62,
          :resizable => false) do

  def canvas
    @canvas.each_with_index do |row, rowi|
      row.size.times { |coli| canvas_square(rowi, coli, @canvas[rowi][coli]) }
    end
  end

  def canvas_square row, col, color
    x= PALETTE_WIDTH + (col * SQUARE_SIZE) + ((col + 1) * SQUARE_MARGIN)
    y= (row * SQUARE_SIZE) + ((row + 1) * SQUARE_MARGIN)
    square(x, y, @color_table[color])
    @canvas[row][col]= color
  end

  def check_canvas_click button, x, y
    col= (x - PALETTE_WIDTH) / (SQUARE_SIZE + SQUARE_MARGIN)
    row= y / (SQUARE_SIZE + SQUARE_MARGIN)
    if 0 <= col && col < 9 && row < 9
      case button
      when 1
        canvas_square(row, col, @color)
        @server.canvas_square(self, row, col, @color)
      when 2
        select_color(@canvas[row][col])
      end
    end
  end

  def check_palette_click x, y
    col= x / (SQUARE_SIZE + SQUARE_MARGIN)
    row= y / (SQUARE_SIZE + SQUARE_MARGIN)
    select_color((row * 2) + col) if col < 2 && row < 9
  end

  def hsl h, s, l
    h, s, l= h.to_f, s.to_f, l.to_f
    q= l < 0.5 ? l * (1 + s) : l + s - (l * s)
    p= (2 * l) - q
    hk= h / 360
    tc= [hk + (1.0 / 3.0), hk, hk - (1.0 / 3.0)].map do |c|
      if c < 0:    c + 1
      elsif c > 1: c - 1
      else         c
      end
    end
    rgb(*tc.map do |c|
      if c < 1.0 / 6.0:                  p + ((q - p) * 6 * c)
      elsif 1.0 / 6.0 <= c && c < 0.5:   q
      elsif 0.5 <= c && c < (2.0 / 3.0): p + ((q - p) * 6 * ((2.0 / 3.0) - c))
      else                               p
      end
    end)
  end

  def palette
    @color_table= []
    col_skip= 360.0 / 16.0
    16.times do |i|
      @color_table << [i * col_skip, 1, 0.5]
      palette_square(i)
    end
    @color_table << [0, 0, 0.1]
    palette_square(16)
    @color_table << [0, 0, 0.9]
    palette_square(17)
  end

  def palette_square color
    square(((color % 2) * (SQUARE_SIZE + SQUARE_MARGIN)) + SQUARE_MARGIN,
           ((color / 2) * (SQUARE_SIZE + SQUARE_MARGIN)) + SQUARE_MARGIN,
           @color_table[color],
           @color == color)
  end

  def select_color color
    old= @color
    @color= color
    palette_square(old) if old
    palette_square(@color)
  end

  def square x, y, hsl_color, stroke=false
    nostroke
    fill(BACKGROUND_COLOR)
    rect(:left   => x - SQUARE_MARGIN,
         :top    => y - SQUARE_MARGIN,
         :width  => SQUARE_SIZE + (2 * SQUARE_MARGIN),
         :height => SQUARE_SIZE + (2 * SQUARE_MARGIN),
         :radius => 0)
    if stroke
      strokewidth(SQUARE_MARGIN + 1)
      stroke('#fff')
    else
      nostroke
    end
    fill(hsl(*hsl_color))
    rect(:left   => x,
         :top    => y,
         :width  => SQUARE_SIZE,
         :height => SQUARE_SIZE,
         :radius => SQUARE_RADIUS)
  end

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

  @server= Server.new(self)
  drb= DRb.start_service('druby://127.0.0.1:0', @server)

  background(BACKGROUND_COLOR)
  palette
  select_color(0)
  @canvas= (1..9).map { (1..9).map { 16 } }
  canvas

  para('You are @ ', strong(drb.uri.to_s),
       :align => 'center', :top => CANVAS_HEIGHT, :stroke => TEXT_COLOR)

  entry= edit_line(:text  => 'druby://127.0.0.1:',
                   :top   => CANVAS_HEIGHT + 30,
                   :left  => 10,
                   :width => CANVAS_WIDTH + PALETTE_WIDTH - 80)
  but= button('Join', :top => CANVAS_HEIGHT + 30, :right => 10, :width => 50) do
    begin
      uri= entry.text.to_s
      @server= DRbObject.new_with_uri(uri)
      @canvas= @server.add_app(self)
      canvas
      entry.remove
      but.remove
      para("and connected to #{uri}",
          :align => 'center', :top => CANVAS_HEIGHT + 30, :stroke => TEXT_COLOR)
    rescue StandardError => err
      alert("Error connecting to server:\n#{err}")
    end
  end

  click do |button, x, y|
    check_palette_click(x, y)
    check_canvas_click(button, x, y)
  end

end
