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);
|
||||
}, 2000);
|
||||
} else if (this.playing_ship.getPlayer().ai) {
|
||||
// TODO If the ship is managed by an AI, let it get to work
|
||||
setTimeout(() => {
|
||||
this.advanceToNextShip(log);
|
||||
}, 2000);
|
||||
// If the ship is managed by an AI, let it get to work
|
||||
this.playing_ship.getPlayer().ai.playShip(this.playing_ship);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -255,6 +255,19 @@ module SpaceTac.Game {
|
|||
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
|
||||
getEquipmentCount(): number {
|
||||
var result = 0;
|
||||
|
|
|
@ -9,9 +9,90 @@ module SpaceTac.Game.AI {
|
|||
// The fleet controlled by this AI
|
||||
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) {
|
||||
this.fleet = fleet;
|
||||
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 {
|
||||
"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
|
||||
export class BullyAI extends AbstractAI {
|
||||
constructor(fleet: 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