import { TileMap } from "./ascii_tile_map.js"; import { Orientation, Vector2i } from "./ascii_types.js"; import { AsciiWindow } from "./ascii_window.js"; export class MiniMap { /** * @param {AsciiWindow} aWindow * @param {TileMap} map */ constructor(aWindow, map) { if (aWindow.width !== aWindow.height) { console.warn("Window now square", { width: aWindow.width, height: aWindow.height }); throw new Error("Window must be square"); } if (aWindow.width % 2 === 0) { console.warn("Window width must not be an even number", { width: aWindow.width, }); throw new Error("Window dimension is even, it must be uneven"); } /** @type {AsciiWindow} */ this.window = aWindow; /** @type {TileMap} */ this.map = map; /** @type {number} how far we can see on the minimap */ this.distance = (aWindow.width - 1) / 2; } /** * @param {number} pX * @param {number} pY * @param {Orientation} orientation */ draw(pX, pY, orientation) { console.log("Updating minimap", { px: pX, py: pY, orientation }); // // 2D array of tiles that are visible const visibleTiles = new Array(this.map.height).fill().map(() => new Array(this.map.width).fill(false)); const radius = this.distance; const radiusSq = radius * radius; // // Mark a tile visible const setVisible = (x, y) => { if (x < 0) return; if (y < 0) return; if (x >= visibleTiles[0].length) return; if (y >= visibleTiles.length) return; visibleTiles[y][x] = true; }; // // Test if a tile is visible const isVisible = (x, y) => { if (x < 0) return false; if (y < 0) return false; if (x >= visibleTiles[0].length) return false; if (y >= visibleTiles.length) return false; return visibleTiles[y][x]; }; // // Recursive shadowcasting const castLight = (row, startSlope, endSlope, xx, xy, yx, yy) => { // if (startSlope < endSlope) { return; } for (let i = row; i <= radius; i++) { let dx = -i; const dy = -i; let blocked = false; let newStart = startSlope; while (dx <= 0) { const X = pX + dx * xx + dy * xy; const Y = pY + dx * yx + dy * yy; const lSlope = (dx - 0.5) / (dy + 0.5); const rSlope = (dx + 0.5) / (dy - 0.5); if (startSlope < rSlope) { dx++; continue; } if (endSlope > lSlope) { break; } if (dx * dx + dy * dy <= radiusSq) { setVisible(X, Y); } if (blocked) { if (this.map.looksLikeWall(X, Y)) { newStart = rSlope; } else { blocked = false; startSlope = newStart; } } else if (i < radius && this.map.looksLikeWall(X, Y)) { blocked = true; castLight(i + 1, startSlope, lSlope, xx, xy, yx, yy); newStart = rSlope; } dx++; } if (blocked) { break; } } }; const computeVisibleTiles = () => { setVisible(pX, pY); const multipliers = [ [1, 0, 0, 1], // Octant 1 (N-NE) [0, 1, 1, 0], // Octant 2 (E-NE) [-1, 0, 0, 1], // Octant 3 (N-NW) [0, 1, -1, 0], // Octant 4 (W-NW) [-1, 0, 0, -1], // Octant 5 (S-SW) [0, -1, -1, 0], // Octant 6 (W-SW) [1, 0, 0, -1], // Octant 7 (S-SE) [0, -1, 1, 0], // Octant 8 (E-SE) ]; for (const m of multipliers) { castLight(1, 1.0, 0.0, ...m); } }; computeVisibleTiles(); let [invertX, invertY, switchXY] = [false, false, false]; switch (orientation) { case Orientation.NORTH: invertX = true; break; case Orientation.SOUTH: invertY = true; break; case Orientation.EAST: switchXY = true; break; case Orientation.WEST: switchXY = true; invertY = true; invertX = true; break; } let [x, y] = [0, 0]; const max = this.window.width - 1; const dX = invertX ? -1 : 1; const dY = invertY ? -1 : 1; const startX = invertX ? max : 0; const startY = invertY ? max : 0; const minX = pX - radius; const minY = pY - radius; const maxX = pX + radius; const maxY = pY + radius; // y = startY; for (let mapY = minY; mapY < maxY; mapY++) { // x = startX; for (let mapX = minX; mapX < maxX; mapX++) { // const [putX, putY] = switchXY ? [y, x] : [x, y]; if (isVisible(mapX, mapY)) { const tile = this.map.get(mapX, mapY); this.window.put(putX, putY, tile.minimapChar, tile.minimapColor); } else { // this.window.put(putX, putY, "░", "#666"); this.window.put(putX, putY, " ", "#666"); } x += dX; } y += dY; } this.window.put(this.distance, this.distance, "@", "#4f4fff"); this.window.commitToDOM(); } } if (Math.PI < 0 && AsciiWindow && TileMap && Vector2i) { ("STFU Linda"); }