2017-02-21 21:16:18 +00:00
|
|
|
/// <reference path="AbstractAI.ts"/>
|
|
|
|
/// <reference path="Maneuver.ts"/>
|
|
|
|
module TS.SpaceTac {
|
|
|
|
|
2017-02-27 00:42:12 +00:00
|
|
|
export type TacticalProducer = Iterator<Maneuver>;
|
2017-03-08 23:18:40 +00:00
|
|
|
export type TacticalEvaluator = (maneuver: Maneuver) => number;
|
2017-02-21 21:16:18 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* AI that applies a set of tactical rules
|
|
|
|
*
|
|
|
|
* It uses a set of producers (to propose new maneuvers), and evaluators (to choose the best maneuver).
|
2017-02-22 01:14:14 +00:00
|
|
|
*
|
|
|
|
* As much work as possible is done using iterators, without materializing every possibilities.
|
2017-02-21 21:16:18 +00:00
|
|
|
*/
|
|
|
|
export class TacticalAI extends AbstractAI {
|
2017-05-17 23:03:33 +00:00
|
|
|
private producers: TacticalProducer[] = []
|
|
|
|
private evaluators: TacticalEvaluator[] = []
|
2017-02-21 21:16:18 +00:00
|
|
|
|
2017-05-17 23:03:33 +00:00
|
|
|
private best: Maneuver | null
|
|
|
|
private best_score: number
|
2017-02-21 21:16:18 +00:00
|
|
|
|
2017-05-17 23:03:33 +00:00
|
|
|
protected initWork(delay?: number): void {
|
2017-03-08 23:18:40 +00:00
|
|
|
this.best = null;
|
|
|
|
this.best_score = -Infinity;
|
|
|
|
|
2017-05-17 23:03:33 +00:00
|
|
|
this.producers = this.getDefaultProducers();
|
|
|
|
this.evaluators = this.getDefaultEvaluators();
|
2017-02-22 01:14:14 +00:00
|
|
|
|
2017-05-17 23:03:33 +00:00
|
|
|
this.addWorkItem(() => this.unitWork(), delay);
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Evaluate a single maneuver
|
|
|
|
*/
|
|
|
|
evaluate(maneuver: Maneuver) {
|
|
|
|
return sum(this.evaluators.map(evaluator => evaluator(maneuver)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-02-27 00:42:12 +00:00
|
|
|
* Single unit of work => produce a batch of maneuvers and evaluate them
|
|
|
|
*
|
|
|
|
* The best produced maneuver (highest evaluation score) is kept to be played.
|
|
|
|
* If two maneuvers have nearly the same score, the best one is randomly chosen.
|
2017-02-21 21:16:18 +00:00
|
|
|
*/
|
|
|
|
private unitWork() {
|
2017-02-27 00:42:12 +00:00
|
|
|
let done = 0;
|
2017-02-21 21:16:18 +00:00
|
|
|
|
2017-02-27 00:42:12 +00:00
|
|
|
while (done < 1000 && this.producers.length > 0) {
|
|
|
|
// Produce a maneuver
|
2017-03-09 17:11:00 +00:00
|
|
|
let maneuver: Maneuver | null = null;
|
2017-02-27 00:42:12 +00:00
|
|
|
let producer = this.producers.shift();
|
2017-03-09 17:11:00 +00:00
|
|
|
if (producer) {
|
|
|
|
[maneuver, producer] = producer();
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
|
2017-02-27 00:42:12 +00:00
|
|
|
if (maneuver) {
|
2017-03-09 17:11:00 +00:00
|
|
|
if (producer) {
|
|
|
|
this.producers.push(producer);
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
|
2017-02-27 00:42:12 +00:00
|
|
|
// Evaluate the maneuver
|
|
|
|
let score = this.evaluate(maneuver);
|
2017-05-17 23:03:33 +00:00
|
|
|
//console.debug("AI evaluation", maneuver, score);
|
2017-02-27 00:42:12 +00:00
|
|
|
if ((Math.abs(score - this.best_score) < 0.0001 && this.random.bool()) || score > this.best_score) {
|
|
|
|
this.best = maneuver;
|
|
|
|
this.best_score = score;
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|
2017-02-27 00:42:12 +00:00
|
|
|
|
|
|
|
done += 1;
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|
|
|
|
|
2017-02-27 00:42:12 +00:00
|
|
|
// Continue or stop
|
2017-05-17 23:03:33 +00:00
|
|
|
if (this.producers.length > 0 && this.getDuration() < 8000) {
|
2017-02-21 21:16:18 +00:00
|
|
|
this.addWorkItem(() => this.unitWork());
|
|
|
|
} else if (this.best) {
|
2017-05-17 23:03:33 +00:00
|
|
|
console.log("AI maneuver", this.best, this.best_score);
|
2017-02-21 21:16:18 +00:00
|
|
|
this.best.apply();
|
2017-05-17 23:03:33 +00:00
|
|
|
if (this.ship.playing) {
|
|
|
|
this.initWork(2000);
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-22 01:14:14 +00:00
|
|
|
|
|
|
|
/**
|
2017-05-17 23:03:33 +00:00
|
|
|
* Get the default set of maneuver producers
|
2017-02-22 01:14:14 +00:00
|
|
|
*/
|
2017-05-17 23:03:33 +00:00
|
|
|
getDefaultProducers() {
|
2017-02-27 00:42:12 +00:00
|
|
|
let producers = [
|
2017-05-17 23:03:33 +00:00
|
|
|
TacticalAIHelpers.produceEndTurn,
|
2017-02-27 00:42:12 +00:00
|
|
|
TacticalAIHelpers.produceDirectShots,
|
|
|
|
TacticalAIHelpers.produceBlastShots,
|
2017-05-09 23:20:05 +00:00
|
|
|
TacticalAIHelpers.produceDroneDeployments,
|
2017-02-27 00:42:12 +00:00
|
|
|
TacticalAIHelpers.produceRandomMoves,
|
|
|
|
]
|
2017-05-17 23:03:33 +00:00
|
|
|
return producers.map(producer => producer(this.ship, this.ship.getBattle() || new Battle()));
|
2017-02-22 01:14:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-05-17 23:03:33 +00:00
|
|
|
* Get the default set of maneuver evaluators
|
2017-02-22 01:14:14 +00:00
|
|
|
*/
|
2017-05-17 23:03:33 +00:00
|
|
|
getDefaultEvaluators() {
|
2017-05-02 21:33:58 +00:00
|
|
|
let scaled = (evaluator: (...args: any[]) => number, factor: number) => (...args: any[]) => factor * evaluator(...args);
|
2017-02-27 00:42:12 +00:00
|
|
|
let evaluators = [
|
2017-05-17 23:03:33 +00:00
|
|
|
scaled(TacticalAIHelpers.evaluateTurnCost, 3),
|
|
|
|
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 20),
|
2017-05-17 23:17:07 +00:00
|
|
|
scaled(TacticalAIHelpers.evaluateDamageToAllies, 30),
|
2017-05-09 23:20:05 +00:00
|
|
|
scaled(TacticalAIHelpers.evaluateClustering, 8),
|
2017-05-09 21:09:44 +00:00
|
|
|
scaled(TacticalAIHelpers.evaluatePosition, 1),
|
2017-05-09 23:20:05 +00:00
|
|
|
scaled(TacticalAIHelpers.evaluateIdling, 5),
|
2017-02-27 00:42:12 +00:00
|
|
|
]
|
2017-05-17 23:03:33 +00:00
|
|
|
|
2017-03-09 17:11:00 +00:00
|
|
|
// TODO evaluator typing is lost
|
2017-05-17 23:03:33 +00:00
|
|
|
return evaluators.map(evaluator => ((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver)));
|
2017-02-22 01:14:14 +00:00
|
|
|
}
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|