AI now uses real simulated effects in evaluation, and produces all toggle actions
This commit is contained in:
parent
4e624dc9db
commit
a6b0f51b9c
4
TODO.md
4
TODO.md
|
@ -83,8 +83,10 @@ 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
|
||||
* Evaluate active effects
|
||||
* 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...)
|
||||
* Use a first batch of producers, and only if no "good" move has been found, go on with some infinite producers
|
||||
* Abandon fight if the AI judges there is no hope of victory
|
||||
|
|
|
@ -20,8 +20,8 @@ module TK.SpaceTac {
|
|||
// Result of move-fire simulation
|
||||
simulation: MoveFireResult
|
||||
|
||||
// List of guessed effects of this maneuver
|
||||
effects: BaseBattleDiff[]
|
||||
// List of guessed effects of this maneuver (lazy property)
|
||||
_effects?: BaseBattleDiff[]
|
||||
|
||||
constructor(ship: Ship, action: BaseAction, target: Target, move_margin = 1) {
|
||||
this.ship = ship;
|
||||
|
@ -31,16 +31,20 @@ module TK.SpaceTac {
|
|||
|
||||
let simulator = new MoveFireSimulator(this.ship);
|
||||
this.simulation = simulator.simulateAction(this.action, this.target, move_margin);
|
||||
|
||||
this.effects = flatten(this.simulation.parts.map(part =>
|
||||
part.action.getDiffs(this.ship, this.battle, part.target)
|
||||
));
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
return `Use ${this.action.code} on ${this.target.jasmineToString()}`;
|
||||
}
|
||||
|
||||
get effects(): BaseBattleDiff[] {
|
||||
if (!this._effects) {
|
||||
let simulator = new MoveFireSimulator(this.ship);
|
||||
this._effects = simulator.getExpectedDiffs(this.battle, this.simulation);
|
||||
}
|
||||
return this._effects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the maneuver has at least one part doable
|
||||
*/
|
||||
|
|
|
@ -95,7 +95,7 @@ module TK.SpaceTac {
|
|||
TacticalAIHelpers.produceEndTurn,
|
||||
TacticalAIHelpers.produceDirectShots,
|
||||
TacticalAIHelpers.produceBlastShots,
|
||||
TacticalAIHelpers.produceDroneDeployments,
|
||||
TacticalAIHelpers.produceToggleActions,
|
||||
TacticalAIHelpers.produceRandomMoves,
|
||||
]
|
||||
return producers.map(producer => producer(this.ship, this.ship.getBattle() || new Battle()));
|
||||
|
@ -116,6 +116,7 @@ module TK.SpaceTac {
|
|||
scaled(TacticalAIHelpers.evaluateOverheat, 3),
|
||||
scaled(TacticalAIHelpers.evaluateEnemyHealth, 5),
|
||||
scaled(TacticalAIHelpers.evaluateAllyHealth, 20),
|
||||
scaled(TacticalAIHelpers.evaluateActiveEffects, 3),
|
||||
scaled(TacticalAIHelpers.evaluateClustering, 4),
|
||||
scaled(TacticalAIHelpers.evaluatePosition, 0.5),
|
||||
scaled(TacticalAIHelpers.evaluateIdling, 2),
|
||||
|
|
|
@ -50,10 +50,8 @@ module TK.SpaceTac.Specs {
|
|||
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);
|
||||
TestTools.setShipModel(ship, 100, 0, 10, 1, [weapon]);
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
ship.actions.addCustom(weapon);
|
||||
|
||||
let result = imaterialize(TacticalAIHelpers.produceInterestingBlastShots(ship, battle));
|
||||
check.equals(result.length, 0);
|
||||
|
@ -83,6 +81,31 @@ module TK.SpaceTac.Specs {
|
|||
]);
|
||||
});
|
||||
|
||||
test.case("produces toggle/untoggle actions", check => {
|
||||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let action1 = new DeployDroneAction("Drone");
|
||||
let action2 = new ToggleAction("Toggle");
|
||||
let action3 = new VigilanceAction("Vigilance", { radius: 150 });
|
||||
TestTools.setShipModel(ship, 100, 0, 10, 1, [action1, action2, action3]);
|
||||
TestTools.addEngine(ship, 1000);
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
|
||||
check.patch(TacticalAIHelpers, "scanArena", () => iarray([
|
||||
Target.newFromLocation(1, 0),
|
||||
Target.newFromLocation(0, 1),
|
||||
]));
|
||||
|
||||
let result = imaterialize(TacticalAIHelpers.produceToggleActions(ship, battle));
|
||||
check.equals(result, [
|
||||
new Maneuver(ship, action2, Target.newFromShip(ship)),
|
||||
new Maneuver(ship, action1, Target.newFromLocation(1, 0)),
|
||||
new Maneuver(ship, action3, Target.newFromLocation(1, 0)),
|
||||
new Maneuver(ship, action1, Target.newFromLocation(0, 1)),
|
||||
new Maneuver(ship, action3, Target.newFromLocation(0, 1)),
|
||||
]);
|
||||
});
|
||||
|
||||
test.case("evaluates turn cost", check => {
|
||||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
|
@ -248,5 +271,43 @@ module TK.SpaceTac.Specs {
|
|||
ship.actions.addCustom(weapon);
|
||||
check.equals(TacticalAIHelpers.evaluateOverheat(ship, battle, maneuver), 0);
|
||||
});
|
||||
|
||||
test.case("evaluates active effects", check => {
|
||||
let battle = TestTools.createBattle();
|
||||
let ship = battle.fleets[0].ships[0];
|
||||
let enemy = battle.fleets[1].ships[0];
|
||||
TestTools.setShipModel(ship, 1, 0, 1);
|
||||
TestTools.setShipModel(enemy, 5, 5);
|
||||
let action = new TriggerAction("Test", { range: 100, power: 1 });
|
||||
ship.actions.addCustom(action);
|
||||
|
||||
let maneuver = new Maneuver(ship, action, Target.newFromShip(enemy));
|
||||
check.equals(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), 0);
|
||||
|
||||
action.effects = [new StickyEffect(new DamageEffect(1), 1)];
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(enemy));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), 0.5);
|
||||
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(ship));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), -0.5);
|
||||
|
||||
action.effects = [new StickyEffect(new CooldownEffect(1), 1)];
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(enemy));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), -0.5);
|
||||
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(ship));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), 0.5);
|
||||
|
||||
battle.fleets[0].addShip();
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), 0.3333333333333333);
|
||||
|
||||
action.effects = [new StickyEffect(new CooldownEffect(1), 1), new StickyEffect(new CooldownEffect(1), 1)];
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(enemy));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), -0.6666666666666666);
|
||||
|
||||
action.effects = range(10).map(() => new StickyEffect(new CooldownEffect(1), 1));
|
||||
maneuver = new Maneuver(ship, action, Target.newFromShip(enemy));
|
||||
check.nears(TacticalAIHelpers.evaluateActiveEffects(ship, battle, maneuver), -1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,15 +1,4 @@
|
|||
module TK.SpaceTac {
|
||||
/**
|
||||
* Iterator of a list of "random" arena coordinates, based on a grid
|
||||
*/
|
||||
function 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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all playable actions (like the actionbar for player) for a ship
|
||||
*/
|
||||
|
@ -55,6 +44,17 @@ module TK.SpaceTac {
|
|||
* These are static methods that may be used as base for TacticalAI ruleset.
|
||||
*/
|
||||
export class TacticalAIHelpers {
|
||||
/**
|
||||
* Iterator of a list of "random" arena coordinates, based on a grid
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a turn end.
|
||||
*/
|
||||
|
@ -77,7 +77,7 @@ module TK.SpaceTac {
|
|||
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, scanArena(battle, cells, random));
|
||||
let moves = icombine(engines, TacticalAIHelpers.scanArena(battle, cells, random));
|
||||
return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target));
|
||||
}));
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ module TK.SpaceTac {
|
|||
*/
|
||||
static produceRandomBlastShots(ship: Ship, battle: Battle): TacticalProducer {
|
||||
let weapons = ifilter(getPlayableActions(ship), action => action instanceof TriggerAction && action.blast > 0);
|
||||
let candidates = ifilter(icombine(weapons, scanArena(battle)), ([weapon, location]) => (<TriggerAction>weapon).getEffects(ship, location).length > 0);
|
||||
let candidates = ifilter(icombine(weapons, TacticalAIHelpers.scanArena(battle)), ([weapon, location]) => (<TriggerAction>weapon).getEffects(ship, location).length > 0);
|
||||
let result = imap(candidates, ([weapon, location]) => new Maneuver(ship, weapon, location));
|
||||
return result;
|
||||
}
|
||||
|
@ -114,12 +114,19 @@ module TK.SpaceTac {
|
|||
}
|
||||
|
||||
/**
|
||||
* Produce drone deployments.
|
||||
* Produce toggle actions at random locations.
|
||||
*/
|
||||
static produceDroneDeployments(ship: Ship, battle: Battle): TacticalProducer {
|
||||
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));
|
||||
static produceToggleActions(ship: Ship, battle: Battle): TacticalProducer {
|
||||
let toggles = ifilter(getPlayableActions(ship), action => action instanceof 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)));
|
||||
|
||||
let distant_toggles = ifilter(toggles, toggle => contains([ActionTargettingMode.SPACE, ActionTargettingMode.SURROUNDINGS], toggle.getTargettingMode(ship)));
|
||||
let grid = TacticalAIHelpers.scanArena(battle);
|
||||
let distant_maneuvers = imap(icombine(grid, distant_toggles), ([location, toggle]) => new Maneuver(ship, toggle, location));
|
||||
|
||||
return ichain(self_maneuvers, distant_maneuvers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,5 +230,29 @@ module TK.SpaceTac {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the gain or loss of active effects
|
||||
*/
|
||||
static evaluateActiveEffects(ship: Ship, battle: Battle, maneuver: Maneuver): number {
|
||||
let result = 0;
|
||||
maneuver.effects.forEach(effect => {
|
||||
if (effect instanceof ShipEffectAddedDiff || effect instanceof ShipEffectRemovedDiff) {
|
||||
let target = battle.getShip(effect.ship_id);
|
||||
let enemy = target && !target.isPlayedBy(ship.getPlayer());
|
||||
let beneficial = effect.effect.isBeneficial();
|
||||
if (effect instanceof ShipEffectRemovedDiff) {
|
||||
beneficial = !beneficial;
|
||||
}
|
||||
// TODO Evaluate the "power" of the effect
|
||||
if ((beneficial && !enemy) || (!beneficial && enemy)) {
|
||||
result += 1;
|
||||
} else {
|
||||
result -= 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
return clamp(result / battle.ships.count(), -1, 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -362,7 +362,7 @@ module TK.SpaceTac.UI {
|
|||
|
||||
let arena = this.battleview.arena.getBoundaries();
|
||||
this.effects_messages.setPosition(
|
||||
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects_messages.width) : (-this.effects_messages.width * 0.5)),
|
||||
(this.ship.arena_x < 100) ? 0 : ((this.ship.arena_x > arena.width - 100) ? (-this.effects_messages.width) : (-this.effects_messages.width * 0.5)),
|
||||
(this.ship.arena_y < arena.height * 0.9) ? 60 : (-60 - this.effects_messages.height)
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue