1
0
Fork 0

AI: Tactical evaluators now work on diffs instead of guessed effects

This commit is contained in:
Michaël Lemaire 2018-01-09 20:23:35 +01:00
parent f9859c157e
commit 4e155b8556
6 changed files with 73 additions and 144 deletions

View file

@ -79,12 +79,14 @@ Artificial Intelligence
----------------------- -----------------------
* Produce interesting "angle" areas * Produce interesting "angle" areas
* AI seems unwanting to use the laser * Evaluate active effects
* Evaluate diffs instead of effects * Account for luck
* 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 * 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 * Abandon fight if the AI judges there is no hope of victory
* Add combination of random small move and actual maneuver, as producer * Add combination of random small move and actual maneuver, as producer
* New duel page with producers/evaluators tweaking * New duel page with producers/evaluators tweaking
* Use tree techniques to account for potential future moves
* Prototype of evolving AI * Prototype of evolving AI
Common UI Common UI

View file

@ -10,6 +10,10 @@
canvas { canvas {
display: none; display: none;
} }
.jasmine-result-message {
white-space: pre-wrap !important;
}
</style> </style>
</head> </head>

View file

@ -179,38 +179,23 @@ module TK.SpaceTac {
/** /**
* Get the full list of diffs caused by applying this action * Get the full list of diffs caused by applying this action
*
* This does not perform any check, and assumes the action is doable
*/ */
getDiffs(ship: Ship, battle: Battle, target = this.getDefaultTarget(ship)): BaseBattleDiff[] { getDiffs(ship: Ship, battle: Battle, target = this.getDefaultTarget(ship)): BaseBattleDiff[] {
let reject = this.checkCannotBeApplied(ship);
if (reject) {
console.warn(`Action rejected - ${reject}`, ship, this, target);
return [];
}
let checked_target = this.checkTarget(ship, target);
if (!checked_target) {
console.warn("Action rejected - invalid target", ship, this, target);
return [];
}
let cost = this.getActionPointsUsage(ship, checked_target);
if (ship.getValue("power") < cost) {
console.warn("Action rejected - not enough power", ship, this, checked_target);
return [];
}
let result: BaseBattleDiff[] = []; let result: BaseBattleDiff[] = [];
// Action usage // Action usage
result.push(new ShipActionUsedDiff(ship, this, checked_target)); result.push(new ShipActionUsedDiff(ship, this, target));
// Power usage // Power usage
let cost = this.getActionPointsUsage(ship, target);
if (cost) { if (cost) {
result = result.concat(ship.getValueDiffs("power", -cost, true)); result = result.concat(ship.getValueDiffs("power", -cost, true));
} }
// Action effects // Action effects
result = result.concat(this.getSpecificDiffs(ship, battle, checked_target)); result = result.concat(this.getSpecificDiffs(ship, battle, target));
return result; return result;
} }
@ -224,19 +209,34 @@ module TK.SpaceTac {
/** /**
* Apply the action on a battle state * Apply the action on a battle state
*
* This will first check that the action can be done, then get the battle diffs and apply them.
*/ */
apply(battle: Battle, ship: Ship, target = this.getDefaultTarget(ship)): boolean { apply(battle: Battle, ship: Ship, target = this.getDefaultTarget(ship)): boolean {
if (this.checkTarget(ship, target)) { let reject = this.checkCannotBeApplied(ship);
let diffs = this.getDiffs(ship, battle, target); if (reject) {
if (diffs.length) { console.warn(`Action rejected - ${reject}`, ship, this, target);
battle.applyDiffs(diffs); return false;
return true; }
} else {
console.error("Could not apply action, no diff produced"); let checked_target = this.checkTarget(ship, target);
return false; if (!checked_target) {
} console.warn("Action rejected - invalid target", ship, this, target);
return false;
}
let cost = this.getActionPointsUsage(ship, checked_target);
if (ship.getValue("power") < cost) {
console.warn("Action rejected - not enough power", ship, this, checked_target);
return false;
}
let diffs = this.getDiffs(ship, battle, checked_target);
if (diffs.length) {
battle.applyDiffs(diffs);
return true;
} else { } else {
console.error("Could not apply action, target rejected"); console.error("Could not apply action, no diff produced");
return false; return false;
} }
} }

View file

@ -1,72 +1,35 @@
module TK.SpaceTac.Specs { module TK.SpaceTac.Specs {
testing("Maneuver", test => { testing("Maneuver", test => {
function compare_maneuver_effects(check: TestContext, meff1: ManeuverEffect[], meff2: ManeuverEffect[]): void { test.case("uses move-fire simulation to build a list of battle diffs", check => {
check.equals(meff1.map(ef => ef.ship), meff2.map(ef => ef.ship), "impacted ships");
compare_effects(check, meff1.map(ef => ef.effect), meff2.map(ef => ef.effect));
check.equals(meff1.map(ef => ef.success), meff2.map(ef => ef.success), "success factor");
}
test.case("guesses weapon effects", check => {
let battle = new Battle(); let battle = new Battle();
let ship1 = battle.fleets[0].addShip(); let ship1 = battle.fleets[0].addShip();
let ship2 = battle.fleets[0].addShip(); let ship2 = battle.fleets[1].addShip();
let ship3 = battle.fleets[1].addShip(); let ship3 = battle.fleets[1].addShip();
let weapon = TestTools.addWeapon(ship1, 50, 0, 100, 10); let ship4 = battle.fleets[1].addShip();
let weapon = TestTools.addWeapon(ship1, 50, 2, 200, 100);
let engine = TestTools.addEngine(ship1, 100);
ship1.setArenaPosition(0, 0); ship1.setArenaPosition(0, 0);
TestTools.setShipHP(ship1, 20, 20); TestTools.setShipHP(ship1, 20, 20);
ship2.setArenaPosition(0, 5); TestTools.setShipAP(ship1, 10);
TestTools.setShipHP(ship2, 30, 30); ship2.setArenaPosition(500, 0);
ship3.setArenaPosition(0, 15); TestTools.setShipHP(ship2, 70, 100);
TestTools.setShipHP(ship3, 30, 30); ship3.setArenaPosition(560, 0);
let maneuver = new Maneuver(ship1, nn(weapon.action), Target.newFromLocation(0, 0)); TestTools.setShipHP(ship3, 80, 30);
compare_maneuver_effects(check, maneuver.effects, [ ship4.setArenaPosition(640, 0);
{ ship: ship1, effect: new DamageEffect(50), success: 1 }, TestTools.setShipHP(ship4, 30, 30);
{ ship: ship2, effect: new DamageEffect(50), success: 1 },
]);
});
test.case("guesses drone effects", check => { let maneuver = new Maneuver(ship1, nn(weapon.action), Target.newFromLocation(530, 0));
let battle = new Battle(); check.contains(maneuver.effects, new ShipActionUsedDiff(ship1, nn(engine.action), Target.newFromLocation(331, 0)), "engine use");
let ship1 = battle.fleets[0].addShip(); check.contains(maneuver.effects, new ShipValueDiff(ship1, "power", -4), "engine power");
let ship2 = battle.fleets[0].addShip(); check.contains(maneuver.effects, new ShipMoveDiff(ship1, ship1.location, new ArenaLocationAngle(331, 0), engine), "move");
let ship3 = battle.fleets[1].addShip(); check.contains(maneuver.effects, new ShipActionUsedDiff(ship1, nn(weapon.action), Target.newFromLocation(530, 0)), "weapon use");
let weapon = ship1.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon)); check.contains(maneuver.effects, new ProjectileFiredDiff(ship1, weapon, Target.newFromLocation(530, 0)), "weapon power");
weapon.action = new DeployDroneAction(weapon, 0, 100, 10, [new ValueEffect("shield", 10)]); check.contains(maneuver.effects, new ShipValueDiff(ship1, "power", -2), "weapon power");
ship1.setArenaPosition(0, 0); check.contains(maneuver.effects, new ShipValueDiff(ship2, "shield", -50), "ship2 shield value");
TestTools.setShipHP(ship1, 20, 20); check.contains(maneuver.effects, new ShipValueDiff(ship3, "shield", -30), "ship3 shield value");
ship2.setArenaPosition(0, 5); check.contains(maneuver.effects, new ShipValueDiff(ship3, "hull", -20), "ship3 hull value");
TestTools.setShipHP(ship2, 30, 30); check.contains(maneuver.effects, new ShipDamageDiff(ship2, 0, 50, 50), "ship2 damage");
ship3.setArenaPosition(0, 15); check.contains(maneuver.effects, new ShipDamageDiff(ship3, 20, 30, 50), "ship3 damage");
TestTools.setShipHP(ship3, 30, 30);
let maneuver = new Maneuver(ship1, weapon.action, Target.newFromLocation(0, 0));
compare_maneuver_effects(check, maneuver.effects, [
{ ship: ship1, effect: new ValueEffect("shield", 10), success: 1 },
{ ship: ship2, effect: new ValueEffect("shield", 10), success: 1 },
]);
});
test.case("guesses area effects on final location", check => {
let battle = new Battle();
let ship = battle.fleets[0].addShip();
let engine = TestTools.addEngine(ship, 500);
let move = nn(engine.action);
TestTools.setShipAP(ship, 10);
let drone = new Drone(ship);
drone.effects = [new AttributeEffect("maneuvrability", 1)];
drone.x = 100;
drone.y = 0;
drone.radius = 50;
battle.addDrone(drone);
let maneuver = new Maneuver(ship, move, Target.newFromLocation(40, 30));
check.containing(maneuver.getFinalLocation(), { x: 40, y: 30 });
check.equals(maneuver.effects, []);
maneuver = new Maneuver(ship, move, Target.newFromLocation(100, 30));
check.containing(maneuver.getFinalLocation(), { x: 100, y: 30 });
compare_maneuver_effects(check, maneuver.effects, [
{ ship: ship, effect: new AttributeEffect("maneuvrability", 1), success: 1 },
]);
}); });
}); });
} }

