1
0
Fork 0

Use of iterators as producers for TacticalAI

This commit is contained in:
Michaël Lemaire 2017-02-22 02:14:14 +01:00
parent 80a82664e1
commit de8651440a
8 changed files with 84 additions and 9 deletions

@ -1 +1 @@
Subproject commit 9a82049a4a898cfb3eb939156cb07f94e66cce2e
Subproject commit d84417976adb78147656616fccd7cefc5eca21bb

View file

@ -61,6 +61,13 @@ module TS.SpaceTac {
return result;
}
/**
* Return an iterator over all ships engaged in the battle
*/
iships(): Iterator<Ship> {
return ichainit(imap(iarray(this.fleets), fleet => iarray(fleet.ships)));
}
// Check if a player is able to play
// This can be used by the UI to determine if player interaction is allowed
canPlay(player: Player): boolean {

View file

@ -46,11 +46,13 @@ module TS.SpaceTac {
}
// Add a ship in this fleet
addShip(ship: Ship): void {
if (this.ships.indexOf(ship) < 0) {
this.ships.push(ship);
addShip(ship: Ship): Ship {
if (ship.fleet && ship.fleet != this) {
remove(ship.fleet.ships, ship);
}
add(this.ships, ship);
ship.fleet = this;
return ship;
}
// Set the current battle

View file

@ -27,7 +27,7 @@ module TS.SpaceTac {
}
// Attach an equipment in this slot
attach(equipment: Equipment): void {
attach(equipment: Equipment): Equipment | null {
if (this.type === equipment.slot && equipment.canBeEquipped(this.ship)) {
this.attached = equipment;
equipment.attached_to = this;
@ -35,6 +35,10 @@ module TS.SpaceTac {
if (this.ship) {
this.ship.updateAttributes();
}
return equipment;
} else {
return null;
}
}
}

View file

@ -94,6 +94,10 @@ module TS.SpaceTac {
* Effectively end the current ship's turn
*/
private effectiveEndTurn() {
if (this.workqueue.length > 0) {
console.error(`${this.name} ends turn, but there is pending work`);
}
if (this.ship.playing) {
let battle = this.ship.getBattle();
this.ship.endTurn();
@ -101,6 +105,8 @@ module TS.SpaceTac {
if (battle) {
battle.advanceToNextShip();
}
} else {
console.error(`${this.name} tries to end turn of another ship`);
}
}

View file

@ -19,7 +19,10 @@ module TS.SpaceTac {
// Apply the maneuver in current battle
apply(): void {
this.equipment.action.apply(this.ship.getBattle(), this.ship, this.target);
let result = this.equipment.action.apply(this.ship.getBattle(), this.ship, this.target);
if (!result) {
console.warn("AI could not apply maneuver", this);
}
}
}
}

View file

@ -14,7 +14,7 @@ module TS.SpaceTac.Specs {
}
// producer of FixedManeuver from a list of scores
let producer = (...scores: number[]) => iterator(scores.map(score => new FixedManeuver(score)));
let producer = (...scores: number[]) => iarray(scores.map(score => new FixedManeuver(score)));
let applied = [];
beforeEach(function () {
@ -30,5 +30,25 @@ module TS.SpaceTac.Specs {
ai.play();
expect(applied).toEqual([7]);
});
it("produces direct weapon hits", function () {
let battle = new Battle();
let ship0a = battle.fleets[0].addShip(new Ship(null, "0A"));
let ship0b = battle.fleets[0].addShip(new Ship(null, "0B"));
let ship1a = battle.fleets[1].addShip(new Ship(null, "1A"));
let ship1b = battle.fleets[1].addShip(new Ship(null, "1B"));
let result = imaterialize(produceDirectWeapon(ship0a, battle));
expect(result.length).toBe(0);
let weapon1 = ship0a.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
let weapon2 = ship0a.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
result = imaterialize(produceDirectWeapon(ship0a, battle));
expect(result.length).toBe(4);
expect(result).toContain(new Maneuver(ship0a, weapon1, Target.newFromShip(ship1a)));
expect(result).toContain(new Maneuver(ship0a, weapon1, Target.newFromShip(ship1b)));
expect(result).toContain(new Maneuver(ship0a, weapon2, Target.newFromShip(ship1a)));
expect(result).toContain(new Maneuver(ship0a, weapon2, Target.newFromShip(ship1b)));
});
});
}

View file

@ -9,15 +9,25 @@ module TS.SpaceTac {
* 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 {
producers: TacticalProducer[] = []
evaluators: TacticalEvaluator[] = []
best: Maneuver | null = null;
best_score = -Infinity;
best: Maneuver | null = null
best_score = -Infinity
protected initWork(): void {
if (this.producers.length == 0) {
this.setupDefaultProducers();
}
if (this.evaluators.length == 0) {
this.setupDefaultEvaluators();
}
this.addWorkItem(() => this.unitWork());
}
@ -56,8 +66,31 @@ module TS.SpaceTac {
this.addWorkItem(() => this.unitWork());
} else if (this.best) {
// TODO Also apply after a certain time of not finding better
// TODO If not in range for action, make an approach move
this.best.apply();
}
}
/**
* Setup the default set of maneuver producers
*/
private setupDefaultProducers() {
this.producers.push(produceDirectWeapon(this.ship, this.ship.getBattle()));
}
/**
* Setup the default set of maneuver evaluators
*/
private setupDefaultEvaluators() {
}
}
/**
* Produce all "direct hit" weapon shots.
*/
export function produceDirectWeapon(ship: Ship, battle: Battle): TacticalProducer {
let enemies = ifilter(battle.iships(), iship => iship.getPlayer() != ship.getPlayer());
let weapons = iarray(ship.listEquipment(SlotType.Weapon));
return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy)));
}
}