wahuuga
This commit is contained in:
203
frontend/ascii_minimap.js
Executable file
203
frontend/ascii_minimap.js
Executable file
@@ -0,0 +1,203 @@
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user