diff --git a/TODO.md b/TODO.md index a1b7c69..89c44ca 100644 --- a/TODO.md +++ b/TODO.md @@ -52,7 +52,6 @@ Battle * Show a cooldown indicator on move action icon, if the simulation would cause the engine to overheat * [WIP] Add an hexagonal grid * Use the grid for "border" exclusion areas - * Check if move target is already occupied (replaces hard exclusion area) * Fix repel effect to snap to grid and use grid units (beware of two ships being moved to the same location!) * Display the grid in targetting mode, with colors (replaces range hint) * Fix move-fire simulator's scanCircle @@ -93,7 +92,7 @@ Artificial Intelligence * If web worker is not responsive, or produces only errors, it should be disabled for the session * Prevent infinite loops of toggle/untoggle -* Produce interesting "angle" areas +* Produce all actions combined with all accepted targets (currently, a self-targetting trigger action is not possible for example) * Evaluate vigilance actions * Evaluate the "interest" of an active effect (e.g healing is better when harmed...) * Evaluators result should be more specific (final state evaluation, diff evaluation, confidence...) diff --git a/src/core/ai/TacticalAI.ts b/src/core/ai/TacticalAI.ts index d58aa30..f8ce1c9 100644 --- a/src/core/ai/TacticalAI.ts +++ b/src/core/ai/TacticalAI.ts @@ -93,10 +93,10 @@ module TK.SpaceTac { getDefaultProducers() { let producers = [ TacticalAIHelpers.produceEndTurn, - TacticalAIHelpers.produceDirectShots, - TacticalAIHelpers.produceBlastShots, + TacticalAIHelpers.produceTriggerActionsOnShip, + TacticalAIHelpers.produceTriggerActionsInSpace, TacticalAIHelpers.produceToggleActions, - TacticalAIHelpers.produceRandomMoves, + TacticalAIHelpers.produceMoveActions, ] return producers.map(producer => producer(this.ship, this.ship.getBattle() || new Battle())); } diff --git a/src/core/ai/TacticalAIHelpers.spec.ts b/src/core/ai/TacticalAIHelpers.spec.ts index 01d6752..a0b7b6e 100644 --- a/src/core/ai/TacticalAIHelpers.spec.ts +++ b/src/core/ai/TacticalAIHelpers.spec.ts @@ -10,12 +10,12 @@ module TK.SpaceTac.Specs { TestTools.setShipModel(ship0a, 100, 0, 10); TestTools.setShipPlaying(battle, ship0a); - let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle)); + let result = imaterialize(TacticalAIHelpers.produceTriggerActionsOnShip(ship0a, battle)); check.equals(result.length, 0); let weapon1 = TestTools.addWeapon(ship0a, 10); let weapon2 = TestTools.addWeapon(ship0a, 15); - result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle)); + result = imaterialize(TacticalAIHelpers.produceTriggerActionsOnShip(ship0a, battle)); check.equals(result.length, 4); check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship1a))); check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship1b))); @@ -32,12 +32,12 @@ module TK.SpaceTac.Specs { TestTools.setShipModel(ship, 100, 0, 10); TestTools.setShipPlaying(battle, ship); - let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1)); + let result = imaterialize(TacticalAIHelpers.produceMoveActions(ship, battle)); check.equals(result.length, 0); let engine = TestTools.addEngine(ship, 1000); - result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1, new SkewedRandomGenerator([0.5], true))); + result = imaterialize(TacticalAIHelpers.produceMoveActions(ship, battle)); check.equals(result, [ new Maneuver(ship, engine, Target.newFromLocation(25, 25)), new Maneuver(ship, engine, Target.newFromLocation(75, 25)), @@ -46,41 +46,6 @@ module TK.SpaceTac.Specs { ]); }); - test.case("produces interesting blast shots", check => { - let battle = new Battle(); - let ship = battle.fleets[0].addShip(); - let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105); - TestTools.setShipModel(ship, 100, 0, 10, 1, [weapon]); - TestTools.setShipPlaying(battle, ship); - - let result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); - check.equals(result.length, 0); - - let enemy1 = battle.fleets[1].addShip(); - enemy1.setArenaPosition(500, 0); - - result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); - check.equals(result.length, 0); - - let enemy2 = battle.fleets[1].addShip(); - enemy2.setArenaPosition(700, 0); - - result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); - check.equals(result, [ - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - ]); - - let enemy3 = battle.fleets[1].addShip(); - enemy3.setArenaPosition(700, 300); - - result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle)); - check.equals(result, [ - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - new Maneuver(ship, weapon, Target.newFromLocation(600, 0)), - ]); - }); - test.case("produces toggle/untoggle actions", check => { let battle = new Battle(); let ship = battle.fleets[0].addShip(); diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index cb436df..269a28c 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -2,8 +2,8 @@ module TK.SpaceTac { /** * Get a list of all playable actions (like the actionbar for player) for a ship */ - function getPlayableActions(ship: Ship): Iterator { - let actions = ship.actions.listAll(); + function getPlayableActions(ship: Ship, type_class: { new(...args: any[]): T }): Iterator { + let actions = cfilter(ship.actions.listAll(), type_class); return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship)); } @@ -45,14 +45,11 @@ module TK.SpaceTac { */ export class TacticalAIHelpers { /** - * Iterator of a list of "random" arena coordinates, based on a grid + * Iterator of a list of arena coordinates */ - static scanArena(battle: Battle, cells = 10, random = RandomGenerator.global): Iterator { - return imap(irange(cells * cells), cellpos => { - let y = Math.floor(cellpos / cells); - let x = cellpos - y * cells; - return Target.newFromLocation((x + random.random()) * battle.width / cells, (y + random.random()) * battle.height / cells); - }); + static scanArena(battle: Battle): Iterator { + let start = { x: battle.width / 2, y: battle.height / 2 }; + return imap(battle.grid.iterate(start), loc => Target.newFromLocation(loc.x, loc.y)); } /** @@ -62,62 +59,39 @@ module TK.SpaceTac { return isingle(new Maneuver(ship, EndTurnAction.SINGLETON, Target.newFromShip(ship))); } - /** - * Produce all "direct hit" weapon shots. - */ - static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer { - let enemies = battle.ienemies(ship, true); - let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction); - return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy))); - } - /** * Produce random moves inside arena cell */ - static produceRandomMoves(ship: Ship, battle: Battle, cells = 10, iterations = 1, random = RandomGenerator.global): TacticalProducer { - let engines = ifilter(getPlayableActions(ship), action => action instanceof MoveAction); - return ichainit(imap(irange(iterations), iteration => { - let moves = icombine(engines, TacticalAIHelpers.scanArena(battle, cells, random)); - return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target)); - })); + static produceMoveActions(ship: Ship, battle: Battle): TacticalProducer { + let engines = getPlayableActions(ship, MoveAction); + let moves = icombine(engines, TacticalAIHelpers.scanArena(battle)); + return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target)); } /** - * Produce blast weapon shots, with multiple targets. + * Produce trigger actions on ships */ - static produceInterestingBlastShots(ship: Ship, battle: Battle): TacticalProducer { - // TODO Work with groups of 3, 4 ... - let weapons = >ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); - let enemies = battle.ienemies(ship, true); - // TODO 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.blast * 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; + static produceTriggerActionsOnShip(ship: Ship, battle: Battle): TacticalProducer { + let ships = battle.iships(true); + let weapons = ifilter(getPlayableActions(ship, TriggerAction), action => action.getTargettingMode(ship) == ActionTargettingMode.SHIP); + return imap(icombine(ships, weapons), ([iship, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(iship))); } /** - * Produce random blast weapon shots, on a grid. + * Produce trigger actions in space */ - static produceRandomBlastShots(ship: Ship, battle: Battle): TacticalProducer { - let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0); - let candidates = ifilter(icombine(weapons, TacticalAIHelpers.scanArena(battle)), ([weapon, location]) => (weapon).getEffects(ship, location).length > 0); + static produceTriggerActionsInSpace(ship: Ship, battle: Battle): TacticalProducer { + let weapons = ifilter(getPlayableActions(ship, TriggerAction), action => action.getTargettingMode(ship) == ActionTargettingMode.SPACE); + let candidates = ifilter(icombine(weapons, TacticalAIHelpers.scanArena(battle)), ([weapon, location]) => weapon.getEffects(ship, location).length > 0); let result = imap(candidates, ([weapon, location]) => new Maneuver(ship, weapon, location)); return result; } - /** - * Produce interesting then random blast shots - */ - static produceBlastShots(ship: Ship, battle: Battle): TacticalProducer { - return ichain(TacticalAIHelpers.produceInterestingBlastShots(ship, battle), TacticalAIHelpers.produceRandomBlastShots(ship, battle)); - } - /** * Produce toggle actions at random locations. */ static produceToggleActions(ship: Ship, battle: Battle): TacticalProducer { - let toggles = ifilter(getPlayableActions(ship), action => action instanceof ToggleAction); + let toggles = getPlayableActions(ship, ToggleAction); let self_toggles = ifilter(toggles, toggle => contains([ActionTargettingMode.SELF_CONFIRM, ActionTargettingMode.SELF], toggle.getTargettingMode(ship))); let self_maneuvers = imap(self_toggles, toggle => new Maneuver(ship, toggle, Target.newFromShip(ship)));