1
0
Fork 0

Started work on Bully A.I.

This commit is contained in:
Michaël Lemaire 2015-02-17 01:00:00 +01:00
parent 7ad81617f3
commit 6d9093061d
5 changed files with 341 additions and 4 deletions

View file

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

View file

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

View file

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

View file

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

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