Fixed some AI behavior
This commit is contained in:
parent
b10725abda
commit
84e56ffc7d
6
TODO
6
TODO
|
@ -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
|
||||
|
|
|
@ -113,8 +113,6 @@ module TS.SpaceTac {
|
|||
if (battle) {
|
||||
battle.advanceToNextShip();
|
||||
}
|
||||
} else {
|
||||
console.error(`${this.name} tries to end turn of another ship`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue