1
0
Fork 0

Fixed some AI behavior

This commit is contained in:
Michaël Lemaire 2017-05-18 01:03:33 +02:00
parent b10725abda
commit 84e56ffc7d
7 changed files with 47 additions and 35 deletions

6
TODO
View file

@ -41,11 +41,11 @@
* Mobile: display tooltips larger and on the side of screen where the finger is not * Mobile: display tooltips larger and on the side of screen where the finger is not
* Mobile: targetting in two times, using a draggable target indicator * Mobile: targetting in two times, using a draggable target indicator
* AI: apply safety distances to move actions * AI: apply safety distances to move actions
* AI: fix not being able to apply simulated maneuver
* AI: do not always move first, they are defenders * AI: do not always move first, they are defenders
* AI: allow to play several moves in the same turn (with pauses)
* AI: add combination of random small move and actual maneuver, as producer * AI: add combination of random small move and actual maneuver, as producer
* AI: evaluate based on simulated list of effects * AI: evaluate drone area effects
* AI: avoid damaging allies
* AI: produce random blast shots
* AI: consider overheat/cooldown * AI: consider overheat/cooldown
* AI: new duel page with producers/evaluators tweaking * AI: new duel page with producers/evaluators tweaking
* Map: remove jump links that cross the radius of other systems * Map: remove jump links that cross the radius of other systems

View file

@ -113,8 +113,6 @@ module TS.SpaceTac {
if (battle) { if (battle) {
battle.advanceToNextShip(); battle.advanceToNextShip();
} }
} else {
console.error(`${this.name} tries to end turn of another ship`);
} }
} }

View file

