2017-02-09 00:26:04 +00:00
|
|
|
module TS.SpaceTac {
|
2015-02-16 00:00:00 +00:00
|
|
|
// Base class for all Artificial Intelligence interaction
|
2017-02-07 18:54:53 +00:00
|
|
|
export class AbstractAI {
|
2017-02-21 21:16:18 +00:00
|
|
|
// Name of the AI
|
|
|
|
name: string;
|
2015-02-16 00:00:00 +00:00
|
|
|
|
2015-02-17 00:00:00 +00:00
|
|
|
// Current ship being played
|
|
|
|
ship: Ship;
|
|
|
|
|
|
|
|
// Time at which work as started
|
2015-03-05 00:00:00 +00:00
|
|
|
started: number;
|
2015-02-17 00:00:00 +00:00
|
|
|
|
2015-04-15 00:00:00 +00:00
|
|
|
// Random generator, if needed
|
|
|
|
random: RandomGenerator;
|
|
|
|
|
2017-02-16 22:59:41 +00:00
|
|
|
// Timer for scheduled calls
|
2017-02-21 21:16:18 +00:00
|
|
|
timer: Timer;
|
2017-02-16 22:59:41 +00:00
|
|
|
|
2015-02-17 00:00:00 +00:00
|
|
|
// Queue of work items to process
|
|
|
|
// Work items will be called successively, leaving time for other processing between them.
|
|
|
|
// So work items should always be as short as possible.
|
|
|
|
// When the queue is empty, the ship will end its turn.
|
|
|
|
private workqueue: Function[];
|
|
|
|
|
2017-02-21 21:16:18 +00:00
|
|
|
constructor(ship: Ship, timer = Timer.global, name: string = null) {
|
|
|
|
this.name = name || classname(this);
|
|
|
|
this.ship = ship;
|
2015-02-17 00:00:00 +00:00
|
|
|
this.workqueue = [];
|
2015-04-15 00:00:00 +00:00
|
|
|
this.random = new RandomGenerator();
|
2017-02-21 21:16:18 +00:00
|
|
|
this.timer = timer;
|
2015-02-17 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
2017-02-24 00:34:31 +00:00
|
|
|
toString = () => this.name;
|
|
|
|
|
2015-02-17 00:00:00 +00:00
|
|
|
// Play a ship turn
|
|
|
|
// This will start asynchronous work. The AI will then call action methods, then advanceToNextShip to
|
|
|
|
// indicate it has finished.
|
2017-02-21 21:16:18 +00:00
|
|
|
play(): void {
|
2015-02-17 00:00:00 +00:00
|
|
|
this.workqueue = [];
|
|
|
|
this.started = (new Date()).getTime();
|
2017-02-24 00:34:31 +00:00
|
|
|
|
|
|
|
if (!this.ship.playing) {
|
|
|
|
console.error(`${this.name} tries to play a ship out of turn`);
|
2017-02-21 21:16:18 +00:00
|
|
|
} else {
|
2017-02-24 00:34:31 +00:00
|
|
|
this.initWork();
|
|
|
|
if (this.workqueue.length > 0) {
|
|
|
|
this.processNextWorkItem();
|
|
|
|
} else {
|
|
|
|
this.endTurn();
|
|
|
|
}
|
2017-02-16 22:59:41 +00:00
|
|
|
}
|
2015-02-17 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add a work item to the work queue
|
2017-02-16 22:59:41 +00:00
|
|
|
addWorkItem(item: Function, delay = 100): void {
|
2017-02-21 21:16:18 +00:00
|
|
|
if (this.timer.isSynchronous()) {
|
2015-02-17 00:00:00 +00:00
|
|
|
if (item) {
|
|
|
|
item();
|
|
|
|
}
|
2017-02-21 21:16:18 +00:00
|
|
|
} else {
|
|
|
|
var wrapped = () => {
|
|
|
|
if (item) {
|
|
|
|
item();
|
|
|
|
}
|
|
|
|
this.processNextWorkItem();
|
|
|
|
};
|
|
|
|
this.workqueue.push(() => this.timer.schedule(delay, wrapped));
|
2015-02-19 00:00:00 +00:00
|
|
|
}
|
2015-02-17 00:00:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initially fill the work queue.
|
|
|
|
// Subclasses MUST reimplement this and call addWorkItem to add work to do.
|
|
|
|
protected initWork(): void {
|
|
|
|
// Abstract method
|
|
|
|
}
|
|
|
|
|
2017-02-16 22:59:41 +00:00
|
|
|
/**
|
|
|
|
* Get the time spent thinking by the AI.
|
|
|
|
*/
|
|
|
|
private getDuration() {
|
|
|
|
return (new Date()).getTime() - this.started;
|
|
|
|
}
|
|
|
|
|
2015-02-17 00:00:00 +00:00
|
|
|
// Process the next work item
|
|
|
|
private processNextWorkItem(): void {
|
|
|
|
if (this.workqueue.length > 0) {
|
2017-02-16 22:59:41 +00:00
|
|
|
if (this.getDuration() >= 10000) {
|
|
|
|
console.warn("AI take too long to play, forcing turn end");
|
|
|
|
this.effectiveEndTurn();
|
|
|
|
} else {
|
|
|
|
// Take the first item
|
|
|
|
var item = this.workqueue.shift();
|
|
|
|
item();
|
|
|
|
}
|
2015-02-17 00:00:00 +00:00
|
|
|
} else {
|
|
|
|
this.endTurn();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-16 22:59:41 +00:00
|
|
|
/**
|
|
|
|
* Effectively end the current ship's turn
|
|
|
|
*/
|
|
|
|
private effectiveEndTurn() {
|
2017-02-22 01:14:14 +00:00
|
|
|
if (this.workqueue.length > 0) {
|
|
|
|
console.error(`${this.name} ends turn, but there is pending work`);
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:16:18 +00:00
|
|
|
if (this.ship.playing) {
|
|
|
|
let battle = this.ship.getBattle();
|
|
|
|
this.ship.endTurn();
|
|
|
|
this.ship = null;
|
|
|
|
if (battle) {
|
|
|
|
battle.advanceToNextShip();
|
|
|
|
}
|
2017-02-22 01:14:14 +00:00
|
|
|
} else {
|
|
|
|
console.error(`${this.name} tries to end turn of another ship`);
|
2017-02-21 21:16:18 +00:00
|
|
|
}
|
2017-02-16 22:59:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called when we want the AI decides to end the ship turn
|
|
|
|
*/
|
2015-02-17 00:00:00 +00:00
|
|
|
private endTurn(): void {
|
2017-02-21 21:16:18 +00:00
|
|
|
// Delay, as to make the AI not too fast to play
|
|
|
|
this.timer.schedule(2000 - this.getDuration(), () => this.effectiveEndTurn());
|
2015-02-16 00:00:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|