class Cell { character; characterColor; backgroundColor; constructor(char, charColor) { this.character = char; } floor() { this.character = "."; } upStair() { this.character = "<"; } downStair() { this.character = ">"; } cooridor() { this.character = "#"; } } class Point { x = 0; y = 0; constructor(x, y) { this.x = x; this.y = y; } } class Size { width = 0; height = 0; constructor(width, height) { this.width = width; this.height = height; } } class Rect { origin = new Point(); size = new Size(); static fromCoordinates(x, y, w, h) { return new Rect(new Point(x, y), new Size(w, h)); } constructor(origin, size) { this.origin = origin; this.size = size; } get minX() { return this.origin.x; } get minY() { return this.origin.y; } get maxX() { return this.origin.x + this.size.width; } get maxY() { return this.origin.y + this.size.height; } get area() { return this.size.width * this.size.height; } insetRect(inset) { const twiceInset = 2 * inset; return Rect.fromCoordinates( this.origin.x + inset, this.origin.y + inset, this.size.width - twiceInset, this.size.height - twiceInset ); } intersects(otherRect) { if (otherRect.minX > this.maxX) return false; if (otherRect.maxX < this.minX) return false; if (otherRect.minY > this.maxY) return false; if (otherRect.maxY < this.minY) return false; return true; } } class Grid { #width; #height; cells = []; constructor(width, height) { this.#width = width; this.#height = height; this.cells = new Array(width * height); for (let i = 0; i < this.cells.length; i++) { this.cells[i] = new Cell(" "); } } get width() { return this.#width; } get height() { return this.#height; } cellAt(x, y) { return this.cells[y * this.#width + x]; } } class Room { x = 0; y = 0; width = 0; height = 0; constructor(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; } get maxX() { return this.x + this.width; } get maxY() { return this.y + this.height; } charAt(x, y) { const minX = this.x; const minY = this.y; const maxX = this.maxX const maxY = this.maxY; if (y == minY && x == minX) { return "┌"; } if (y == minY && x == maxX) { return "┐"; } if (y == maxY && x == minX) { return "└"; } if (y == maxY && x == maxX) { return "┘"; } if (y == minY || y == maxY) { return "─"; } if (x == minX || x == maxX) { return "│"; } if ((x > minX && x < maxX) && (y > minY && y < maxY)) { return "."; } return undefined; } } class BSPNode { static MIN_AREA = 36; static MIN_ROOM_DIMENSION = 5; x; y; width; height; leftChild; rightChild; room; #done = false; constructor(x, y, w, h) { this.x = x; this.y = y; this.width = w; this.height = h; this.leftChild; this.rightChild; this.room; } get maxX() { return this.x + this.width; } get maxY() { return this.y + this.height; } get rooms() { let rooms = new Array(); if (this.room) { rooms.push(this.room); return rooms; } if (this.leftChild) { rooms = rooms.concat(this.leftChild.rooms); } if (this.rightChild) { rooms = rooms.concat(this.rightChild.rooms); } return rooms; } divide() { if (this.#done) { return; } const area = this.width * this.height; if (area < BSPNode.MIN_AREA) { if (!this.#done && Math.random() > 0.8) { this.#createRoom(); } this.#done = true; return; } if (area < 100 && Math.random() > 0.9) { this.#createRoom(); this.#done = true; return; } let shouldSplitVertically = Math.random() < 0.5; console.debug("Should split vertically:", shouldSplitVertically); if (shouldSplitVertically) { let xCoordinateOfDivision = this.#randomIntBetween(this.x, this.maxX); if (xCoordinateOfDivision) { this.leftChild = new BSPNode(this.x, this.y, xCoordinateOfDivision - this.x, this.height); this.rightChild = new BSPNode(xCoordinateOfDivision, this.y, this.maxX - xCoordinateOfDivision, this.height); } } else { let yCoordinateOfDivision = this.#randomIntBetween(this.y, this.maxY); if (yCoordinateOfDivision) { this.leftChild = new BSPNode(this.x, this.y, this.width, yCoordinateOfDivision - this.y); this.rightChild = new BSPNode(this.x, yCoordinateOfDivision, this.width, this.maxY - yCoordinateOfDivision); } } if (!this.leftChild && !this.rightChild) { if (!this.#done && Math.random() > 0.5) { this.#createRoom(); } this.#done = true; } } divideRecursively() { this.divide(); if (this.room) { return; } if (this.leftChild) { this.leftChild.divideRecursively(); } if (this.rightChild) { this.rightChild.divideRecursively(); } } #createRoom() { this.room = new Room(this.x + 1, this.y + 1, this.width - 2, this.height - 2); console.log("Created a room:", this.room); } #randomIntBetween(lower, upper) { const randomMin = lower + BSPNode.MIN_ROOM_DIMENSION; const randomMax = upper - BSPNode.MIN_ROOM_DIMENSION; console.debug(`Random int between: ${lower} -> ${randomMin} and ${upper} -> ${randomMax}`); const result = randomMax > randomMin ? randomMin + Math.floor(Math.random() * (randomMax - randomMin + 1)) : null; console.debug("Result:", result); return result; } } let grid; new p5(p => { const CELL_WIDTH = 20; const CELL_HEIGHT = Math.floor(CELL_WIDTH * 1.333); p.setup = () => { const container = document.querySelector('#dungeon-background'); canvasWidth = parseFloat(getComputedStyle(container).width); canvasHeight = parseFloat(getComputedStyle(container).height); let canvas = p.createCanvas(canvasWidth, canvasHeight); canvas.canvas.removeAttribute('style'); container.appendChild(canvas.canvas); p.pixelDensity(p.displayDensity()); p.textFont("Courier"); grid = new Grid(Math.ceil(canvasWidth / CELL_WIDTH), Math.ceil(canvasHeight / CELL_HEIGHT)); let bsp = new BSPNode(0, 0, grid.width, grid.height); bsp.divideRecursively(); for (let room of bsp.rooms) { for (let y = room.y; y <= room.maxY; y++) { for (let x = room.x; x <= room.maxX; x++) { let charAtXY = room.charAt(x, y); if (charAtXY) { let cell = grid.cellAt(x, y); cell.character = charAtXY; cell.characterColor = p.color(255); } } } } } p.draw = () => { p.textSize(CELL_HEIGHT); for (let y = 0; y < grid.height; y++) { for (let x = 0; x < grid.width; x++) { let cell = grid.cellAt(x, y); let fillColor = cell.characterColor ? cell.characterColor : p.color(255); p.fill(fillColor); p.textAlign(p.CENTER, p.CENTER); p.text(cell.character, x * CELL_WIDTH, y * CELL_HEIGHT, CELL_WIDTH, CELL_HEIGHT); } } p.noLoop(); }; }, '#dungeon-background');