WIP
This commit is contained in:
parent
1a27c99225
commit
74debe585d
|
@ -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 () { };
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"dom",
|
||||
"es6"
|
||||
],
|
||||
"target": "es5"
|
||||
"target": "es6"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
|
Loading…
Reference in New Issue