1
0
Fork 0
This commit is contained in:
Michaël Lemaire 2018-07-20 22:20:15 +02:00
parent 6d414a8f5e
commit d4452e9753
8 changed files with 48 additions and 67 deletions

View File

@ -53,7 +53,6 @@ Battle
* [WIP] Add an hexagonal grid
* Use the grid for "border" exclusion areas
* Fix repel effect to snap to grid and use grid units (beware of two ships being moved to the same location!)
* Fix move-fire simulator's scanCircle
* Apply to action's default targets
* Add engine trail effect, and sound
* Find incentives to move from starting position (permanent drones or anomalies?)

View File

@ -33,9 +33,11 @@ module TK.SpaceTac.Specs {
checkLocation(check, grid.left({ x: 6, y: 2 }), 3, 2);
checkLocation(check, grid.right({ x: 3, y: 1 }), 6, 1);
});
});
testing("SquareArenaGrid", test => {
test.case("iterates around a location", check => {
let grid = new PixelArenaGrid(undefined, 5);
let grid = new SquareArenaGrid(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);

View File

@ -44,7 +44,7 @@ module TK.SpaceTac {
* This returns a distance in grid units
*/
measure(loc1: IArenaLocation, loc2: IArenaLocation): number {
return arenaDistance(this.snap(loc1), this.snap(loc2));
return arenaDistance(this.snap(loc1), this.snap(loc2)) / this.getUnit();
}
/**
@ -85,22 +85,23 @@ 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)): Iterable<IArenaLocation> {
iterate(from: IArenaLocation, bounds?: ArenaBounds): Iterable<IArenaLocation> {
from = this.snap(from);
let fbounds = arenaBoundsIntersect(this.bounds || ARENA_INFINITE, bounds || ARENA_INFINITE);
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),
irecur(rfrom, loc => loc.x <= fbounds.xmax ? this.right(loc) : null),
irecur(this.left(rfrom), loc => loc.x >= fbounds.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)),
ichainit(imap(irecur(from, loc => loc.y <= fbounds.ymax ? this.down(loc) : null), row)),
ichainit(imap(irecur(this.up(from), loc => loc.y >= fbounds.ymin ? this.up(loc) : null), row)),
);
return ifilter(result, loc => this.check(loc, this.bounds));
return ifilter(result, loc => this.check(loc, fbounds));
}
}
@ -121,33 +122,12 @@ module TK.SpaceTac {
snap(loc: IArenaLocation): IArenaLocation {
return new ArenaLocation(Math.round(loc.x), Math.round(loc.y));
}
measure(loc1: IArenaLocation, loc2: IArenaLocation): number {
// FIXME
let d = super.measure(loc1, loc2) / this.unit;
let r = Math.round(d);
if (r >= d) {
return Math.ceil(d);
} else if (d - r < 1e-8) {
return r;
} else {
return Math.floor(d);
}
}
}
/**
* Square grid
*/
export class SquareGrid extends PixelArenaGrid {
constructor(bounds?: ArenaBounds, unit = 1) {
super(bounds, unit);
}
getUnit(): number {
return this.unit;
}
export class SquareArenaGrid extends PixelArenaGrid {
snap(loc: IArenaLocation): IArenaLocation {
return new ArenaLocation(Math.round(loc.x / this.unit) * this.unit, Math.round(loc.y / this.unit) * this.unit);
}

View File

@ -61,6 +61,20 @@ module TK.SpaceTac {
ymax: number,
}
export const ARENA_INFINITE = { xmin: -Infinity, xmax: Infinity, ymin: -Infinity, ymax: Infinity };
/**
* Intersection of two arena bounds
*/
export function arenaBoundsIntersect(bounds1: ArenaBounds, bounds2: ArenaBounds): ArenaBounds {
return {
xmin: Math.max(bounds1.xmin, bounds2.xmin),
xmax: Math.min(bounds1.xmax, bounds2.xmax),
ymin: Math.max(bounds1.ymin, bounds2.ymin),
ymax: Math.min(bounds1.ymax, bounds2.ymax),
}
}
/**
* Get the normalized angle (in radians) between two locations
*/

View File

@ -92,35 +92,20 @@ module TK.SpaceTac.Specs {
});
test.case("scans a circle for move targets", check => {
let simulator = new MoveFireSimulator(new Ship(), new PixelArenaGrid());
let simulator = new MoveFireSimulator(new Ship(), new SquareArenaGrid(undefined, 1));
let result = simulator.scanCircle(50, 30, 10, 1, 1);
let result = simulator.scanCircle(10, 5, 0);
check.equals(imaterialize(result), [
new Target(50, 30)
new Target(10, 5)
]);
result = simulator.scanCircle(50, 30, 10, 2, 1);
result = simulator.scanCircle(10, 5, 1);
check.equals(imaterialize(result), [
new Target(50, 30),
new Target(60, 30)
]);
result = simulator.scanCircle(50, 30, 10, 2, 2);
check.equals(imaterialize(result), [
new Target(50, 30),
new Target(60, 30),
new Target(40, 30)
]);
result = simulator.scanCircle(50, 30, 10, 3, 4);
check.equals(imaterialize(result), [
new Target(50, 30),
new Target(55, 30),
new Target(45, 30),
new Target(60, 30),
new Target(50, 40),
new Target(40, 30),
new Target(50, 20)
new Target(10, 5),
new Target(11, 5),
new Target(9, 5),
new Target(10, 6),
new Target(10, 4),
]);
});
@ -128,6 +113,7 @@ module TK.SpaceTac.Specs {
let [ship, simulator, action] = simpleWeaponCase(100, 5, 1, 50);
ship.setArenaPosition(300, 200);
let battle = new Battle();
battle.grid = new PixelArenaGrid();
battle.fleets[0].addShip(ship);
let ship1 = battle.fleets[0].addShip();
let moveaction = nn(simulator.findEngine());
@ -145,7 +131,7 @@ module TK.SpaceTac.Specs {
new Target(420, 210),
new Target(480, 260),
]));
check.equals(simulator.getApproach(moveaction, Target.newFromLocation(500, 200), 100), new Target(410, 230));
check.equals(simulator.getApproach(moveaction, Target.newFromLocation(500, 200), 100), new Target(480, 260));
});
test.case("moves to get in range, even if not enough AP to fire", check => {

View File

@ -75,15 +75,15 @@ module TK.SpaceTac {
/**
* Get an iterator for scanning a circle
*/
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 => {
let angles = Math.max(1, Math.ceil(na * r));
return imap(istep(0, irepeat(2 * Math.PI / angles, angles - 1)), a => {
return new Target(x + r * radius * Math.cos(a), y + r * radius * Math.sin(a)).snap(this.grid);
});
}));
scanCircle(x: number, y: number, radius: number): Iterable<Target> {
let cells = this.grid.iterate({ x, y }, {
xmin: x - radius,
xmax: x + radius,
ymin: y - radius,
ymax: y + radius,
});
cells = ifilter(cells, cell => this.grid.inRange({ x, y }, cell, radius / this.grid.getUnit()));
return imap(cells, cell => Target.newFromLocation(cell.x, cell.y));
}
/**

View File

@ -127,7 +127,7 @@ module TK.SpaceTac.Specs {
test.case("can only be used on the grid, and in range", check => {
let battle = new Battle();
battle.grid = new SquareGrid({ xmin: 0, xmax: 10, ymin: 0, ymax: 5 }, 1);
battle.grid = new SquareArenaGrid({ xmin: 0, xmax: 10, ymin: 0, ymax: 5 }, 1);
let ship = battle.fleets[0].addShip();
let action = ship.actions.addCustom(new TriggerAction("Weapon", { range: 8, blast: 1 }));

View File

@ -29,7 +29,7 @@ module TK.SpaceTac.Specs {
test.case("produces random moves inside a grid", check => {
let battle = new Battle();
battle.grid = new SquareGrid({ xmin: 0, ymin: 0, xmax: 20, ymax: 10 }, 10);
battle.grid = new SquareArenaGrid({ xmin: 0, ymin: 0, xmax: 20, ymax: 10 }, 10);
let ship = battle.fleets[0].addShip();
TestTools.setShipModel(ship, 100, 0, 10);