-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtasks.py
632 lines (502 loc) · 17.2 KB
/
tasks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
import server
from things import Reference, Asset, OrderCreate, OrderRemove, dist
"""
Task Requirements
-------------------------------------------------------------------------------
A Task requires a bunch of assets to be completed. Assets fall into two
categories when acting on a task.
* primary role assets,
These are the assets which are required to make the task occur. A task can
not be completed unless all the primary roles can be met. Normally only one
asset is need to fulfill each primary role (any other assets are put in the
auxiliary pile).
For example:
- A frigate is required to complete a takeover order.
* auxiliary assets
These are assets which can help the asset be completed. These assets may
either be unable to complete the task by themselves, or excess of a primary
type.
For example:
- A battleship can no fulfill a TakeOver order by itself. It can however
provide support.
- If there is already a frigate in a TakeOver order, any other frigate
assets are auxiliary assets.
The Flagship
-------------------------------------------------------------------------------
Each Task has a "flagship" this is the asset all other assets will collect
at before starting on the task.
The flagship is normally the closest asset to where the task location (IE
the place that it needs to be completed at).
If a fleet asset is too close to the task it won't be considered to be the
flagship.
The flagship will remain stationary until all assets have merged with it.
"""
class Role(object):
"""
A role that must be fulfilled for a task to be completed.
"""
def __init__(self):
self.fulfilment = None
def assign(self, f):
# FIXME: This does not work with an asset which must have multiple roles fulfilled.
if not isinstance(f, Task.Fulfilment):
raise TypeError("Can only assign a fulfilment")
# Check that the asset can fulfil this role?
if self.check(f):
# Has any other asset been assigned to this role?
if self.fulfilment is None:
self.fulfilment = f
return None
# Can this asset fulfil this role sooner?
if f.soon < self.fulfilment.soon:
self.fulfilment, f = f, self.fulfilment
return f
def unassign(self):
f = self.fulfilment
self.fulfilment = None
return f
class Coloniser(Role):
"""
This object which fulfils this role will be used to colonise the
planet.
"""
def check(self, f):
# Check that the object can colonise a planet...
return server.COLONISE_ORDER in f.asset.ref.order_types or not f.direct
class Task(Reference):
"""\
A thing which needs to be done.
"""
class Fulfilment(object):
"""\
Fulfilment class contains a 'request' to fulfill a certain task.
asset, The asset which is going to fulfill the task
soon, How long it will take for the asset to fulfill the task
portion, The portion of this task this asset can complete
direct, Can asset can directly fulfill the task?
"""
def __init__(self, asset, soon, portion=100.0, direct=True):
## Error checking....
if not isinstance(soon, float):
try:
soon = float(soon)
except:
raise TypeError("Fulfilment's 'soon' argument must be a float.")
if not isinstance(portion, float):
try:
portion = float(portion)
except:
raise TypeError("Fulfilment's 'portion' argument must be a float.")
if not isinstance(direct, bool):
raise TypeError("Fulfilment's 'direct' argument must be a bool.")
if not isinstance(asset, Asset):
raise TypeError("Fulfilment's 'asset' argument must be a Asset object.")
self.soon = soon
self.asset = asset
self.portion = portion
self.direct = direct
def __str__(self):
if self.direct:
brackets = " [%s]"
else:
brackets = " {%s}"
return brackets % ("%s in %.2f turns will complete %.2f%%" % (self.asset.__str__(True), self.soon, self.portion))
__repr__ = __str__
def __cmp__(self, other):
"""
Fuilfilments are compared by how long the fulfilment will take,
and then by the portion of the task they will fulfull.
"""
if not isinstance(other, Task.Fulfilment):
raise TypeError("Don't know how to compare these types?")
return cmp((self.soon, self.portion), (other.soon, other.portion))
def __init__(self, ref, roles=[]):
if self.__class__ == Task:
raise SyntaxError("Can not instantiate base Task class!")
if not isinstance(ref, Reference):
raise TypeError("Task's reference must be a Reference object.")
## Actual method...
Reference.__init__(self, [ref])
## The primary role fulfilments
self.roles = roles
## The auxiliary fulfilments
self.auxiliary = []
def __eq__(self, other):
"""
Too tasks are equal if they refer to the same thing.
"""
if not isinstance(other, Task):
return False
return self.ref == other.ref
def __neq__(self, other):
return not self.__eq__(other)
def __cmp__(self, other):
"""
Tasks are compared by the portions completed.
"""
if self == other:
return 0
return cmp(self.portion(), other.portion())
def ref(self):
return self.refs[0]
ref = property(ref)
def type(self):
return self.__class__
type = property(type)
def long(self):
"""
How long this task will take to complete in turns.
"""
l = -1
# Take the maximum of the auxiliary
if len(self.auxiliary) > 0:
l = self.auxiliary[-1].soon
# Make sure that none of the roles will take longer
for role in self.roles:
if role.fulfilment is None:
continue
l = max(l, role.fulfilment.soon)
if l == -1:
return float('inf')
return l
def fulfilments(self):
"""
Returns all the fulfilments (include ones in roles and auxiliary).
"""
return self.auxiliary+[role.fulfilment for role in self.roles if not role.fulfilment is None]
def portion(self):
"""
The portion of this Task which has been fulfilled.
"""
portion = 0
for fulfilment in self.auxiliary:
portion += fulfilment.portion
# Have all the roles been fulfilled?
rolesfulfilled = True
for role in self.roles:
if role.fulfilment is None:
rolesfulfilled = False
else:
portion += role.fulfilment.portion
# If they haven't the maximum the portion is 99%
if not rolesfulfilled:
portion = min(portion, 99)
return portion
def assign(self, fulfilment):
"""
Assign an asset to this task.
Soon is how soon this asset will complete it's "portion" of the task.
Adding a new asset which would take the number of assets working on
the task above 100% will cause the method to return a list of
assets which are no longer needed.
"""
## Error checking....
if not isinstance(fulfilment, Task.Fulfilment):
raise TypeError("Assign's argument must be a Fulfilment object.")
if self == fulfilment.asset:
raise TypeError("Can not be auxiliary to oneself...")
# Try assign the fulfilment to a role
portion = 0
for role in self.roles:
if not fulfilment is None:
fulfilment = role.assign(fulfilment)
if not role.fulfilment is None:
portion += role.fulfilment.portion
# Else assign the fulfilment to a auxiliary role
if not fulfilment is None:
self.auxiliary.append(fulfilment)
self.auxiliary.sort()
# Remove any excess auxiliary assets
i = 0
while portion < 100 and i < len(self.auxiliary):
portion += self.auxiliary[i].portion
i += 1
leftover = self.auxiliary[i:]
del self.auxiliary[i:]
# Return any excess assets
return [fulfilment.asset for fulfilment in leftover]
def unassign(self):
fulfilments = self.fulfilments()
# Unassign the roles
for role in self.roles:
role.unassign()
# Unassign the auxiliary positions
self.auxiliary = []
return fulfilments
def __str__(self, short=False):
if short:
if len(self.fulfilments()) > 0:
return "<Task %s - %s (%.0f%%)>" % (self.name, self.ref, self.portion())
else:
return "<Task %s - %s (unassigned)>" % (self.name, self.ref)
# Add the fulfilments to given roles
s = "\n"
for role in self.roles:
s += "\t%s:\t%s,\n" % (role.__class__.__name__, role.fulfilment)
# Add any auxiliary fulfilments
if len(self.auxiliary) > 0:
s += "\tAuxiliary:\t%s, " % self.auxiliary[0]
for fulfilment in self.auxiliary[1:]:
s+= "\n\t\t\t%s, " % fulfilment
s = s[:-2] + "\n"
return "<Task %s - %s\n %.0f%% assigned to %s>" % (self.name, self.ref, self.portion(), s[:-1])
__repr__ = __str__
def flagship(self):
"""
Returns the flagship for the fleet which will fulfil the task.
Also returns if this flagship has yet to be built.
(asset, built?)
"""
distances = {}
for fulfilment in self.fulfilments():
distances[dist(fulfilment.asset.ref.pos, self.ref.pos[0])] = (fulfilment.asset, fulfilment.direct)
keys = distances.keys()
keys.sort()
# Don't want to assemble too close to the target!
# FIXME: If we are orbiting a planet, probably safe to use this ship...
while len(keys) > 1:
if keys[0] > server.ASSEMBLE_DISTANCE:
break
keys.pop(0)
return distances[keys[0]]
def issue(self):
"""\
Issue the correct orders to the assigned assets..
"""
used_assets = []
# First job is to collect all the assets together
if len(self.fulfilments()) > 1 or self.portion() < 100:
# Find the flagship
flagship, flagbuilt = self.flagship()
print "Flagship is", flagship, "assembling at", flagship.pos[0]
print
for fulfilment in self.fulfilments():
used_assets.append(fulfilment.asset)
print "Orders for", fulfilment.asset.__str__(True)
slot=0
if fulfilment.direct:
slot += OrderAdd_Move(fulfilment.asset, flagship.pos[0], slot)
if flagbuilt:
slot += OrderAdd_Merge(fulfilment.asset, flagship, slot)
else:
slot += OrderAdd_Build(fulfilment.asset, self, slot)
OrderAdd_Nothing(fulfilment.asset, slot)
OrderPrint(fulfilment.asset)
return used_assets
def requirements(self, asset):
"""\
Issues the correct orders to fufill this task..
"""
raise SyntaxError("This task doesn't impliment the requirements order!")
class TaskDestroy(Task):
name = 'Destroy '
def issue(self):
"""\
Issue the correct orders to the assigned assets..
"""
# First job is to collect all the assets together
used_assets = Task.issue(self)
# Second job is to move the asset to the target's position
if len(self.fulfilments()) == 1 and self.portion() >= 100:
fulfilment = self.fulfilments()[0]
used_assets.append(fulfilment.asset)
print "Orders for", fulfilment.asset.__str__(True)
slot = 0
if fulfilment.direct:
# FIXME: Should actually try an intercept the target!
slot += OrderAdd_Move(fulfilment.asset, self.ref.pos[0], slot)
else:
slot += OrderAdd_Build(fulfilment.asset, self, slot)
OrderAdd_Nothing(fulfilment.asset, slot)
OrderPrint(fulfilment.asset)
return used_assets
class TaskColonise(Task):
name = 'Colonise'
def __init__(self, ref):
Task.__init__(self, ref, [Coloniser()])
def issue(self):
# First job is to collect all the assets together
used_assets = Task.issue(self)
# Second job is to move the asset to the target's position
# Third job is to colonise the target
if len(self.fulfilments()) == 1 and self.portion() >= 100:
fulfilment = self.fulfilments()[0]
used_assets.append(fulfilment.asset)
print "Orders for", fulfilment.asset.__str__(True)
slot = 0
if fulfilment.direct:
slot += OrderAdd_Move(fulfilment.asset, self.ref.pos[0], slot)
slot += OrderAdd_Colonise(fulfilment.asset, self.ref, slot)
else:
slot += OrderAdd_Build(fulfilment.asset, self, slot)
OrderAdd_Nothing(fulfilment.asset, slot)
OrderPrint(fulfilment.asset)
return used_assets
class TaskTakeOver(TaskColonise):
name = 'TakeOver'
def issue(self):
# First job is to collect all the assets together
used_assets = Task.issue(self)
# Second job is to move the asset to the target's position
# Third job is to colonise the target
if len(self.fulfilments()) == 1 and self.portion() >= 100:
fulfilment = self.fulfilments()[0]
used_assets.append(fulfilment.asset)
print "Orders for", fulfilment.asset.__str__(True)
slot=0
if fulfilment.direct:
slot += OrderAdd_Move(fulfilment.asset, self.ref.pos[0], slot)
slot += OrderAdd_Colonise(fulfilment.asset, self.ref, slot)
else:
slot += OrderAdd_Build(fulfilment.asset, self, slot)
OrderAdd_Nothing(fulfilment.asset, slot)
OrderPrint(fulfilment.asset)
return used_assets
# FIXME: Better way to do this...
Task.DESTROY = TaskDestroy
Task.COLONISE = TaskColonise
Task.TAKEOVER = TaskTakeOver
Task.types = (Task.DESTROY, Task.COLONISE, Task.TAKEOVER)
def OrderPrint(asset):
"""\
Print out the order completion time...
"""
for i, order in enumerate(server.cache.orders[asset.ref.id]):
print "Order %i will complete in %.2f turns (%r)" % (i, order.turns, order)
print
def OrderAdd_Nothing(asset, slot):
"""\
This function removed any remaining orders which might still exist!
"""
oid = asset.ref.id
while True:
# Check if the asset already has this order
if asset.ref.order_number > slot:
order = server.cache.orders[oid][slot]
print "Extra order - Remove this %r extra order" % (order,)
OrderRemove(oid, slot)
else:
break
return True
def OrderAdd_Move(asset, pos, slot):
"""\
This function issues orders for the asset to move to the given position.
It won't add any orders if the asset is at the given position.
"""
if asset.ref.pos == pos:
print "Move Order - Object already at destination!"
return False
# FIXME: Check that asset can move!
oid = asset.ref.id
while True:
# Check if the asset already has this order
if asset.ref.order_number > slot:
order = server.cache.orders[oid][slot]
# Remove the order if it isn't a move order
if order.subtype != server.MOVE_ORDER:
print "Move order - Current order (%r) wasn't a move order!" % order
OrderRemove(oid, slot)
continue
# Remove the order if it isn't a move order to the correct location
if order.pos != pos:
print "Move order - Current order (%r) was too wrong destination!" % order
OrderRemove(oid, slot)
continue
# Order is correct
print "Move order - Already had correct move order."
break
else:
print "Move order - Issuing new order to move too %s" % (pos,)
# We need to issue a move order instead.
OrderCreate(oid, -1, server.MOVE_ORDER, pos)
break
return True
def OrderAdd_Colonise(asset, targets, slot):
# Find the planet which we want to colonise
target = None
for ref in targets.refs:
if ref._subtype == server.PLANET_TYPE:
target = ref
break
if target is None:
raise TypeError("Trying to colonise something which isn't a planet!")
oid = asset.ref.id
while True:
if asset.ref.order_number > slot:
order = server.cache.orders[oid][slot]
# Remove the order if it isn't a colonise order
if order.subtype != server.COLONISE_ORDER:
print "Colonise order - Current order (%r) wasn't a colonise order!" % order
OrderRemove(oid, slot)
continue
# Order is correct
print "Colonise order - Already had correct colonise order (%r)." % (target,)
break
else:
print "Colonise order - Issuing new order colonise %r" % (target,)
# We need to issue a move order instead.
OrderCreate(oid, -1, server.COLONISE_ORDER, target.id)
break
return True
def OrderAdd_Merge(asset, target, slot):
# FIXME: Check that asset and target are both Fleets!
if asset.ref.id == target.ref.id:
return False
oid = asset.ref.id
while True:
if asset.ref.order_number > slot:
order = server.cache.orders[oid][slot]
# Remove the order if it isn't a move order
if order.subtype != server.MERGEFLEET_ORDER:
print "Merge order - Current order (%r) wasn't a Merge order!" % order
OrderRemove(oid, slot)
continue
# Order is correct
print "Merge order - Object already had correct MergeFleet order."
break
else:
print "Merge order - Issuing orders to merge with %r" % (target.ref,)
# We need to issue a move order instead.
OrderCreate(oid, -1, server.MERGEFLEET_ORDER)
break
return True
def OrderAdd_Build(asset, task, slot):
oid = asset.ref.id
# Do a "probe" to work out the types
OrderCreate(oid, 0, server.BUILDFLEET_ORDER, [], [], 0, "")
result = server.cache.orders[oid][0]
OrderRemove(oid, 0)
ships = {}
for id, name, max in result.ships[0]:
ships[name] = id
# Add the new build order
tobuild = []
if task.type in (Task.COLONISE, Task.TAKEOVER):
# If we are referencing a colonise, better build a frigate
print "Issuing orders to build a frigate"
tobuild.append((ships['Frigate'],1))
if task.type in (Task.DESTROY, Task.TAKEOVER):
# Better build a battleship
print "Issuing orders to build a battleship"
tobuild.append((ships['Battleship'],1))
while True:
if asset.ref.order_number > slot:
order = server.cache.orders[oid][slot]
# Remove the order if it isn't a colonise order
if order.subtype != server.BUILDFLEET_ORDER:
print "Build order - Current order (%r) wasn't a build order!" % order
OrderRemove(oid, slot)
continue
if order.ships[1] != tobuild:
print "Build order - Current order (%r) wasn't building the correct stuff!" % order
OrderRemove(oid, slot)
continue
# Order is correct
print "Build order - Already had correct build order."
break
else:
print "Build order - Issuing new order build."
# We need to issue a move order instead.
OrderCreate(oid, 0, server.BUILDFLEET_ORDER, [], tobuild, 0, "A robot army!")
break
return True