1
0
Fork 0

Removed Bully AI and fixed AI trying unavailable actions

This commit is contained in:
Michaël Lemaire 2017-05-18 00:07:16 +02:00
parent a3df49ae0b
commit b10725abda
8 changed files with 64 additions and 450 deletions

1
TODO
View file

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

View file

@ -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++) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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