WIP
This commit is contained in:
parent
416f17d7ea
commit
9b051c3ef8
|
@ -1 +1 @@
|
|||
Subproject commit 1425cb08935dd996a4c7a644ab793ff3b8355c9b
|
||||
Subproject commit 8a11307e3fb1b2c562674a475fa50b8511592bfe
|
|
@ -4,7 +4,7 @@ module TK.SpaceTac.Specs {
|
|||
check.equals(got.y, expected_y, `y differs (${got.x},${got.y}) (${expected_x},${expected_y})`);
|
||||
}
|
||||
|
||||
testing("ArenaGrid", test => {
|
||||
testing("ArenaGrid", test => {
|
||||
test.case("checks if a location is in range of another", check => {
|
||||
let grid = new ArenaGrid();
|
||||
|
||||
|
@ -21,12 +21,41 @@ module TK.SpaceTac.Specs {
|
|||
check.equals(grid.inRange({ x: 0, y: 0 }, { x: 0.99999999999999, y: 0 }, 1), true, "0.99999999999999");
|
||||
check.equals(grid.inRange({ x: 0, y: 0 }, { x: 1.00000000000001, y: 0 }, 1), true, "1.00000000000001");
|
||||
check.equals(grid.inRange({ x: 0, y: 0 }, { x: 1.000001, y: 0 }, 1), false, "1.000001");
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
testing("PixelArenaGrid", test => {
|
||||
test.case("moves to cardinal points", check => {
|
||||
let grid = new PixelArenaGrid(undefined, 3);
|
||||
checkLocation(check, grid.down({ x: 0, y: 0 }), 0, 3);
|
||||
checkLocation(check, grid.down({ x: 7, y: 5 }), 7, 8);
|
||||
checkLocation(check, grid.up({ x: 7, y: 5 }), 7, 2);
|
||||
checkLocation(check, grid.left({ x: 6, y: 2 }), 3, 2);
|
||||
checkLocation(check, grid.right({ x: 3, y: 1 }), 6, 1);
|
||||
});
|
||||
|
||||
test.case("iterates around a location", check => {
|
||||
let grid = new PixelArenaGrid(undefined, 5);
|
||||
let result = imaterialize(grid.iterate({ x: 5, y: 5 }, { xmin: 0, xmax: 15, ymin: 0, ymax: 10 }));
|
||||
check.equals(result.length, 12);
|
||||
checkLocation(check, result[0], 5, 5);
|
||||
checkLocation(check, result[1], 10, 5);
|
||||
checkLocation(check, result[2], 15, 5);
|
||||
checkLocation(check, result[3], 0, 5);
|
||||
checkLocation(check, result[4], 5, 10);
|
||||
checkLocation(check, result[5], 10, 10);
|
||||
checkLocation(check, result[6], 15, 10);
|
||||
checkLocation(check, result[7], 0, 10);
|
||||
checkLocation(check, result[8], 5, 0);
|
||||
checkLocation(check, result[9], 10, 0);
|
||||
checkLocation(check, result[10], 15, 0);
|
||||
checkLocation(check, result[11], 0, 0);
|
||||
});
|
||||
});
|
||||
|
||||
testing("HexagonalArenaGrid", test => {
|
||||
test.case("checks coordinates", check => {
|
||||
let grid = new HexagonalArenaGrid(5, 1);
|
||||
let grid = new HexagonalArenaGrid(undefined, 5, 1);
|
||||
check.equals(grid.check({ x: 0, y: 0 }), true, "0,0");
|
||||
check.equals(grid.check({ x: 1, y: 0 }), false, "1,0");
|
||||
check.equals(grid.check({ x: 5, y: 0 }), true, "5,0");
|
||||
|
@ -38,7 +67,7 @@ module TK.SpaceTac.Specs {
|
|||
});
|
||||
|
||||
test.case("snaps coordinates to the nearest grid point, on a biased grid", check => {
|
||||
let grid = new HexagonalArenaGrid(4, 0.75);
|
||||
let grid = new HexagonalArenaGrid(undefined, 4, 0.75);
|
||||
checkLocation(check, grid.snap({ x: 0, y: 0 }), 0, 0);
|
||||
checkLocation(check, grid.snap({ x: 1, y: 0 }), 0, 0);
|
||||
checkLocation(check, grid.snap({ x: 1.9, y: 0 }), 0, 0);
|
||||
|
@ -52,10 +81,23 @@ module TK.SpaceTac.Specs {
|
|||
});
|
||||
|
||||
test.case("snaps coordinates to the nearest grid point, on a regular grid", check => {
|
||||
let grid = new HexagonalArenaGrid(10);
|
||||
let grid = new HexagonalArenaGrid(undefined, 10);
|
||||
checkLocation(check, grid.snap({ x: 0, y: 0 }), 0, 0);
|
||||
checkLocation(check, grid.snap({ x: 8, y: 0 }), 10, 0);
|
||||
checkLocation(check, grid.snap({ x: 1, y: 6 }), 5, 10 * Math.sqrt(0.75));
|
||||
});
|
||||
|
||||
test.case("iterates around a location", check => {
|
||||
let grid = new HexagonalArenaGrid({ xmin: -10, xmax: 10, ymin: -10, ymax: 10 }, 8, 1);
|
||||
let result = imaterialize(grid.iterate({ x: 0, y: 0 }));
|
||||
check.equals(result.length, 7);
|
||||
checkLocation(check, result[0], 0, 0);
|
||||
checkLocation(check, result[1], 8, 0);
|
||||
checkLocation(check, result[2], -8, 0);
|
||||
checkLocation(check, result[3], 4, 8);
|
||||
checkLocation(check, result[4], -4, 8);
|
||||
checkLocation(check, result[5], 4, -8);
|
||||
checkLocation(check, result[6], -4, -8);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,8 +5,12 @@ module TK.SpaceTac {
|
|||
* The grid is used to snap arena coordinates on grid vertices, for ships and targets
|
||||
*
|
||||
* The default implementation does not enforce any grid or unit (leaves coordinates as they are)
|
||||
* It only applies optional boundaries (which does not impact *snap*, but *check* and *iterate*)
|
||||
*/
|
||||
export class ArenaGrid {
|
||||
constructor(private bounds?: ArenaBounds) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base unit of measurement between two points
|
||||
*/
|
||||
|
@ -15,10 +19,16 @@ module TK.SpaceTac {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check that an arena location is on a grid vertex
|
||||
* Check that an arena location is on a grid vertex, and inside bounds
|
||||
*/
|
||||
check(loc: IArenaLocation): boolean {
|
||||
return arenaDistance(loc, this.snap(loc)) < 1e-8;
|
||||
check(loc: IArenaLocation, bounds = this.bounds): boolean {
|
||||
if (arenaDistance(loc, this.snap(loc)) > 1e-8) {
|
||||
return false;
|
||||
} else if (bounds) {
|
||||
return (loc.x - bounds.xmin) > -1e-8 && (loc.x - bounds.xmax) < 1e-8 && (loc.y - bounds.ymin) > -1e-8 && (loc.y - bounds.ymax) < 1e-8;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,16 +53,65 @@ module TK.SpaceTac {
|
|||
inRange(loc1: IArenaLocation, loc2: IArenaLocation, range: number): boolean {
|
||||
return this.measure(loc1, loc2) - range < 1e-8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinate "up" from a given one (at a unit distance)
|
||||
*/
|
||||
up(loc: IArenaLocation): IArenaLocation {
|
||||
return this.snap({ x: loc.x, y: loc.y - this.getUnit() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinate "down" from a given one (at a unit distance)
|
||||
*/
|
||||
down(loc: IArenaLocation): IArenaLocation {
|
||||
return this.snap({ x: loc.x, y: loc.y + this.getUnit() });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinate "left" of a given one (at a unit distance)
|
||||
*/
|
||||
left(loc: IArenaLocation): IArenaLocation {
|
||||
return this.snap({ x: loc.x - this.getUnit(), y: loc.y });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coordinate "right" of a given one (at a unit distance)
|
||||
*/
|
||||
right(loc: IArenaLocation): IArenaLocation {
|
||||
return this.snap({ x: loc.x + this.getUnit(), y: loc.y });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
from = this.snap(from);
|
||||
|
||||
let row = (rfrom: IArenaLocation): Iterator<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),
|
||||
);
|
||||
};
|
||||
|
||||
let result = ichain(
|
||||
ichainit(imap(irecur(from, loc => loc.y <= bounds.ymax ? this.down(loc) : null), row)),
|
||||
ichainit(imap(irecur(this.up(from), loc => loc.y >= bounds.ymin ? this.up(loc) : null), row)),
|
||||
);
|
||||
|
||||
return ifilter(result, loc => this.check(loc, this.bounds));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixel unbounded grid
|
||||
* Pixel grid
|
||||
*
|
||||
* This will only round the coordinates to the pixels, with an optional unit for distance measurements
|
||||
*/
|
||||
export class PixelGrid extends ArenaGrid {
|
||||
constructor(protected readonly unit = 1) {
|
||||
super();
|
||||
export class PixelArenaGrid extends ArenaGrid {
|
||||
constructor(bounds?: ArenaBounds, protected readonly unit = 1) {
|
||||
super(bounds);
|
||||
}
|
||||
|
||||
getUnit(): number {
|
||||
|
@ -78,15 +137,15 @@ module TK.SpaceTac {
|
|||
}
|
||||
|
||||
/**
|
||||
* Hexagonal unbounded arena grid
|
||||
* Hexagonal arena grid
|
||||
*
|
||||
* This grid is composed of regular hexagons where all vertices are at a same distance "unit" of the hexagon center
|
||||
*/
|
||||
export class HexagonalArenaGrid extends PixelGrid {
|
||||
export class HexagonalArenaGrid extends PixelArenaGrid {
|
||||
private readonly yunit: number;
|
||||
|
||||
constructor(unit: number, yfactor = Math.sqrt(0.75)) {
|
||||
super(unit);
|
||||
constructor(bounds?: ArenaBounds, unit = 1, yfactor = Math.sqrt(0.75)) {
|
||||
super(bounds, unit);
|
||||
|
||||
this.yunit = unit * yfactor;
|
||||
}
|
||||
|
|
|
@ -51,6 +51,16 @@ module TK.SpaceTac {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boundaries for an arena area
|
||||
*/
|
||||
export type ArenaBounds = {
|
||||
xmin: number,
|
||||
xmax: number,
|
||||
ymin: number,
|
||||
ymax: number,
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the normalized angle (in radians) between two locations
|
||||
*/
|
||||
|
|
|
@ -36,7 +36,7 @@ module TK.SpaceTac {
|
|||
var ship5 = new Ship(fleet2, "F2S2");
|
||||
|
||||
var battle = new Battle(fleet1, fleet2, 1000, 500);
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
battle.placeShips();
|
||||
|
||||
check.nears(ship1.arena_x, 250);
|
||||
|
@ -280,7 +280,7 @@ module TK.SpaceTac {
|
|||
|
||||
test.case("lists area effects", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let peer = battle.fleets[1].addShip();
|
||||
peer.setArenaPosition(100, 50);
|
||||
|
|
|
@ -39,7 +39,7 @@ module TK.SpaceTac {
|
|||
ai_playing = false
|
||||
|
||||
constructor(fleet1 = new Fleet(new Player("Attacker")), fleet2 = new Fleet(new Player("Defender")), width = 1808, height = 948) {
|
||||
this.grid = new HexagonalArenaGrid(50);
|
||||
this.grid = new HexagonalArenaGrid({ xmin: 50, xmax: width - 50, ymin: 50, ymax: height - 50 }, 50);
|
||||
|
||||
this.fleets = [fleet1, fleet2];
|
||||
this.ships = new RObjectContainer(fleet1.ships.concat(fleet2.ships));
|
||||
|
|
|
@ -72,7 +72,7 @@ module TK.SpaceTac.Specs {
|
|||
|
||||
test.case("applies vigilance actions", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship1 = battle.fleets[0].addShip();
|
||||
ship1.setArenaPosition(100, 100);
|
||||
TestTools.setShipModel(ship1, 10, 0, 5);
|
||||
|
|
|
@ -6,14 +6,14 @@ module TK.SpaceTac.Specs {
|
|||
TestTools.setShipModel(ship, 100, 0, ship_ap);
|
||||
TestTools.addEngine(ship, engine_distance);
|
||||
let action = new TriggerAction("weapon", { power: weapon_ap, range: distance, blast: 0.1 });
|
||||
let simulator = new MoveFireSimulator(ship, new PixelGrid());
|
||||
let simulator = new MoveFireSimulator(ship, new PixelArenaGrid());
|
||||
ship.actions.addCustom(action);
|
||||
return [ship, simulator, action];
|
||||
}
|
||||
|
||||
test.case("finds a suitable engine to make an approach", check => {
|
||||
let ship = new Ship();
|
||||
let simulator = new MoveFireSimulator(ship, new PixelGrid());
|
||||
let simulator = new MoveFireSimulator(ship, new PixelArenaGrid());
|
||||
check.equals(simulator.findEngine(), null, "no engine");
|
||||
let engine1 = TestTools.addEngine(ship, 100);
|
||||
engine1.configureCooldown(1, 1);
|
||||
|
@ -92,7 +92,7 @@ module TK.SpaceTac.Specs {
|
|||
});
|
||||
|
||||
test.case("scans a circle for move targets", check => {
|
||||
let simulator = new MoveFireSimulator(new Ship(), new PixelGrid());
|
||||
let simulator = new MoveFireSimulator(new Ship(), new PixelArenaGrid());
|
||||
|
||||
let result = simulator.scanCircle(50, 30, 10, 1, 1);
|
||||
check.equals(imaterialize(result), [
|
||||
|
@ -198,7 +198,7 @@ module TK.SpaceTac.Specs {
|
|||
TestTools.setShipModel(enemy, 2, 1);
|
||||
let engine = TestTools.addEngine(ship, 80);
|
||||
let weapon = TestTools.addWeapon(ship, 5, 1, 150);
|
||||
let simulator = new MoveFireSimulator(ship, new PixelGrid());
|
||||
let simulator = new MoveFireSimulator(ship, new PixelArenaGrid());
|
||||
let result = simulator.simulateAction(weapon, Target.newFromShip(enemy), 5);
|
||||
let diffs = simulator.getExpectedDiffs(nn(ship.getBattle()), result);
|
||||
check.equals(diffs, [
|
||||
|
|
|
@ -30,7 +30,7 @@ module TK.SpaceTac.Specs {
|
|||
});
|
||||
|
||||
test.case("snaps to a grid", check => {
|
||||
let grid = new HexagonalArenaGrid(5, 1);
|
||||
let grid = new HexagonalArenaGrid(undefined, 5, 1);
|
||||
check.equals(Target.newFromLocation(1, 1).snap(grid), Target.newFromLocation(0, 0), "1,1");
|
||||
check.equals(Target.newFromLocation(6, 4).snap(grid), Target.newFromLocation(7.5, 5), "6,4");
|
||||
let ship = new Ship();
|
||||
|
|
|
@ -16,7 +16,7 @@ module TK.SpaceTac {
|
|||
|
||||
var battle = new Battle(fleet1, fleet2);
|
||||
if (!hexgrid) {
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
}
|
||||
battle.ships.list().forEach(ship => TestTools.setShipModel(ship, 1, 0));
|
||||
battle.play_order = fleet1.ships.concat(fleet2.ships);
|
||||
|
|
|
@ -3,7 +3,7 @@ module TK.SpaceTac.Specs {
|
|||
test.case("checks target is on the grid", check => {
|
||||
let ship = new Ship();
|
||||
let battle = new Battle(ship.fleet);
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
TestTools.setShipModel(ship, 1, 0, 1);
|
||||
let action = new MoveAction("Engine", { distance_per_power: 1 });
|
||||
|
|
|
@ -21,7 +21,7 @@ module TK.SpaceTac.Specs {
|
|||
test.case("collects impacted ships", check => {
|
||||
let action = new ToggleAction("testtoggle", { radius: 50 });
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship1 = battle.fleets[0].addShip();
|
||||
ship1.setArenaPosition(0, 0);
|
||||
let ship2 = battle.fleets[0].addShip();
|
||||
|
|
|
@ -28,7 +28,7 @@ module TK.SpaceTac.Specs {
|
|||
ship3.alive = false;
|
||||
|
||||
let battle = new Battle(fleet);
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
battle.play_order = [ship, ship1, ship2, ship3];
|
||||
TestTools.setShipPlaying(battle, ship);
|
||||
fleet.setBattle(battle);
|
||||
|
|
|
@ -47,7 +47,7 @@ module TK.SpaceTac.Specs {
|
|||
|
||||
test.case("handles the vigilance effect to know who to target", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship1a = battle.fleets[0].addShip();
|
||||
ship1a.setArenaPosition(0, 0);
|
||||
TestTools.setShipModel(ship1a, 10, 0, 5);
|
||||
|
|
|
@ -2,7 +2,7 @@ module TK.SpaceTac.Specs {
|
|||
testing("Maneuver", test => {
|
||||
test.case("uses move-fire simulation to build a list of battle diffs", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship1 = battle.fleets[0].addShip();
|
||||
let ship2 = battle.fleets[1].addShip();
|
||||
let ship3 = battle.fleets[1].addShip();
|
||||
|
|
|
@ -108,7 +108,7 @@ module TK.SpaceTac.Specs {
|
|||
|
||||
test.case("evaluates turn cost", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let weapon = TestTools.addWeapon(ship, 50, 5, 100);
|
||||
let action = weapon;
|
||||
|
@ -173,7 +173,7 @@ module TK.SpaceTac.Specs {
|
|||
|
||||
test.case("evaluates damage to enemies", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
let action = TestTools.addWeapon(ship, 50, 5, 500, 100);
|
||||
|
||||
|
@ -199,7 +199,7 @@ module TK.SpaceTac.Specs {
|
|||
|
||||
test.case("evaluates ship clustering", check => {
|
||||
let battle = new Battle();
|
||||
battle.grid = new PixelGrid();
|
||||
battle.grid = new PixelArenaGrid();
|
||||
let ship = battle.fleets[0].addShip();
|
||||
TestTools.setShipModel(ship, 100, 0, 10);
|
||||
TestTools.addEngine(ship, 100);
|
||||
|
|
|
@ -82,7 +82,7 @@ module TK.SpaceTac.UI.Specs {
|
|||
view.splash = false;
|
||||
|
||||
let battle = Battle.newQuickRandom();
|
||||
battle.grid = new ArenaGrid();
|
||||
battle.grid = new ArenaGrid({ xmin: 0, xmax: 10, ymin: 0, ymax: 10 });
|
||||
battle.placeShips();
|
||||
let player = new Player();
|
||||
nn(battle.playing_ship).fleet.setPlayer(player);
|
||||
|
|
|
@ -242,18 +242,16 @@ module TK.SpaceTac.UI {
|
|||
let action = this.action;
|
||||
let simulator = new MoveFireSimulator(this.ship, grid);
|
||||
|
||||
range(35).forEach(x => {
|
||||
range(20).forEach(y => {
|
||||
let target = Target.newFromLocation(x * 50, y * 50).snap(grid);
|
||||
let result = simulator.simulateAction(action, target);
|
||||
if (result.status == MoveFireStatus.OK) {
|
||||
this.grid_ok.add(target.x, target.y);
|
||||
} else if (result.status == MoveFireStatus.NOT_ENOUGH_POWER) {
|
||||
this.grid_power.add(target.x, target.y);
|
||||
} else {
|
||||
this.grid_invalid.add(target.x, target.y);
|
||||
}
|
||||
});
|
||||
iforeach(grid.iterate(this.ship.location), loc => {
|
||||
let target = Target.newFromLocation(loc.x, loc.y);
|
||||
let result = simulator.simulateAction(action, target);
|
||||
if (result.status == MoveFireStatus.OK) {
|
||||
this.grid_ok.add(target.x, target.y);
|
||||
} else if (result.status == MoveFireStatus.NOT_ENOUGH_POWER) {
|
||||
this.grid_power.add(target.x, target.y);
|
||||
} else {
|
||||
this.grid_invalid.add(target.x, target.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue