1
0
Fork 0
This commit is contained in:
Michaël Lemaire 2018-07-17 16:58:42 +02:00
parent 9b051c3ef8
commit 1a27c99225
4 changed files with 28 additions and 90 deletions

View File

@ -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...)

View File

@ -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()));
}

View File

@ -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();

View File

@ -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<BaseAction> {
let actions = ship.actions.listAll();
function getPlayableActions<T extends BaseAction>(ship: Ship, type_class: { new(...args: any[]): T }): Iterator<T> {
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<Target> {
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<Target> {
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 = <Iterator<TriggerAction>>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]) => (<TriggerAction>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)));