Improved tactical AI and set it as default
This commit is contained in:
parent
92129eb512
commit
33ec46e27a
7
TODO
7
TODO
|
@ -34,12 +34,11 @@
|
|||
* Mobile: display tooltips larger and on the side of screen where the finger is not
|
||||
* Mobile: targetting in two times, using a draggable target indicator
|
||||
* AI: apply safety distances to move actions
|
||||
* AI: use support equipments (repair drones...)
|
||||
* AI: fix not being able to apply simulated maneuver
|
||||
* AI: do not always move first, they are defenders
|
||||
* TacticalAI: allow to play several moves in the same turn
|
||||
* TacticalAI: add pauses to not play too quickly
|
||||
* TacticalAI: replace BullyAI
|
||||
* AI: allow to play several moves in the same turn (with pauses)
|
||||
* AI: add combination of random small move and actual maneuver, as producer
|
||||
* AI: evaluate based on simulated list of effects
|
||||
* Map: restore fog of war
|
||||
* Map: add information on current star/location + information on hovered location
|
||||
* Map: generate random shops
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e3c03f16d3d1e953c1e1faffd2e28cba2c7c518c
|
||||
Subproject commit 9957f153b1da5f1d30beebd670cc0a952d541304
|
|
@ -221,7 +221,7 @@ module TS.SpaceTac {
|
|||
if (this.playing_ship) {
|
||||
if (!ai) {
|
||||
// TODO Use an AI adapted to the fleet
|
||||
ai = new BullyAI(this.playing_ship, this.timer);
|
||||
ai = new TacticalAI(this.playing_ship, this.timer);
|
||||
}
|
||||
ai.play();
|
||||
}
|
||||
|
|
|
@ -32,6 +32,14 @@ module TS.SpaceTac {
|
|||
this.ship = ship;
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
if (this.ship) {
|
||||
return this.ship.jasmineToString();
|
||||
} else {
|
||||
return `(${this.x},${this.y})`;
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor to target a single ship
|
||||
static newFromShip(ship: Ship): Target {
|
||||
return new Target(ship.arena_x, ship.arena_y, ship);
|
||||
|
@ -43,14 +51,14 @@ module TS.SpaceTac {
|
|||
}
|
||||
|
||||
// Get distance to another target
|
||||
getDistanceTo(other: Target): number {
|
||||
getDistanceTo(other: { x: number, y: number }): number {
|
||||
var dx = other.x - this.x;
|
||||
var dy = other.y - this.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
// Get the normalized angle, in radians, to another target
|
||||
getAngleTo(other: Target): number {
|
||||
getAngleTo(other: { x: number, y: number }): number {
|
||||
var dx = other.x - this.x;
|
||||
var dy = other.y - this.y;
|
||||
return Math.atan2(dy, dx);
|
||||
|
|
|
@ -26,6 +26,10 @@ module TS.SpaceTac {
|
|||
this.simulation = simulator.simulateAction(this.equipment.action, this.target, move_margin);
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
return `Use ${this.equipment.jasmineToString()} on ${this.target.jasmineToString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the maneuver in current battle
|
||||
*/
|
||||
|
@ -49,5 +53,12 @@ module TS.SpaceTac {
|
|||
return { x: this.ship.arena_x, y: this.ship.arena_y };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total power usage of this maneuver
|
||||
*/
|
||||
getPowerUsage(): number {
|
||||
return this.simulation.total_move_ap + this.simulation.total_fire_ap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ module TS.SpaceTac {
|
|||
let producers = [
|
||||
TacticalAIHelpers.produceDirectShots,
|
||||
TacticalAIHelpers.produceBlastShots,
|
||||
TacticalAIHelpers.produceDroneDeployments,
|
||||
TacticalAIHelpers.produceRandomMoves,
|
||||
]
|
||||
producers.forEach(producer => this.producers.push(producer(this.ship, this.ship.getBattle() || new Battle())));
|
||||
|
@ -103,8 +104,9 @@ module TS.SpaceTac {
|
|||
let evaluators = [
|
||||
scaled(TacticalAIHelpers.evaluateTurnCost, 1),
|
||||
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 30),
|
||||
scaled(TacticalAIHelpers.evaluateClustering, 3),
|
||||
scaled(TacticalAIHelpers.evaluateClustering, 8),
|
||||
scaled(TacticalAIHelpers.evaluatePosition, 1),
|
||||
scaled(TacticalAIHelpers.evaluateIdling, 5),
|
||||
]
|
||||
// TODO evaluator typing is lost
|
||||
evaluators.forEach(evaluator => this.evaluators.push((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver)));
|
||||
|
|
|
@ -10,8 +10,8 @@ module TS.SpaceTac.Specs {
|
|||
let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle));
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
let weapon1 = ship0a.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
|
||||
let weapon2 = ship0a.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
|
||||
let weapon1 = TestTools.addWeapon(ship0a, 10);
|
||||
let weapon2 = TestTools.addWeapon(ship0a, 15);
|
||||
result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle));
|
||||
expect(result.length).toBe(4);
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1, Target.newFromShip(ship1a)));
|
||||
|
@ -60,6 +60,7 @@ module TS.SpaceTac.Specs {
|
|||
result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, weapon, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon, Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
|
||||
let enemy3 = battle.fleets[1].addShip();
|
||||
|
@ -68,6 +69,7 @@ module TS.SpaceTac.Specs {
|
|||
result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, weapon, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon, Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -93,6 +95,28 @@ module TS.SpaceTac.Specs {
|
|||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-1); // can't do in one turn
|
||||
});
|
||||
|
||||
it("evaluates the drawback of doing nothing", function () {
|
||||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
TestTools.setShipAP(ship, 10, 5);
|
||||
let engine = TestTools.addEngine(ship, 50);
|
||||
let weapon = TestTools.addWeapon(ship, 10, 2, 100, 10);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.3);
|
||||
|
||||
maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.5);
|
||||
|
||||
ship.setValue("power", 2);
|
||||
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0.5);
|
||||
|
||||
maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0);
|
||||
});
|
||||
|
||||
it("evaluates damage to enemies", function () {
|
||||
let battle = new Battle();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
|
|
|
@ -1,4 +1,15 @@
|
|||
module TS.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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard producers and evaluators for TacticalAI
|
||||
*
|
||||
|
@ -9,8 +20,8 @@ module TS.SpaceTac {
|
|||
* Produce all "direct hit" weapon shots.
|
||||
*/
|
||||
static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer {
|
||||
let enemies = ifilter(battle.iships(), iship => iship.getPlayer() != ship.getPlayer());
|
||||
let weapons = iarray(ship.listEquipment(SlotType.Weapon));
|
||||
let enemies = ifilter(battle.iships(), iship => iship.alive && iship.getPlayer() !== ship.getPlayer());
|
||||
let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof FireWeaponAction);
|
||||
return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy)));
|
||||
}
|
||||
|
||||
|
@ -23,14 +34,9 @@ module TS.SpaceTac {
|
|||
return IEMPTY;
|
||||
}
|
||||
|
||||
return ichainit(imap(irange(iterations), iteration =>
|
||||
imap(irange(cells * cells), cellpos => {
|
||||
let y = Math.floor(cellpos / cells);
|
||||
let x = cellpos - y * cells;
|
||||
let target = Target.newFromLocation((x + random.random()) * battle.width / cells, (y + random.random()) * battle.height / cells);
|
||||
return new Maneuver(ship, engines[0], target);
|
||||
})
|
||||
));
|
||||
return ichainit(imap(irange(iterations), iteration => {
|
||||
return imap(scanArena(battle, cells, random), target => new Maneuver(ship, engines[0], target))
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,13 +45,23 @@ module TS.SpaceTac {
|
|||
static produceBlastShots(ship: Ship, battle: Battle): TacticalProducer {
|
||||
// TODO Work with groups of 3, 4 ...
|
||||
let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof FireWeaponAction && weapon.action.blast > 0);
|
||||
let enemies = battle.ienemies(ship.getPlayer());
|
||||
let enemies = battle.ienemies(ship.getPlayer(), true);
|
||||
// FIXME 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.action.getBlastRadius(ship) * 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce drone deployments.
|
||||
*/
|
||||
static produceDroneDeployments(ship: Ship, battle: Battle): TacticalProducer {
|
||||
let drones = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof DeployDroneAction);
|
||||
let grid = scanArena(battle);
|
||||
return imap(icombine(grid, drones), ([target, drone]) => new Maneuver(ship, drone, target));
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the number of turns necessary for the maneuver, between -1 and 1
|
||||
*/
|
||||
|
@ -60,6 +76,20 @@ module TS.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate doing nothing, between -1 and 1
|
||||
*/
|
||||
static evaluateIdling(ship: Ship, battle: Battle, maneuver: Maneuver): number {
|
||||
let lost = ship.getValue("power") - maneuver.getPowerUsage() + ship.getAttribute("power_recovery") - ship.getAttribute("power_capacity");
|
||||
if (lost > 0) {
|
||||
return -lost / ship.getAttribute("power_capacity");
|
||||
} else if (maneuver.simulation.need_fire) {
|
||||
return 0.5;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the damage done to the enemy, between -1 and 1
|
||||
*/
|
||||
|
@ -102,7 +132,6 @@ module TS.SpaceTac {
|
|||
} else {
|
||||
let factor = max([battle.width, battle.height]) * 0.01;
|
||||
let result = -clamp(sum(distances.map(distance => factor / distance)), 0, 1);
|
||||
//console.log(distances, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue