module TK.SpaceTac { export type AIPlanProducer = Iterable; 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 { // 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 { // 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; } } }