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: targetting in two times, using a draggable target indicator
* 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: allow to play several moves in the same turn (with pauses)
* 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: new duel page with producers/evaluators tweaking
* Map: remove jump links that cross the radius of other systems

View file

@ -113,8 +113,6 @@ module TS.SpaceTac {
if (battle) {
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[] = [];
beforeEach(function () {
spyOn(console, "log").and.stub();
applied = [];
});
it("applies the highest evaluated maneuver", function () {
let ai = new TacticalAI(new Ship(), Timer.synchronous);
ai.evaluators.push(maneuver => (<FixedManeuver>maneuver).score);
ai.producers.push(producer(1, -8, 4));
ai.producers.push(producer(3, 7, 0, 6, 1));
spyOn(ai, "getDefaultProducers").and.returnValue([
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.play();

View file

@ -13,25 +13,20 @@ module TS.SpaceTac {
* As much work as possible is done using iterators, without materializing every possibilities.
*/
export class TacticalAI extends AbstractAI {
producers: TacticalProducer[] = []
evaluators: TacticalEvaluator[] = []
private producers: TacticalProducer[] = []
private evaluators: TacticalEvaluator[] = []
best: Maneuver | null
best_score: number
private best: Maneuver | null
private best_score: number
protected initWork(): void {
protected initWork(delay?: number): void {
this.best = null;
this.best_score = -Infinity;
if (this.producers.length == 0) {
this.setupDefaultProducers();
}
this.producers = this.getDefaultProducers();
this.evaluators = this.getDefaultEvaluators();
if (this.evaluators.length == 0) {
this.setupDefaultEvaluators();
}
this.addWorkItem(() => this.unitWork());
this.addWorkItem(() => this.unitWork(), delay);
}
/**
@ -65,7 +60,7 @@ module TS.SpaceTac {
// Evaluate the 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) {
this.best = maneuver;
this.best_score = score;
@ -76,40 +71,46 @@ module TS.SpaceTac {
}
// Continue or stop
if (this.producers.length > 0 && this.getDuration() < 3000) {
if (this.producers.length > 0 && this.getDuration() < 8000) {
this.addWorkItem(() => this.unitWork());
} else if (this.best) {
console.log("AI maneuver", this.best, this.best_score);
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 = [
TacticalAIHelpers.produceEndTurn,
TacticalAIHelpers.produceDirectShots,
TacticalAIHelpers.produceBlastShots,
TacticalAIHelpers.produceDroneDeployments,
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 evaluators = [
scaled(TacticalAIHelpers.evaluateTurnCost, 1),
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 30),
scaled(TacticalAIHelpers.evaluateTurnCost, 3),
scaled(TacticalAIHelpers.evaluateDamageToEnemy, 20),
scaled(TacticalAIHelpers.evaluateClustering, 8),
scaled(TacticalAIHelpers.evaluatePosition, 1),
scaled(TacticalAIHelpers.evaluateIdling, 5),
]
// 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
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
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 () {

View file

@ -24,6 +24,13 @@ module TS.SpaceTac {
* These are static methods that may be used as base for TacticalAI ruleset.
*/
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.
*/
@ -118,7 +125,7 @@ module TS.SpaceTac {
}
});
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;
} else {
return 0;

View file

@ -186,7 +186,7 @@ module TS.SpaceTac.UI {
let arena = this.battleview.arena.getBoundaries();
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_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);