Line data Source code
1 : /**
2 : * This takes the input queues and picks which items to fund with resources until no more resources are left to distribute.
3 : *
4 : * Currently this manager keeps accounts for each queue, split between the 4 main resources
5 : *
6 : * Each time resources are available (ie not in any account), it is split between the different queues
7 : * Mostly based on priority of the queue, and existing needs.
8 : * Each turn, the queue Manager checks if a queue can afford its next item, then it does.
9 : *
10 : * A consequence of the system it's not really revertible. Once a queue has an account of 500 food, it'll keep it
11 : * If for some reason the AI stops getting new food, and this queue lacks, say, wood, no other queues will
12 : * be able to benefit form the 500 food (even if they only needed food).
13 : * This is not to annoying as long as all goes well. If the AI loses many workers, it starts being problematic.
14 : *
15 : * It also has the effect of making the AI more or less always sit on a few hundreds resources since most queues
16 : * get some part of the total, and if all queues have 70% of their needs, nothing gets done
17 : * Particularly noticeable when phasing: the AI often overshoots by a good 200/300 resources before starting.
18 : *
19 : * This system should be improved. It's probably not flexible enough.
20 : */
21 :
22 0 : PETRA.QueueManager = function(Config, queues)
23 : {
24 0 : this.Config = Config;
25 0 : this.queues = queues;
26 0 : this.priorities = {};
27 0 : for (let i in Config.priorities)
28 0 : this.priorities[i] = Config.priorities[i];
29 0 : this.accounts = {};
30 :
31 : // the sorting is updated on priority change.
32 0 : this.queueArrays = [];
33 0 : for (let q in this.queues)
34 : {
35 0 : this.accounts[q] = new API3.Resources();
36 0 : this.queueArrays.push([q, this.queues[q]]);
37 : }
38 0 : let priorities = this.priorities;
39 0 : this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]);
40 : };
41 :
42 0 : PETRA.QueueManager.prototype.getAvailableResources = function(gameState)
43 : {
44 0 : let resources = gameState.getResources();
45 0 : for (let key in this.queues)
46 0 : resources.subtract(this.accounts[key]);
47 0 : return resources;
48 : };
49 :
50 0 : PETRA.QueueManager.prototype.getTotalAccountedResources = function()
51 : {
52 0 : let resources = new API3.Resources();
53 0 : for (let key in this.queues)
54 0 : resources.add(this.accounts[key]);
55 0 : return resources;
56 : };
57 :
58 0 : PETRA.QueueManager.prototype.currentNeeds = function(gameState)
59 : {
60 0 : let needed = new API3.Resources();
61 : // queueArrays because it's faster.
62 0 : for (let q of this.queueArrays)
63 : {
64 0 : let queue = q[1];
65 0 : if (!queue.hasQueuedUnits() || !queue.plans[0].isGo(gameState))
66 0 : continue;
67 0 : let costs = queue.plans[0].getCost();
68 0 : needed.add(costs);
69 : }
70 : // get out current resources, not removing accounts.
71 0 : let current = gameState.getResources();
72 0 : for (let res of Resources.GetCodes())
73 0 : needed[res] = Math.max(0, needed[res] - current[res]);
74 :
75 0 : return needed;
76 : };
77 :
78 : // calculate the gather rates we'd want to be able to start all elements in our queues
79 : // TODO: many things.
80 0 : PETRA.QueueManager.prototype.wantedGatherRates = function(gameState)
81 : {
82 : // default values for first turn when we have not yet set our queues.
83 0 : if (gameState.ai.playedTurn === 0)
84 : {
85 0 : let ret = {};
86 0 : for (let res of Resources.GetCodes())
87 0 : ret[res] = this.Config.queues.firstTurn[res] || this.Config.queues.firstTurn.default;
88 0 : return ret;
89 : }
90 :
91 : // get out current resources, not removing accounts.
92 0 : let current = gameState.getResources();
93 : // short queue is the first item of a queue, assumed to be ready in 30s
94 : // medium queue is the second item of a queue, assumed to be ready in 60s
95 : // long queue contains the isGo=false items, assumed to be ready in 300s
96 0 : let totalShort = {};
97 0 : let totalMedium = {};
98 0 : let totalLong = {};
99 0 : for (let res of Resources.GetCodes())
100 : {
101 0 : totalShort[res] = this.Config.queues.short[res] || this.Config.queues.short.default;
102 0 : totalMedium[res] = this.Config.queues.medium[res] || this.Config.queues.medium.default;
103 0 : totalLong[res] = this.Config.queues.long[res] || this.Config.queues.long.default;
104 : }
105 : let total;
106 : // queueArrays because it's faster.
107 0 : for (let q of this.queueArrays)
108 : {
109 0 : let queue = q[1];
110 0 : if (queue.paused)
111 0 : continue;
112 0 : for (let j = 0; j < queue.length(); ++j)
113 : {
114 0 : if (j > 1)
115 0 : break;
116 0 : let cost = queue.plans[j].getCost();
117 0 : if (queue.plans[j].isGo(gameState))
118 : {
119 0 : if (j === 0)
120 0 : total = totalShort;
121 : else
122 0 : total = totalMedium;
123 : }
124 : else
125 0 : total = totalLong;
126 0 : for (let type in total)
127 0 : total[type] += cost[type];
128 0 : if (!queue.plans[j].isGo(gameState))
129 0 : break;
130 : }
131 : }
132 : // global rates
133 0 : let rates = {};
134 : let diff;
135 0 : for (let res of Resources.GetCodes())
136 : {
137 0 : if (current[res] > 0)
138 : {
139 0 : diff = Math.min(current[res], totalShort[res]);
140 0 : totalShort[res] -= diff;
141 0 : current[res] -= diff;
142 0 : if (current[res] > 0)
143 : {
144 0 : diff = Math.min(current[res], totalMedium[res]);
145 0 : totalMedium[res] -= diff;
146 0 : current[res] -= diff;
147 0 : if (current[res] > 0)
148 0 : totalLong[res] -= Math.min(current[res], totalLong[res]);
149 : }
150 : }
151 0 : rates[res] = totalShort[res]/30 + totalMedium[res]/60 + totalLong[res]/300;
152 : }
153 :
154 0 : return rates;
155 : };
156 :
157 0 : PETRA.QueueManager.prototype.printQueues = function(gameState)
158 : {
159 0 : let numWorkers = 0;
160 0 : gameState.getOwnUnits().forEach(ent => {
161 0 : if (ent.getMetadata(PlayerID, "role") === PETRA.Worker.ROLE_WORKER && ent.getMetadata(PlayerID, "plan") === undefined)
162 0 : numWorkers++;
163 : });
164 0 : API3.warn("---------- QUEUES ------------ with pop " + gameState.getPopulation() + " and workers " + numWorkers);
165 0 : for (let i in this.queues)
166 : {
167 0 : let q = this.queues[i];
168 0 : if (q.hasQueuedUnits())
169 : {
170 0 : API3.warn(i + ": ( with priority " + this.priorities[i] +" and accounts " + uneval(this.accounts[i]) +")");
171 0 : API3.warn(" while maxAccountWanted(0.6) is " + uneval(q.maxAccountWanted(gameState, 0.6)));
172 : }
173 0 : for (let plan of q.plans)
174 : {
175 0 : let qStr = " " + plan.type + " ";
176 0 : if (plan.number)
177 0 : qStr += "x" + plan.number;
178 0 : qStr += " isGo " + plan.isGo(gameState);
179 0 : API3.warn(qStr);
180 : }
181 : }
182 0 : API3.warn("Accounts");
183 0 : for (let p in this.accounts)
184 0 : API3.warn(p + ": " + uneval(this.accounts[p]));
185 0 : API3.warn("Current Resources: " + uneval(gameState.getResources()));
186 0 : API3.warn("Available Resources: " + uneval(this.getAvailableResources(gameState)));
187 0 : API3.warn("Wanted Gather Rates: " + uneval(gameState.ai.HQ.GetWantedGatherRates(gameState)));
188 0 : API3.warn("Current Gather Rates: " + uneval(gameState.ai.HQ.GetCurrentGatherRates(gameState)));
189 0 : API3.warn("Most needed resources: " + uneval(gameState.ai.HQ.pickMostNeededResources(gameState)));
190 0 : API3.warn("------------------------------------");
191 : };
192 :
193 0 : PETRA.QueueManager.prototype.clear = function()
194 : {
195 0 : for (let i in this.queues)
196 0 : this.queues[i].empty();
197 : };
198 :
199 : /**
200 : * set accounts of queue i from the unaccounted resources
201 : */
202 0 : PETRA.QueueManager.prototype.setAccounts = function(gameState, cost, i)
203 : {
204 0 : let available = this.getAvailableResources(gameState);
205 0 : for (let res of Resources.GetCodes())
206 : {
207 0 : if (this.accounts[i][res] >= cost[res])
208 0 : continue;
209 0 : this.accounts[i][res] += Math.min(available[res], cost[res] - this.accounts[i][res]);
210 : }
211 : };
212 :
213 : /**
214 : * transfer accounts from queue i to queue j
215 : */
216 0 : PETRA.QueueManager.prototype.transferAccounts = function(cost, i, j)
217 : {
218 0 : for (let res of Resources.GetCodes())
219 : {
220 0 : if (this.accounts[j][res] >= cost[res])
221 0 : continue;
222 0 : let diff = Math.min(this.accounts[i][res], cost[res] - this.accounts[j][res]);
223 0 : this.accounts[i][res] -= diff;
224 0 : this.accounts[j][res] += diff;
225 : }
226 : };
227 :
228 : /**
229 : * distribute the resources between the different queues according to their priorities
230 : */
231 0 : PETRA.QueueManager.prototype.distributeResources = function(gameState)
232 : {
233 0 : let availableRes = this.getAvailableResources(gameState);
234 0 : for (let res of Resources.GetCodes())
235 : {
236 0 : if (availableRes[res] < 0) // rescale the accounts if we've spent resources already accounted (e.g. by bartering)
237 : {
238 0 : let total = gameState.getResources()[res];
239 0 : let scale = total / (total - availableRes[res]);
240 0 : availableRes[res] = total;
241 0 : for (let j in this.queues)
242 : {
243 0 : this.accounts[j][res] = Math.floor(scale * this.accounts[j][res]);
244 0 : availableRes[res] -= this.accounts[j][res];
245 : }
246 : }
247 :
248 0 : if (!availableRes[res])
249 : {
250 0 : this.switchResource(gameState, res);
251 0 : continue;
252 : }
253 :
254 0 : let totalPriority = 0;
255 0 : let tempPrio = {};
256 0 : let maxNeed = {};
257 : // Okay so this is where it gets complicated.
258 : // If a queue requires "res" for the next elements (in the queue)
259 : // And the account is not high enough for it.
260 : // Then we add it to the total priority.
261 : // To try and be clever, we don't want a long queue to hog all resources. So two things:
262 : // -if a queue has enough of resource X for the 1st element, its priority is decreased (factor 2).
263 : // -queues accounts are capped at "resources for the first + 60% of the next"
264 : // This avoids getting a high priority queue with many elements hogging all of one resource
265 : // uselessly while it awaits for other resources.
266 0 : for (let j in this.queues)
267 : {
268 : // returns exactly the correct amount, ie 0 if we're not go.
269 0 : let queueCost = this.queues[j].maxAccountWanted(gameState, 0.6);
270 0 : if (this.queues[j].hasQueuedUnits() && this.accounts[j][res] < queueCost[res] && !this.queues[j].paused)
271 : {
272 : // adding us to the list of queues that need an update.
273 0 : tempPrio[j] = this.priorities[j];
274 0 : maxNeed[j] = queueCost[res] - this.accounts[j][res];
275 : // if we have enough of that resource for our first item in the queue, diminish our priority.
276 0 : if (this.accounts[j][res] >= this.queues[j].getNext().getCost()[res])
277 0 : tempPrio[j] /= 2;
278 :
279 0 : if (tempPrio[j])
280 0 : totalPriority += tempPrio[j];
281 : }
282 0 : else if (this.accounts[j][res] > queueCost[res])
283 : {
284 0 : availableRes[res] += this.accounts[j][res] - queueCost[res];
285 0 : this.accounts[j][res] = queueCost[res];
286 : }
287 : }
288 : // Now we allow resources to the accounts. We can at most allow "TempPriority/totalpriority*available"
289 : // But we'll sometimes allow less if that would overflow.
290 0 : let available = availableRes[res];
291 0 : let missing = false;
292 0 : for (let j in tempPrio)
293 : {
294 : // we'll add at much what can be allowed to this queue.
295 0 : let toAdd = Math.floor(availableRes[res] * tempPrio[j]/totalPriority);
296 0 : if (toAdd >= maxNeed[j])
297 0 : toAdd = maxNeed[j];
298 : else
299 0 : missing = true;
300 0 : this.accounts[j][res] += toAdd;
301 0 : maxNeed[j] -= toAdd;
302 0 : available -= toAdd;
303 : }
304 0 : if (missing && available > 0) // distribute the rest (due to floor) in any queue
305 : {
306 0 : for (let j in tempPrio)
307 : {
308 0 : let toAdd = Math.min(maxNeed[j], available);
309 0 : this.accounts[j][res] += toAdd;
310 0 : available -= toAdd;
311 0 : if (available <= 0)
312 0 : break;
313 : }
314 : }
315 0 : if (available < 0)
316 0 : API3.warn("Petra: problem with remaining " + res + " in queueManager " + available);
317 : }
318 : };
319 :
320 0 : PETRA.QueueManager.prototype.switchResource = function(gameState, res)
321 : {
322 : // We have no available resources, see if we can't "compact" them in one queue.
323 : // compare queues 2 by 2, and if one with a higher priority could be completed by our amount, give it.
324 : // TODO: this isn't perfect compression.
325 0 : for (let j in this.queues)
326 : {
327 0 : if (!this.queues[j].hasQueuedUnits() || this.queues[j].paused)
328 0 : continue;
329 :
330 0 : let queue = this.queues[j];
331 0 : let queueCost = queue.maxAccountWanted(gameState, 0);
332 0 : if (this.accounts[j][res] >= queueCost[res])
333 0 : continue;
334 :
335 0 : for (let i in this.queues)
336 : {
337 0 : if (i === j)
338 0 : continue;
339 0 : let otherQueue = this.queues[i];
340 0 : if (this.priorities[i] >= this.priorities[j] || otherQueue.switched !== 0)
341 0 : continue;
342 0 : if (this.accounts[j][res] + this.accounts[i][res] < queueCost[res])
343 0 : continue;
344 :
345 0 : let diff = queueCost[res] - this.accounts[j][res];
346 0 : this.accounts[j][res] += diff;
347 0 : this.accounts[i][res] -= diff;
348 0 : ++otherQueue.switched;
349 0 : if (this.Config.debug > 2)
350 0 : API3.warn ("switching queue " + res + " from " + i + " to " + j + " in amount " + diff);
351 0 : break;
352 : }
353 : }
354 : };
355 :
356 : // Start the next item in the queue if we can afford it.
357 0 : PETRA.QueueManager.prototype.startNextItems = function(gameState)
358 : {
359 0 : for (let q of this.queueArrays)
360 : {
361 0 : let name = q[0];
362 0 : let queue = q[1];
363 0 : if (queue.hasQueuedUnits() && !queue.paused)
364 : {
365 0 : let item = queue.getNext();
366 0 : if (this.accounts[name].canAfford(item.getCost()) && item.canStart(gameState))
367 : {
368 : // canStart may update the cost because of the costMultiplier so we must check it again
369 0 : if (this.accounts[name].canAfford(item.getCost()))
370 : {
371 0 : this.finishingTime = gameState.ai.elapsedTime;
372 0 : this.accounts[name].subtract(item.getCost());
373 0 : queue.startNext(gameState);
374 0 : queue.switched = 0;
375 : }
376 : }
377 : }
378 0 : else if (!queue.hasQueuedUnits())
379 : {
380 0 : this.accounts[name].reset();
381 0 : queue.switched = 0;
382 : }
383 : }
384 : };
385 :
386 0 : PETRA.QueueManager.prototype.update = function(gameState)
387 : {
388 0 : Engine.ProfileStart("Queue Manager");
389 :
390 0 : for (let i in this.queues)
391 : {
392 0 : this.queues[i].check(gameState); // do basic sanity checks on the queue
393 0 : if (this.priorities[i] > 0)
394 0 : continue;
395 0 : API3.warn("QueueManager received bad priorities, please report this error: " + uneval(this.priorities));
396 0 : this.priorities[i] = 1; // TODO: make the Queue Manager not die when priorities are zero.
397 : }
398 :
399 : // Pause or unpause queues depending on the situation
400 0 : this.checkPausedQueues(gameState);
401 :
402 : // Let's assign resources to plans that need them
403 0 : this.distributeResources(gameState);
404 :
405 : // Start the next item in the queue if we can afford it.
406 0 : this.startNextItems(gameState);
407 :
408 0 : if (this.Config.debug > 1 && gameState.ai.playedTurn%50 === 0)
409 0 : this.printQueues(gameState);
410 :
411 0 : Engine.ProfileStop();
412 : };
413 :
414 : // Recovery system: if short of workers after an attack, pause (and reset) some queues to favor worker training
415 0 : PETRA.QueueManager.prototype.checkPausedQueues = function(gameState)
416 : {
417 0 : const numWorkers = gameState.countOwnEntitiesAndQueuedWithRole(PETRA.Worker.ROLE_WORKER);
418 0 : let workersMin = Math.min(Math.max(12, 24 * this.Config.popScaling), this.Config.Economy.popPhase2);
419 0 : for (let q in this.queues)
420 : {
421 0 : let toBePaused = false;
422 0 : if (!gameState.ai.HQ.hasPotentialBase())
423 0 : toBePaused = q != "dock" && q != "civilCentre";
424 0 : else if (numWorkers < workersMin / 3)
425 0 : toBePaused = q != "citizenSoldier" && q != "villager" && q != "emergency";
426 0 : else if (numWorkers < workersMin * 2 / 3)
427 0 : toBePaused = q == "civilCentre" || q == "economicBuilding" ||
428 : q == "militaryBuilding" || q == "defenseBuilding" || q == "healer" ||
429 : q == "majorTech" || q == "minorTech" || q.indexOf("plan_") != -1;
430 0 : else if (numWorkers < workersMin)
431 0 : toBePaused = q == "civilCentre" || q == "defenseBuilding" ||
432 : q == "majorTech" || q.indexOf("_siege") != -1 || q.indexOf("_champ") != -1;
433 :
434 0 : if (toBePaused)
435 : {
436 0 : if (q == "field" && gameState.ai.HQ.needFarm &&
437 : !gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).hasEntities())
438 0 : toBePaused = false;
439 0 : if (q == "corral" && gameState.ai.HQ.needCorral &&
440 : !gameState.getOwnStructures().filter(API3.Filters.byClass("Field")).hasEntities())
441 0 : toBePaused = false;
442 0 : if (q == "dock" && gameState.ai.HQ.needFish &&
443 : !gameState.getOwnStructures().filter(API3.Filters.byClass("Dock")).hasEntities())
444 0 : toBePaused = false;
445 0 : if (q == "ships" && gameState.ai.HQ.needFish &&
446 : !gameState.ai.HQ.navalManager.ships.filter(API3.Filters.byClass("FishingBoat")).hasEntities())
447 0 : toBePaused = false;
448 : }
449 :
450 0 : let queue = this.queues[q];
451 0 : if (!queue.paused && toBePaused)
452 : {
453 0 : queue.paused = true;
454 0 : this.accounts[q].reset();
455 : }
456 0 : else if (queue.paused && !toBePaused)
457 0 : queue.paused = false;
458 :
459 : // And reduce the batch sizes of attack queues
460 0 : if (q.indexOf("plan_") != -1 && numWorkers < workersMin && queue.plans[0])
461 : {
462 0 : queue.plans[0].number = 1;
463 0 : if (queue.plans[1])
464 0 : queue.plans[1].number = 1;
465 : }
466 : }
467 : };
468 :
469 0 : PETRA.QueueManager.prototype.canAfford = function(queue, cost)
470 : {
471 0 : if (!this.accounts[queue])
472 0 : return false;
473 0 : return this.accounts[queue].canAfford(cost);
474 : };
475 :
476 0 : PETRA.QueueManager.prototype.pauseQueue = function(queue, scrapAccounts)
477 : {
478 0 : if (!this.queues[queue])
479 0 : return;
480 0 : this.queues[queue].paused = true;
481 0 : if (scrapAccounts)
482 0 : this.accounts[queue].reset();
483 : };
484 :
485 0 : PETRA.QueueManager.prototype.unpauseQueue = function(queue)
486 : {
487 0 : if (this.queues[queue])
488 0 : this.queues[queue].paused = false;
489 : };
490 :
491 0 : PETRA.QueueManager.prototype.pauseAll = function(scrapAccounts, but)
492 : {
493 0 : for (let q in this.queues)
494 : {
495 0 : if (q == but)
496 0 : continue;
497 0 : if (scrapAccounts)
498 0 : this.accounts[q].reset();
499 0 : this.queues[q].paused = true;
500 : }
501 : };
502 :
503 0 : PETRA.QueueManager.prototype.unpauseAll = function(but)
504 : {
505 0 : for (let q in this.queues)
506 0 : if (q != but)
507 0 : this.queues[q].paused = false;
508 : };
509 :
510 :
511 0 : PETRA.QueueManager.prototype.addQueue = function(queueName, priority)
512 : {
513 0 : if (this.queues[queueName] !== undefined)
514 0 : return;
515 :
516 0 : this.queues[queueName] = new PETRA.Queue();
517 0 : this.priorities[queueName] = priority;
518 0 : this.accounts[queueName] = new API3.Resources();
519 :
520 0 : this.queueArrays = [];
521 0 : for (let q in this.queues)
522 0 : this.queueArrays.push([q, this.queues[q]]);
523 0 : let priorities = this.priorities;
524 0 : this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]);
525 : };
526 :
527 0 : PETRA.QueueManager.prototype.removeQueue = function(queueName)
528 : {
529 0 : if (this.queues[queueName] === undefined)
530 0 : return;
531 :
532 0 : delete this.queues[queueName];
533 0 : delete this.priorities[queueName];
534 0 : delete this.accounts[queueName];
535 :
536 0 : this.queueArrays = [];
537 0 : for (let q in this.queues)
538 0 : this.queueArrays.push([q, this.queues[q]]);
539 0 : let priorities = this.priorities;
540 0 : this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]);
541 : };
542 :
543 0 : PETRA.QueueManager.prototype.getPriority = function(queueName)
544 : {
545 0 : return this.priorities[queueName];
546 : };
547 :
548 0 : PETRA.QueueManager.prototype.changePriority = function(queueName, newPriority)
549 : {
550 0 : if (this.Config.debug > 1)
551 0 : API3.warn(">>> Priority of queue " + queueName + " changed from " + this.priorities[queueName] + " to " + newPriority);
552 0 : if (this.queues[queueName] !== undefined)
553 0 : this.priorities[queueName] = newPriority;
554 0 : let priorities = this.priorities;
555 0 : this.queueArrays.sort((a, b) => priorities[b[0]] - priorities[a[0]]);
556 : };
557 :
558 0 : PETRA.QueueManager.prototype.Serialize = function()
559 : {
560 0 : let accounts = {};
561 0 : let queues = {};
562 0 : for (let q in this.queues)
563 : {
564 0 : queues[q] = this.queues[q].Serialize();
565 0 : accounts[q] = this.accounts[q].Serialize();
566 0 : if (this.Config.debug == -100)
567 0 : API3.warn("queueManager serialization: queue " + q + " >>> " +
568 : uneval(queues[q]) + " with accounts " + uneval(accounts[q]));
569 : }
570 :
571 0 : return {
572 : "priorities": this.priorities,
573 : "queues": queues,
574 : "accounts": accounts
575 : };
576 : };
577 :
578 0 : PETRA.QueueManager.prototype.Deserialize = function(gameState, data)
579 : {
580 0 : this.priorities = data.priorities;
581 0 : this.queues = {};
582 0 : this.accounts = {};
583 :
584 : // the sorting is updated on priority change.
585 0 : this.queueArrays = [];
586 0 : for (let q in data.queues)
587 : {
588 0 : this.queues[q] = new PETRA.Queue();
589 0 : this.queues[q].Deserialize(gameState, data.queues[q]);
590 0 : this.accounts[q] = new API3.Resources();
591 0 : this.accounts[q].Deserialize(data.accounts[q]);
592 0 : this.queueArrays.push([q, this.queues[q]]);
593 : }
594 0 : this.queueArrays.sort((a, b) => data.priorities[b[0]] - data.priorities[a[0]]);
595 : };
|