135 lines
3.6 KiB
TypeScript
135 lines
3.6 KiB
TypeScript
module TK.SpaceTac {
|
|
export type AIPlanProducer = Iterable<AIPlan>;
|
|
export type AIPlanScoring = (plan: AIPlan) => number;
|
|
|
|
/**
|
|
* Standard system for Artificial Intelligence interaction
|
|
*
|
|
* An AI should work indefinitely on a battle state, to provide the best TurnPlan possible
|
|
*/
|
|
export class ContinuousAI {
|
|
// Timer for scheduled calls
|
|
timer = Timer.global
|
|
|
|
// Time at which work as started
|
|
started = 0
|
|
|
|
// Best plan so far
|
|
best_plan = new AIPlan()
|
|
|
|
// Number of plans produced
|
|
produced = 0
|
|
|
|
// Is the work interrupted
|
|
interrupted = false
|
|
|
|
constructor(readonly settings: AISettings, public debug = false) {
|
|
}
|
|
|
|
/**
|
|
* Start working on the current battle state.
|
|
*
|
|
* This will only be interrupted by a call to getPlan.
|
|
*/
|
|
async play(): Promise<void> {
|
|
// Init
|
|
this.interrupted = false;
|
|
this.produced = 0;
|
|
this.best_plan = new AIPlan();
|
|
this.started = (new Date()).getTime();
|
|
|
|
// Work loop
|
|
const producer = this.getPlanProducer();
|
|
const scoring = this.getPlanScoring();
|
|
for (let plan of producer) {
|
|
if (plan.isValid()) {
|
|
if (!plan.isScored()) {
|
|
plan.setScore(scoring(plan));
|
|
}
|
|
this.produced++;
|
|
this.pushScoredPlan(plan);
|
|
} else {
|
|
console.warn("AI produced an invalid plan", this, plan);
|
|
}
|
|
|
|
if (this.interrupted) {
|
|
break;
|
|
}
|
|
|
|
await this.timer.sleep(10);
|
|
}
|
|
|
|
this.interrupted = true;
|
|
}
|
|
|
|
/**
|
|
* Interrupt the thinking
|
|
*/
|
|
interrupt(): void {
|
|
this.interrupted = true;
|
|
}
|
|
|
|
/**
|
|
* Ask for a final plan to play
|
|
*/
|
|
async getPlan(): Promise<AIPlan> {
|
|
// Leave at least 5 seconds of thinking
|
|
while (!this.interrupted && this.getDuration() < 5000) {
|
|
await this.timer.sleep(1000);
|
|
}
|
|
|
|
// TODO Wait for a sufficient score, at most 5 seconds
|
|
|
|
this.interrupt();
|
|
|
|
const result = this.peekBestPlan();
|
|
if (this.debug) {
|
|
console.log(`AI plan after ${this.produced} produced:`, result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get the producer of plans for this AI
|
|
*
|
|
* Plans produced may be scored or not.
|
|
* The iterable may (in fact, should) be infinite.
|
|
*/
|
|
getPlanProducer(): AIPlanProducer {
|
|
return this.settings.producer;
|
|
}
|
|
|
|
/**
|
|
* Get the plan scoring method for this AI
|
|
*
|
|
* A standard scoring system is provided by default
|
|
*/
|
|
getPlanScoring(): AIPlanScoring {
|
|
return this.settings.scoring;
|
|
}
|
|
|
|
/**
|
|
* Add a scored plan to the memory (by default, it keeps only the best one)
|
|
*/
|
|
pushScoredPlan(plan: AIPlan): void {
|
|
if (plan.score >= this.best_plan.score) {
|
|
this.best_plan = plan;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Peek at the current best plan the AI came with
|
|
*/
|
|
peekBestPlan(): AIPlan {
|
|
return this.best_plan;
|
|
}
|
|
|
|
/**
|
|
* Get the time spent thinking on this turn
|
|
*/
|
|
getDuration() {
|
|
return (new Date()).getTime() - this.started;
|
|
}
|
|
}
|
|
}
|