@ -18,14 +18,20 @@ module TS.SpaceTac.Specs {
let applied: number[] = []; let applied: number[] = [];
beforeEach(function () { beforeEach(function () {
spyOn(console, "log").and.stub();
applied = []; applied = [];
}); });
it("applies the highest evaluated maneuver", function () { it("applies the highest evaluated maneuver", function () {
let ai = new TacticalAI(new Ship(), Timer.synchronous); let ai = new TacticalAI(new Ship(), Timer.synchronous);
ai.evaluators.push(maneuver => (<FixedManeuver>maneuver).score);
ai.producers.push(producer(1, -8, 4)); spyOn(ai, "getDefaultProducers").and.returnValue([
ai.producers.push(producer(3, 7, 0, 6, 1)); producer(1, -8, 4),
producer(3, 7, 0, 6, 1)
]);
spyOn(ai, "getDefaultEvaluators").and.returnValue([
(maneuver: Maneuver) => (<FixedManeuver>maneuver).score
]);
ai.ship.playing = true; ai.ship.playing = true;
ai.play(); ai.play();

View file

@ -13,25 +13,20 @@ module TS.SpaceTac {
* As much work as possible is done using iterators, without materializing every possibilities. * As much work as possible is done using iterators, without materializing every possibilities.
*/ */
export class TacticalAI extends AbstractAI { export class TacticalAI extends AbstractAI {
producers: TacticalProducer[] = [] private producers: TacticalProducer[] = []
evaluators: TacticalEvaluator[] = [] private evaluators: TacticalEvaluator[] = []
best: Maneuver | null private best: Maneuver | null
best_score: number private best_score: number
protected initWork(): void { protected initWork(delay?: number): void {
this.best = null; this.best = null;
this.best_score = -Infinity; this.best_score = -Infinity;
if (this.producers.length == 0) { this.producers = this.getDefaultProducers();
this.setupDefaultProducers(); this.evaluators = this.getDefaultEvaluators();
}
if (this.evaluators.length == 0) { this.addWorkItem(() => this.unitWork(), delay);
this.setupDefaultEvaluators();
}
this.addWorkItem(() => this.unitWork());
} }
/** /**
@ -65,7 +60,7 @@ module TS.SpaceTac {
// Evaluate the maneuver // Evaluate the maneuver
let score = this.evaluate(maneuver); let score = this.evaluate(maneuver);
//console.log(maneuver, score); //console.debug("AI evaluation", maneuver, score);
if ((Math.abs(score - this.best_score) < 0.0001 && this.random.bool()) || score > this.best_score) { if ((Math.abs(score - this.best_score) < 0.0001 && this.random.bool()) || score > this.best_score) {
this.best = maneuver; this.best = maneuver;
this.best_score = score; this.best_score = score;
@ -76,40 +71,46 @@ module TS.SpaceTac {
} }
// Continue or stop // Continue or stop
if (this.producers.length > 0 && this.getDuration() < 3000) { if (this.producers.length > 0 && this.getDuration() < 8000) {
this.addWorkItem(() => this.unitWork()); this.addWorkItem(() => this.unitWork());
} else if (this.best) { } else if (this.best) {
console.log("AI maneuver", this.best, this.best_score);
this.best.apply(); this.best.apply();
if (this.ship.playing) {
this.initWork(2000);
}
} }
} }
/** /**
* Setup the default set of maneuver producers * Get the default set of maneuver producers
*/ */
private setupDefaultProducers() { getDefaultProducers() {
let producers = [ let producers = [
TacticalAIHelpers.produceEndTurn,
TacticalAIHelpers.produceDirectShots, TacticalAIHelpers.produceDirectShots,
TacticalAIHelpers.produceBlastShots, TacticalAIHelpers.produceBlastShots,
TacticalAIHelpers.produceDroneDeployments, TacticalAIHelpers.produceDroneDeployments,
TacticalAIHelpers.produceRandomMoves, TacticalAIHelpers.produceRandomMoves,
] ]
producers.forEach(producer => this.producers.push(producer(this.ship, this.ship.getBattle() || new Battle()))); return producers.map(producer => producer(this.ship, this.ship.getBattle() || new Battle()));
} }
/** /**
* Setup the default set of maneuver evaluators * Get the default set of maneuver evaluators
*/ */
private setupDefaultEvaluators() { getDefaultEvaluators() {
let scaled = (evaluator: (...args: any[]) => number, factor: number) => (...args: any[]) => factor * evaluator(...args); let scaled = (evaluator: (...args: any[]) => number, factor: number) => (...args: any[]) => factor * evaluator(...args);
let evaluators = [ let evaluators = [
scaled(TacticalAIHelpers.evaluateTurnCost, 1), scaled(TacticalAIHelpers.evaluateTurnCost, 3),
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 30), scaled(TacticalAIHelpers.evaluateDamageToEnemy, 20),
scaled(TacticalAIHelpers.evaluateClustering, 8), scaled(TacticalAIHelpers.evaluateClustering, 8),
scaled(TacticalAIHelpers.evaluatePosition, 1), scaled(TacticalAIHelpers.evaluatePosition, 1),
scaled(TacticalAIHelpers.evaluateIdling, 5), scaled(TacticalAIHelpers.evaluateIdling, 5),
] ]
// TODO evaluator typing is lost // TODO evaluator typing is lost
evaluators.forEach(evaluator => this.evaluators.push((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver))); return evaluators.map(evaluator => ((maneuver: Maneuver) => evaluator(this.ship, this.ship.getBattle(), maneuver)));
} }
} }
} }

View file

@ -144,11 +144,11 @@ module TS.SpaceTac.Specs {
// one enemy loses half-life // one enemy loses half-life
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(180, 0)); maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(180, 0));
expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.25); expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.35);
// one enemy loses half-life, the other one is dead // one enemy loses half-life, the other one is dead
maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(280, 0)); maneuver = new Maneuver(ship, weapon.action, Target.newFromLocation(280, 0));
expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.625); expect(TacticalAIHelpers.evaluateDamageToEnemy(ship, battle, maneuver)).toEqual(0.775);
}); });
it("evaluates ship clustering", function () { it("evaluates ship clustering", function () {

View file

@ -24,6 +24,13 @@ module TS.SpaceTac {
* These are static methods that may be used as base for TacticalAI ruleset. * These are static methods that may be used as base for TacticalAI ruleset.
*/ */
export class TacticalAIHelpers { export class TacticalAIHelpers {
/**
* Produce a turn end.
*/
static produceEndTurn(ship: Ship, battle: Battle): TacticalProducer {
return isingle(new Maneuver(ship, new EndTurnAction(), Target.newFromShip(ship)));
}
/** /**
* Produce all "direct hit" weapon shots. * Produce all "direct hit" weapon shots.
*/ */
@ -118,7 +125,7 @@ module TS.SpaceTac {
} }
}); });
let hp = sum(enemies.map(enemy => enemy.getValue("hull") + enemy.getValue("shield"))); let hp = sum(enemies.map(enemy => enemy.getValue("hull") + enemy.getValue("shield")));
let result = 0.5 * (damage / hp) + 0.5 * (dead / enemies.length); let result = (damage ? 0.2 : 0) + 0.3 * (damage / hp) + (dead ? 0.2 : 0) + 0.3 * (dead / enemies.length);
return result; return result;
} else { } else {
return 0; return 0;

View file

@ -186,7 +186,7 @@ module TS.SpaceTac.UI {
let arena = this.battleview.arena.getBoundaries(); let arena = this.battleview.arena.getBoundaries();
this.effects.position.set( this.effects.position.set(
(this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects.width) : (-this.effects.width * 0.5)), (this.ship.arena_x < 100) ? -35 : ((this.ship.arena_x > arena.width - 100) ? (35 - this.effects.width) : (-this.effects.width * 0.5)),
(this.ship.arena_y < arena.height * 0.5) ? 40 : (-40 - this.effects.height) (this.ship.arena_y < arena.height * 0.9) ? 40 : (-40 - this.effects.height)
); );
this.game.tweens.removeFrom(this.effects); this.game.tweens.removeFrom(this.effects);