182 lines
5.8 KiB
TypeScript
182 lines
5.8 KiB
TypeScript
module TK.SpaceTac {
|
|
/**
|
|
* Abstract grid for the arena where the battle takes place
|
|
*
|
|
* 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
|
|
*/
|
|
getUnit(): number {
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Check that an arena location is on a grid vertex, and inside bounds
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Snap a floating point arena location to a grid vertex
|
|
*/
|
|
snap(loc: IArenaLocation): IArenaLocation {
|
|
return loc;
|
|
}
|
|
|
|
/**
|
|
* Measure the distance between two points
|
|
*
|
|
* This returns a distance in grid units
|
|
*/
|
|
measure(loc1: IArenaLocation, loc2: IArenaLocation): number {
|
|
return arenaDistance(this.snap(loc1), this.snap(loc2));
|
|
}
|
|
|
|
/**
|
|
* Check that a location is in range of another
|
|
*/
|
|
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)): Iterable<IArenaLocation> {
|
|
from = this.snap(from);
|
|
|
|
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),
|
|
);
|
|
};
|
|
|
|
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 grid
|
|
*
|
|
* This will only round the coordinates to the pixels, with an optional unit for distance measurements
|
|
*/
|
|
export class PixelArenaGrid extends ArenaGrid {
|
|
constructor(bounds?: ArenaBounds, protected readonly unit = 1) {
|
|
super(bounds);
|
|
}
|
|
|
|
getUnit(): number {
|
|
return this.unit;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
*
|
|
* This grid is composed of regular hexagons where all vertices are at a same distance "unit" of the hexagon center
|
|
*/
|
|
export class HexagonalArenaGrid extends PixelArenaGrid {
|
|
private readonly yunit: number;
|
|
|
|
constructor(bounds?: ArenaBounds, unit = 1, yfactor = Math.sqrt(0.75)) {
|
|
super(bounds, unit);
|
|
|
|
this.yunit = unit * yfactor;
|
|
}
|
|
|
|
snap(loc: IArenaLocation): IArenaLocation {
|
|
let yr = Math.round(loc.y / this.yunit);
|
|
let xr: number;
|
|
if (yr % 2 == 0) {
|
|
xr = Math.round(loc.x / this.unit);
|
|
} else {
|
|
xr = Math.round((loc.x - 0.5 * this.unit) / this.unit) + 0.5;
|
|
}
|
|
return new ArenaLocation((xr * this.unit) || 0, (yr * this.yunit) || 0);
|
|
}
|
|
}
|
|
}
|