View file

@ -1,11 +1,4 @@
module TK.SpaceTac { module TK.SpaceTac {
// Single effect of a maneuver
export type ManeuverEffect = {
ship: Ship
effect: BaseEffect
success: number
}
/** /**
* Ship maneuver for an artifical intelligence * Ship maneuver for an artifical intelligence
* *
@ -28,7 +21,7 @@ module TK.SpaceTac {
simulation: MoveFireResult simulation: MoveFireResult
// List of guessed effects of this maneuver // List of guessed effects of this maneuver
effects: ManeuverEffect[] effects: BaseBattleDiff[]
constructor(ship: Ship, action: BaseAction, target: Target, move_margin = 1) { constructor(ship: Ship, action: BaseAction, target: Target, move_margin = 1) {
this.ship = ship; this.ship = ship;
@ -39,7 +32,9 @@ module TK.SpaceTac {
let simulator = new MoveFireSimulator(this.ship); let simulator = new MoveFireSimulator(this.ship);
this.simulation = simulator.simulateAction(this.action, this.target, move_margin); this.simulation = simulator.simulateAction(this.action, this.target, move_margin);
this.effects = this.guessEffects(); this.effects = flatten(this.simulation.parts.map(part =>
part.action.getDiffs(this.ship, this.battle, part.target)
));
} }
jasmineToString() { jasmineToString() {
@ -78,37 +73,6 @@ module TK.SpaceTac {
return this.simulation.total_move_ap + this.simulation.total_fire_ap; return this.simulation.total_move_ap + this.simulation.total_fire_ap;
} }
/**
* Guess what will be the effects applied on any ship by this maneuver
*/
guessEffects(): ManeuverEffect[] {
let result: ManeuverEffect[] = [];
// Effects of weapon
if (this.action instanceof TriggerAction) {
this.action.getEffects(this.ship, this.target).forEach(([ship, effect, success]) => {
result.push({ ship: ship, effect: effect, success: success });
})
} else if (this.action instanceof DeployDroneAction) {
let ships = this.battle.collectShipsInCircle(this.target, this.action.drone_radius, true);
this.action.drone_effects.forEach(effect => {
result = result.concat(ships.map(ship => ({ ship: ship, effect: effect, success: 1 })));
});
}
// Area effects on final location
let location = this.getFinalLocation();
let effects = this.battle.drones.list().forEach(drone => {
if (Target.newFromLocation(location.x, location.y).isInRange(drone.x, drone.y, drone.radius)) {
result = result.concat(drone.effects.map(effect => (
{ ship: this.ship, effect: effect, success: 1 }
)));
}
});
return result;
}
/** /**
* Standard feedback for this maneuver. It will apply it on the battle state. * Standard feedback for this maneuver. It will apply it on the battle state.
*/ */

View file

@ -29,17 +29,13 @@ module TK.SpaceTac {
let dhull = 0; let dhull = 0;
let dshield = 0; let dshield = 0;
maneuver.effects.forEach(result => { maneuver.effects.forEach(diff => {
if (result.ship === ship) { if (diff instanceof ShipValueDiff) {
if (result.effect instanceof DamageEffect) { if (ship.is(diff.ship_id)) {
let damage = result.effect.getEffectiveDamage(ship, result.success); if (diff.code == "hull") {
dhull -= damage.hull; dhull += clamp(hull + diff.diff, 0, chull) - hull;
dshield -= damage.shield; } else if (diff.code == "shield") {
} else if (result.effect instanceof ValueEffect) { dshield += clamp(shield + diff.diff, 0, cshield) - shield;
if (result.effect.valuetype == "hull") {
dhull = clamp(hull + result.effect.value_on, 0, chull) - hull;
} else if (result.effect.valuetype == "shield") {
dshield += clamp(shield + result.effect.value_on, 0, cshield) - shield;
} }
} }
} }