Work in progress nethack dungeon generator background for the nethack page
This commit is contained in:
parent
d524a7dd9e
commit
186929921b
5 changed files with 342 additions and 0 deletions
264
assets/scripts/nethack/dungeon.js
Normal file
264
assets/scripts/nethack/dungeon.js
Normal 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');
|
|
@ -5,6 +5,46 @@
|
||||||
--logentry-foreground-color: var(--tag-text-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 {
|
.logfile {
|
||||||
margin-block-start: 0;
|
margin-block-start: 0;
|
||||||
margin-inline-start: 0;
|
margin-inline-start: 0;
|
|
@ -2,6 +2,7 @@
|
||||||
title: "Nethack"
|
title: "Nethack"
|
||||||
description: In which I play way too much of a silly command line Roguelike game.
|
description: In which I play way too much of a silly command line Roguelike game.
|
||||||
date: 2022-04-13T08:43:46-07:00
|
date: 2022-04-13T08:43:46-07:00
|
||||||
|
type: nethack
|
||||||
---
|
---
|
||||||
|
|
||||||
Every so often I get hooked on [this game][nethack]. It's a command line
|
Every so often I get hooked on [this game][nethack]. It's a command line
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{ block "body" . -}}
|
{{ block "body" . -}}
|
||||||
|
{{ block "before" . }}{{ end }}
|
||||||
{{ block "header" . }}{{ partial "header.html" .}}{{ end }}
|
{{ block "header" . }}{{ partial "header.html" .}}{{ end }}
|
||||||
<main class="{{ .Type }} {{ .Kind }}{{ if gt (len .Pages) 0 }} list{{ end }}">
|
<main class="{{ .Type }} {{ .Kind }}{{ if gt (len .Pages) 0 }} list{{ end }}">
|
||||||
{{ block "main" . }}{{ end }}
|
{{ block "main" . }}{{ end }}
|
||||||
|
|
36
layouts/nethack/single.html
Normal file
36
layouts/nethack/single.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{{ define "before" }}
|
||||||
|
<div id="dungeon-background"></div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "main" }}
|
||||||
|
{{ partial "single_main.html" . }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "styles" }}
|
||||||
|
{{- with partial "secure_asset.html" "styles/nethack.css" -}}
|
||||||
|
{{ with .Secure }}
|
||||||
|
<link rel="stylesheet" href="{{ .RelPermalink }}">
|
||||||
|
{{ else }}
|
||||||
|
{{- errorf "Unable to find nethack.css" -}}
|
||||||
|
{{ end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- range .Resources.Match "*.css" -}}
|
||||||
|
{{- $stylesheet := . | fingerprint "md5" }}
|
||||||
|
<link rel="stylesheet" href="{{ $stylesheet.RelPermalink }}">
|
||||||
|
{{- end -}}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "scripts" }}
|
||||||
|
{{- with partial "secure_asset.html" "scripts/lib/p5-1.5.0.js" -}}
|
||||||
|
<script defer src="{{ .Secure.RelPermalink }}"></script>
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- with partial "secure_asset.html" "scripts/nethack/dungeon.js" -}}
|
||||||
|
<script defer src="{{ .Secure.RelPermalink }}"></script>
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- range $script := .Resources.Match "*.js" -}}
|
||||||
|
{{- $isModule := default true $script.Params.is_module -}}
|
||||||
|
<script defer {{ if $isModule }}type="module"{{ end }} src="{{ $script.Permalink | relURL }}"></script>
|
||||||
|
{{- end -}}
|
||||||
|
{{ end }}
|
Loading…
Add table
Add a link
Reference in a new issue