From b10725abda9c8a5be8dfb82e6bdf07f842773aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Thu, 18 May 2017 00:07:16 +0200 Subject: [PATCH] Removed Bully AI and fixed AI trying unavailable actions --- TODO | 1 + src/core/ai/AIDuel.ts | 2 +- src/core/ai/BullyAI.spec.ts | 243 -------------------------- src/core/ai/BullyAI.ts | 158 ----------------- src/core/ai/Maneuver.ts | 14 +- src/core/ai/TacticalAI.spec.ts | 2 +- src/core/ai/TacticalAIHelpers.spec.ts | 67 ++++--- src/core/ai/TacticalAIHelpers.ts | 27 +-- 8 files changed, 64 insertions(+), 450 deletions(-) delete mode 100644 src/core/ai/BullyAI.spec.ts delete mode 100644 src/core/ai/BullyAI.ts diff --git a/TODO b/TODO index 323b70b..77bfa3a 100644 --- a/TODO +++ b/TODO @@ -47,6 +47,7 @@ * AI: add combination of random small move and actual maneuver, as producer * AI: evaluate based on simulated list of effects * AI: consider overheat/cooldown +* AI: new duel page with producers/evaluators tweaking * Map: remove jump links that cross the radius of other systems * Map: disable interaction (zoom, selection) while moving/jumping * Tutorial diff --git a/src/core/ai/AIDuel.ts b/src/core/ai/AIDuel.ts index 14429dc..9f7a037 100644 --- a/src/core/ai/AIDuel.ts +++ b/src/core/ai/AIDuel.ts @@ -107,7 +107,7 @@ module TS.SpaceTac { */ static setup(element: HTMLElement) { let fakeship = new Ship(); - let ais = [new BullyAI(fakeship), new TacticalAI(fakeship), new AbstractAI(fakeship)]; + let ais = [new TacticalAI(fakeship), new AbstractAI(fakeship)]; ais.forEach((ai, idx) => { let selects = element.getElementsByTagName("select"); for (let i = 0; i < selects.length; i++) { diff --git a/src/core/ai/BullyAI.spec.ts b/src/core/ai/BullyAI.spec.ts deleted file mode 100644 index ab273bc..0000000 --- a/src/core/ai/BullyAI.spec.ts +++ /dev/null @@ -1,243 +0,0 @@ -module TS.SpaceTac.Specs { - describe("BullyAI", function () { - it("lists enemies", function () { - var battle = new Battle(); - battle.fleets[0].addShip(new Ship(null, "0-0")); - battle.fleets[1].addShip(new Ship(null, "1-0")); - battle.fleets[1].addShip(new Ship(null, "1-1")); - iforeach(battle.iships(), ship => ship.setAttribute("initiative", 1)); - - var random = new SkewedRandomGenerator([0, 0.5, 1]); - battle.throwInitiative(random); - - var ai = new BullyAI(battle.fleets[0].ships[0], Timer.synchronous); - - var result = ai.listAllEnemies(); - expect(result).toEqual([battle.fleets[1].ships[1], battle.fleets[1].ships[0]]); - }); - - it("lists weapons", function () { - var ship = new Ship(); - - var ai = new BullyAI(ship, Timer.synchronous); - ai.ship = ship; - - var result = ai.listAllWeapons(); - expect(result.length).toBe(0); - - var weapon1 = new Equipment(SlotType.Weapon, "weapon1"); - weapon1.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(50)]); - ai.ship.addSlot(SlotType.Weapon).attach(weapon1); - var weapon2 = new Equipment(SlotType.Weapon, "weapon2"); - weapon2.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(100)]); - ai.ship.addSlot(SlotType.Weapon).attach(weapon2); - var weapon3 = new Equipment(SlotType.Weapon, "weapon3"); - ai.ship.addSlot(SlotType.Weapon).attach(weapon3); - - ai.ship.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield)); - - result = ai.listAllWeapons(); - expect(result).toEqual([weapon1, weapon2]); - }); - - it("checks a firing possibility", function () { - var ship = new Ship(); - let engine = TestTools.addEngine(ship, 1 / 3); - TestTools.setShipAP(ship, 10); - var enemy = new Ship(); - var ai = new BullyAI(ship, Timer.synchronous); - ai.ship = ship; - ai.move_margin = 0; - let weapon = TestTools.addWeapon(ship, 0, 2, 3); - - // enemy in range, the ship can fire without moving - ship.values.power.set(8); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 3; - enemy.arena_y = 0; - var result = ai.checkBullyManeuver(enemy, weapon); - if (result) { - expect(result.simulation.need_move).toBe(false); - expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy)); - expect(result.equipment).toBe(weapon); - } else { - fail("No maneuver proposed"); - } - - // enemy out of range, but moving can bring it in range - ship.values.power.set(8); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 6; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - if (result) { - expect(result.simulation.move_location).toEqual(Target.newFromLocation(3, 0)); - expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy)); - expect(result.equipment).toBe(weapon); - } else { - fail("No maneuver proposed"); - } - - // enemy out of range, but moving can bring it in range, except for the safety margin - ai.move_margin = 0.1; - ship.values.power.set(8); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 6; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - expect(result).toBeNull(); - ai.move_margin = 0; - - // enemy totally out of range - ship.values.power.set(8); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 30; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - expect(result).toBeNull(); - - // enemy in range but not enough AP to fire - ship.values.power.set(1); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 3; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - expect(result).toBeNull(); - - // can move in range of enemy, but not enough AP to fire - ship.values.power.set(7); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 6; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - expect(result).toBeNull(); - - // no engine, can't move - engine.detach(); - ship.values.power.set(8); - ship.arena_x = 1; - ship.arena_y = 0; - enemy.arena_x = 6; - enemy.arena_y = 0; - result = ai.checkBullyManeuver(enemy, weapon); - expect(result).toBeNull(); - }); - - it("lists available firing actions", function () { - var battle = new Battle(); - var ship1 = new Ship(); - ship1.setArenaPosition(3, 2); - battle.fleets[0].addShip(ship1); - var ship2 = new Ship(); - ship2.setArenaPosition(5, 3); - battle.fleets[1].addShip(ship2); - var ship3 = new Ship(); - ship3.setArenaPosition(11, 15); - battle.fleets[1].addShip(ship3); - battle.throwInitiative(new SkewedRandomGenerator([1, 0.5, 0])); - - var ai = new BullyAI(ship1, Timer.synchronous); - ai.ship = ship1; - - var result = ai.listAllManeuvers(); - expect(result.length).toBe(0); - - TestTools.setShipAP(ai.ship, 8); - let weapon1 = TestTools.addWeapon(ai.ship, 10, 1, 50); - let weapon2 = TestTools.addWeapon(ai.ship, 5, 1, 10); - - result = ai.listAllManeuvers(); - expect(result.length).toBe(3); - }); - - it("gets a fallback maneuver", function () { - var battle = TestTools.createBattle(1, 3); - var ai = new BullyAI(battle.fleets[0].ships[0], Timer.synchronous); - - TestTools.setShipAP(ai.ship, 5); - var engine = TestTools.addEngine(ai.ship, 100); - (engine.action).safety_distance = 20; - - var maneuver: BullyManeuver | null; - - battle.fleets[1].ships.forEach((ship: Ship) => { - ai.ship.setArenaPosition(0, 0); - }); - - // Too much near an enemy, don't move - ai.ship.setArenaPosition(10, 0); - maneuver = ai.getFallbackManeuver(); - expect(maneuver).toBeNull(); - ai.ship.setArenaPosition(20, 0); - maneuver = ai.getFallbackManeuver(); - expect(maneuver).toBeNull(); - - // Move towards an enemy (up to minimal distance) - ai.ship.setArenaPosition(30, 0); - maneuver = ai.getFallbackManeuver(); - if (maneuver) { - expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(25, 0)); - } else { - fail("No maneuver proposed"); - } - ai.ship.setArenaPosition(25, 0); - maneuver = ai.getFallbackManeuver(); - if (maneuver) { - expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(22.5, 0)); - } else { - fail("No maneuver proposed"); - } - }); - - it("applies the chosen move", function () { - var battle = new Battle(); - var ship1 = new Ship(); - ship1.setArenaPosition(0, 0); - battle.fleets[0].addShip(ship1); - var ship2 = new Ship(); - ship2.setArenaPosition(8, 0); - battle.fleets[1].addShip(ship2); - - var ai = new BullyAI(ship1, Timer.synchronous); - ai.move_margin = 0; - - var engine = new Equipment(SlotType.Engine); - engine.action = new MoveAction(engine, 0.5); - ai.ship.addSlot(SlotType.Engine).attach(engine); - - var weapon = new Equipment(SlotType.Weapon); - weapon.action = new FireWeaponAction(weapon, 1, 6, 0, [new DamageEffect(20)]); - ai.ship.addSlot(SlotType.Weapon).attach(weapon); - - ai.ship.values.power.setMaximal(10); - ai.ship.values.power.set(6); - - ship2.values.hull.set(15); - ship2.values.shield.set(10); - - var move = ai.checkBullyManeuver(ship2, weapon); - expect(move).not.toBeNull(); - - battle.playing_ship = ai.ship; - battle.log.clear(); - ai.applyManeuver(move); - - expect(battle.log.events.length).toBe(7); - - expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 2, 10), -4)); - expect(battle.log.events[1]).toEqual(new MoveEvent(ship1, 2, 0)); - - expect(battle.log.events[2]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 1, 10), -1)); - expect(battle.log.events[3]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2))); - expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2, new ShipValue("shield", 0), -10)); - expect(battle.log.events[5]).toEqual(new ValueChangeEvent(ship2, new ShipValue("hull", 5), -10)); - expect(battle.log.events[6]).toEqual(new DamageEvent(ship2, 10, 10)); - }); - }); -} diff --git a/src/core/ai/BullyAI.ts b/src/core/ai/BullyAI.ts deleted file mode 100644 index 06b00b1..0000000 --- a/src/core/ai/BullyAI.ts +++ /dev/null @@ -1,158 +0,0 @@ -/// -/// -module TS.SpaceTac { - export class BullyManeuver extends Maneuver { - // Get a sorting score, by distance to another point - // Nearest means higher score - getScoreByDistance(point: Target): number { - return -point.getDistanceTo(this.simulation.fire_location); - } - } - - // Basic Artificial Intelligence, with a tendency to move forward and shoot the nearest enemy - export class BullyAI extends AbstractAI { - // Safety margin in moves to account for floating-point rounding errors - move_margin = 0.1; - - protected initWork(): void { - if (this.ship.getValue("power") > 0) { - this.addWorkItem(() => { - var maneuvers = this.listAllManeuvers(); - var maneuver: BullyManeuver | null; - - if (maneuvers.length > 0) { - maneuver = this.pickManeuver(maneuvers); - this.applyManeuver(maneuver); - - // Try to make another maneuver - this.initWork(); - } else { - // No bullying available, going to fallback move - maneuver = this.getFallbackManeuver(); - this.applyManeuver(maneuver); - } - }); - } - } - - // List all enemy ships that can be a target - listAllEnemies(): Ship[] { - var result: Ship[] = []; - - let battle = this.ship.getBattle(); - if (battle) { - battle.play_order.forEach((ship: Ship) => { - if (ship.alive && ship.getPlayer() !== this.ship.getPlayer()) { - result.push(ship); - } - }); - } - - return result; - } - - // List all weapons - listAllWeapons(): Equipment[] { - return this.ship.listEquipment(SlotType.Weapon).filter(equipement => equipement.action instanceof FireWeaponAction && any(equipement.action.effects, effect => effect instanceof DamageEffect)); - } - - // List all available maneuvers for the playing ship - listAllManeuvers(): BullyManeuver[] { - var result: BullyManeuver[] = []; - - var enemies = this.listAllEnemies(); - var weapons = this.listAllWeapons(); - - enemies.forEach((ship: Ship) => { - weapons.forEach((weapon: Equipment) => { - var maneuver = this.checkBullyManeuver(ship, weapon); - if (maneuver) { - result.push(maneuver); - } - }); - }); - - return result; - } - - // Get an equipped engine to make a move - getEngine(): Equipment | null { - var engines = this.ship.listEquipment(SlotType.Engine); - if (engines.length === 0) { - return null; - } else { - return engines[0]; - } - } - - // Check if a weapon can be used against an enemy - // Returns the BullyManeuver, or null if impossible to fire - checkBullyManeuver(enemy: Ship, weapon: Equipment): BullyManeuver | null { - let maneuver = new BullyManeuver(this.ship, weapon, Target.newFromShip(enemy), this.move_margin); - // TODO In case of blast weapon, check that this would be a hit ! - if (maneuver.simulation.can_fire) { - return maneuver; - } else { - return null; - } - } - - // When no bully action is available, pick a random enemy, and go towards it - getFallbackManeuver(): BullyManeuver | null { - var enemies = this.listAllEnemies(); - if (enemies.length === 0) { - return null; - } - - var APPROACH_FACTOR = 0.5; - - var picked = this.random.choice(enemies); - var target = Target.newFromShip(picked); - var distance = target.getDistanceTo(Target.newFromShip(this.ship)); - var engine = this.getEngine(); - if (engine) { - var safety_distance = (engine.action).safety_distance; - if (distance > safety_distance) { // Don't move too close - target = target.constraintInRange(this.ship.arena_x, this.ship.arena_y, - (distance - safety_distance) * APPROACH_FACTOR); - let loctarget = engine.action.checkLocationTarget(this.ship, target); - if (loctarget) { - return new BullyManeuver(this.ship, engine, loctarget); - } else { - return null; - } - } else { - return null; - } - } else { - return null; - } - } - - // Pick a maneuver from a list of available ones - // By default, it chooses the nearest enemy - pickManeuver(available: BullyManeuver[]): BullyManeuver | null { - if (available.length === 0) { - return null; - } - - // Sort by descending score - available.sort((m1: BullyManeuver, m2: BullyManeuver): number => { - var point = Target.newFromShip(this.ship); - return m1.getScoreByDistance(point) < m2.getScoreByDistance(point) ? 1 : -1; - }); - return available[0]; - } - - // Effectively apply the chosen maneuver - applyManeuver(maneuver: BullyManeuver | null): void { - if (maneuver) { - this.addWorkItem(() => { - maneuver.apply(); - }, 1500); - } - - this.addWorkItem(null, 1500); - } - } -} diff --git a/src/core/ai/Maneuver.ts b/src/core/ai/Maneuver.ts index 26f92d1..65ddf4f 100644 --- a/src/core/ai/Maneuver.ts +++ b/src/core/ai/Maneuver.ts @@ -2,14 +2,14 @@ module TS.SpaceTac { /** * Ship maneuver for an artifical intelligence * - * A maneuver is like a human player action, choosing an equipment and using it + * A maneuver is like a human player action, choosing an action and using it */ export class Maneuver { // Concerned ship ship: Ship; - // Equipment to use - equipment: Equipment; + // Action to use + action: BaseAction; // Target for the action; target: Target; @@ -17,17 +17,17 @@ module TS.SpaceTac { // Result of move-fire simulation simulation: MoveFireResult; - constructor(ship: Ship, equipment: Equipment, target: Target, move_margin = 0.1) { + constructor(ship: Ship, action: BaseAction, target: Target, move_margin = 0.1) { this.ship = ship; - this.equipment = equipment; + this.action = action; this.target = target; let simulator = new MoveFireSimulator(this.ship); - this.simulation = simulator.simulateAction(this.equipment.action, this.target, move_margin); + this.simulation = simulator.simulateAction(this.action, this.target, move_margin); } jasmineToString() { - return `Use ${this.equipment.jasmineToString()} on ${this.target.jasmineToString()}`; + return `Use ${this.action.code} on ${this.target.jasmineToString()}`; } /** diff --git a/src/core/ai/TacticalAI.spec.ts b/src/core/ai/TacticalAI.spec.ts index fadff17..8918068 100644 --- a/src/core/ai/TacticalAI.spec.ts +++ b/src/core/ai/TacticalAI.spec.ts @@ -5,7 +5,7 @@ module TS.SpaceTac.Specs { class FixedManeuver extends Maneuver { score: number; constructor(score: number) { - super(new Ship(), new Equipment(), new Target(0, 0)); + super(new Ship(), new BaseAction("nothing", "Do nothing", true), new Target(0, 0)); this.score = score; } apply() { diff --git a/src/core/ai/TacticalAIHelpers.spec.ts b/src/core/ai/TacticalAIHelpers.spec.ts index 809efb5..5e9347a 100644 --- a/src/core/ai/TacticalAIHelpers.spec.ts +++ b/src/core/ai/TacticalAIHelpers.spec.ts @@ -7,6 +7,9 @@ module TS.SpaceTac.Specs { let ship1a = battle.fleets[1].addShip(new Ship(null, "1A")); let ship1b = battle.fleets[1].addShip(new Ship(null, "1B")); + TestTools.setShipAP(ship0a, 10); + battle.playing_ship = ship0a; + let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle)); expect(result.length).toBe(0); @@ -14,10 +17,10 @@ module TS.SpaceTac.Specs { let weapon2 = TestTools.addWeapon(ship0a, 15); result = imaterialize(TacticalAIHelpers.produceDirectShots(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))); + expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1a))); + expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1b))); + expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1a))); + expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1b))); }); it("produces random moves inside a grid", function () { @@ -26,17 +29,20 @@ module TS.SpaceTac.Specs { battle.height = 100; let ship = battle.fleets[0].addShip(); + TestTools.setShipAP(ship, 10); + battle.playing_ship = ship; + let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1)); expect(result.length).toBe(0); - let engine = ship.addSlot(SlotType.Engine).attach(new Equipment(SlotType.Engine)); + let engine = TestTools.addEngine(ship, 1000); result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1, new SkewedRandomGenerator([0.5], true))); expect(result).toEqual([ - new Maneuver(ship, engine, Target.newFromLocation(25, 25)), - new Maneuver(ship, engine, Target.newFromLocation(75, 25)), - new Maneuver(ship, engine, Target.newFromLocation(25, 75)), - new Maneuver(ship, engine, Target.newFromLocation(75, 75)), + new Maneuver(ship, engine.action, Target.newFromLocation(25, 25)), + new Maneuver(ship, engine.action, Target.newFromLocation(75, 25)), + new Maneuver(ship, engine.action, Target.newFromLocation(25, 75)), + new Maneuver(ship, engine.action, Target.newFromLocation(75, 75)), ]); }); @@ -45,6 +51,9 @@ module TS.SpaceTac.Specs { let ship = battle.fleets[0].addShip(); let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105); + TestTools.setShipAP(ship, 10); + battle.playing_ship = ship; + let result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle)); expect(result.length).toBe(0); @@ -59,8 +68,8 @@ module TS.SpaceTac.Specs { result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle)); expect(result).toEqual([ - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), + new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)), + new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)), ]); let enemy3 = battle.fleets[1].addShip(); @@ -68,8 +77,8 @@ module TS.SpaceTac.Specs { result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle)); expect(result).toEqual([ - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), + new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)), + new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)), ]); }); @@ -79,19 +88,19 @@ module TS.SpaceTac.Specs { let weapon = TestTools.addWeapon(ship, 50, 5, 100); let engine = TestTools.addEngine(ship, 25); - let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(100, 0)); + let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0)); expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-Infinity); TestTools.setShipAP(ship, 10); expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.5); // 5 power remaining on 10 - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(110, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(110, 0)); expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.4); // 4 power remaining on 10 - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(140, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(140, 0)); expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.3); // 3 power remaining on 10 - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(310, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(310, 0)); expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-1); // can't do in one turn }); @@ -102,18 +111,18 @@ module TS.SpaceTac.Specs { let engine = TestTools.addEngine(ship, 50); let weapon = TestTools.addWeapon(ship, 10, 2, 100, 10); - let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(0, 0)); + let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0)); expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.3); - maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0)); + maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(0, 0)); expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.5); ship.setValue("power", 2); - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(0, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0)); expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0.5); - maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0)); + maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(0, 0)); expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0); }); @@ -130,15 +139,15 @@ module TS.SpaceTac.Specs { TestTools.setShipHP(enemy2, 25, 0); // no enemies hurt - let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(100, 0)); + let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0)); expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0); // one enemy loses half-life - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(180, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(180, 0)); expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.25); // one enemy loses half-life, the other one is dead - maneuver = new Maneuver(ship, weapon, Target.newFromLocation(280, 0)); + maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(280, 0)); expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.625); }); @@ -149,7 +158,7 @@ module TS.SpaceTac.Specs { TestTools.setShipAP(ship, 10); let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10); - let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0), 0.5); + let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(200, 0), 0.5); expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(100.5, 0)); expect(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver)).toEqual(0); @@ -172,19 +181,19 @@ module TS.SpaceTac.Specs { let weapon = TestTools.addWeapon(ship, 1, 1, 400); ship.setArenaPosition(0, 0); - let maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0); + let maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0); expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1); ship.setArenaPosition(100, 0); - maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0); + maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0); expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1); ship.setArenaPosition(100, 10); - maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0); + maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0); expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-0.6); ship.setArenaPosition(100, 50); - maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0); + maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0); expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(1); }); }); diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index 459b34b..bdaa2a5 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -10,6 +10,14 @@ module TS.SpaceTac { }); } + /** + * Get a list of all playable actions (like the actionbar for player) for a ship + */ + function getPlayableActions(ship: Ship): Iterator { + let actions = ship.getAvailableActions(); + return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship)); + } + /** * Standard producers and evaluators for TacticalAI * @@ -21,7 +29,7 @@ module TS.SpaceTac { */ static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer { let enemies = ifilter(battle.iships(), iship => iship.alive && iship.getPlayer() !== ship.getPlayer()); - let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof FireWeaponAction); + let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction); return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy))); } @@ -29,13 +37,10 @@ module TS.SpaceTac { * Produce random moves inside arena cell */ static produceRandomMoves(ship: Ship, battle: Battle, cells = 10, iterations = 1, random = RandomGenerator.global): TacticalProducer { - let engines = ship.listEquipment(SlotType.Engine); - if (engines.length == 0) { - return IEMPTY; - } - + let engines = ifilter(getPlayableActions(ship), action => action instanceof MoveAction); return ichainit(imap(irange(iterations), iteration => { - return imap(scanArena(battle, cells, random), target => new Maneuver(ship, engines[0], target)) + let moves = icombine(engines, scanArena(battle, cells, random)); + return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target)); })); } @@ -44,11 +49,11 @@ module TS.SpaceTac { */ static produceBlastShots(ship: Ship, battle: Battle): TacticalProducer { // TODO Work with groups of 3, 4 ... - let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof FireWeaponAction && weapon.action.blast > 0); + let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction && action.blast > 0); let enemies = battle.ienemies(ship.getPlayer(), true); // FIXME This produces duplicates (x, y) and (y, x) let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2); - let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.action.getBlastRadius(ship) * 2); + let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.getBlastRadius(ship) * 2); let result = imap(candidates, ([weapon, [e1, e2]]) => new Maneuver(ship, weapon, Target.newFromLocation((e1.arena_x + e2.arena_x) / 2, (e1.arena_y + e2.arena_y) / 2))); return result; } @@ -57,7 +62,7 @@ module TS.SpaceTac { * Produce drone deployments. */ static produceDroneDeployments(ship: Ship, battle: Battle): TacticalProducer { - let drones = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof DeployDroneAction); + let drones = ifilter(getPlayableActions(ship), action => action instanceof DeployDroneAction); let grid = scanArena(battle); return imap(icombine(grid, drones), ([target, drone]) => new Maneuver(ship, drone, target)); } @@ -94,7 +99,7 @@ module TS.SpaceTac { * Evaluate the damage done to the enemy, between -1 and 1 */ static evaluateDamageToEnemy(ship: Ship, battle: Battle, maneuver: Maneuver): number { - let action = maneuver.equipment.action; + let action = maneuver.action; if (action instanceof FireWeaponAction) { let enemies = imaterialize(battle.ienemies(ship.getPlayer(), true)); if (enemies.length == 0) {