1
0
Fork 0
This commit is contained in:
Michaël Lemaire 2018-07-19 15:59:56 +02:00
parent 1a27c99225
commit 74debe585d
11 changed files with 70 additions and 48 deletions

View file

@ -1,9 +1,9 @@
var handler = { var handler = {
get(target, name) { 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 = console.log;
var debug = function () { }; var debug = function () { };

View file

@ -23,7 +23,9 @@ if (typeof window != "undefined") {
} }
module TK.SpaceTac { module TK.SpaceTac {
// Router between game views /**
* Main class to bootstrap the whole game
*/
export class MainUI extends Phaser.Game { export class MainUI extends Phaser.Game {
// Current game session // Current game session
session: GameSession session: GameSession

@ -1 +1 @@
Subproject commit 8a11307e3fb1b2c562674a475fa50b8511592bfe Subproject commit 9b05b53c998b177a49a3dd43542bfa3cb1874af1

View file

@ -85,10 +85,10 @@ module TK.SpaceTac {
/** /**
* Produce all valid coordinates on the grid inside a rectangular area, starting from a given point * Produce all valid coordinates on the grid inside a rectangular area, starting from a given point
*/ */
iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterator<IArenaLocation> { iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterable<IArenaLocation> {
from = this.snap(from); from = this.snap(from);
let row = (rfrom: IArenaLocation): Iterator<IArenaLocation> => { let row = (rfrom: IArenaLocation): Iterable<IArenaLocation> => {
return ichain( return ichain(
irecur(rfrom, loc => loc.x <= bounds.xmax ? this.right(loc) : null), irecur(rfrom, loc => loc.x <= bounds.xmax ? this.right(loc) : null),
irecur(this.left(rfrom), loc => loc.x >= bounds.xmin ? this.left(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 * Hexagonal arena grid
* *

View file

@ -111,7 +111,7 @@ module TK.SpaceTac {
/** /**
* Return an iterator over all ships engaged in the battle * Return an iterator over all ships engaged in the battle
*/ */
iships(alive_only = false): Iterator<Ship> { iships(alive_only = false): Iterable<Ship> {
let result = ichainit(imap(iarray(this.fleets), fleet => iarray(fleet.ships))); let result = ichainit(imap(iarray(this.fleets), fleet => iarray(fleet.ships)));
return alive_only ? ifilter(result, ship => ship.alive) : result; 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 * Return an iterator over ships allies of (or owned by) a player
*/ */
iallies(ship: Ship, alive_only = false): Iterator<Ship> { iallies(ship: Ship, alive_only = false): Iterable<Ship> {
return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player)); return ifilter(this.iships(alive_only), iship => iship.fleet.player.is(ship.fleet.player));
} }
/** /**
* Return an iterator over ships enemy of a player * Return an iterator over ships enemy of a player
*/ */
ienemies(ship: Ship, alive_only = false): Iterator<Ship> { ienemies(ship: Ship, alive_only = false): Iterable<Ship> {
return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player)); return ifilter(this.iships(alive_only), iship => !iship.fleet.player.is(ship.fleet.player));
} }

View file

@ -75,7 +75,7 @@ module TK.SpaceTac {
/** /**
* Get an iterator for scanning a circle * Get an iterator for scanning a circle
*/ */
scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterator<Target> { scanCircle(x: number, y: number, radius: number, nr = 6, na = 30): Iterable<Target> {
// TODO If there is a grid, produce only grid coordinates // TODO If there is a grid, produce only grid coordinates
let rcount = nr ? 1 / (nr - 1) : 0; let rcount = nr ? 1 / (nr - 1) : 0;
return ichainit(imap(istep(0, irepeat(rcount, nr - 1)), r => { return ichainit(imap(istep(0, irepeat(rcount, nr - 1)), r => {

View file

@ -49,21 +49,14 @@ module TK.SpaceTac {
* Returns true if the maneuver has at least one part doable * Returns true if the maneuver has at least one part doable
*/ */
isPossible(): boolean { isPossible(): boolean {
return any(this.simulation.parts, part => part.possible); return this.simulation.status == MoveFireStatus.OK;
}
/**
* 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);
} }
/** /**
* Returns true if another maneuver could be done next on the same ship * Returns true if another maneuver could be done next on the same ship
*/ */
mayContinue(): boolean { mayContinue(): boolean {
return this.ship.playing && !this.isIncomplete() && !(this.action instanceof EndTurnAction); return this.ship.playing && !(this.action instanceof EndTurnAction);
} }
/** /**

View file

@ -2,7 +2,7 @@
/// <reference path="Maneuver.ts"/> /// <reference path="Maneuver.ts"/>
module TK.SpaceTac { module TK.SpaceTac {
export type TacticalProducer = Iterator<Maneuver>; export type TacticalProducer = Iterable<Maneuver>;
export type TacticalEvaluator = (maneuver: Maneuver) => number; export type TacticalEvaluator = (maneuver: Maneuver) => number;
/** /**
@ -14,17 +14,23 @@ module TK.SpaceTac {
*/ */
export class TacticalAI extends AbstractAI { export class TacticalAI extends AbstractAI {
private producers: TacticalProducer[] = [] private producers: TacticalProducer[] = []
private work: Iterator<Maneuver> = IATEND
private evaluators: TacticalEvaluator[] = [] private evaluators: TacticalEvaluator[] = []
private best: Maneuver | null = null private best: Maneuver | null = null
private best_score = 0 private best_score = 0
private produced = 0
private evaluated = 0
protected initWork(): void { protected initWork(): void {
this.best = null; this.best = null;
this.best_score = -Infinity; this.best_score = -Infinity;
this.producers = this.getDefaultProducers(); this.producers = this.getDefaultProducers();
this.work = ialternate(this.producers)[Symbol.iterator]();
this.evaluators = this.getDefaultEvaluators(); this.evaluators = this.getDefaultEvaluators();
this.produced = 0;
this.evaluated = 0;
if (this.debug) { if (this.debug) {
console.log("AI started", this.name, this.ship.name); console.log("AI started", this.name, this.ship.name);
@ -32,22 +38,18 @@ module TK.SpaceTac {
} }
protected doWorkUnit(): boolean { protected doWorkUnit(): boolean {
if (this.producers.length > 0 && this.getDuration() < 8000) { let state = this.work.next();
// 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);
}
if (!state.done && this.getDuration() < 8000) {
let maneuver = state.value;
this.produced++;
if (maneuver.isPossible()) {
// Evaluate the maneuver // Evaluate the maneuver
let score = this.evaluate(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) { 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;
@ -56,6 +58,10 @@ module TK.SpaceTac {
return true; return true;
} else if (this.best) { } 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 // Choose the best maneuver so far
let best_maneuver = this.best; let best_maneuver = this.best;
if (this.debug) { if (this.debug) {

View file

@ -16,17 +16,20 @@ module TK.SpaceTac.Specs {
let weapon1 = TestTools.addWeapon(ship0a, 10); let weapon1 = TestTools.addWeapon(ship0a, 10);
let weapon2 = TestTools.addWeapon(ship0a, 15); let weapon2 = TestTools.addWeapon(ship0a, 15);
result = imaterialize(TacticalAIHelpers.produceTriggerActionsOnShip(ship0a, battle)); 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(ship1a)));
check.contains(result, new Maneuver(ship0a, weapon1, Target.newFromShip(ship1b))); 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(ship1a)));
check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship1b))); check.contains(result, new Maneuver(ship0a, weapon2, Target.newFromShip(ship1b)));
}); });
test.case("produces random moves inside a grid", check => { test.case("produces random moves inside a grid", check => {
let battle = new Battle(); let battle = new Battle();
battle.width = 100; battle.grid = new SquareGrid({ xmin: 0, ymin: 0, xmax: 20, ymax: 10 }, 10);
battle.height = 100;
let ship = battle.fleets[0].addShip(); let ship = battle.fleets[0].addShip();
TestTools.setShipModel(ship, 100, 0, 10); TestTools.setShipModel(ship, 100, 0, 10);
@ -39,10 +42,12 @@ module TK.SpaceTac.Specs {
result = imaterialize(TacticalAIHelpers.produceMoveActions(ship, battle)); result = imaterialize(TacticalAIHelpers.produceMoveActions(ship, battle));
check.equals(result, [ check.equals(result, [
new Maneuver(ship, engine, Target.newFromLocation(25, 25)), new Maneuver(ship, engine, Target.newFromLocation(0, 0)),
new Maneuver(ship, engine, Target.newFromLocation(75, 25)), new Maneuver(ship, engine, Target.newFromLocation(10, 0)),
new Maneuver(ship, engine, Target.newFromLocation(25, 75)), new Maneuver(ship, engine, Target.newFromLocation(20, 0)),
new Maneuver(ship, engine, Target.newFromLocation(75, 75)), new Maneuver(ship, engine, Target.newFromLocation(0, 10)),
new Maneuver(ship, engine, Target.newFromLocation(10, 10)),
new Maneuver(ship, engine, Target.newFromLocation(20, 10)),
]); ]);
}); });

View file

@ -2,7 +2,7 @@ module TK.SpaceTac {
/** /**
* Get a list of all playable actions (like the actionbar for player) for a ship * Get a list of all playable actions (like the actionbar for player) for a ship
*/ */
function getPlayableActions<T extends BaseAction>(ship: Ship, type_class: { new(...args: any[]): T }): Iterator<T> { function getPlayableActions<T extends BaseAction>(ship: Ship, type_class: { new(...args: any[]): T }): Iterable<T> {
let actions = cfilter(ship.actions.listAll(), type_class); let actions = cfilter(ship.actions.listAll(), type_class);
return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship)); return ifilter(iarray(actions), action => !action.checkCannotBeApplied(ship));
} }
@ -47,9 +47,8 @@ module TK.SpaceTac {
/** /**
* Iterator of a list of arena coordinates * Iterator of a list of arena coordinates
*/ */
static scanArena(battle: Battle): Iterator<Target> { static scanArena(battle: Battle, from: IArenaLocation): Iterable<Target> {
let start = { x: battle.width / 2, y: battle.height / 2 }; return imap(battle.grid.iterate(from), loc => Target.newFromLocation(loc.x, loc.y));
return imap(battle.grid.iterate(start), loc => Target.newFromLocation(loc.x, loc.y));
} }
/** /**
@ -64,7 +63,7 @@ module TK.SpaceTac {
*/ */
static produceMoveActions(ship: Ship, battle: Battle): TacticalProducer { static produceMoveActions(ship: Ship, battle: Battle): TacticalProducer {
let engines = getPlayableActions(ship, MoveAction); 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)); return imap(moves, ([engine, target]) => new Maneuver(ship, engine, target));
} }
@ -82,7 +81,7 @@ module TK.SpaceTac {
*/ */
static produceTriggerActionsInSpace(ship: Ship, battle: Battle): TacticalProducer { static produceTriggerActionsInSpace(ship: Ship, battle: Battle): TacticalProducer {
let weapons = ifilter(getPlayableActions(ship, TriggerAction), action => action.getTargettingMode(ship) == ActionTargettingMode.SPACE); 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)); let result = imap(candidates, ([weapon, location]) => new Maneuver(ship, weapon, location));
return result; return result;
} }
@ -97,7 +96,7 @@ module TK.SpaceTac {
let self_maneuvers = imap(self_toggles, toggle => new Maneuver(ship, toggle, Target.newFromShip(ship))); 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 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)); let distant_maneuvers = imap(icombine(grid, distant_toggles), ([location, toggle]) => new Maneuver(ship, toggle, location));
return ichain(self_maneuvers, distant_maneuvers); return ichain(self_maneuvers, distant_maneuvers);

View file

@ -13,7 +13,7 @@
"dom", "dom",
"es6" "es6"
], ],
"target": "es5" "target": "es6"
}, },
"include": [ "include": [
"src/**/*.ts" "src/**/*.ts"