#!/usr/bin/ruby
require 'socket'
require 'timeout'

def pr(*line)
  # uncomment following line for verbosity
  print line
end


#
# Base classes...
#

class Array
  def shuffle
    newarray = Array.new
    length.times {newarray << delete_at(rand(length))}
    newarray
  end
  def find(id)
    f = nil
    for item in self
      f = item if item.id == id
    end
    f
  end
end

class Server < TCPSocket
  def get
    str = gets while str == nil
    str
  end
  def put(s)
    print s, "\n"
    flush
  end
end

class Point
  def initialize(x, y)
    @x = x
    @y = y
  end
  def x
    @x
  end
  def y
    @y
  end
  def move(dir)
    case dir
      when 'N' then Point.new(x, y + 1)
      when 'E' then Point.new(x + 1, y)
      when 'S' then Point.new(x, y - 1)
      when 'W' then Point.new(x - 1, y)
      else Point.new(x, y)
    end
  end
  def move!(dir)
    p = move(dir)
    @x = p.x
    @y = p.y
  end
  def around
    f = FALSE
    for dir in ['N', 'E', 'S', 'W']
      f = f or yield move(dir)
    end
    f
  end
end

class Entity < Point
  def initialize(x, y, id = 0)
    @x = x
    @y = y
    @id = id
  end
  def id
    @id
  end
end

class Store < Entity
  def packages=(p)
    @packages = p
  end
  def packages
    if @packages == nil
      @packages = Array.new
    end
    @packages
  end
end

class Package < Entity
  def weight=(w)
    @weight = w
  end
  def weight
    @weight
  end
end

class Base < Store
end

class Robot < Store
end

#
# The light map...
#

