diff --git a/src/app/game/TestTools.ts b/src/app/game/TestTools.ts index 8d3aa6e..d366885 100644 --- a/src/app/game/TestTools.ts +++ b/src/app/game/TestTools.ts @@ -36,6 +36,13 @@ module SpaceTac.Game { return equipment; } + // Add an engine, allowing a ship to move *distance*, for each action points + static addEngine(ship: Ship, distance: number): void { + var equipment = this.getOrGenEquipment(ship, SlotType.Engine, new Equipments.ConventionalEngine()); + equipment.ap_usage = 1; + equipment.distance = distance; + } + // Set a ship action points, adding/updating an equipment if needed static setShipAP(ship: Ship, points: number, recovery: number = 0): void { var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.BasicPowerCore()); diff --git a/src/app/game/ai/AbstractAI.ts b/src/app/game/ai/AbstractAI.ts index 6fab782..9cdbefa 100644 --- a/src/app/game/ai/AbstractAI.ts +++ b/src/app/game/ai/AbstractAI.ts @@ -17,6 +17,9 @@ module SpaceTac.Game.AI { // Time at which work as started started: number; + // Random generator, if needed + random: RandomGenerator; + // 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. @@ -29,6 +32,7 @@ module SpaceTac.Game.AI { this.fleet = fleet; this.async = true; this.workqueue = []; + this.random = new RandomGenerator(); } postSerialize(fields: any): void { diff --git a/src/app/game/ai/BullyAI.ts b/src/app/game/ai/BullyAI.ts index 497a3e9..1b16747 100644 --- a/src/app/game/ai/BullyAI.ts +++ b/src/app/game/ai/BullyAI.ts @@ -36,13 +36,18 @@ module SpaceTac.Game.AI { protected initWork(): void { this.addWorkItem(() => { var maneuvers = this.listAllManeuvers(); + var maneuver: BullyManeuver; if (maneuvers.length > 0) { - var maneuver = this.pickManeuver(maneuvers); + 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); } }); } @@ -84,6 +89,16 @@ module SpaceTac.Game.AI { return result; } + // Get an equipped engine to make a move + getEngine(): Equipment { + 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 { @@ -98,12 +113,11 @@ module SpaceTac.Game.AI { move = null; } else { // Move to be in range, using first engine - var engines = this.ship.listEquipment(SlotType.Engine); - if (engines.length === 0) { + engine = this.getEngine(); + if (!engine) { // No engine available to move return null; } else { - engine = engines[0]; var move_distance = distance - weapon.distance + this.move_margin; var move_ap = engine.ap_usage * move_distance / engine.distance; if (move_ap > remaining_ap) { @@ -130,6 +144,29 @@ module SpaceTac.Game.AI { } } + // When no bully action is available, pick a random enemy, and go towards it + getFallbackManeuver(): BullyManeuver { + var enemies = this.listAllEnemies(); + if (enemies.length === 0) { + return null; + } + + var MIN_DISTANCE = 20; + 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 (distance > MIN_DISTANCE) { // Don't move too close + target = target.constraintInRange(this.ship.arena_x, this.ship.arena_y, (distance - MIN_DISTANCE) * APPROACH_FACTOR); + target = engine.action.checkLocationTarget(this.fleet.battle, this.ship, target); + return new BullyManeuver(new Maneuver(this.ship, engine, target)); + } else { + return null; + } + } + // Pick a maneuver from a list of available ones // By default, it chooses the nearest enemy pickManeuver(available: BullyManeuver[]): BullyManeuver { @@ -153,9 +190,11 @@ module SpaceTac.Game.AI { }, 500); } - this.addWorkItem(() => { - maneuver.fire.apply(); - }, 1500); + if (maneuver.fire) { + this.addWorkItem(() => { + maneuver.fire.apply(); + }, 1500); + } this.addWorkItem(null, 1500); } diff --git a/src/app/game/ai/specs/BullyAI.spec.ts b/src/app/game/ai/specs/BullyAI.spec.ts index 081d856..dcbc765 100644 --- a/src/app/game/ai/specs/BullyAI.spec.ts +++ b/src/app/game/ai/specs/BullyAI.spec.ts @@ -163,6 +163,38 @@ module SpaceTac.Game.AI.Specs { expect(result.length).toBe(3); }); + it("gets a fallback maneuver", function () { + var battle = TestTools.createBattle(1, 3); + var ai = new BullyAI(battle.fleets[0]); + ai.async = false; + ai.ship = battle.fleets[0].ships[0]; + + TestTools.setShipAP(ai.ship, 5); + TestTools.addEngine(ai.ship, 100); + + var maneuver: BullyManeuver; + + 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(); + expect(maneuver.move.target).toEqual(Target.newFromLocation(25, 0)); + ai.ship.setArenaPosition(25, 0); + maneuver = ai.getFallbackManeuver(); + expect(maneuver.move.target).toEqual(Target.newFromLocation(22.5, 0)); + }); + it("applies the chosen move", function () { var battle = new Battle(); var ship1 = new Ship(); diff --git a/src/app/game/equipments/ConventionalEngine.ts b/src/app/game/equipments/ConventionalEngine.ts index ebcbb66..02e0178 100644 --- a/src/app/game/equipments/ConventionalEngine.ts +++ b/src/app/game/equipments/ConventionalEngine.ts @@ -9,7 +9,7 @@ module SpaceTac.Game.Equipments { super(SlotType.Engine, "Conventional Engine"); this.min_level = new IntegerRange(1, 1); - this.distance = new Range(500, 500); + this.distance = new Range(300, 300); this.ap_usage = new Range(3); this.addPermanentAttributeMaxEffect(AttributeCode.Initiative, 1); diff --git a/src/app/game/equipments/GatlingGun.ts b/src/app/game/equipments/GatlingGun.ts index 2fbf629..5b6047f 100644 --- a/src/app/game/equipments/GatlingGun.ts +++ b/src/app/game/equipments/GatlingGun.ts @@ -7,7 +7,7 @@ module SpaceTac.Game.Equipments { constructor() { super("Gatling Gun", 50, 100); - this.setRange(500, 500, false); + this.setRange(400, 400, false); this.ap_usage = new Range(2, 3); this.min_level = new IntegerRange(1, 3);