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 { from = this.snap(from); let row = (rfrom: IArenaLocation): Iterator => { 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); } } }