Geoffrey Frogeye
7f9a034fd5
The fact that calculated score differs from offical score really starts to annoy me
405 lines
11 KiB
Python
Executable file
405 lines
11 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
import math
|
|
import copy
|
|
import progressbar
|
|
|
|
DEBUG = False
|
|
|
|
outLines = []
|
|
|
|
def log(*data):
|
|
if DEBUG:
|
|
print(*data)
|
|
|
|
def output(*values):
|
|
global outLines
|
|
outLines.append(' '.join([str(val) for val in values]))
|
|
|
|
def distance(A, B):
|
|
return math.ceil(math.sqrt(pow(B[0] - A[0], 2) + pow(B[1] - A[1], 2)))
|
|
|
|
class Product:
|
|
|
|
ALL = []
|
|
|
|
def __init__(self, weight):
|
|
self.id = len(self.ALL)
|
|
self.ALL.append(self)
|
|
|
|
self.weight = weight
|
|
|
|
def totalWeight(items):
|
|
s = 0
|
|
for i in items:
|
|
s += Product.get(i).weight
|
|
return s
|
|
|
|
def get(pid):
|
|
return __class__.ALL[pid]
|
|
|
|
def len():
|
|
return len(__class__.ALL)
|
|
|
|
class Warehouse:
|
|
|
|
ALL = []
|
|
|
|
def __init__(self, pos, items):
|
|
self.id = len(self.ALL)
|
|
self.ALL.append(self)
|
|
|
|
self.pos = pos
|
|
self.items = items
|
|
|
|
self.plannedItems = self.items.copy()
|
|
|
|
def plan(self, items):
|
|
for i in items:
|
|
self.plannedItems.remove(i)
|
|
|
|
# Set functions
|
|
def near(pos):
|
|
couples = []
|
|
for el in __class__.ALL:
|
|
couples.append([el, distance(el.pos, pos)])
|
|
return [couple[0] for couple in sorted(couples, key=lambda c: c[1])]
|
|
|
|
def get(pid):
|
|
return __class__.ALL[pid]
|
|
|
|
def len():
|
|
return len(__class__.ALL)
|
|
|
|
class Client:
|
|
|
|
ALL = []
|
|
|
|
def __init__(self, pos, needs):
|
|
self.id = len(self.ALL)
|
|
self.ALL.append(self)
|
|
|
|
self.pos = pos
|
|
self.needs = needs
|
|
|
|
self.plannedNeeds = self.needs.copy()
|
|
|
|
def plan(self, needs):
|
|
for n in needs:
|
|
self.plannedNeeds.remove(n)
|
|
|
|
def satisfied(self):
|
|
return len(self.needs) == 0
|
|
|
|
def pack(self, payload=-1):
|
|
if payload == -1:
|
|
payload = Drone.PAYLOAD
|
|
p = []
|
|
load = 0
|
|
# # Sort occurences
|
|
# occ = [(i, self.plannedNeeds.count(i)) for i in self.plannedNeeds]
|
|
# occ.sort(key=lambda c: c[1])
|
|
# # TODO Optimise for same product wanted more than once
|
|
# Looks like it's not necessary for set 2, we'll see that later
|
|
# Sort needs by weight
|
|
couples = [(i, Product.get(i).weight) for i in self.plannedNeeds]
|
|
couples.sort(key=lambda c: c[1])
|
|
for couple in couples:
|
|
need, weight = couple
|
|
if load + weight <= payload:
|
|
p.append(need)
|
|
load += weight
|
|
return p
|
|
|
|
# Set functions
|
|
def near(pos):
|
|
couples = []
|
|
for el in __class__.ALL:
|
|
couples.append([el, distance(el.pos, pos)])
|
|
return [couple[0] for couple in sorted(couples, key=lambda c: c[1])]
|
|
|
|
def get(pid):
|
|
return __class__.ALL[pid]
|
|
|
|
def len():
|
|
return len(__class__.ALL)
|
|
|
|
class Drone:
|
|
|
|
ALL = []
|
|
PAYLOAD = 0
|
|
|
|
def __init__(self):
|
|
self.id = len(self.ALL)
|
|
self.ALL.append(self)
|
|
|
|
self.pos = Warehouse.get(0).pos
|
|
self.items = []
|
|
self.avail = 0
|
|
|
|
self.tasks = []
|
|
|
|
def addTask(self, *task):
|
|
self.tasks.append(task)
|
|
|
|
def executeTask(self):
|
|
if self.available():
|
|
if len(self.tasks):
|
|
task = self.tasks[0]
|
|
getattr(self, task[0])(*task[1:])
|
|
self.tasks = self.tasks[1:]
|
|
else:
|
|
self.wait()
|
|
|
|
def weight(self):
|
|
return Product.totalWeight(self.items)
|
|
|
|
def busyFor(self, time):
|
|
self.avail += time
|
|
assert(self.avail < T)
|
|
|
|
def available(self):
|
|
return self.avail <= turn
|
|
|
|
def load(self, warehouse, product, qt):
|
|
assert(self.available())
|
|
if (self.pos != warehouse.pos):
|
|
self.busyFor(distance(self.pos, warehouse.pos))
|
|
self.pos = warehouse.pos
|
|
for q in range(qt):
|
|
warehouse.items.remove(product.id)
|
|
self.items.append(product.id)
|
|
self.busyFor(1)
|
|
assert(self.weight() <= __class__.PAYLOAD)
|
|
log("Drone", self.id, "loads", qt, "of", product.id, "from warehouse", warehouse.id, "→", self.avail)
|
|
output(self.id, 'L', warehouse.id, product.id, qt)
|
|
|
|
def unload(self, warehouse, product, qt):
|
|
assert(self.available())
|
|
if (self.pos != warehouse.pos):
|
|
self.busyFor(distance(self.pos, warehouse.pos))
|
|
self.pos = warehouse.pos
|
|
for q in range(qt):
|
|
self.items.remove(product.id)
|
|
warehouse.items.append(product.id)
|
|
self.busyFor(1)
|
|
log("Drone", self.id, "unloads", qt, "of", product.id, "to warehouse", warehouse.id, "→", self.avail)
|
|
output(self.id, 'U', warehouse.id, product.id, qt)
|
|
|
|
def deliver(self, client, product, qt):
|
|
assert(self.available())
|
|
if (self.pos != client.pos):
|
|
self.busyFor(distance(self.pos, client.pos))
|
|
self.pos = client.pos
|
|
for q in range(qt):
|
|
self.items.remove(product.id)
|
|
client.needs.remove(product.id)
|
|
self.busyFor(1)
|
|
log("Drone", self.id, "delivers", qt, "of", product.id, "to client", client.id, "→", self.avail)
|
|
output(self.id, 'D', client.id, product.id, qt)
|
|
if client.satisfied():
|
|
global score
|
|
score += math.ceil((T-(self.avail+1))/T*100)
|
|
log("Client", client.id, "satisfied!", "New score:", score)
|
|
|
|
def wait(self, turns=1):
|
|
assert(self.available())
|
|
self.busyFor(1)
|
|
log("Drone", self.id, "waits", turns, "turn" + ('s' if turns >= 2 else ''), "→", self.avail)
|
|
output(self.id, 'W', turns)
|
|
|
|
# Set functions
|
|
def near(pos):
|
|
couples = []
|
|
for el in __class__.ALL:
|
|
couples.append([el, distance(el.pos, pos)])
|
|
return [couple[0] for couple in sorted(couples, key=lambda c: c[1])]
|
|
|
|
def get(pid):
|
|
return __class__.ALL[pid]
|
|
|
|
def len():
|
|
return len(__class__.ALL)
|
|
|
|
|
|
X = 0 # Nb rows
|
|
Y = 0 # Nb columns
|
|
T = 0 # Deadline
|
|
|
|
turn = 0 # Turn
|
|
score = 0 # Score
|
|
done = False
|
|
|
|
def readFile(filename):
|
|
global X, Y, T
|
|
|
|
with open(filename, 'r') as f:
|
|
# Parameters
|
|
X, Y, D, T, Drone.PAYLOAD = [int(i) for i in f.readline().split(' ')]
|
|
|
|
# Products
|
|
P = int(f.readline())
|
|
weights = [int(i) for i in f.readline().split(' ')]
|
|
assert(len(weights) == P)
|
|
for w in weights:
|
|
Product(w)
|
|
|
|
# Warehouses
|
|
for i in range(0, int(f.readline())):
|
|
pos = [int(i) for i in f.readline().split(' ')]
|
|
qtItems = [int(i) for i in f.readline().split(' ')]
|
|
assert(len(qtItems) == P)
|
|
|
|
items = []
|
|
for p in range(P):
|
|
for i in range(qtItems[p]):
|
|
items.append(p)
|
|
|
|
Warehouse(pos, items)
|
|
|
|
# Clients
|
|
for i in range(0, int(f.readline())):
|
|
pos = [int(i) for i in f.readline().split(' ')]
|
|
|
|
N = int(f.readline())
|
|
|
|
needs = [int(i) for i in f.readline().split(' ')]
|
|
assert(len(needs) == N)
|
|
|
|
Client(pos, needs)
|
|
|
|
# Create drones
|
|
for d in range(D):
|
|
Drone()
|
|
|
|
readFile(sys.argv[1])
|
|
|
|
def newTurn():
|
|
global turn
|
|
# Finishing turn
|
|
for drone in Drone.ALL:
|
|
drone.executeTask()
|
|
# New turn
|
|
turn += 1
|
|
log("--- Turn", turn)
|
|
availableDrones = [str(drone.id) for drone in Drone.ALL if drone.available()]
|
|
#log("Drones", ", ".join(availableDrones), "("+str(len(availableDrones))+")", "are available")
|
|
|
|
# Algorithm that only works for 1 warehouse
|
|
|
|
# Determined by trial and error. Not really reliable
|
|
CLIENT_TRIES = 3
|
|
def efficiency(pack, time):
|
|
return Product.totalWeight(pack) / time
|
|
#return len(pack) / time
|
|
#return 1 / time
|
|
|
|
def route(roadmap):
|
|
# Find the nearest client that still has things to be delivered
|
|
remainingClients = [c for c in Client.near(roadmap['pos']) if c.plannedNeeds and c not in roadmap['clients']]
|
|
#for client in remainingClients:
|
|
options = []
|
|
for client in remainingClients[:CLIENT_TRIES]:
|
|
# Create a pack to deliver
|
|
pack = client.pack(Drone.PAYLOAD - Product.totalWeight(roadmap['loads']))
|
|
if not pack:
|
|
continue
|
|
|
|
# Calcultes the efficiency of adding a stop
|
|
routeTime = distance(roadmap['pos'], client.pos) + len(list(set(pack)))
|
|
routeEfficiency = efficiency(pack, routeTime)
|
|
if roadmap['stops']:
|
|
# Calculates the efficiency of coming back to warehouse and to the client again
|
|
backPack = client.pack()
|
|
backTime = len(list(set(backPack))) + distance(roadmap['pos'], roadmap['warehouse'].pos) + distance(roadmap['warehouse'].pos, client.pos)
|
|
backEfficiency = efficiency(backPack, backTime)
|
|
if backEfficiency > routeEfficiency:
|
|
continue
|
|
options.append({
|
|
'client': client,
|
|
'efficiency': routeEfficiency,
|
|
'routeTime': routeTime,
|
|
'pack': pack
|
|
})
|
|
if not roadmap['stops']: # If it is the first stop, don't provide any alternative
|
|
break
|
|
|
|
|
|
if options:
|
|
# Choose the best option (i.e. the max efficiency)
|
|
#option = sorted(options, key=lambda c: c['efficiency'])[-1]
|
|
option = options[0]
|
|
# Plan the delivery
|
|
roadmap['warehouse'].plan(option['pack'])
|
|
option['client'].plan(option['pack'])
|
|
roadmap['deliverTime'] += option['routeTime']
|
|
roadmap['pos'] = option['client'].pos
|
|
roadmap['loads'] += option['pack']
|
|
roadmap['clients'].append(option['client'])
|
|
roadmap['stops'].append((option['client'], option['pack']))
|
|
return route(roadmap)
|
|
else:
|
|
return roadmap
|
|
|
|
|
|
def think():
|
|
# For each drone that has nothing to do
|
|
for drone in [d for d in Drone.ALL if d.available() and not d.tasks]:
|
|
# Find the nearest warehouse
|
|
warehouse = Warehouse.near(drone.pos)[0]
|
|
roadmap = route({
|
|
'pos': warehouse.pos,
|
|
'warehouse': warehouse,
|
|
'deliverTime': 0,
|
|
'loads': [],
|
|
'clients': [],
|
|
'stops': []
|
|
})
|
|
|
|
if not roadmap['stops']:
|
|
global done
|
|
done = True
|
|
break
|
|
|
|
loadOcc = dict((i, roadmap['loads'].count(i)) for i in roadmap['loads'])
|
|
for i in loadOcc:
|
|
drone.addTask('load', warehouse, Product.get(i), loadOcc[i])
|
|
|
|
for client, items in roadmap['stops']:
|
|
itemsOcc = dict((j, items.count(j)) for j in items)
|
|
for i in itemsOcc:
|
|
drone.addTask('deliver', client, Product.get(i), itemsOcc[i])
|
|
|
|
|
|
|
|
if DEBUG:
|
|
SIMULATION = 1000
|
|
else:
|
|
SIMULATION = 8*T/10
|
|
|
|
try:
|
|
if not DEBUG:
|
|
bar = progressbar.ProgressBar(max_value=SIMULATION)
|
|
while turn < SIMULATION and not done:
|
|
think()
|
|
newTurn()
|
|
if not DEBUG:
|
|
bar.update(turn)
|
|
while turn < SIMULATION and [d for d in Drone.ALL if d.tasks]:
|
|
newTurn()
|
|
if not DEBUG:
|
|
bar.update(turn)
|
|
if not DEBUG:
|
|
bar.finish()
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
with open(sys.argv[1] + 'o', 'w') as f:
|
|
f.write(str(len(outLines)) + '\n' + '\n'.join(outLines) + '\n')
|
|
print("Turn:", turn)
|
|
print("Score:", score)
|
|
|