#!/usr/bin/python

#  entry by Paul Fernhout, Pointrel Research, 
# email: pdfernhout@kurtz-fernhout.com

#consider the license as GPL

"""
Single player game servers
Use these servers to test your robot without interference from other robots. A new game will start whenever a client connects. These will be available on:

    * Host: icfp1.cse.ogi.edu
* Ports: 22001, 22002, ..., 22010

Multiple player game servers
These severs will be running continuous games were robots can connect and disconnect at any time.

    * Host: icfp1.cse.ogi.edu
* Ports: 22101, 22102, ..., 22110 
"""

"""
# goodness map idea


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

#########
#    @  #
#       #
#####   #
#   #   #
#  *#   #
#       #
#########

Goodness map:

#########
#6789@98#
#5678987#
#####876#
# 0 #765#
#01*#654#
#1234543#
#########

Could also do something with badness
where reduce potentials, but too lazy to 
do calculation for this example:
	
#########
#6789@98#
#5678987#
#####876#
# 0 #B65#
#01*#654#
#1234543#
#########

Realizing easier for goodness to count up from zero...
so lower goodness number is better
"""

import sys

HOST = 'localhost'  
if len(sys.argv) > 1:
	HOST = sys.argv[1]

PORT = 2001 
if len(sys.argv) > 2:
	PORT = int(sys.argv[2])

import socket

s = None

from random import *

generator = Random()

def getline():
	line = ""
	c = s.recv(1)
	while c <> "\n":
		line = line + c
		c = s.recv(1)
	return line
	
def buildStringList(list):
	stringList = ""
	for element in list:
		if stringList:
			stringList = stringList + ' '
		stringList = stringList + element
	return stringList
	
def sendCommand(bid, operation, arguments):
	commandString = `bid` + ' ' + operation + ' ' + arguments + '\n'
	#print "COMMAND: '" + commandString + "'"
	s.send(commandString)
	
# direction string of N, S, E, W
def move(bid, direction):
	sendCommand(bid, 'Move', direction)

def pick(bid, list):
	sendCommand(bid, 'Pick', buildStringList(list))

def drop(bid, list):
	sendCommand(bid, 'Drop', buildStringList(list))
	
class Package:
	def __init__(self, id = "", x = 1, y = 1, weight = 100, desiredX = 1, desiredY = 1, carrier = ""):
		self.id = id
		self.currentX = x
		self.currentY = y
		self.weight = weight
		self.desiredX = desiredX
		self.desiredY = desiredY
		self.carrier = carrier
		#assume valid if created at a location
		self.checked = 1
		self.carried = 0

class World:
	def __init__(self):
		self.map = []
		self.width = 0
		self.height = 0
		
		self.fastmap = []
		
		self.robots = {}
		
		self.planner = Planner(self)
		
		self.packages = []
		
	def makePseudoPackagesToAttractToBases(self):
		# go over maps and make one package at each base
		for y in range(1, self.height + 1):
			for x in range(1, self.width + 1):
				c = self.mapElement(x, y)
				if c == "@":
					self.packages.append(Package("bogus", x, y, 100))
		
	def mapElement(self, x, y):
		if x < 1 or x > self.width: return '#'
		if y < 1 or y > self.height: return '#'
		c = self.fastmap[(y - 1) * self.width + x - 1]
		return c
	
	def mapElementWithRobots(self, x, y):
		c = self.mapElement(x, y)
		for robot in self.robots.values():
			if robot.x == x and robot.y == y:
				c = "*"
		return c
	
	def printMap(self):
		for y in range(self.height + 1, -1, -1):
			string = ""
			for x in range(0, self.width + 2):
				string = string + self.mapElementWithRobots(x, y)
			print string
	
	def readMap(self):
		dimensions_line = getline()
		self.width = int(dimensions_line.split()[0])
		self.height = int(dimensions_line.split()[1])
		print "ENTRY: width,height", self.width, self.height
		for line in range(self.height):
			lineString = getline()
			self.map.append(lineString)
			for c in lineString:
				self.fastmap.append(c)
		self.map.reverse()
		print "[[[[[[[[[[[]]]]]]]]]]]]]]"
		for line in self.map:
			print line
		print "[[[[[[[[[[[]]]]]]]]]]]]]]"
		

	def processServerResponse(self, string):
		items = string.split()
		index = 0
		robotID = None
		
		# robots will be dead unless listed
		for robot in self.robots.values():
			robot.alive = 0
			
		while index < len(items):
			item = items[index]
			if item[0] == '#':
				robotID = item[1:]
				if self.robots.has_key(robotID):
					self.robots[robotID].alive = 1
				index = index + 1
			elif item == 'X':
				if not self.robots.has_key(robotID):
					self.robots[robotID] = Robot(robotID, int(items[index+1]), int(items[index+3]), world)
				else:
					# probably this should never happen
					self.robots[robotID].x = int(items[index+1])
					self.robots[robotID].y = int(items[index+3])				
				index = index + 4
			elif item in ['N', 'S', 'E', 'W']:
				self.robots[robotID].move(item)
				index = index + 1 
			elif item == 'P':
				self.robots[robotID].pick(items[index+1])
				for package in self.packages:
					if package.id == items[index+1]:
						package.carried = 1
						package.carrier = robotID
						break
				index = index + 2 
			elif item == 'D':
				self.robots[robotID].drop(items[index+1])
				for package in self.packages:
					if package.id == items[index+1]:
						package.carried = 0
						package.carrier = ""
						break
				index = index + 2 
			elif item == 'Robot':
				#robot died
				print string 
				print getline()
				print getline()
				print getline()
				sys.exit(0)
			else:
				print string
				print "processServerResponse unexpected value" + item 

	def processAvailablePackages(self, line):
		if line: print "line", line
		items = line.split()
		index = 0
		robot = self.robots[self.planner.identifier]
		x = robot.x
		y = robot.y

		for package in self.packages:
			package.checked = 0
			
		while index < len(items):
			id = items[index]
			found = 0
			for packages in self.packages:
				if package.id == id:
					package.currentX = x
					package.currentY = y
					package.desiredX = int(items[index+1])
					package.desiredY = int(items[index+2])
					package.weight = int(items[index+3])
					package.checked = 1
					found = 1
					break
			if not found:
				print "adding package", id
				self.packages.append(Package(id, x, y, int(items[index+3]), int(items[index+1]), int(items[index+2])))
			index = index + 4
		#remove any packages thought to be here and not found
		lost = []
		#print self.packages
		for package in self.packages:
			#print package.id, package.currentX, package.currentY, x, y
			if package.currentX == x and package.currentY == y and not package.checked and not package.carried:
				print "lost:", package.id
				lost.append(package)
		for package in lost:
			print "removing", package.id
			self.packages.remove(package)

