Work in progress nethack dungeon generator background for the nethack page

This commit is contained in:
Eryn Wells 2023-01-29 08:05:54 -08:00
parent d524a7dd9e
commit 186929921b
5 changed files with 342 additions and 0 deletions

View file

@ -0,0 +1,264 @@
class Cell {
character;
characterColor;
backgroundColor;
constructor(char, charColor) {
this.character = char;
}
}
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');

120
assets/styles/nethack.css Normal file
View file

@ -0,0 +1,120 @@
/* Eryn Wells <eryn@erynwells.me> */
:root {
--logentry-background-color: var(--tag-background-color);
--logentry-foreground-color: var(--tag-text-color);
}
:root {
--separator-color: rgb(var(--dk-gray));
--box-shadow-color: rgba(var(--dk-gray), 0.8);
--body-code-background-color: rgb(var(--dk-gray));
--heading-color: rgb(var(--white));
--header-series-arrow-foreground-color: rgb(var(--super-dk-gray));
--html-background-color: rgb(var(--black));
--html-color: rgba(var(--white), 0.8);
--platter-background-color: rgba(var(--black), var(--platter-background-opacity));
--platter-backdrop-filter: brightness(0.66) blur(10px);
--tag-foreground-color: rgb(var(--sub-lt-gray));
--tag-background-color: rgb(var(--dk-gray));
--tag-spacer-foreground-color: rgb(var(--super-dk-gray));
--tag-hover-background-color: rgb(var(--super-dk-gray));
--tag-hover-foreground-color: rgb(var(--mid-gray));
--twitter-icon: url(/icons/twitter-dark.svg);
--github-icon: url(/icons/github-dark.svg);
--instagram-icon: url(/icons/instagram-dark.svg);
--feed-icon: url(/icons/rss-dark.svg);
}
main {
background: black;
}
#dungeon-background {
width: 100vw;
height: 100vh;
position: fixed;
top: 0;
left: 0;
z-index: -1;
filter: brightness(0.3);
}
.logfile {
margin-block-start: 0;
margin-inline-start: 0;
padding-inline-start: 0;
}
.logfile > li {
align-items: first baseline;
color: var(--logentry-foreground-color);
background-color: var(--logentry-background-color);
border-radius: 6px;
display: grid;
grid-template-columns: 32px auto auto;
row-gap: 1rem;
margin-block-start: 2rem;
margin-inline-start: 0;
padding: 3rem;
}
.logfile > li > .date {
grid-column-start: 2;
grid-row-start: 1;
}
.logfile > li > h4.date {
margin: 0;
padding: 0;
}
.logfile > li > .character-descriptor {
color: #aaa;
font-size: 1.5rem;
font-weight: 700;
grid-column-start: 3;
grid-row-start: 1;
text-align: right;
text-transform: uppercase;
}
.logfile > li > p {
grid-column-start: 2;
grid-column-end: 4;
grid-row-start: 2;
margin: 0;
}
.logfile > li > table.stats {
border: 0;
grid-column-start: 2;
grid-column-end: 4;
grid-row-start: 3;
margin-block-end: 0;
width: 100%;
-webkit-border-horizontal-spacing: 0;
-webkit-border-vertical-spacing: 0;
}
.logfile > li > table.stats > tbody > tr > td {
border: 0;
color: #aaa;
font-size: 1.5rem;
font-weight: 700;
padding: 0;
text-transform: uppercase;
vertical-align: bottom;
}
.logfile > li > table.stats > tbody > tr > td.score,
.logfile > li > table.stats > tbody > tr > td.hp,
.logfile > li > table.stats > tbody > tr > td.level {
width: 16rem;
text-align: right;
}