Removed Bully AI and fixed AI trying unavailable actions
This commit is contained in:
parent
a3df49ae0b
commit
b10725abda
1
TODO
1
TODO
|
@ -47,6 +47,7 @@
|
|||
* AI: add combination of random small move and actual maneuver, as producer
|
||||
* AI: evaluate based on simulated list of effects
|
||||
* AI: consider overheat/cooldown
|
||||
* AI: new duel page with producers/evaluators tweaking
|
||||
* Map: remove jump links that cross the radius of other systems
|
||||
* Map: disable interaction (zoom, selection) while moving/jumping
|
||||
* Tutorial
|
||||
|
|
|
@ -107,7 +107,7 @@ module TS.SpaceTac {
|
|||
*/
|
||||
static setup(element: HTMLElement) {
|
||||
let fakeship = new Ship();
|
||||
let ais = [new BullyAI(fakeship), new TacticalAI(fakeship), new AbstractAI(fakeship)];
|
||||
let ais = [new TacticalAI(fakeship), new AbstractAI(fakeship)];
|
||||
ais.forEach((ai, idx) => {
|
||||
let selects = element.getElementsByTagName("select");
|
||||
for (let i = 0; i < selects.length; i++) {
|
||||
|
|
|
@ -1,243 +0,0 @@
|
|||
module TS.SpaceTac.Specs {
|
||||
describe("BullyAI", function () {
|
||||
it("lists enemies", function () {
|
||||
var battle = new Battle();
|
||||
battle.fleets[0].addShip(new Ship(null, "0-0"));
|
||||
battle.fleets[1].addShip(new Ship(null, "1-0"));
|
||||
battle.fleets[1].addShip(new Ship(null, "1-1"));
|
||||
iforeach(battle.iships(), ship => ship.setAttribute("initiative", 1));
|
||||
|
||||
var random = new SkewedRandomGenerator([0, 0.5, 1]);
|
||||
battle.throwInitiative(random);
|
||||
|
||||
var ai = new BullyAI(battle.fleets[0].ships[0], Timer.synchronous);
|
||||
|
||||
var result = ai.listAllEnemies();
|
||||
expect(result).toEqual([battle.fleets[1].ships[1], battle.fleets[1].ships[0]]);
|
||||
});
|
||||
|
||||
it("lists weapons", function () {
|
||||
var ship = new Ship();
|
||||
|
||||
var ai = new BullyAI(ship, Timer.synchronous);
|
||||
ai.ship = ship;
|
||||
|
||||
var result = ai.listAllWeapons();
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
var weapon1 = new Equipment(SlotType.Weapon, "weapon1");
|
||||
weapon1.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(50)]);
|
||||
ai.ship.addSlot(SlotType.Weapon).attach(weapon1);
|
||||
var weapon2 = new Equipment(SlotType.Weapon, "weapon2");
|
||||
weapon2.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(100)]);
|
||||
ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
|
||||
var weapon3 = new Equipment(SlotType.Weapon, "weapon3");
|
||||
ai.ship.addSlot(SlotType.Weapon).attach(weapon3);
|
||||
|
||||
ai.ship.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield));
|
||||
|
||||
result = ai.listAllWeapons();
|
||||
expect(result).toEqual([weapon1, weapon2]);
|
||||
});
|
||||
|
||||
it("checks a firing possibility", function () {
|
||||
var ship = new Ship();
|
||||
let engine = TestTools.addEngine(ship, 1 / 3);
|
||||
TestTools.setShipAP(ship, 10);
|
||||
var enemy = new Ship();
|
||||
var ai = new BullyAI(ship, Timer.synchronous);
|
||||
ai.ship = ship;
|
||||
ai.move_margin = 0;
|
||||
let weapon = TestTools.addWeapon(ship, 0, 2, 3);
|
||||
|
||||
// enemy in range, the ship can fire without moving
|
||||
ship.values.power.set(8);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 3;
|
||||
enemy.arena_y = 0;
|
||||
var result = ai.checkBullyManeuver(enemy, weapon);
|
||||
if (result) {
|
||||
expect(result.simulation.need_move).toBe(false);
|
||||
expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy));
|
||||
expect(result.equipment).toBe(weapon);
|
||||
} else {
|
||||
fail("No maneuver proposed");
|
||||
}
|
||||
|
||||
// enemy out of range, but moving can bring it in range
|
||||
ship.values.power.set(8);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 6;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
if (result) {
|
||||
expect(result.simulation.move_location).toEqual(Target.newFromLocation(3, 0));
|
||||
expect(result.simulation.fire_location).toEqual(Target.newFromShip(enemy));
|
||||
expect(result.equipment).toBe(weapon);
|
||||
} else {
|
||||
fail("No maneuver proposed");
|
||||
}
|
||||
|
||||
// enemy out of range, but moving can bring it in range, except for the safety margin
|
||||
ai.move_margin = 0.1;
|
||||
ship.values.power.set(8);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 6;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
expect(result).toBeNull();
|
||||
ai.move_margin = 0;
|
||||
|
||||
// enemy totally out of range
|
||||
ship.values.power.set(8);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 30;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
expect(result).toBeNull();
|
||||
|
||||
// enemy in range but not enough AP to fire
|
||||
ship.values.power.set(1);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 3;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
expect(result).toBeNull();
|
||||
|
||||
// can move in range of enemy, but not enough AP to fire
|
||||
ship.values.power.set(7);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 6;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
expect(result).toBeNull();
|
||||
|
||||
// no engine, can't move
|
||||
engine.detach();
|
||||
ship.values.power.set(8);
|
||||
ship.arena_x = 1;
|
||||
ship.arena_y = 0;
|
||||
enemy.arena_x = 6;
|
||||
enemy.arena_y = 0;
|
||||
result = ai.checkBullyManeuver(enemy, weapon);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("lists available firing actions", function () {
|
||||
var battle = new Battle();
|
||||
var ship1 = new Ship();
|
||||
ship1.setArenaPosition(3, 2);
|
||||
battle.fleets[0].addShip(ship1);
|
||||
var ship2 = new Ship();
|
||||
ship2.setArenaPosition(5, 3);
|
||||
battle.fleets[1].addShip(ship2);
|
||||
var ship3 = new Ship();
|
||||
ship3.setArenaPosition(11, 15);
|
||||
battle.fleets[1].addShip(ship3);
|
||||
battle.throwInitiative(new SkewedRandomGenerator([1, 0.5, 0]));
|
||||
|
||||
var ai = new BullyAI(ship1, Timer.synchronous);
|
||||
ai.ship = ship1;
|
||||
|
||||
var result = ai.listAllManeuvers();
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
TestTools.setShipAP(ai.ship, 8);
|
||||
let weapon1 = TestTools.addWeapon(ai.ship, 10, 1, 50);
|
||||
let weapon2 = TestTools.addWeapon(ai.ship, 5, 1, 10);
|
||||
|
||||
result = ai.listAllManeuvers();
|
||||
expect(result.length).toBe(3);
|
||||
});
|
||||
|
||||
it("gets a fallback maneuver", function () {
|
||||
var battle = TestTools.createBattle(1, 3);
|
||||
var ai = new BullyAI(battle.fleets[0].ships[0], Timer.synchronous);
|
||||
|
||||
TestTools.setShipAP(ai.ship, 5);
|
||||
var engine = TestTools.addEngine(ai.ship, 100);
|
||||
(<MoveAction>engine.action).safety_distance = 20;
|
||||
|
||||
var maneuver: BullyManeuver | null;
|
||||
|
||||
battle.fleets[1].ships.forEach((ship: Ship) => {
|
||||
ai.ship.setArenaPosition(0, 0);
|
||||
});
|
||||
|
||||
// Too much near an enemy, don't move
|
||||
ai.ship.setArenaPosition(10, 0);
|
||||
maneuver = ai.getFallbackManeuver();
|
||||
expect(maneuver).toBeNull();
|
||||
ai.ship.setArenaPosition(20, 0);
|
||||
maneuver = ai.getFallbackManeuver();
|
||||
expect(maneuver).toBeNull();
|
||||
|
||||
// Move towards an enemy (up to minimal distance)
|
||||
ai.ship.setArenaPosition(30, 0);
|
||||
maneuver = ai.getFallbackManeuver();
|
||||
if (maneuver) {
|
||||
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(25, 0));
|
||||
} else {
|
||||
fail("No maneuver proposed");
|
||||
}
|
||||
ai.ship.setArenaPosition(25, 0);
|
||||
maneuver = ai.getFallbackManeuver();
|
||||
if (maneuver) {
|
||||
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(22.5, 0));
|
||||
} else {
|
||||
fail("No maneuver proposed");
|
||||
}
|
||||
});
|
||||
|
||||
it("applies the chosen move", function () {
|
||||
var battle = new Battle();
|
||||
var ship1 = new Ship();
|
||||
ship1.setArenaPosition(0, 0);
|
||||
battle.fleets[0].addShip(ship1);
|
||||
var ship2 = new Ship();
|
||||
ship2.setArenaPosition(8, 0);
|
||||
battle.fleets[1].addShip(ship2);
|
||||
|
||||
var ai = new BullyAI(ship1, Timer.synchronous);
|
||||
ai.move_margin = 0;
|
||||
|
||||
var engine = new Equipment(SlotType.Engine);
|
||||
engine.action = new MoveAction(engine, 0.5);
|
||||
ai.ship.addSlot(SlotType.Engine).attach(engine);
|
||||
|
||||
var weapon = new Equipment(SlotType.Weapon);
|
||||
weapon.action = new FireWeaponAction(weapon, 1, 6, 0, [new DamageEffect(20)]);
|
||||
ai.ship.addSlot(SlotType.Weapon).attach(weapon);
|
||||
|
||||
ai.ship.values.power.setMaximal(10);
|
||||
ai.ship.values.power.set(6);
|
||||
|
||||
ship2.values.hull.set(15);
|
||||
ship2.values.shield.set(10);
|
||||
|
||||
var move = ai.checkBullyManeuver(ship2, weapon);
|
||||
expect(move).not.toBeNull();
|
||||
|
||||
battle.playing_ship = ai.ship;
|
||||
battle.log.clear();
|
||||
ai.applyManeuver(move);
|
||||
|
||||
expect(battle.log.events.length).toBe(7);
|
||||
|
||||
expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 2, 10), -4));
|
||||
expect(battle.log.events[1]).toEqual(new MoveEvent(ship1, 2, 0));
|
||||
|
||||
expect(battle.log.events[2]).toEqual(new ValueChangeEvent(ship1, new ShipValue("power", 1, 10), -1));
|
||||
expect(battle.log.events[3]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2)));
|
||||
expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2, new ShipValue("shield", 0), -10));
|
||||
expect(battle.log.events[5]).toEqual(new ValueChangeEvent(ship2, new ShipValue("hull", 5), -10));
|
||||
expect(battle.log.events[6]).toEqual(new DamageEvent(ship2, 10, 10));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
/// <reference path="AbstractAI.ts"/>
|
||||
/// <reference path="Maneuver.ts"/>
|
||||
module TS.SpaceTac {
|
||||
export class BullyManeuver extends Maneuver {
|
||||
// Get a sorting score, by distance to another point
|
||||
// Nearest means higher score
|
||||
getScoreByDistance(point: Target): number {
|
||||
return -point.getDistanceTo(this.simulation.fire_location);
|
||||
}
|
||||
}
|
||||
|
||||
// Basic Artificial Intelligence, with a tendency to move forward and shoot the nearest enemy
|
||||
export class BullyAI extends AbstractAI {
|
||||
// Safety margin in moves to account for floating-point rounding errors
|
||||
move_margin = 0.1;
|
||||
|
||||
protected initWork(): void {
|
||||
if (this.ship.getValue("power") > 0) {
|
||||
this.addWorkItem(() => {
|
||||
var maneuvers = this.listAllManeuvers();
|
||||
var maneuver: BullyManeuver | null;
|
||||
|
||||
if (maneuvers.length > 0) {
|
||||
maneuver = this.pickManeuver(maneuvers);
|
||||
this.applyManeuver(maneuver);
|
||||
|
||||
// Try to make another maneuver
|
||||
this.initWork();
|
||||
} else {
|
||||
// No bullying available, going to fallback move
|
||||
maneuver = this.getFallbackManeuver();
|
||||
this.applyManeuver(maneuver);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// List all enemy ships that can be a target
|
||||
listAllEnemies(): Ship[] {
|
||||
var result: Ship[] = [];
|
||||
|
||||
let battle = this.ship.getBattle();
|
||||
if (battle) {
|
||||
battle.play_order.forEach((ship: Ship) => {
|
||||
if (ship.alive && ship.getPlayer() !== this.ship.getPlayer()) {
|
||||
result.push(ship);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// List all weapons
|
||||
listAllWeapons(): Equipment[] {
|
||||
return this.ship.listEquipment(SlotType.Weapon).filter(equipement => equipement.action instanceof FireWeaponAction && any(equipement.action.effects, effect => effect instanceof DamageEffect));
|
||||
}
|
||||
|
||||
// List all available maneuvers for the playing ship
|
||||
listAllManeuvers(): BullyManeuver[] {
|
||||
var result: BullyManeuver[] = [];
|
||||
|
||||
var enemies = this.listAllEnemies();
|
||||
var weapons = this.listAllWeapons();
|
||||
|
||||
enemies.forEach((ship: Ship) => {
|
||||
weapons.forEach((weapon: Equipment) => {
|
||||
var maneuver = this.checkBullyManeuver(ship, weapon);
|
||||
if (maneuver) {
|
||||
result.push(maneuver);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get an equipped engine to make a move
|
||||
getEngine(): Equipment | null {
|
||||
var engines = this.ship.listEquipment(SlotType.Engine);
|
||||
if (engines.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return engines[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a weapon can be used against an enemy
|
||||
// Returns the BullyManeuver, or null if impossible to fire
|
||||
checkBullyManeuver(enemy: Ship, weapon: Equipment): BullyManeuver | null {
|
||||
let maneuver = new BullyManeuver(this.ship, weapon, Target.newFromShip(enemy), this.move_margin);
|
||||
// TODO In case of blast weapon, check that this would be a hit !
|
||||
if (maneuver.simulation.can_fire) {
|
||||
return maneuver;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// When no bully action is available, pick a random enemy, and go towards it
|
||||
getFallbackManeuver(): BullyManeuver | null {
|
||||
var enemies = this.listAllEnemies();
|
||||
if (enemies.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var APPROACH_FACTOR = 0.5;
|
||||
|
||||
var picked = this.random.choice(enemies);
|
||||
var target = Target.newFromShip(picked);
|
||||
var distance = target.getDistanceTo(Target.newFromShip(this.ship));
|
||||
var engine = this.getEngine();
|
||||
if (engine) {
|
||||
var safety_distance = (<MoveAction>engine.action).safety_distance;
|
||||
if (distance > safety_distance) { // Don't move too close
|
||||
target = target.constraintInRange(this.ship.arena_x, this.ship.arena_y,
|
||||
(distance - safety_distance) * APPROACH_FACTOR);
|
||||
let loctarget = engine.action.checkLocationTarget(this.ship, target);
|
||||
if (loctarget) {
|
||||
return new BullyManeuver(this.ship, engine, loctarget);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Pick a maneuver from a list of available ones
|
||||
// By default, it chooses the nearest enemy
|
||||
pickManeuver(available: BullyManeuver[]): BullyManeuver | null {
|
||||
if (available.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort by descending score
|
||||
available.sort((m1: BullyManeuver, m2: BullyManeuver): number => {
|
||||
var point = Target.newFromShip(this.ship);
|
||||
return m1.getScoreByDistance(point) < m2.getScoreByDistance(point) ? 1 : -1;
|
||||
});
|
||||
return available[0];
|
||||
}
|
||||
|
||||
// Effectively apply the chosen maneuver
|
||||
applyManeuver(maneuver: BullyManeuver | null): void {
|
||||
if (maneuver) {
|
||||
this.addWorkItem(() => {
|
||||
maneuver.apply();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
this.addWorkItem(null, 1500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,14 +2,14 @@ module TS.SpaceTac {
|
|||
/**
|
||||
* Ship maneuver for an artifical intelligence
|
||||
*
|
||||
* A maneuver is like a human player action, choosing an equipment and using it
|
||||
* A maneuver is like a human player action, choosing an action and using it
|
||||
*/
|
||||
export class Maneuver {
|
||||
// Concerned ship
|
||||
ship: Ship;
|
||||
|
||||
// Equipment to use
|
||||
equipment: Equipment;
|
||||
// Action to use
|
||||
action: BaseAction;
|
||||
|
||||
// Target for the action;
|
||||
target: Target;
|
||||
|
@ -17,17 +17,17 @@ module TS.SpaceTac {
|
|||
// Result of move-fire simulation
|
||||
simulation: MoveFireResult;
|
||||
|
||||
constructor(ship: Ship, equipment: Equipment, target: Target, move_margin = 0.1) {
|
||||
constructor(ship: Ship, action: BaseAction, target: Target, move_margin = 0.1) {
|
||||
this.ship = ship;
|
||||
this.equipment = equipment;
|
||||
this.action = action;
|
||||
this.target = target;
|
||||
|
||||
let simulator = new MoveFireSimulator(this.ship);
|
||||
this.simulation = simulator.simulateAction(this.equipment.action, this.target, move_margin);
|
||||
this.simulation = simulator.simulateAction(this.action, this.target, move_margin);
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
return `Use ${this.equipment.jasmineToString()} on ${this.target.jasmineToString()}`;
|
||||
return `Use ${this.action.code} on ${this.target.jasmineToString()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@ module TS.SpaceTac.Specs {
|
|||
class FixedManeuver extends Maneuver {
|
||||
score: number;
|
||||
constructor(score: number) {
|
||||
super(new Ship(), new Equipment(), new Target(0, 0));
|
||||
super(new Ship(), new BaseAction("nothing", "Do nothing", true), new Target(0, 0));
|
||||
this.score = score;
|
||||
}
|
||||
apply() {
|
||||
|
|
|
@ -7,6 +7,9 @@ module TS.SpaceTac.Specs {
|
|||
let ship1a = battle.fleets[1].addShip(new Ship(null, "1A"));
|
||||
let ship1b = battle.fleets[1].addShip(new Ship(null, "1B"));
|
||||
|
||||
TestTools.setShipAP(ship0a, 10);
|
||||
battle.playing_ship = ship0a;
|
||||
|
||||
let result = imaterialize(TacticalAIHelpers.produceDirectShots(ship0a, battle));
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
|
@ -14,10 +17,10 @@ module TS.SpaceTac.Specs {
|
|||
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)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1, Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2, Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2, Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon1.action, Target.newFromShip(ship1b)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1a)));
|
||||
expect(result).toContain(new Maneuver(ship0a, weapon2.action, Target.newFromShip(ship1b)));
|
||||
});
|
||||
|
||||
it("produces random moves inside a grid", function () {
|
||||
|
@ -26,17 +29,20 @@ module TS.SpaceTac.Specs {
|
|||
battle.height = 100;
|
||||
let ship = battle.fleets[0].addShip();
|
||||
|
||||
TestTools.setShipAP(ship, 10);
|
||||
battle.playing_ship = ship;
|
||||
|
||||
let result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1));
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
let engine = ship.addSlot(SlotType.Engine).attach(new Equipment(SlotType.Engine));
|
||||
let engine = TestTools.addEngine(ship, 1000);
|
||||
|
||||
result = imaterialize(TacticalAIHelpers.produceRandomMoves(ship, battle, 2, 1, new SkewedRandomGenerator([0.5], true)));
|
||||
expect(result).toEqual([
|
||||
new Maneuver(ship, engine, Target.newFromLocation(25, 25)),
|
||||
new Maneuver(ship, engine, Target.newFromLocation(75, 25)),
|
||||
new Maneuver(ship, engine, Target.newFromLocation(25, 75)),
|
||||
new Maneuver(ship, engine, Target.newFromLocation(75, 75)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(25, 25)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(75, 25)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(25, 75)),
|
||||
new Maneuver(ship, engine.action, Target.newFromLocation(75, 75)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -45,6 +51,9 @@ module TS.SpaceTac.Specs {
|
|||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 50, 1, 1000, 105);
|
||||
|
||||
TestTools.setShipAP(ship, 10);
|
||||
battle.playing_ship = ship;
|
||||
|
||||
let result = imaterialize(TacticalAIHelpers.produceBlastShots(ship, battle));
|
||||
expect(result.length).toBe(0);
|
||||
|
||||
|
@ -59,8 +68,8 @@ 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)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
|
||||
let enemy3 = battle.fleets[1].addShip();
|
||||
|
@ -68,8 +77,8 @@ 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)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
new Maneuver(ship, weapon.action, Target.newFromLocation(600, 0)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -79,19 +88,19 @@ module TS.SpaceTac.Specs {
|
|||
let weapon = TestTools.addWeapon(ship, 50, 5, 100);
|
||||
let engine = TestTools.addEngine(ship, 25);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(100, 0));
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0));
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-Infinity);
|
||||
|
||||
TestTools.setShipAP(ship, 10);
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.5); // 5 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(110, 0));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(110, 0));
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.4); // 4 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(140, 0));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(140, 0));
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(0.3); // 3 power remaining on 10
|
||||
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(310, 0));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(310, 0));
|
||||
expect(TacticalAIHelpers.evaluateTurnCost(ship, battle, maneuver)).toBe(-1); // can't do in one turn
|
||||
});
|
||||
|
||||
|
@ -102,18 +111,18 @@ module TS.SpaceTac.Specs {
|
|||
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));
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(-0.3);
|
||||
|
||||
maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0));
|
||||
maneuver = new Maneuver(ship, engine.action, 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));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0.5);
|
||||
|
||||
maneuver = new Maneuver(ship, engine, Target.newFromLocation(0, 0));
|
||||
maneuver = new Maneuver(ship, engine.action, Target.newFromLocation(0, 0));
|
||||
expect(TacticalAIHelpers.evaluateIdling(ship, battle, maneuver)).toEqual(0);
|
||||
});
|
||||
|
||||
|
@ -130,15 +139,15 @@ module TS.SpaceTac.Specs {
|
|||
TestTools.setShipHP(enemy2, 25, 0);
|
||||
|
||||
// no enemies hurt
|
||||
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(100, 0));
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(100, 0));
|
||||
expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0);
|
||||
|
||||
// one enemy loses half-life
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(180, 0));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(180, 0));
|
||||
expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.25);
|
||||
|
||||
// one enemy loses half-life, the other one is dead
|
||||
maneuver = new Maneuver(ship, weapon, Target.newFromLocation(280, 0));
|
||||
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(280, 0));
|
||||
expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.625);
|
||||
});
|
||||
|
||||
|
@ -149,7 +158,7 @@ module TS.SpaceTac.Specs {
|
|||
TestTools.setShipAP(ship, 10);
|
||||
let weapon = TestTools.addWeapon(ship, 100, 1, 100, 10);
|
||||
|
||||
let maneuver = new Maneuver(ship, weapon, Target.newFromLocation(200, 0), 0.5);
|
||||
let maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(200, 0), 0.5);
|
||||
expect(maneuver.simulation.move_location).toEqual(Target.newFromLocation(100.5, 0));
|
||||
expect(TacticalAIHelpers.evaluateClustering(ship, battle, maneuver)).toEqual(0);
|
||||
|
||||
|
@ -172,19 +181,19 @@ module TS.SpaceTac.Specs {
|
|||
let weapon = TestTools.addWeapon(ship, 1, 1, 400);
|
||||
|
||||
ship.setArenaPosition(0, 0);
|
||||
let maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0);
|
||||
let maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1);
|
||||
|
||||
ship.setArenaPosition(100, 0);
|
||||
maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-1);
|
||||
|
||||
ship.setArenaPosition(100, 10);
|
||||
maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(-0.6);
|
||||
|
||||
ship.setArenaPosition(100, 50);
|
||||
maneuver = new Maneuver(ship, weapon, new Target(0, 0), 0);
|
||||
maneuver = new Maneuver(ship, weapon.action, new Target(0, 0), 0);
|
||||
expect(TacticalAIHelpers.evaluatePosition(ship, battle, maneuver)).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,14 @@ module TS.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.getAvailableActions();
|
||||
return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship));
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard producers and evaluators for TacticalAI
|
||||
*
|
||||
|
@ -21,7 +29,7 @@ module TS.SpaceTac {
|
|||
*/
|
||||
static produceDirectShots(ship: Ship, battle: Battle): TacticalProducer {
|
||||
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);
|
||||
let weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction);
|
||||
return imap(icombine(enemies, weapons), ([enemy, weapon]) => new Maneuver(ship, weapon, Target.newFromShip(enemy)));
|
||||
}
|
||||
|
||||
|
@ -29,13 +37,10 @@ module TS.SpaceTac {
|
|||
* Produce random moves inside arena cell
|
||||
*/
|
||||
static produceRandomMoves(ship: Ship, battle: Battle, cells = 10, iterations = 1, random = RandomGenerator.global): TacticalProducer {
|
||||
let engines = ship.listEquipment(SlotType.Engine);
|
||||
if (engines.length == 0) {
|
||||
return IEMPTY;
|
||||
}
|
||||
|
||||
let engines = ifilter(getPlayableActions(ship), action => action instanceof MoveAction);
|
||||
return ichainit(imap(irange(iterations), iteration => {
|
||||
return imap(scanArena(battle, cells, random), target => new Maneuver(ship, engines[0], target))
|
||||
let moves = icombine(engines, scanArena(battle, cells, random));
|
||||
return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target));
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -44,11 +49,11 @@ 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 weapons = ifilter(getPlayableActions(ship), action => action instanceof FireWeaponAction && action.blast > 0);
|
||||
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 candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.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;
|
||||
}
|
||||
|
@ -57,7 +62,7 @@ module TS.SpaceTac {
|
|||
* Produce drone deployments.
|
||||
*/
|
||||
static produceDroneDeployments(ship: Ship, battle: Battle): TacticalProducer {
|
||||
let drones = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof DeployDroneAction);
|
||||
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));
|
||||
}
|
||||
|
@ -94,7 +99,7 @@ module TS.SpaceTac {
|
|||
* Evaluate the damage done to the enemy, between -1 and 1
|
||||
*/
|
||||
static evaluateDamageToEnemy(ship: Ship, battle: Battle, maneuver: Maneuver): number {
|
||||
let action = maneuver.equipment.action;
|
||||
let action = maneuver.action;
|
||||
if (action instanceof FireWeaponAction) {
|
||||
let enemies = imaterialize(battle.ienemies(ship.getPlayer(), true));
|
||||
if (enemies.length == 0) {
|
||||
|
|
Loading…
Reference in a new issue