Started work on Bully A.I.
This commit is contained in:
parent
7ad81617f3
commit
6d9093061d
|
@ -182,10 +182,8 @@ module SpaceTac.Game {
|
||||||
this.advanceToNextShip(log);
|
this.advanceToNextShip(log);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else if (this.playing_ship.getPlayer().ai) {
|
} else if (this.playing_ship.getPlayer().ai) {
|
||||||
// TODO If the ship is managed by an AI, let it get to work
|
// If the ship is managed by an AI, let it get to work
|
||||||
setTimeout(() => {
|
this.playing_ship.getPlayer().ai.playShip(this.playing_ship);
|
||||||
this.advanceToNextShip(log);
|
|
||||||
}, 2000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -255,6 +255,19 @@ module SpaceTac.Game {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List all attached equipments of a given type (all types if null)
|
||||||
|
listEquipment(slottype: SlotType = null): Equipment[] {
|
||||||
|
var result: Equipment[] = [];
|
||||||
|
|
||||||
|
this.slots.forEach((slot: Slot) => {
|
||||||
|
if (slot.type === slottype && slot.attached) {
|
||||||
|
result.push(slot.attached);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the number of attached equipments
|
// Get the number of attached equipments
|
||||||
getEquipmentCount(): number {
|
getEquipmentCount(): number {
|
||||||
var result = 0;
|
var result = 0;
|
||||||
|
|
|
@ -9,9 +9,90 @@ module SpaceTac.Game.AI {
|
||||||
// The fleet controlled by this AI
|
// The fleet controlled by this AI
|
||||||
fleet: Fleet;
|
fleet: Fleet;
|
||||||
|
|
||||||
|
// Current ship being played
|
||||||
|
ship: Ship;
|
||||||
|
|
||||||
|
// Set this to false to force synchronous behavior (playShip will block until finished)
|
||||||
|
async: boolean;
|
||||||
|
|
||||||
|
// Time at which work as started
|
||||||
|
private started: number;
|
||||||
|
|
||||||
|
// Queue of work items to process
|
||||||
|
// Work items will be called successively, leaving time for other processing between them.
|
||||||
|
// So work items should always be as short as possible.
|
||||||
|
// When the queue is empty, the ship will end its turn.
|
||||||
|
private workqueue: Function[];
|
||||||
|
|
||||||
constructor(fleet: Fleet) {
|
constructor(fleet: Fleet) {
|
||||||
this.fleet = fleet;
|
this.fleet = fleet;
|
||||||
this.battle = fleet.battle;
|
this.battle = fleet.battle;
|
||||||
|
this.async = true;
|
||||||
|
this.workqueue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play a ship turn
|
||||||
|
// This will start asynchronous work. The AI will then call action methods, then advanceToNextShip to
|
||||||
|
// indicate it has finished.
|
||||||
|
playShip(ship: Ship): void {
|
||||||
|
this.ship = ship;
|
||||||
|
this.workqueue = [];
|
||||||
|
this.started = (new Date()).getTime();
|
||||||
|
this.initWork();
|
||||||
|
this.processNextWorkItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a work item to the work queue
|
||||||
|
addWorkItem(item: Function): void {
|
||||||
|
this.workqueue.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially fill the work queue.
|
||||||
|
// Subclasses MUST reimplement this and call addWorkItem to add work to do.
|
||||||
|
protected initWork(): void {
|
||||||
|
// Abstract method
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the next work item
|
||||||
|
private processNextWorkItem(): void {
|
||||||
|
if (this.workqueue.length > 0) {
|
||||||
|
// Take the first item
|
||||||
|
var item = this.workqueue.shift();
|
||||||
|
this.processWorkItem(item);
|
||||||
|
} else {
|
||||||
|
this.endTurn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process one work item, then call processNextWorkItem
|
||||||
|
private processWorkItem(item: Function): void {
|
||||||
|
// Process current item
|
||||||
|
item();
|
||||||
|
|
||||||
|
// On to the next item
|
||||||
|
if (this.async) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.processNextWorkItem();
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
this.processNextWorkItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when we want to end the ship turn
|
||||||
|
private endTurn(): void {
|
||||||
|
if (this.async) {
|
||||||
|
var duration = (new Date()).getTime() - this.started;
|
||||||
|
if (duration < 2000) {
|
||||||
|
// Delay, as to make the AI not too fast to play
|
||||||
|
setTimeout(() => {
|
||||||
|
this.endTurn();
|
||||||
|
}, 2000 - duration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.ship = null;
|
||||||
|
this.battle.advanceToNextShip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,102 @@
|
||||||
module SpaceTac.Game.AI {
|
module SpaceTac.Game.AI {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
export class BullyMove {
|
||||||
|
// Position to move to, before firing
|
||||||
|
move_to: Target;
|
||||||
|
|
||||||
|
// Weapon to use
|
||||||
|
weapon: Equipment;
|
||||||
|
|
||||||
|
// Ship to target
|
||||||
|
target: Ship;
|
||||||
|
}
|
||||||
|
|
||||||
// Basic Artificial Intelligence, with a tendency to move forward and shoot the nearest enemy
|
// Basic Artificial Intelligence, with a tendency to move forward and shoot the nearest enemy
|
||||||
export class BullyAI extends AbstractAI {
|
export class BullyAI extends AbstractAI {
|
||||||
constructor(fleet: Fleet) {
|
constructor(fleet: Fleet) {
|
||||||
super(fleet);
|
super(fleet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List all enemy ships that can be a target
|
||||||
|
listAllEnemies(): Ship[] {
|
||||||
|
var result: Ship[] = [];
|
||||||
|
|
||||||
|
this.fleet.battle.play_order.forEach((ship: Ship) => {
|
||||||
|
if (ship.getPlayer() !== this.ship.getPlayer()) {
|
||||||
|
result.push(ship);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all weapons
|
||||||
|
listAllWeapons(): Equipment[] {
|
||||||
|
return this.ship.listEquipment(SlotType.Weapon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all available "moves" for the playing ship
|
||||||
|
listAllMoves(): BullyMove[] {
|
||||||
|
var result: BullyMove[] = [];
|
||||||
|
|
||||||
|
var enemies = this.listAllEnemies();
|
||||||
|
var weapons = this.listAllWeapons();
|
||||||
|
|
||||||
|
enemies.forEach((ship: Ship) => {
|
||||||
|
weapons.forEach((weapon: Equipment) => {
|
||||||
|
var move = this.checkBullyMove(ship, weapon);
|
||||||
|
if (move) {
|
||||||
|
result.push(move);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a weapon can be used against an enemy
|
||||||
|
// Returns the BullyMove, or null if impossible to fire
|
||||||
|
checkBullyMove(enemy: Ship, weapon: Equipment): BullyMove {
|
||||||
|
// Check if enemy in range
|
||||||
|
var target = Target.newFromShip(enemy);
|
||||||
|
var distance = target.getDistanceTo(Target.newFromShip(this.ship));
|
||||||
|
var move: Target;
|
||||||
|
var remaining_ap = this.ship.ap_current.current;
|
||||||
|
if (distance <= weapon.distance) {
|
||||||
|
// No need to move
|
||||||
|
move = null;
|
||||||
|
} else {
|
||||||
|
// Move to be in range, using first engine
|
||||||
|
var engines = this.ship.listEquipment(SlotType.Engine);
|
||||||
|
if (engines.length === 0) {
|
||||||
|
// No engine available to move
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var engine = engines[0];
|
||||||
|
var move_distance = distance - weapon.distance;
|
||||||
|
var move_ap = engine.ap_usage * move_distance;
|
||||||
|
if (move_ap > remaining_ap) {
|
||||||
|
// Not enough AP to move in range
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
move = target.constraintInRange(this.ship.arena_x, this.ship.arena_y, move_distance);
|
||||||
|
remaining_ap -= move_ap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fire
|
||||||
|
if (weapon.ap_usage > remaining_ap) {
|
||||||
|
// Not enough AP to fire
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var result = new BullyMove();
|
||||||
|
result.move_to = move;
|
||||||
|
result.target = enemy;
|
||||||
|
result.weapon = weapon;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
153
src/scripts/game/ai/specs/BullyAI.spec.ts
Normal file
153
src/scripts/game/ai/specs/BullyAI.spec.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/// <reference path="../../../definitions/jasmine.d.ts"/>
|
||||||
|
|
||||||
|
module SpaceTac.Game.AI.Specs {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
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"));
|
||||||
|
|
||||||
|
var random = new RandomGenerator(0, 0.5, 1);
|
||||||
|
battle.throwInitiative(random);
|
||||||
|
|
||||||
|
var ai = new BullyAI(battle.fleets[0]);
|
||||||
|
ai.ship = battle.fleets[0].ships[0];
|
||||||
|
|
||||||
|
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.fleet);
|
||||||
|
ai.ship = ship;
|
||||||
|
|
||||||
|
var result = ai.listAllWeapons();
|
||||||
|
expect(result.length).toBe(0);
|
||||||
|
|
||||||
|
var weapon1 = new Equipment(SlotType.Weapon);
|
||||||
|
ai.ship.addSlot(SlotType.Weapon).attach(weapon1);
|
||||||
|
var weapon2 = new Equipment(SlotType.Weapon);
|
||||||
|
ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
|
||||||
|
ai.ship.addSlot(SlotType.Shield).attach(new Equipment(SlotType.Shield));
|
||||||
|
|
||||||
|
result = ai.listAllWeapons();
|
||||||
|
expect(result.length).toBe(2);
|
||||||
|
expect(result[0]).toBe(weapon1);
|
||||||
|
expect(result[1]).toBe(weapon2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("checks a firing possibility", function () {
|
||||||
|
var ship = new Ship();
|
||||||
|
var engine = new Equipment(SlotType.Engine);
|
||||||
|
engine.ap_usage = 3;
|
||||||
|
ship.addSlot(SlotType.Engine).attach(engine);
|
||||||
|
ship.ap_current.setMaximal(10);
|
||||||
|
ship.ap_current.set(8);
|
||||||
|
var enemy = new Ship();
|
||||||
|
var ai = new BullyAI(ship.fleet);
|
||||||
|
ai.ship = ship;
|
||||||
|
var weapon = new Equipment(SlotType.Weapon);
|
||||||
|
weapon.ap_usage = 2;
|
||||||
|
weapon.distance = 3;
|
||||||
|
|
||||||
|
// enemy in range, the ship can fire without moving
|
||||||
|
ship.ap_current.set(8);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 3;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
var result = ai.checkBullyMove(enemy, weapon);
|
||||||
|
expect(result.move_to).toBeNull();
|
||||||
|
expect(result.target).toBe(enemy);
|
||||||
|
expect(result.weapon).toBe(weapon);
|
||||||
|
|
||||||
|
// enemy out of range, but moving can bring it in range
|
||||||
|
ship.ap_current.set(8);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 6;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
result = ai.checkBullyMove(enemy, weapon);
|
||||||
|
expect(result.move_to).toEqual(Target.newFromLocation(3, 0));
|
||||||
|
expect(result.target).toBe(enemy);
|
||||||
|
expect(result.weapon).toBe(weapon);
|
||||||
|
|
||||||
|
// enemy totally out of range
|
||||||
|
ship.ap_current.set(8);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 30;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
result = ai.checkBullyMove(enemy, weapon);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
// enemy in range but not enough AP to fire
|
||||||
|
ship.ap_current.set(1);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 3;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
result = ai.checkBullyMove(enemy, weapon);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
// can move in range of enemy, but not enough AP to fire
|
||||||
|
ship.ap_current.set(7);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 6;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
result = ai.checkBullyMove(enemy, weapon);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
|
||||||
|
// no engine, can't move
|
||||||
|
ship.slots[0].attached.detach();
|
||||||
|
ship.ap_current.set(8);
|
||||||
|
ship.arena_x = 1;
|
||||||
|
ship.arena_y = 0;
|
||||||
|
enemy.arena_x = 6;
|
||||||
|
enemy.arena_y = 0;
|
||||||
|
result = ai.checkBullyMove(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 RandomGenerator(1, 0.5, 0));
|
||||||
|
|
||||||
|
var ai = new BullyAI(ship1.fleet);
|
||||||
|
ai.ship = ship1;
|
||||||
|
|
||||||
|
var result = ai.listAllMoves();
|
||||||
|
expect(result.length).toBe(0);
|
||||||
|
|
||||||
|
var weapon1 = new Equipment(SlotType.Weapon);
|
||||||
|
weapon1.distance = 50;
|
||||||
|
weapon1.ap_usage = 1;
|
||||||
|
ai.ship.addSlot(SlotType.Weapon).attach(weapon1);
|
||||||
|
var weapon2 = new Equipment(SlotType.Weapon);
|
||||||
|
weapon2.distance = 10;
|
||||||
|
weapon2.ap_usage = 1;
|
||||||
|
ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
|
||||||
|
|
||||||
|
ai.ship.ap_current.setMaximal(10);
|
||||||
|
ai.ship.ap_current.set(8);
|
||||||
|
|
||||||
|
result = ai.listAllMoves();
|
||||||
|
expect(result.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue