From 74debe585d8cd559d2ddf2ebceffe256f07af3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Thu, 19 Jul 2018 15:59:56 +0200 Subject: [PATCH] WIP --- out/aiworker.js | 4 ++-- src/MainUI.ts | 4 +++- src/common | 2 +- src/core/ArenaGrid.ts | 21 +++++++++++++++-- src/core/Battle.ts | 6 ++--- src/core/MoveFireSimulator.ts | 2 +- src/core/ai/Maneuver.ts | 11 ++------- src/core/ai/TacticalAI.ts | 34 ++++++++++++++++----------- src/core/ai/TacticalAIHelpers.spec.ts | 19 +++++++++------ src/core/ai/TacticalAIHelpers.ts | 13 +++++----- tsconfig.json | 2 +- 11 files changed, 70 insertions(+), 48 deletions(-) diff --git a/out/aiworker.js b/out/aiworker.js index ab3d70a..386d119 100644 --- a/out/aiworker.js +++ b/out/aiworker.js @@ -1,9 +1,9 @@ var handler = { get(target, name) { - return new Proxy({}, handler); + return new Proxy(function () { }, handler); } } -var Phaser = new Proxy({}, handler); +var Phaser = new Proxy(function () { }, handler); //var debug = console.log; var debug = function () { }; diff --git a/src/MainUI.ts b/src/MainUI.ts index f12e1b2..8d09611 100644 --- a/src/MainUI.ts +++ b/src/MainUI.ts @@ -23,7 +23,9 @@ if (typeof window != "undefined") { } module TK.SpaceTac { - // Router between game views + /** + * Main class to bootstrap the whole game + */ export class MainUI extends Phaser.Game { // Current game session session: GameSession diff --git a/src/common b/src/common index 8a11307..9b05b53 160000 --- a/src/common +++ b/src/common @@ -1 +1 @@ -Subproject commit 8a11307e3fb1b2c562674a475fa50b8511592bfe +Subproject commit 9b05b53c998b177a49a3dd43542bfa3cb1874af1 diff --git a/src/core/ArenaGrid.ts b/src/core/ArenaGrid.ts index 115fb5b..26ff0ac 100644 --- a/src/core/ArenaGrid.ts +++ b/src/core/ArenaGrid.ts @@ -85,10 +85,10 @@ module TK.SpaceTac { /** * Produce all valid coordinates on the grid inside a rectangular area, starting from a given point */ - iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterator { + iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterable { from = this.snap(from); - let row = (rfrom: IArenaLocation): Iterator => { + let row = (rfrom: IArenaLocation): Iterable => { return ichain( irecur(rfrom, loc => loc.x <= bounds.xmax ? this.right(loc) : null), irecur(this.left(rfrom), loc => loc.x >= bounds.xmin ? this.left(loc) : null), @@ -136,6 +136,23 @@ module TK.SpaceTac { } } + /** + * Square grid + */ + export class SquareGrid extends PixelArenaGrid { + constructor(bounds?: ArenaBounds, unit = 1) { + super(bounds, unit); + } + + getUnit(): number { + return this.unit; + } + + snap(loc: IArenaLocation): IArenaLocation { + return new ArenaLocation(Math.round(loc.x / this.unit) * this.unit, Math.round(loc.y / this.unit) * this.unit); + } + } + /** * Hexagonal arena grid * diff --git a/src/core/Battle.ts b/src/core/Battle.ts index d1e78c9..9a36a61 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -111,7 +111,7 @@ module TK.SpaceTac { /** * Return an iterator over all ships engaged in the battle */ - iships(alive_only = false): Iterator { + iships(alive_only = false): Iterable { let result = ichainit(imap(iarray(this.fleets), fleet => iarray(fleet.ships))); return alive_only ? ifilter(result, ship => ship.alive) : result; } @@ -119,14 +119,14 @@ module TK.SpaceTac { /** * Return an iterator over ships allies of (or owned by) a player */ - iallies(ship: Ship, alive_only = false): Iterator { + iallies(ship: Ship, alive_only = false): Iterable { return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player)); } /** * Return an iterator over ships enemy of a player */ - ienemies(ship: Ship, alive_only = false): Iterator { + ienemies(ship: Ship, alive_only = false): Iterable { return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player)); } diff --git a/src/core/MoveFireSimulator.ts b/src/core/MoveFireSimulator.ts index 470c673..0b73652 100644 --- a/src/core/MoveFireSimulator.ts +++ b/src/core/MoveFireSimulator.ts @@ -75,7 +75,7 @@ module TK.SpaceTac { /** * Get an iterator for scanning a circle */ - scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterator { + scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterable { // TODO If there is a grid, produce only grid coordinates let rcount = nr ? 1 / (nr - 1) : 0; return ichainit(imap(istep(0, irepeat(rcount, nr - 1)), r => { diff --git a/src/core/ai/Maneuver.ts b/src/core/ai/Maneuver.ts index b691fd6..e7238a5 100644 --- a/src/core/ai/Maneuver.ts +++ b/src/core/ai/Maneuver.ts @@ -49,21 +49,14 @@ module TK.SpaceTac { * Returns true if the maneuver has at least one part doable */ isPossible(): boolean { - return any(this.simulation.parts, part => part.possible); - } - - /** - * Returns true if the maneuver cannot be fully done this turn - */ - isIncomplete(): boolean { - return (this.simulation.need_move && !this.simulation.can_end_move) || (this.simulation.need_fire && !this.simulation.can_fire); + return this.simulation.status == MoveFireStatus.OK; } /** * Returns true if another maneuver could be done next on the same ship */ mayContinue(): boolean { - return this.ship.playing && !this.isIncomplete() && !(this.action instanceof EndTurnAction); + return this.ship.playing && !(this.action instanceof EndTurnAction); } /** diff --git a/src/core/ai/TacticalAI.ts b/src/core/ai/TacticalAI.ts index f8ce1c9..aef5eee 100644 --- a/src/core/ai/TacticalAI.ts +++ b/src/core/ai/TacticalAI.ts @@ -2,7 +2,7 @@ /// module TK.SpaceTac { - export type TacticalProducer = Iterator; + export type TacticalProducer = Iterable; export type TacticalEvaluator = (maneuver: Maneuver) => number; /** @@ -14,17 +14,23 @@ module TK.SpaceTac { */ export class TacticalAI extends AbstractAI { private producers: TacticalProducer[] = [] + private work: Iterator = IATEND private evaluators: TacticalEvaluator[] = [] private best: Maneuver | null = null private best_score = 0 + private produced = 0 + private evaluated = 0 protected initWork(): void { this.best = null; this.best_score = -Infinity; this.producers = this.getDefaultProducers(); + this.work = ialternate(this.producers)[Symbol.iterator](); this.evaluators = this.getDefaultEvaluators(); + this.produced = 0; + this.evaluated = 0; if (this.debug) { console.log("AI started", this.name, this.ship.name); @@ -32,22 +38,18 @@ module TK.SpaceTac { } protected doWorkUnit(): boolean { - if (this.producers.length > 0 && this.getDuration() < 8000) { - // Produce a maneuver - let maneuver: Maneuver | null = null; - let producer = this.producers.shift(); - if (producer) { - [maneuver, producer] = producer(); - } - - if (maneuver && maneuver.isPossible()) { - if (producer) { - this.producers.push(producer); - } + let state = this.work.next(); + if (!state.done && this.getDuration() < 8000) { + let maneuver = state.value; + this.produced++; + if (maneuver.isPossible()) { // Evaluate the maneuver let score = this.evaluate(maneuver); - //console.debug("AI evaluation", maneuver, score); + this.evaluated++; + if (this.debug) { + 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; @@ -56,6 +58,10 @@ module TK.SpaceTac { return true; } else if (this.best) { + if (!state.done) { + console.warn(`AI did not analyze every possible maneuver (${this.produced} produced, ${this.evaluated} evaluated)`); + } + // Choose the best maneuver so far let best_maneuver = this.best; if (this.debug) { diff --git a/src/core/ai/TacticalAIHelpers.spec.ts b/src/core/ai/TacticalAIHelpers.spec.ts index a0b7b6e..24ddba3 100644 --- a/src/core/ai/TacticalAIHelpers.spec.ts +++ b/src/core/ai/TacticalAIHelpers.spec.ts @@ -16,17 +16,20 @@ module TK.SpaceTac.Specs { let weapon1 = TestTools.addWeapon(ship0a, 10); let weapon2 = TestTools.addWeapon(ship0a, 15); result = imaterialize(TacticalAIHelpers.produceTriggerActionsOnShip(ship0a, battle)); - check.equals(result.length, 4); + check.equals(result.length, 8); + check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship0a))); + check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship0b))); check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship1a))); check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship1b))); + check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship0a))); + check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship0b))); check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship1a))); check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship1b))); }); test.case("produces random moves inside a grid", check => { let battle = new Battle(); - battle.width = 100; - battle.height = 100; + battle.grid = new SquareGrid({ xmin: 0, ymin: 0, xmax: 20, ymax: 10 }, 10); let ship = battle.fleets[0].addShip(); TestTools.setShipModel(ship, 100, 0, 10); @@ -39,10 +42,12 @@ module TK.SpaceTac.Specs { result = imaterialize(TacticalAIHelpers.produceMoveActions(ship, battle)); check.equals(result, [ - new Maneuver(ship, engine, Target.newFromLocation(25, 25)), - new Maneuver(ship, engine, Target.newFromLocation(75, 25)), - new Maneuver(ship, engine, Target.newFromLocation(25, 75)), - new Maneuver(ship, engine, Target.newFromLocation(75, 75)), + new Maneuver(ship, engine, Target.newFromLocation(0, 0)), + new Maneuver(ship, engine, Target.newFromLocation(10, 0)), + new Maneuver(ship, engine, Target.newFromLocation(20, 0)), + new Maneuver(ship, engine, Target.newFromLocation(0, 10)), + new Maneuver(ship, engine, Target.newFromLocation(10, 10)), + new Maneuver(ship, engine, Target.newFromLocation(20, 10)), ]); }); diff --git a/src/core/ai/TacticalAIHelpers.ts b/src/core/ai/TacticalAIHelpers.ts index 269a28c..e5cf9f6 100644 --- a/src/core/ai/TacticalAIHelpers.ts +++ b/src/core/ai/TacticalAIHelpers.ts @@ -2,7 +2,7 @@ module TK.SpaceTac { /** * Get a list of all playable actions (like the actionbar for player) for a ship */ - function getPlayableActions(ship: Ship, type_class: { new(...args: any[]): T }): Iterator { + function getPlayableActions(ship: Ship, type_class: { new(...args: any[]): T }): Iterable { let actions = cfilter(ship.actions.listAll(), type_class); return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship)); } @@ -47,9 +47,8 @@ module TK.SpaceTac { /** * Iterator of a list of arena coordinates */ - static scanArena(battle: Battle): Iterator { - let start = { x: battle.width / 2, y: battle.height / 2 }; - return imap(battle.grid.iterate(start), loc => Target.newFromLocation(loc.x, loc.y)); + static scanArena(battle: Battle, from: IArenaLocation): Iterable { + return imap(battle.grid.iterate(from), loc => Target.newFromLocation(loc.x, loc.y)); } /** @@ -64,7 +63,7 @@ module TK.SpaceTac { */ static produceMoveActions(ship: Ship, battle: Battle): TacticalProducer { let engines = getPlayableActions(ship, MoveAction); - let moves = icombine(engines, TacticalAIHelpers.scanArena(battle)); + let moves = icombine(engines, TacticalAIHelpers.scanArena(battle, ship.location)); return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target)); } @@ -82,7 +81,7 @@ module TK.SpaceTac { */ static produceTriggerActionsInSpace(ship: Ship, battle: Battle): TacticalProducer { let weapons = ifilter(getPlayableActions(ship, TriggerAction), action => action.getTargettingMode(ship) == ActionTargettingMode.SPACE); - let candidates = ifilter(icombine(weapons, TacticalAIHelpers.scanArena(battle)), ([weapon, location]) => weapon.getEffects(ship, location).length > 0); + let candidates = ifilter(icombine(weapons, TacticalAIHelpers.scanArena(battle, ship.location)), ([weapon, location]) => weapon.getEffects(ship, location).length > 0); let result = imap(candidates, ([weapon, location]) => new Maneuver(ship, weapon, location)); return result; } @@ -97,7 +96,7 @@ module TK.SpaceTac { let self_maneuvers = imap(self_toggles, toggle => new Maneuver(ship, toggle, Target.newFromShip(ship))); let distant_toggles = ifilter(toggles, toggle => contains([ActionTargettingMode.SPACE, ActionTargettingMode.SURROUNDINGS], toggle.getTargettingMode(ship))); - let grid = TacticalAIHelpers.scanArena(battle); + let grid = TacticalAIHelpers.scanArena(battle, ship.location); let distant_maneuvers = imap(icombine(grid, distant_toggles), ([location, toggle]) => new Maneuver(ship, toggle, location)); return ichain(self_maneuvers, distant_maneuvers); diff --git a/tsconfig.json b/tsconfig.json index cdbfaf5..5a2418c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "dom", "es6" ], - "target": "es5" + "target": "es6" }, "include": [ "src/**/*.ts"