class Robot:
	def __init__(self, id, x, y, world):
		self.world = world
		self.id = id
		self.x = x
		self.y = y
		self.packages = []
		self.alive = 1
		
	# Coordinates on the board are pairs of integers, 
	# ranging from (1,1) to (width, height), 
	# where (1,1) is the southwest corner of the board.

	def move(self, direction):
		if direction == 'N':
			self.y = self.y + 1
		elif direction == 'S':
			self.y = self.y - 1
		elif direction == 'E':
			self.x = self.x + 1
		elif direction == 'W':
			self.x = self.x - 1
		else:
			raise "PROBLEM in Robot.move"

	def pick(self, package):
		self.packages.append(package)
		
	def drop(self, package):
		#handle case where joined after game in progress
		if package in self.packages:
			self.packages.remove(package)
		
	def __repr__(self):
		return 'id %s x %d y % d alive %d packages %s' % (self.id, self.x, self.y, self.alive, `self.packages`)

class Planner:
	def __init__(self, world):
		self.identifier = ""
		self.capacity = 0
		self.money = 0
		self.world = world
		
		#need two maps so can modify one while look at other
		self.goodmap = []
		self.goodmap2 = []
		
		self.iteration = 0
		
		self.updateFrequency = 10
		
		self.forcemapupdate = 0
		
	def readConfiguration(self):
		configuration_line = getline()
		self.identifier = configuration_line.split()[0]
		self.capacity = int(configuration_line.split()[1])
		self.money = int(configuration_line.split()[2])
		print "ENTRY I C M", self.identifier, self.capacity, self.money

	#ignore money allocation and bidding for now
	# use goodness map idea
	def planAndDo(self):
		print "plan and do"
		
		self.iteration = self.iteration + 1
		
		if not self.world.packages:
			self.world.makePseudoPackagesToAttractToBases()
			print "made pseudo packages at bases"
			
		#check if over any packages or destinations
		robot = self.world.robots[self.identifier]
		x = robot.x
		y = robot.y

		todrop = []
		for package in self.world.packages:
			if package.carrier == robot.id and package.desiredX == x and package.desiredY == y:
				todrop.append(package.id)
		
		if todrop:
			print "droppping", todrop
			drop(10, todrop)
			self.forcemapupdate = 1
			return

		#test to prevent greedy cycles
		if not robot.packages:
			totake = []
			for package in self.world.packages:
				if package.currentX == x and package.currentY == y:
					totake.append(package.id)
					
			if totake:
				pick(10, totake)
				self.forcemapupdate = 1
				return

		# update map as needed
		if self.iteration % self.updateFrequency == 1 or self.forcemapupdate:
			self.goodmap = []
			self.forcemapupdate = 0
			# build initial goodmap
			for y in range(0, self.world.height + 2):
				for x in range(0, self.world.width + 2):
					c = self.world.mapElement(x, y)
					if c == "@":
						value = None
					elif c == ".":
						value = None
					else:
						value = -1
					#ignoring case of other robots in the way
					#ignoring positioning of destinations
					self.goodmap.append(value)
			if robot.packages:
				#seek delivery locations
				print "delivering"
				for package in self.world.packages:
					if package.id in robot.packages:
						self.set_good1(package.desiredX, package.desiredY, 1)
			else:
				#seek packages
				print "searching"
				for package in world.packages:
					self.set_good1(package.currentX, package.currentY, 1)
				
			# do multiple passes
			changes = 1
			while changes:
				#print "sweeping", changes
				self.goodmap2 = []
				self.goodmap2.extend(self.goodmap)
				changes = 0
				for y in range(1, self.world.height + 1):
					for x in range(1, self.world.width + 1):
						changes = changes + self.perhapschange(x, y)
				self.goodmap = []
				self.goodmap.extend(self.goodmap2)
				
			print "done making goodmap"
			for y in range(self.world.height, 0, -1):
				for x in range(1, self.world.width + 1):
					print self.get_good(x, y),
				print
			
		direction = self.determineMoveDirection()
		move(1, direction)
		#move(1, randomDirection())

	def get_good(self, x, y):
		return self.goodmap[x + y * (self.world.width + 2)]
	
	def set_good1(self, x, y, value):
		self.goodmap[x + y * (self.world.width + 2)] = value

	def set_good(self, x, y, value):
		self.goodmap2[x + y * (self.world.width + 2)] = value
	
	def perhapschange(self, x, y):
		if self.get_good(x, y) == None:
			min = None
			if self.get_good(x-1, y) and self.get_good(x-1, y) > 0:
				min = self.get_good(x-1, y)
			if self.get_good(x, y-1) and self.get_good(x, y-1) > 0:
				if not min:
					min = self.get_good(x, y-1)
				else:
					if self.get_good(x, y-1) < min:
						min = self.get_good(x, y-1)
			if self.get_good(x, y+1) and self.get_good(x, y+1) > 0:
				if not min:
					min = self.get_good(x, y+1)
				else:
					if self.get_good(x, y+1) < min:
						min = self.get_good(x, y+1)
			if self.get_good(x+1, y) and self.get_good(x+1, y) > 0:
				if not min:
					min = self.get_good(x+1, y)
				else:
					if self.get_good(x+1, y) < min:
						min = self.get_good(x+1, y)
			if min:
				self.set_good(x, y, min + 1)
				return 1
		return 0
							
	def determineMoveDirection(self):
		robot = self.world.robots[self.identifier]
		x = robot.x
		y = robot.y
		min = None
		minDir = None
		if self.get_good(x-1, y) and self.get_good(x-1, y) > 0:
			min = self.get_good(x-1, y)
			minDir = "W"
		if self.get_good(x, y-1) and self.get_good(x, y-1) > 0:
			if not min:
				min = self.get_good(x, y-1)
				minDir = "S"
	
			else:
				if self.get_good(x, y-1) < min:
					min = self.get_good(x, y-1)
					minDir = "S"
	
		if self.get_good(x, y+1) and self.get_good(x, y+1) > 0:
			if not min:
				min = self.get_good(x, y+1)
				minDir = "N"
			else:
				if self.get_good(x, y+1) < min:
					min = self.get_good(x, y+1)
					minDir = "N"
		if self.get_good(x+1, y) and self.get_good(x+1, y) > 0:
			if not min:
				min = self.get_good(x+1, y)
				minDir = "E"
			else:
				if self.get_good(x+1, y) < min:
					min = self.get_good(x+1, y)
					minDir = "E"
		if min:
			return minDir
		# no way to go that makes sense
		#pick a random one
		return self.randomDirection()
	
	def randomDirection(self):
		n = generator.randrange(0, 4)
		if n == 0: return "N"
		if n == 1: return "S"
		if n == 2: return "E"
		if n == 3: return "W"
		raise "randomDirection: unexpected value" + `n`
	
#main loop
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
	s.connect((HOST, PORT))
	s.send('Player\n')
	world = World()
	world.readMap()
	world.planner.readConfiguration()
	
	while 1:
		print "=========================="

		serverresponse_line = getline()
		world.processServerResponse(serverresponse_line)
		#print "ENTRY RESPONSE", serverresponse_line
		
		availablepackages_line = getline()
		#print "ENTRY PACKAGES", availablepackages_line
		world.processAvailablePackages(availablepackages_line)

		world.printMap()
		
		for robot in world.robots.values():
			if robot.id == world.planner.identifier:
				print robot, "<me>"
			else:
				print robot
				
		world.planner.planAndDo()
		
finally:
	s.close()

