1
0
Fork 0
spacetac/src/core/ai/TacticalAI.ts

136 lines
5 KiB
TypeScript
Raw Normal View History

/// <reference path="AbstractAI.ts"/>
/// <reference path="Maneuver.ts"/>
2017-09-24 22:23:22 +00:00
module TK.SpaceTac {
2019-05-13 21:17:58 +00:00
export type TacticalProducer = Iterable<Maneuver>;
export type TacticalEvaluator = (maneuver: Maneuver) => number;
/**
* 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).
*
* As much work as possible is done using iterators, without materializing every possibilities.
*/
export class TacticalAI extends AbstractAI {
2017-05-17 23:03:33 +00:00
private producers: TacticalProducer[] = []
2019-05-13 21:17:58 +00:00
private work: Iterator<Maneuver> = IATEND
2017-05-17 23:03:33 +00:00
private evaluators: TacticalEvaluator[] = []
2018-01-31 18:19:50 +00:00
private best: Maneuver | null = null
private best_score = 0
2019-05-13 21:17:58 +00:00
private produced = 0
private evaluated = 0
2017-05-18 23:19:05 +00:00
protected initWork(): void {
this.best = null;
this.best_score = -Infinity;
2017-05-17 23:03:33 +00:00
this.producers = this.getDefaultProducers();
2019-05-13 21:17:58 +00:00
this.work = ialternate(this.producers)[Symbol.iterator]();
2017-05-17 23:03:33 +00:00
this.evaluators = this.getDefaultEvaluators();
2019-05-13 21:17:58 +00:00
this.produced = 0;
this.evaluated = 0;
if (this.debug) {
console.log("AI started", this.name, this.ship.name);
}
}
protected doWorkUnit(): boolean {
2019-05-13 21:17:58 +00:00
let state = this.work.next();
2019-05-13 21:17:58 +00:00
if (!state.done && this.getDuration() < 8000) {
let maneuver = state.value;
this.produced++;
if (maneuver.isPossible()) {
// Evaluate the maneuver
let score = this.evaluate(maneuver);
2019-05-13 21:17:58 +00:00
this.evaluated++;
if (this.debug) {
console.debug("AI evaluation", maneuver, score);
}
if ((Math.abs(score - this.best_score) < 0.0001 && this.random.bool()) || score > this.best_score) {
this.best = maneuver;
this.best_score = score;
}
}
return true;
} else if (this.best) {
2019-05-13 21:17:58 +00:00
if (!state.done) {
console.warn(`AI did not analyze every possible maneuver (${this.produced} produced, ${this.evaluated} evaluated)`);
}
// Choose the best maneuver so far
2017-05-18 23:19:05 +00:00
let best_maneuver = this.best;
if (this.debug) {
console.log("AI maneuver", this.name, this.ship.name, best_maneuver, this.best_score);
}
if (this.best.action instanceof EndTurnAction) {
return false;
}
let success = this.feedback(best_maneuver);
if (success) {
// Try to play another maneuver
this.initWork();
return true;
} else {
return false;
}
} else {
// No maneuver produced
return false;
}
}
/**
* Evaluate a single maneuver
*/
evaluate(maneuver: Maneuver) {
return sum(this.evaluators.map(evaluator => evaluator(maneuver)));
}
/**
2017-05-17 23:03:33 +00:00
* Get the default set of maneuver producers
*/
2017-05-17 23:03:33 +00:00
getDefaultProducers() {
let producers = [
2017-05-17 23:03:33 +00:00
TacticalAIHelpers.produceEndTurn,
TacticalAIHelpers.produceDirectShots,
TacticalAIHelpers.produceBlastShots,
TacticalAIHelpers.produceToggleActions,
TacticalAIHelpers.produceRandomMoves,
]
2017-05-17 23:03:33 +00:00
return producers.map(producer => producer(this.ship, this.ship.getBattle() || new Battle()));
}
/**
2017-05-17 23:03:33 +00:00
* Get the default set of maneuver evaluators
*/
2017-05-17 23:03:33 +00:00
getDefaultEvaluators() {
type EvaluatorHelper = (ship: Ship, battle: Battle, maneuver: Maneuver) => number;
function scaled(evaluator: EvaluatorHelper, factor: number): EvaluatorHelper {
return (ship: Ship, battle: Battle, maneuver: Maneuver) => factor * evaluator(ship, battle, maneuver);
}
let evaluators: EvaluatorHelper[] = [
2017-05-18 21:10:16 +00:00
scaled(TacticalAIHelpers.evaluateTurnCost, 1),
2018-03-01 23:14:09 +00:00
scaled(TacticalAIHelpers.evaluateOverheat, 3),
scaled(TacticalAIHelpers.evaluateEnemyHealth, 5),
scaled(TacticalAIHelpers.evaluateAllyHealth, 20),
scaled(TacticalAIHelpers.evaluateActiveEffects, 3),
2018-03-01 23:14:09 +00:00
scaled(TacticalAIHelpers.evaluateClustering, 4),
scaled(TacticalAIHelpers.evaluatePosition, 0.5),
scaled(TacticalAIHelpers.evaluateIdling, 2),
]
2017-05-17 23:03:33 +00:00
let battle = nn(this.ship.getBattle());
return evaluators.map(evaluator => ((maneuver: Maneuver) => evaluator(this.ship, battle, maneuver)));
}
}
}