From 84e56ffc7d9658749ae78b79d862ef3fa7bd4c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Thu, 18 May 2017 01:03:33 +0200 Subject: [PATCH] Fixed some AI behavior --- TODO | 6 ++-- src/core/ai/AbstractAI.ts | 2 -- src/core/ai/TacticalAI.spec.ts | 12 +++++-- src/core/ai/TacticalAI.ts | 47 ++++++++++++++------------- src/core/ai/TacticalAIHelpers.spec.ts | 4 +-- src/core/ai/TacticalAIHelpers.ts | 9 ++++- src/ui/battle/ArenaShip.ts | 2 +- 7 files changed, 47 insertions(+), 35 deletions(-) diff --git a/TODO b/TODO index 77bfa3a..4269720 100644 --- a/TODO +++ b/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 diff --git a/src/core/ai/AbstractAI.ts b/src/core/ai/AbstractAI.ts index 193d649..f583032 100644 --- a/src/core/ai/AbstractAI.ts +++ b/src/core/ai/AbstractAI.ts @@ -113,8 +113,6 @@ module TS.SpaceTac { if (battle) { battle.advanceToNextShip(); } - } else { - console.error(`${this.name} tries to end turn of another ship`); } } diff --git a/src/core/ai/TacticalAI.spec.ts b/src/core/ai/TacticalAI.spec.ts index 8918068..44f64e5 100644 --- a/src/core/ai/TacticalAI.spec.ts +++ b/src/core/ai/TacticalAI.spec.ts @@ -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 => (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) => (maneuver).score + ]); ai.ship.playing = true; ai.play(); diff --git a/src/core/ai/TacticalAI.ts b/src/core/ai/TacticalAI.ts index f92050c..a233ea4 100644 --- a/src/core/ai/TacticalAI.ts +++ b/src/core/ai/TacticalAI.ts @@ -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))); } } } diff --git a/src/core/ai/TacticalAIHelpers.spec.ts b/src/core/ai/TacticalAIHelpers.spec.ts index 5e9347a..5194d51 100644 --- a/src/core/ai/TacticalAIHelpers.spec.ts +++ b/src/core/ai/TacticalAIHelpers.spec.ts @@ -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 () { diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index bdaa2a5..88cb86a 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -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; diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 28b10e1..4436196 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -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);