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: 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
|
||||||
|
|
|
@ -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`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue