1
0
Fork 0
spacetac/src/core/ArenaGrid.ts
2018-07-17 16:17:46 +02:00

165 lines
5.4 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)): 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 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);
}
}
}
/**
* 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);
}
}
}