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 = {
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 () { };

View File

@ -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

@ -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
*/
iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterator<IArenaLocation> {
iterate(from: IArenaLocation, bounds = nn(this.bounds)): Iterable<IArenaLocation> {
from = this.snap(from);
let row = (rfrom: IArenaLocation): Iterator<IArenaLocation> => {
let row = (rfrom: IArenaLocation): Iterable<IArenaLocation> => {
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
*

View File

@ -111,7 +111,7 @@ module TK.SpaceTac {
/**
* 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)));
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<Ship> {
iallies(ship: Ship, alive_only = false): Iterable<Ship> {
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<Ship> {
ienemies(ship: Ship, alive_only = false): Iterable<Ship> {
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
*/
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
let rcount = nr ? 1 / (nr - 1) : 0;
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
*/
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);
}
/**

View File

@ -2,7 +2,7 @@
/// <reference path="Maneuver.ts"/>
module TK.SpaceTac {
export type TacticalProducer = Iterator<Maneuver>;
export type TacticalProducer = Iterable<Maneuver>;
export type TacticalEvaluator = (maneuver: Maneuver) => number;
/**
@ -14,17 +14,23 @@ module TK.SpaceTac {
*/
export class TacticalAI extends AbstractAI {
private producers: TacticalProducer[] = []
private work: Iterator<Maneuver> = 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) {

View File

@ -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)),
]);
});

View File

@ -2,7 +2,7 @@ module TK.SpaceTac {
/**
* 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);
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<Target> {
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<Target> {
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);

View File

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