class Light

  def initialize
    @light = Array.new
    @width = 0
    @height = 0
  end

  def get(x, y)
    if x < 0 or x >= @width or y < 0 or y >= @height
      0
    else
      @light[y][x]
    end
  end

  def [](x, y)
    l = get(x, y)
    # slight shadow around water...
    l -= 1 if Point.new(x, y).around {|p| get(p.x, p.y) == -1}
    l
  end

  def []=(x, y, v)
    @light = Array.new if @light == nil
    @light[y] = Array.new if @light[y] == nil
    @light[y][x] = v
  end

  def parserow(y, str)
    pr str
    bases = Array.new
    str = str.scan /[\.~#@]/
    width.times do |x|
      case str[x]
        # ambient light...
        when '.' then self[x, y] = 1
        # water...
        when '~' then self[x, y] = -1
        # darkness...
        when '#' then self[x, y] = 0
        # base...
        when '@'
          self[x, y] = 1
          bases << Base.new(x, y)
      end
    end
    bases
  end

  def clear
    # set light back to ambient...
    height.times do |y|
      width.times {|x| self[x, y] = 1 if self[x, y] > 0}
    end
  end

  def beacon(point, intensity)
    # create a beacon...
    self[point.x, point.y] = get(point.x, point.y) + intensity
  end

  def increase
    # increase intensity around beacons...
    array = Array.new
    height.times do |r|
      array[r] = Array.new
      width.times {|c| propagate(c, r, array)}
    end
    height.times do |r|
      width.times {|c| self[c, r] = get(c, r) + 1 if array[r][c]}
    end
    Thread.pass
  end

  def propagate(x, y, array)
    # propagate light intensity around a point...
    if get(x, y) > 0
      array[y][x] = Point.new(x, y).around {|p| self[p.x, p.y] > 1}
    end
  end

  def width=(w)
    @width = w.to_i
  end

  def width
    @width
  end

  def height=(h)
    @height = h.to_i
  end

  def height
    @height
  end

end

#
# The game...
#

class Game

  def initialize(host, port)
    @light = Light.new
    @bases = Array.new
    pr "Connecting to server...\n"
    @server = Server.new host, port
    @server.put 'Player'

    pr "Retrieving map...\n"
    # world dimensions...
    s = @server.get.scan /([0-9]+) ([0-9]+)/
    @light.width = s[0][0]
    @light.height = s[0][1]

    # world map...
    @light.height.times do |row|
      @bases.concat(@light.parserow(row, @server.get))
    end

    pr "Retrieving robot configuration...\n"
    # initial robot state...
    s = @server.get.scan /([0-9]+) ([0-9]+) ([0-9]+)/
    @robot = s[0][0].to_i
    @capacity = s[0][1].to_i
    @money = s[0][2].to_i

    pr "Retrieving initial conditions...\n"
    # initial robot positions
    parseresponse
  end

  def look(dir)
    print "looking ", dir, ": "
    p = robot.move(dir)
    print @light[p.x, p.y], "\n"
    @light[p.x, p.y]
  end
  
  def move(dir)
    bid = look(dir) - @light[robot.x, robot.y]
    bid = 1 if bid == 0
    pr 'Moving ', dir, "\n"
    @server.put bid.to_s + ' Move ' + dir
    @money -= bid
    parseresponse
  end

  def pick(id)
    bid = 1
    pr 'Picking ', id.to_s, "\n"
    @server.put bid.to_s + ' Pick ' + id.to_s
    @money -= bid
    parseresponse
  end
  
  def drop(id)
    bid = 1
    pr 'Dropping ', id.to_s, "\n"
    @server.put bid.to_s + ' Drop ' + id.to_s
    @money -= bid
    parseresponse
  end
    
  def parseresponse
    # Robot movements...
    rs = @server.get.scan /#([0-9]+)( X ([0-9]+) Y ([0-9]+)| ([NESW])| ([DP]) ([0-9]+))*/
    newrs = Array.new
    for r in rs
      id = r[0].to_i
      newr = @robots.find(id) unless @robots == nil or @robots.find(id) == nil
      newr = Robot.new(r[2].to_i - 1, r[3].to_i - 1, id) unless r[2] == nil
      newr.move!(r[4]) unless r[4] == nil
      if r[5] == 'P'
        findbase(newr.x, newr.y) do |b|
          b.packages.delete_if do |p|
            if p.id == r[6].to_i
              newr.packages << p
              TRUE
            else
              FALSE
            end
          end
        end
      end
      if r[5] == 'D'
        newr.packages.delete_if {|p| p.id == r[6].to_i}
      end
      newrs << newr
    end
    @robots = newrs

    # Packages at this location...
    ps = @server.get.scan /([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/
    findbase do |b|
      b.packages = Array.new
      for p in ps
        package = Package.new(p[1].to_i - 1, p[2].to_i - 1, p[0].to_i)
        package.weight = p[3].to_i
        b.packages << package
      end
      if b.packages.length == 0
        # no more packages...
        @bases.delete_if {|b| (b.x == robot.x) and (b.y == robot.y)}
      else
        # sort packages by weight...
        b.packages.sort! {|a, b| b.weight <=> a.weight}
      end
    end
  end
  
  def showrobots
    for r in @robots
      unless r == nil
        pr 'Robot ', r.id, ': ', r.x, ', ', r.y
        unless r.packages == nil or r.packages.length == 0
          pr ' has packages '
          for p in r.packages
            pr p.id.to_s, ' ' unless p == nil
          end
        end
      end
      pr "\n"
    end
  end

  def go

    # increase map brightness...
    begin
      timeout(1) do
        while @light[robot.x, robot.y] == 1
          @light.increase
        end
      end
    rescue TimeoutError
    end

    # Drop
    findpackage do |p|
      drop(p.id)
      return
    end

    # Pick
    findbase do |b|
      for p in b.packages
        if p != nil and p.weight <= (@capacity - load)
          pick(p.id)
          return
        end
      end
    end

    # Move
    showrobots
    dirs = ['N', 'E', 'S', 'W'].shuffle
    best = 0
    for dir in dirs
      l = look(dir)
      if l > best
        best = l
        m = dir
      end
    end
    move(m)
  end

  def clear
    @light.clear
  end

  def beacon(point, intensity)
    pr 'Beacon at ', point.x, ', ', point.y, "\n"
    @light.beacon(point, intensity)
  end

  def load
    l = 0
    for p in robot.packages
      l += p.weight unless p == nil
    end
    l
  end

  def findbase(x=nil, y=nil)
    unless robot == nil
      x = robot.x if x == nil
      y = robot.y if y == nil
      for base in bases
          yield base if base.x == x and base.y == y
      end
    end
  end
  
  def findpackage
    unless robot == nil
      x = robot.x
      y = robot.y
      ps = Array.new.concat(robot.packages)
      for package in ps
        yield package if (package != nil) and (package.x == x) and (package.y == y)
      end
    end
  end

  def capacity
    @capacity
  end

  def money
    @money
  end

  def robot
    @robots.find(@robot)
  end

  def bases
    @bases
  end

  def packages
    robot.packages
  end

end

newline = "\n"

begin

#
# Initialise the game...
#

unless ARGV.length == 2
  print "Usage: ", $0, " <hostname> <port>\n"
  exit
end

g = Game.new(ARGV[0], ARGV[1].to_i)
pr "Beginning game...\n"
#
# Tactics...
#

phase = 'any'
init = FALSE
bid = 2

load = 0
bases = g.bases.length
until g.robot == nil
  if init == FALSE
    g.clear
    case phase
      when 'retrieve'
        # put beacons at all bases...
        for base in g.bases
          g.beacon(base, bid)
        end
      when 'deliver'
        # put beacons at all delivery points...
        for package in g.packages
          g.beacon(package, bid) unless package == nil
        end
      else
        # put beacons at bases and delivery points
        for base in g.bases
          g.beacon(base, bid)
        end
        for package in g.packages
          g.beacon(package, bid) unless package == nil
        end
    end
    init = TRUE
  end
  
  g.go

  if g.load != load or g.bases.length != bases
    # either we picked a package, we dropped a package, or a base dried up
    if g.load == 0
      # if we have no packages, get some...
      phase = 'retrieve'
    elsif g.load < (g.capacity / 3)
      # if we have space, we can get packages if we're close to bases...
      phase = 'any'
    else
      # otherwise, deliver the packages we have...
      phase = 'deliver'
    end
    init = FALSE
  end
  load = g.load
  bases = g.bases.length
 
end

rescue Interrupt
end

pr "Robot dies.\n"
# darn...
