From 186929921b721f589f6feed2384e5fa642082841 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 29 Jan 2023 08:05:54 -0800 Subject: [PATCH 01/17] Work in progress nethack dungeon generator background for the nethack page --- assets/scripts/nethack/dungeon.js | 264 ++++++++++++++++++ .../nethack => assets/styles}/nethack.css | 40 +++ content/nethack/index.md | 1 + layouts/_default/baseof.html | 1 + layouts/nethack/single.html | 36 +++ 5 files changed, 342 insertions(+) create mode 100644 assets/scripts/nethack/dungeon.js rename {content/nethack => assets/styles}/nethack.css (60%) create mode 100644 layouts/nethack/single.html diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js new file mode 100644 index 0000000..efaaca4 --- /dev/null +++ b/assets/scripts/nethack/dungeon.js @@ -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'); diff --git a/content/nethack/nethack.css b/assets/styles/nethack.css similarity index 60% rename from content/nethack/nethack.css rename to assets/styles/nethack.css index 49f97a3..66a659b 100644 --- a/content/nethack/nethack.css +++ b/assets/styles/nethack.css @@ -5,6 +5,46 @@ --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; diff --git a/content/nethack/index.md b/content/nethack/index.md index b8bda90..b6c9a5c 100644 --- a/content/nethack/index.md +++ b/content/nethack/index.md @@ -2,6 +2,7 @@ title: "Nethack" description: In which I play way too much of a silly command line Roguelike game. 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 diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index bdc0e5a..ae0dbb9 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -4,6 +4,7 @@ {{ block "body" . -}} + {{ block "before" . }}{{ end }} {{ block "header" . }}{{ partial "header.html" .}}{{ end }}
{{ block "main" . }}{{ end }} diff --git a/layouts/nethack/single.html b/layouts/nethack/single.html new file mode 100644 index 0000000..8c8591d --- /dev/null +++ b/layouts/nethack/single.html @@ -0,0 +1,36 @@ +{{ define "before" }} +
+{{ end }} + +{{ define "main" }} + {{ partial "single_main.html" . }} +{{ end }} + +{{ define "styles" }} + {{- with partial "secure_asset.html" "styles/nethack.css" -}} + {{ with .Secure }} + + {{ else }} + {{- errorf "Unable to find nethack.css" -}} + {{ end }} + {{- end -}} + {{- range .Resources.Match "*.css" -}} + {{- $stylesheet := . | fingerprint "md5" }} + + {{- end -}} +{{ end }} + +{{ define "scripts" }} + {{- with partial "secure_asset.html" "scripts/lib/p5-1.5.0.js" -}} + + {{- end -}} + + {{- with partial "secure_asset.html" "scripts/nethack/dungeon.js" -}} + + {{- end -}} + + {{- range $script := .Resources.Match "*.js" -}} + {{- $isModule := default true $script.Params.is_module -}} + + {{- end -}} +{{ end }} From 2033e0c3a11fac1bf8af482bfda49bfff1161669 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:01:11 -0800 Subject: [PATCH 02/17] Add some basic geometry types: Point, Size, Rect --- assets/scripts/nethack/dungeon.js | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index efaaca4..c8eaa37 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -8,6 +8,73 @@ class Cell { } } +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; From 8a5fc9c8206d666267a5ca4b872118c77b47abff Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:01:34 -0800 Subject: [PATCH 03/17] Add some methods to Cell to turn the Cell into a real Cell --- assets/scripts/nethack/dungeon.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index c8eaa37..0886cd2 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -6,6 +6,11 @@ class Cell { constructor(char, charColor) { this.character = char; } + + floor() { this.character = "."; } + upStair() { this.character = "<"; } + downStair() { this.character = ">"; } + cooridor() { this.character = "#"; } } class Point { From b8ac3126da39598c1ae3ca774fcf6002f44f251e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:01:53 -0800 Subject: [PATCH 04/17] Redo Room in terms of Rect --- assets/scripts/nethack/dungeon.js | 96 ++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 0886cd2..f11131b 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -81,58 +81,98 @@ class Rect { } class Grid { - #width; - #height; - cells = []; + #size; + #cells = []; + + #rooms = []; constructor(width, height) { - this.#width = width; - this.#height = height; + this.#size = new Size(width, height); - this.cells = new Array(width * height); - for (let i = 0; i < this.cells.length; i++) { - this.cells[i] = new Cell(" "); + 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; + return this.#size.width; } get height() { - return this.#height; + return this.#size.height; + } + + generate(p, roomGenerator) { + this.#generateRooms(p, roomGenerator); + this.#placeStairs(); + } + + #generateRooms(p, generator) { + this.#rooms = generator.rooms; + + for (let room of this.#rooms) { + for (let y = room.minY; y <= room.maxY; y++) { + for (let x = room.minX; x <= room.maxX; x++) { + let charAtXY = room.charAt(x, y); + if (!charAtXY) { + continue; + } + + let cell = this.cellAt(x, y); + cell.character = charAtXY; + cell.characterColor = p.color(255); + } + } + } + } + + #placeStairs() { + const indexOfRoomWithUpStairs = randomInt(this.#rooms.length); + const coordinateOfUpStair = this.#rooms[indexOfRoomWithUpStairs].randomPoint(); + this.cellAt(coordinateOfUpStair.x, coordinateOfUpStair.y).upStair(); + + while (true) { + let indexOfRoomForDownStair = randomInt(this.#rooms.length); + if (indexOfRoomForDownStair == indexOfRoomWithUpStairs) { + continue; + } + + const coordinateOfDownStair = this.#rooms[indexOfRoomForDownStair].randomPoint(); + this.cellAt(coordinateOfDownStair.x, coordinateOfDownStair.y).downStair(); + + break; + } } cellAt(x, y) { - return this.cells[y * this.#width + x]; + return this.#cells[y * this.width + x]; } } class Room { - x = 0; - y = 0; - width = 0; - height = 0; + #rect; - constructor(x, y, w, h) { - this.x = x; - this.y = y; - this.width = w; - this.height = h; + constructor(rect) { + this.#rect = rect; } - get maxX() { - return this.x + this.width; - } + get minX() { return this.#rect.minX; } + get minY() { return this.#rect.minY; } + get maxX() { return this.#rect.maxX; } + get maxY() { return this.#rect.maxY; } - get maxY() { - return this.y + this.height; + randomPoint() { + return new Point( + this.#rect.minX + 1 + randomInt(this.#rect.size.width - 2), + this.#rect.minY + 1 + randomInt(this.#rect.size.height - 2) + ); } charAt(x, y) { - const minX = this.x; - const minY = this.y; - const maxX = this.maxX + const minX = this.minX; + const minY = this.minY; + const maxX = this.maxX; const maxY = this.maxY; if (y == minY && x == minX) { return "┌"; } From a283cae416d2c1bc0107b82c67463ad3e1fec8bb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:02:10 -0800 Subject: [PATCH 05/17] Add randomInt() --- assets/scripts/nethack/dungeon.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index f11131b..6882d8b 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -319,6 +319,11 @@ class BSPNode { } } +function randomInt(n) { + max = Math.floor(n); + return Math.floor(Math.random() * max); +} + let grid; new p5(p => { From 5517fd0110cade235672cdb531f730337a67e648 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:02:44 -0800 Subject: [PATCH 06/17] Clean up grid generation in setUp() --- assets/scripts/nethack/dungeon.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 6882d8b..a9b0c17 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -342,22 +342,15 @@ new p5(p => { 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(); + const gridBounds = Rect.fromCoordinates( + 0, 0, + Math.ceil(canvasWidth / CELL_WIDTH) - 1, Math.ceil(canvasHeight / CELL_HEIGHT) - 1 + ); - 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); - } - } - } - } + console.log(`Generating grid with size ${gridBounds.size.width} x ${gridBounds.size.height}`); + + grid = new Grid(gridBounds.size.width, gridBounds.size.height); + grid.generate(p, new NRandomRoomsGenerator(gridBounds)); } p.draw = () => { From e4a7550abc73171c24ed16370715af9f3c3299d7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:03:00 -0800 Subject: [PATCH 07/17] Add NRandomRoomsGenerator --- assets/scripts/nethack/dungeon.js | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index a9b0c17..c7f071b 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -319,6 +319,67 @@ class BSPNode { } } +class NRandomRoomsGenerator { + static MIN_ROOM_DIMENSION = 7; + static MAX_ROOM_DIMENSION = 12; + + #numberOfRooms = 12; + #rooms; + + #bounds; + + constructor(bounds, numberOfRooms) { + if (bounds) { + this.#bounds = bounds; + } + + if (numberOfRooms) { + this.#numberOfRooms = numberOfRooms; + } + } + + get numberOfRooms() { + return this.#numberOfRooms; + } + + get rooms() { + if (!this.#rooms) { + this.#generateRooms(); + } + + return this.#rooms; + } + + #generateRooms() { + let rects = new Array(); + + const sizeRange = NRandomRoomsGenerator.MAX_ROOM_DIMENSION - NRandomRoomsGenerator.MIN_ROOM_DIMENSION; + + while (rects.length < this.#numberOfRooms) { + const randomSize = new Size( + NRandomRoomsGenerator.MIN_ROOM_DIMENSION + randomInt(sizeRange), + NRandomRoomsGenerator.MIN_ROOM_DIMENSION + randomInt(sizeRange) + ); + + const randomOrigin = new Point( + this.#bounds.minX + randomInt(this.#bounds.maxX - randomSize.width), + this.#bounds.minY + randomInt(this.#bounds.maxY - randomSize.height) + ); + + const proposedRoomRect = new Rect(randomOrigin, randomSize); + + // Check that the rect doesn't intersect with any other rects. + if (rects.some(e => e.intersects(proposedRoomRect))) { + continue; + } + + rects.push(proposedRoomRect); + } + + this.#rooms = rects.map(r => new Room(r.insetRect(1))); + } +} + function randomInt(n) { max = Math.floor(n); return Math.floor(Math.random() * max); From 89902ccc03b4df69a1e99e6158c5c4c90948f197 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:03:13 -0800 Subject: [PATCH 08/17] Tweak the width/height proportion of cells --- assets/scripts/nethack/dungeon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index c7f071b..4638317 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -389,7 +389,7 @@ let grid; new p5(p => { const CELL_WIDTH = 20; - const CELL_HEIGHT = Math.floor(CELL_WIDTH * 1.333); + const CELL_HEIGHT = Math.floor(CELL_WIDTH * 1.3); p.setup = () => { const container = document.querySelector('#dungeon-background'); From aa50054108e21f75acdc04e64754105cfaccd2b5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:12:07 -0800 Subject: [PATCH 09/17] Convert setting cell in room generation method into cell transform --- assets/scripts/nethack/dungeon.js | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 4638317..cc8e148 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -11,6 +11,12 @@ class Cell { upStair() { this.character = "<"; } downStair() { this.character = ">"; } cooridor() { this.character = "#"; } + topLeftWall() { this.character = "┌"; } + topRightWall() { this.character = "┐"; } + bottomLeftWall() { this.character = "└"; } + bottomRightWall() { this.character = "┘"; } + horizontalWall() { this.character = "─"; } + verticalWall() { this.character = "│"; } } class Point { @@ -114,13 +120,9 @@ class Grid { for (let room of this.#rooms) { for (let y = room.minY; y <= room.maxY; y++) { for (let x = room.minX; x <= room.maxX; x++) { - let charAtXY = room.charAt(x, y); - if (!charAtXY) { - continue; - } - + let point = new Point(x, y); let cell = this.cellAt(x, y); - cell.character = charAtXY; + room.transformCellAt(point, cell); cell.characterColor = p.color(255); } } @@ -169,24 +171,22 @@ class Room { ); } - charAt(x, y) { + transformCellAt(pt, cell) { const minX = this.minX; const minY = this.minY; 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 "│"; } + const x = pt.x; + const y = pt.y; - if ((x > minX && x < maxX) && (y > minY && y < maxY)) { - return "."; - } - - return undefined; + if (y === minY && x === minX) { cell.topLeftWall(); } + else if (y === minY && x === maxX) { cell.topRightWall(); } + else if (y === maxY && x === minX) { cell.bottomLeftWall(); } + else if (y === maxY && x === maxX) { cell.bottomRightWall(); } + else if (y === minY || y === maxY) { cell.horizontalWall(); } + else if (x === minX || x === maxX) { cell.verticalWall(); } + else if ((x > minX && x < maxX) && (y > minY && y < maxY)) { return cell.floor(); } } } From 78caa686c2c26b43fa68c95cf78bd9905489af32 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 3 Feb 2023 18:12:25 -0800 Subject: [PATCH 10/17] Remove BSPNode --- assets/scripts/nethack/dungeon.js | 129 ------------------------------ 1 file changed, 129 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index cc8e148..9c2f706 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -190,135 +190,6 @@ class Room { } } -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; - } -} - class NRandomRoomsGenerator { static MIN_ROOM_DIMENSION = 7; static MAX_ROOM_DIMENSION = 12; From c384c07051ad9e0bcfc238ce14379927365d4b52 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:18:09 -0800 Subject: [PATCH 11/17] Nethack map generator! --- assets/scripts/nethack/dungeon.js | 414 +++++++++++++++++++++++++++--- 1 file changed, 377 insertions(+), 37 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 9c2f706..75d06e0 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -1,4 +1,11 @@ +const DEBUG = true; +const NUMBER_OF_ROOMS = 12; +const TUNNEL_PASSES = 2; + class Cell { + static CORRIDOR = "#"; + static DOOR_CLOSED = "+"; + character; characterColor; backgroundColor; @@ -7,16 +14,24 @@ class Cell { this.character = char; } + empty() { this.character = " "; } floor() { this.character = "."; } upStair() { this.character = "<"; } downStair() { this.character = ">"; } - cooridor() { this.character = "#"; } + corridor() { this.character = Cell.CORRIDOR; } topLeftWall() { this.character = "┌"; } topRightWall() { this.character = "┐"; } bottomLeftWall() { this.character = "└"; } bottomRightWall() { this.character = "┘"; } horizontalWall() { this.character = "─"; } verticalWall() { this.character = "│"; } + doorClosed() { this.character = Cell.DOOR_CLOSED; } + + isEmpty() { return !this.character || this.character === " "; } + isDoor() { return this.character === Cell.DOOR_CLOSED; } + isCorridor() { return this.character === Cell.CORRIDOR; } + + canBecomeDoor() { return this.character === "─" || this.character === "│" } } class Point { @@ -24,9 +39,25 @@ class Point { y = 0; constructor(x, y) { - this.x = x; - this.y = y; + if (x) { this.x = x; } + if (y) { this.y = y; } } + + *neighbors() { + const x = this.x; + const y = this.y; + + yield new Point(x - 1, y - 1); + yield new Point(x, y - 1); + yield new Point(x + 1, y - 1); + yield new Point(x - 1, y); + yield new Point(x + 1, y); + yield new Point(x - 1, y + 1); + yield new Point(x, y + 1); + yield new Point(x + 1, y + 1); + } + + equalsPoint(other) { return this.x === other.x && this.y === other.y; } } class Size { @@ -56,6 +87,9 @@ class Rect { 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 width() { return this.size.width; } + get height() { return this.size.height; } get area() { return this.size.width * this.size.height; } insetRect(inset) { @@ -89,7 +123,6 @@ class Rect { class Grid { #size; #cells = []; - #rooms = []; constructor(width, height) { @@ -101,32 +134,20 @@ class Grid { } } - get width() { - return this.#size.width; - } + get bounds() { return new Rect(new Point(), this.#size); } + get width() { return this.#size.width; } + get height() { return this.#size.height; } + get rooms() { return this.#rooms; } - get height() { - return this.#size.height; - } - - generate(p, roomGenerator) { - this.#generateRooms(p, roomGenerator); + generate(p, roomGeneratorClass, tunnelGeneratorClass) { + this.#generateRooms(p, new roomGeneratorClass(this, NUMBER_OF_ROOMS)); this.#placeStairs(); + this.#digCorridors(p, new tunnelGeneratorClass(this, TUNNEL_PASSES)); } #generateRooms(p, generator) { + generator.generate(p); this.#rooms = generator.rooms; - - for (let room of this.#rooms) { - for (let y = room.minY; y <= room.maxY; y++) { - for (let x = room.minX; x <= room.maxX; x++) { - let point = new Point(x, y); - let cell = this.cellAt(x, y); - room.transformCellAt(point, cell); - cell.characterColor = p.color(255); - } - } - } } #placeStairs() { @@ -147,9 +168,13 @@ class Grid { } } - cellAt(x, y) { - return this.#cells[y * this.width + x]; + #digCorridors(p, generator) { + generator.generate(p); } + + pointIsInBounds(pt) { return pt.x >= 0 && pt.x < this.width && pt.y >= 0 && pt.y < this.height; } + + cellAt(x, y) { return this.#cells[y * this.width + x]; } } class Room { @@ -159,6 +184,8 @@ class Room { this.#rect = rect; } + get bounds() { return this.#rect; } + get minX() { return this.#rect.minX; } get minY() { return this.#rect.minY; } get maxX() { return this.#rect.maxX; } @@ -196,23 +223,16 @@ class NRandomRoomsGenerator { #numberOfRooms = 12; #rooms; + #grid; - #bounds; - - constructor(bounds, numberOfRooms) { - if (bounds) { - this.#bounds = bounds; - } + constructor(grid, numberOfRooms) { + this.#grid = grid; if (numberOfRooms) { this.#numberOfRooms = numberOfRooms; } } - get numberOfRooms() { - return this.#numberOfRooms; - } - get rooms() { if (!this.#rooms) { this.#generateRooms(); @@ -221,6 +241,32 @@ class NRandomRoomsGenerator { return this.#rooms; } + get #bounds() { return this.#grid.bounds; } + + generate(p) { + this.#generateRooms(); + + for (let i = 0; i < this.#rooms.length; i++) { + let room = this.#rooms[i]; + for (let y = room.minY; y <= room.maxY; y++) { + for (let x = room.minX; x <= room.maxX; x++) { + let point = new Point(x, y); + let cell = this.#grid.cellAt(x, y); + + room.transformCellAt(point, cell); + cell.characterColor = p.color(255); + + if (DEBUG) { + if (x == room.minX + 1 && y == room.minY + 1) { + cell.character = (i % 10).toString(); + cell.characterColor = p.color(255, 128, 255); + } + } + } + } + } + } + #generateRooms() { let rects = new Array(); @@ -247,10 +293,304 @@ class NRandomRoomsGenerator { rects.push(proposedRoomRect); } + rects.sort((a, b) => { + if (a.origin.x < b.origin.x) { return -1; } + if (a.origin.x > b.origin.x) { return 1; } + if (a.origin.y < b.origin.y) { return -1; } + if (a.origin.y > b.origin.y) { return 1; } + return 0; + }); + this.#rooms = rects.map(r => new Room(r.insetRect(1))); } } +class TunnelGenerator { + static MAX_DOORS = 100; + + #grid; + #passes = 3; + #numberOfDoorsPlaced = 0; + + constructor(grid, passes) { + this.#grid = grid; + if (passes) { this.#passes = passes; } + } + + generate(p) { + console.group("Digging tunnels"); + + if (this.#passes >= 1) { + this.#doPassOne(p); + } + + if (this.#passes >= 2) { + this.#doPassTwo(p); + } + + if (this.#passes >= 3) { + this.#doPassThree(p); + } + + console.groupEnd(); + } + + #doPassOne(p) { + console.group("Pass 1"); + this.#iterateAndConnectRoomsWithOffset(p, 1); + console.groupEnd(); + } + + #doPassTwo(p) { + console.group("Pass 2"); + this.#iterateAndConnectRoomsWithOffset(p, 2); + console.groupEnd(); + } + + #doPassThree(p) { + console.group("Pass 3"); + this.#iterateAndConnectRoomsWithOffset(p, 3); + console.groupEnd(); + } + + #iterateAndConnectRoomsWithOffset(p, offset) { + let rooms = this.#grid.rooms; + const numberOfRooms = rooms.length; + for (let i = 0; i < numberOfRooms - offset; i++) { + let fromRoom = rooms[i]; + let toRoom = rooms[i + offset]; + + let [fromPoint, toPoint] = this.#findPointFromRoomToRoom(fromRoom, toRoom); + + for (let neighbor of fromPoint.neighbors()) { + let cell = this.#grid.cellAt(neighbor.x, neighbor.y); + if ((neighbor.x === fromPoint.x || neighbor.y === fromPoint.y) && cell.canBecomeDoor()) { + cell.doorClosed(); + this.#numberOfDoorsPlaced++; + break; + } + } + + const successfullyFoundTargetPoint = this.#digCorridorFromPointToPoint(p, fromPoint, toPoint); + + if (successfullyFoundTargetPoint) { + for (let neighbor of toPoint.neighbors()) { + let cell = this.#grid.cellAt(neighbor.x, neighbor.y); + if ((neighbor.x === toPoint.x || neighbor.y === toPoint.y) && cell.canBecomeDoor()) { + cell.doorClosed(); + this.#numberOfDoorsPlaced++; + break; + } + } + } + } + } + + /** + * Dig a corridor from fromPoint to toPoint, assuming that both points are adjacent to valid locations for doors on + * the map. + * + * This is as close a copy of dig_corridor in the Nethack source as I could muster. It's not exactly pretty. This + * method assumed + */ + #digCorridorFromPointToPoint(p, fromPoint, toPoint) { + const MAX_STEPS = 500; + + if (!fromPoint || !toPoint) { + return; + } + + const fromX = fromPoint.x; + const toX = toPoint.x; + const fromY = fromPoint.y; + const toY = toPoint.y; + + let curX = fromX; + let curY = fromY; + + console.log(`Digging a corridor from (${fromX}, ${fromY}) to (${toX}, ${toY})`); + + let dx = 0; + let dy = 0; + + // Set up the initial direction of the dig. + if (toX > curX) { + dx = 1; + } else if (toY > curY) { + dy = 1; + } else if (toX < curX) { + dx = -1; + } else { + dy = -1; + } + + console.log("dx, dy", dx, dy); + curX -= dx; + curY -= dy; + + let steps = 0; + while (curX !== toX || curY !== toY) { + if (steps++ > MAX_STEPS) { + return false; + } + + curX += dx; + curY += dy; + + if (curX >= this.#grid.width - 1 || curX <= 0 || curY <= 0 || curY >= this.#grid.height - 1) { + return false; + } + + let cell = this.#grid.cellAt(curX, curY); + if (cell.isEmpty()) { + cell.corridor(); + } else if (!cell.isCorridor()) { + return false; + } + + let dix = Math.abs(curX - toX); + let diy = Math.abs(curY - toY); + + if (dix > diy && diy) { + const random = randomInt(dix - diy + 1); + if (!random) { + dix = 0; + } + } else if (diy > dix && dix) { + const random = randomInt(dix - diy + 1); + if (!random) { + diy = 0; + } + } + + if (dy && dix > diy) { + const ddx = curX > toX ? -1 : 1; + + let cell = this.#grid.cellAt(curX + ddx, curY); + if (cell.isEmpty() || cell.isCorridor()) { + dx = ddx; + dy = 0; + continue; + } + } else if (dx && diy > dix) { + const ddy = curY > toY ? -1 : 1; + + let cell = this.#grid.cellAt(curX, curY + ddy); + if (cell.isEmpty() || cell.isCorridor()) { + dy = ddy; + dx = 0; + continue; + } + } + + cell = this.#grid.cellAt(curX + dx, curY + dy); + if (cell.isEmpty() || cell.isCorridor()) { + continue; + } + + if (dx) { + dx = 0; + dy = toY < curY ? -1 : 1; + } else { + dy = 0; + dx = toX < curX ? -1 : 1; + } + + cell = this.#grid.cellAt(curX + dx, curY + dy); + if (cell.isEmpty() || cell.isCorridor()) { + continue; + } + + dy = -dy; + dx = -dx; + } + + return true; + } + + #findPointFromRoomToRoom(fromRoom, toRoom) { + const fromRoomBounds = fromRoom.bounds; + const toRoomBounds = toRoom.bounds; + + let fromPoint; + let toPoint; + if (fromRoomBounds.maxX < (toRoomBounds.minX - 1)) { + // fromRoom is farther left than toRoomBounds. + do { + fromPoint = new Point(fromRoomBounds.maxX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); + } while (!this.#canPlaceDoorAt(fromPoint)); + + do { + toPoint = new Point(toRoomBounds.minX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); + } while (!this.#canPlaceDoorAt(toPoint)); + + fromPoint.x += 1; + toPoint.x -= 1; + } else if (fromRoomBounds.minX > (toRoomBounds.maxX + 1)) { + // fromRoom is farther right than toRoomBounds. + do { + fromPoint = new Point(toRoomBounds.maxX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); + } while (!this.#canPlaceDoorAt(fromPoint)); + + do { + toPoint = new Point(fromRoomBounds.minX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); + } while (!this.#canPlaceDoorAt(toPoint)); + + fromPoint.x -= 1; + toPoint.x += 1; + } else if (fromRoomBounds.maxY < (toRoomBounds.minY - 1)) { + // fromRoom is above toRoom + do { + fromPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.maxY); + } while (!this.#canPlaceDoorAt(fromPoint)); + + do { + toPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.minY); + } while (!this.#canPlaceDoorAt(toPoint)); + + fromPoint.y += 1; + toPoint.y -= 1; + } else if (fromRoomBounds.minY > (toRoomBounds.maxY + 1)) { + // fromRoom is below toRoom + do { + fromPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.maxY); + } while (!this.#canPlaceDoorAt(fromPoint)); + + do { + toPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.minY); + } while (!this.#canPlaceDoorAt(toPoint)); + + fromPoint.y += 1; + toPoint.y -= 1; + } + + return [fromPoint, toPoint]; + } + + #canPlaceDoorAt(pt) { + if (this.#numberOfDoorsPlaced > TunnelGenerator.MAX_DOORS) { + return false; + } + + if (!this.#grid.cellAt(pt.x, pt.y).canBecomeDoor()) { + return false; + } + + for (let neighbor of pt.neighbors()) { + if (!this.#grid.pointIsInBounds(neighbor)) { + continue; + } + + let cell = this.#grid.cellAt(neighbor.x, neighbor.y); + if (cell.isDoor()) { + return false; + } + } + + return true; + } +} + function randomInt(n) { max = Math.floor(n); return Math.floor(Math.random() * max); @@ -282,7 +622,7 @@ new p5(p => { console.log(`Generating grid with size ${gridBounds.size.width} x ${gridBounds.size.height}`); grid = new Grid(gridBounds.size.width, gridBounds.size.height); - grid.generate(p, new NRandomRoomsGenerator(gridBounds)); + grid.generate(p, NRandomRoomsGenerator, TunnelGenerator); } p.draw = () => { From 6f0f1fd3373ee5c23ce18728373a42b1deffc6ba Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:18:49 -0800 Subject: [PATCH 12/17] Logging for the tunnel digger --- assets/scripts/nethack/dungeon.js | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 75d06e0..095d9ae 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -360,6 +360,8 @@ class TunnelGenerator { let fromRoom = rooms[i]; let toRoom = rooms[i + offset]; + console.log(`Connecting rooms ${i} to ${i + offset}:`, fromRoom.bounds, toRoom.bounds); + let [fromPoint, toPoint] = this.#findPointFromRoomToRoom(fromRoom, toRoom); for (let neighbor of fromPoint.neighbors()) { @@ -431,60 +433,87 @@ class TunnelGenerator { let steps = 0; while (curX !== toX || curY !== toY) { if (steps++ > MAX_STEPS) { + console.log("Exceeded max step count for this corridor"); return false; } curX += dx; curY += dy; + console.groupCollapsed(`${steps}: (${curX}, ${curY})`); + console.log(`dx = ${dx}, dy = ${dy}`); + if (curX >= this.#grid.width - 1 || curX <= 0 || curY <= 0 || curY >= this.#grid.height - 1) { + console.error(`Out of bounds: (${curX}, ${curY})`); + console.groupEnd(); return false; } let cell = this.#grid.cellAt(curX, curY); if (cell.isEmpty()) { + console.log("Digging corridor"); cell.corridor(); } else if (!cell.isCorridor()) { + console.error("Found a weird cell type:", cell.character); + console.groupEnd(); return false; } let dix = Math.abs(curX - toX); let diy = Math.abs(curY - toY); + console.log(`1. dix = ${dix}, diy = ${diy}`); + if (dix > diy && diy) { const random = randomInt(dix - diy + 1); + console.log(`Randomness: ${random}`); if (!random) { dix = 0; } } else if (diy > dix && dix) { const random = randomInt(dix - diy + 1); + console.log(`Randomness: ${random}`); if (!random) { diy = 0; } } + console.log(`2. dix = ${dix}, diy = ${diy}`); + if (dy && dix > diy) { const ddx = curX > toX ? -1 : 1; + console.log(`ddx = ${ddx}`); let cell = this.#grid.cellAt(curX + ddx, curY); + console.log(`Checking cell at (${curX + ddx}, ${curY})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { dx = ddx; dy = 0; + + console.log(`Adjusted dx = ${dx}, dy = ${dy}`); + console.groupEnd(); continue; } } else if (dx && diy > dix) { const ddy = curY > toY ? -1 : 1; + console.log(`ddy = ${ddy}`); let cell = this.#grid.cellAt(curX, curY + ddy); + console.log(`Checking cell at (${curX}, ${curY + ddy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { dy = ddy; dx = 0; + + console.log(`Adjusted dx = ${dx}, dy = ${dy}`); + console.groupEnd(); continue; } } cell = this.#grid.cellAt(curX + dx, curY + dy); + console.log(`2. Checking cell at (${curX + dx}, ${curY + dy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { + console.groupEnd(); continue; } @@ -496,13 +525,20 @@ class TunnelGenerator { dx = toX < curX ? -1 : 1; } + console.log(`Adjusting dx = ${dx}, dy = ${dy}`); + cell = this.#grid.cellAt(curX + dx, curY + dy); + console.log(`3. Checking cell at (${curX + dx}, ${curY + dy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { + console.groupEnd(); continue; } dy = -dy; dx = -dx; + console.log(`Adjusting dx = ${dx}, dy = ${dy}`); + + console.groupEnd(); } return true; From 3c25f2041a7cc4a0efdf8b61630464fe30697017 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:18:52 -0800 Subject: [PATCH 13/17] Revert "Logging for the tunnel digger" This reverts commit 6f0f1fd3373ee5c23ce18728373a42b1deffc6ba. --- assets/scripts/nethack/dungeon.js | 36 ------------------------------- 1 file changed, 36 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 095d9ae..75d06e0 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -360,8 +360,6 @@ class TunnelGenerator { let fromRoom = rooms[i]; let toRoom = rooms[i + offset]; - console.log(`Connecting rooms ${i} to ${i + offset}:`, fromRoom.bounds, toRoom.bounds); - let [fromPoint, toPoint] = this.#findPointFromRoomToRoom(fromRoom, toRoom); for (let neighbor of fromPoint.neighbors()) { @@ -433,87 +431,60 @@ class TunnelGenerator { let steps = 0; while (curX !== toX || curY !== toY) { if (steps++ > MAX_STEPS) { - console.log("Exceeded max step count for this corridor"); return false; } curX += dx; curY += dy; - console.groupCollapsed(`${steps}: (${curX}, ${curY})`); - console.log(`dx = ${dx}, dy = ${dy}`); - if (curX >= this.#grid.width - 1 || curX <= 0 || curY <= 0 || curY >= this.#grid.height - 1) { - console.error(`Out of bounds: (${curX}, ${curY})`); - console.groupEnd(); return false; } let cell = this.#grid.cellAt(curX, curY); if (cell.isEmpty()) { - console.log("Digging corridor"); cell.corridor(); } else if (!cell.isCorridor()) { - console.error("Found a weird cell type:", cell.character); - console.groupEnd(); return false; } let dix = Math.abs(curX - toX); let diy = Math.abs(curY - toY); - console.log(`1. dix = ${dix}, diy = ${diy}`); - if (dix > diy && diy) { const random = randomInt(dix - diy + 1); - console.log(`Randomness: ${random}`); if (!random) { dix = 0; } } else if (diy > dix && dix) { const random = randomInt(dix - diy + 1); - console.log(`Randomness: ${random}`); if (!random) { diy = 0; } } - console.log(`2. dix = ${dix}, diy = ${diy}`); - if (dy && dix > diy) { const ddx = curX > toX ? -1 : 1; - console.log(`ddx = ${ddx}`); let cell = this.#grid.cellAt(curX + ddx, curY); - console.log(`Checking cell at (${curX + ddx}, ${curY})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { dx = ddx; dy = 0; - - console.log(`Adjusted dx = ${dx}, dy = ${dy}`); - console.groupEnd(); continue; } } else if (dx && diy > dix) { const ddy = curY > toY ? -1 : 1; - console.log(`ddy = ${ddy}`); let cell = this.#grid.cellAt(curX, curY + ddy); - console.log(`Checking cell at (${curX}, ${curY + ddy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { dy = ddy; dx = 0; - - console.log(`Adjusted dx = ${dx}, dy = ${dy}`); - console.groupEnd(); continue; } } cell = this.#grid.cellAt(curX + dx, curY + dy); - console.log(`2. Checking cell at (${curX + dx}, ${curY + dy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { - console.groupEnd(); continue; } @@ -525,20 +496,13 @@ class TunnelGenerator { dx = toX < curX ? -1 : 1; } - console.log(`Adjusting dx = ${dx}, dy = ${dy}`); - cell = this.#grid.cellAt(curX + dx, curY + dy); - console.log(`3. Checking cell at (${curX + dx}, ${curY + dy})`, cell.character); if (cell.isEmpty() || cell.isCorridor()) { - console.groupEnd(); continue; } dy = -dy; dx = -dx; - console.log(`Adjusting dx = ${dx}, dy = ${dy}`); - - console.groupEnd(); } return true; From 94a09d0c819b2f29dffb7b9865cb14076c699988 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:23:39 -0800 Subject: [PATCH 14/17] Update Nethack logfile for electra --- data/nethack/logfile/electra.json | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/data/nethack/logfile/electra.json b/data/nethack/logfile/electra.json index 94ee19f..d0f871e 100644 --- a/data/nethack/logfile/electra.json +++ b/data/nethack/logfile/electra.json @@ -1,5 +1,5 @@ { - "generated": "2023-01-22T14:30:06.645622", + "generated": "2023-02-04T18:23:39.913496", "logfile": [ { "score": 1395, @@ -1350,6 +1350,56 @@ "user_id": 501, "nethack_version": "3.6.6" } + }, + { + "score": 0, + "dungeon": { + "n": 0, + "name": "The Dungeons of Doom", + "level": { + "n": 1, + "descriptive": "Level 1" + }, + "max_level": { + "n": 1, + "descriptive": "Level 1" + } + }, + "end_date": "2023-02-04", + "start_date": "2023-02-04", + "character": { + "name": "Eryn", + "descriptor": "Eryn-Val-Hum-Fem-Law", + "hp": { + "n": 16, + "max": 16 + }, + "role": { + "short": "Val", + "descriptive": "Valkyrie" + }, + "race": { + "short": "Hum", + "descriptive": "Human" + }, + "gender": { + "short": "Fem", + "descriptive": "Female" + }, + "alignment": { + "short": "Law", + "descriptive": "Lawful" + } + }, + "death": { + "n": 0, + "cause": "quit" + }, + "system": { + "hostname": "electra", + "user_id": 501, + "nethack_version": "3.6.6" + } } ] } From 4f4312b944c9ecc2ca69feedc8f9df94682a164d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:26:32 -0800 Subject: [PATCH 15/17] Don't secure assets on localhost; it just messes with the JS debugger --- layouts/partials/secure_asset.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/layouts/partials/secure_asset.html b/layouts/partials/secure_asset.html index b6e83c4..a24bd13 100644 --- a/layouts/partials/secure_asset.html +++ b/layouts/partials/secure_asset.html @@ -1,5 +1,9 @@ {{ $asset := dict }} {{ with resources.Get . }} - {{ $asset = dict "Resource" . "Secure" (. | resources.Fingerprint "md5") }} + {{ $secureAsset := . }} + {{ if not (in (site.BaseURL | string) "localhost") }} + {{ $secureAsset = $secureAsset | resources.Fingerprint "md5" }} + {{ end }} + {{ $asset = dict "Resource" . "Secure" $secureAsset }} {{ end }} {{ return $asset }} From 9f1c6e134592a60060d02c4ef20608ce57c9596a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 18:26:41 -0800 Subject: [PATCH 16/17] Make site a phony target --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 10a8c04..e90d7d7 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ HOSTNAME=$(shell hostname -s) NETHACK_LOGFILE=$(shell nethack --showpaths | grep scoredir | sed 's/.*"\(.*\)".*/\1/g')/logfile NETHACK_LOGFILE_DATA_FILE=data/nethack/logfile/$(HOSTNAME).json -.PHONY: deploy clean +.PHONY: site deploy clean -site: public/index.html nethack +site: nethack @echo "Building site" hugo From 61be4cad61336a7c3545ad9298bb8b86f0082be8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Feb 2023 19:20:52 -0800 Subject: [PATCH 17/17] Clean up the find to and from point method in the Tunnel generator --- assets/scripts/nethack/dungeon.js | 185 +++++++++++++++++++++++------- 1 file changed, 146 insertions(+), 39 deletions(-) diff --git a/assets/scripts/nethack/dungeon.js b/assets/scripts/nethack/dungeon.js index 75d06e0..7277d9a 100644 --- a/assets/scripts/nethack/dungeon.js +++ b/assets/scripts/nethack/dungeon.js @@ -1,6 +1,5 @@ -const DEBUG = true; const NUMBER_OF_ROOMS = 12; -const TUNNEL_PASSES = 2; +const TUNNEL_PASSES = 3; class Cell { static CORRIDOR = "#"; @@ -92,6 +91,18 @@ class Rect { get height() { return this.size.height; } get area() { return this.size.width * this.size.height; } + *xCoordinates() { + for (let x = this.minX; x <= this.maxX; x++) { + yield x; + } + } + + *yCoordinates() { + for (let y = this.minY; y <= this.maxY; y++) { + yield y; + } + } + insetRect(inset) { const twiceInset = 2 * inset; @@ -255,13 +266,6 @@ class NRandomRoomsGenerator { room.transformCellAt(point, cell); cell.characterColor = p.color(255); - - if (DEBUG) { - if (x == room.minX + 1 && y == room.minY + 1) { - cell.character = (i % 10).toString(); - cell.characterColor = p.color(255, 128, 255); - } - } } } } @@ -361,8 +365,15 @@ class TunnelGenerator { let toRoom = rooms[i + offset]; let [fromPoint, toPoint] = this.#findPointFromRoomToRoom(fromRoom, toRoom); + if (!fromPoint || !toPoint) { + continue; + } for (let neighbor of fromPoint.neighbors()) { + if (!this.#grid.pointIsInBounds(neighbor)) { + continue; + } + let cell = this.#grid.cellAt(neighbor.x, neighbor.y); if ((neighbor.x === fromPoint.x || neighbor.y === fromPoint.y) && cell.canBecomeDoor()) { cell.doorClosed(); @@ -375,6 +386,10 @@ class TunnelGenerator { if (successfullyFoundTargetPoint) { for (let neighbor of toPoint.neighbors()) { + if (!this.#grid.pointIsInBounds(neighbor)) { + continue; + } + let cell = this.#grid.cellAt(neighbor.x, neighbor.y); if ((neighbor.x === toPoint.x || neighbor.y === toPoint.y) && cell.canBecomeDoor()) { cell.doorClosed(); @@ -408,8 +423,6 @@ class TunnelGenerator { let curX = fromX; let curY = fromY; - console.log(`Digging a corridor from (${fromX}, ${fromY}) to (${toX}, ${toY})`); - let dx = 0; let dy = 0; @@ -424,7 +437,6 @@ class TunnelGenerator { dy = -1; } - console.log("dx, dy", dx, dy); curX -= dx; curY -= dy; @@ -512,53 +524,148 @@ class TunnelGenerator { const fromRoomBounds = fromRoom.bounds; const toRoomBounds = toRoom.bounds; + let foundFromPoint = false; + let foundToPoint = false; + let fromPoint; let toPoint; - if (fromRoomBounds.maxX < (toRoomBounds.minX - 1)) { - // fromRoom is farther left than toRoomBounds. - do { - fromPoint = new Point(fromRoomBounds.maxX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); - } while (!this.#canPlaceDoorAt(fromPoint)); + if (fromRoomBounds.maxX < toRoomBounds.minX) { + // fromRoom is farther left than toRoom + + fromPoint = new Point(fromRoomBounds.maxX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); + foundFromPoint = this.#canPlaceDoorAt(fromPoint); - do { - toPoint = new Point(toRoomBounds.minX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); - } while (!this.#canPlaceDoorAt(toPoint)); + for (let y of fromRoomBounds.yCoordinates()) { + fromPoint.y = y; + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + if (foundFromPoint) { + break; + } + } + + if (!foundFromPoint) { + return []; + } + + toPoint = new Point(toRoomBounds.minX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); + foundToPoint = this.#canPlaceDoorAt(toPoint); + + for (let y of toRoomBounds.yCoordinates()) { + toPoint.y = y; + foundToPoint = this.#canPlaceDoorAt(toPoint); + if (foundToPoint) { + break; + } + } + + if (!foundToPoint) { + return []; + } fromPoint.x += 1; toPoint.x -= 1; - } else if (fromRoomBounds.minX > (toRoomBounds.maxX + 1)) { + } else if (fromRoomBounds.minX > toRoomBounds.maxX) { // fromRoom is farther right than toRoomBounds. - do { - fromPoint = new Point(toRoomBounds.maxX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); - } while (!this.#canPlaceDoorAt(fromPoint)); - do { - toPoint = new Point(fromRoomBounds.minX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); - } while (!this.#canPlaceDoorAt(toPoint)); + fromPoint = new Point(toRoomBounds.maxX, toRoomBounds.minY + 1 + randomInt(toRoomBounds.height - 2)); + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + + for (let y of toRoomBounds.yCoordinates()) { + fromPoint.y = y; + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + if (foundFromPoint) { + break; + } + } + + if (!foundFromPoint) { + return []; + } + + toPoint = new Point(fromRoomBounds.minX, fromRoomBounds.minY + 1 + randomInt(fromRoomBounds.height - 2)); + foundToPoint = this.#canPlaceDoorAt(toPoint); + + for (let y of fromRoomBounds.yCoordinates()) { + toPoint.y = y; + foundToPoint = this.#canPlaceDoorAt(toPoint); + if (foundToPoint) { + break; + } + } + + if (!foundToPoint) { + return []; + } fromPoint.x -= 1; toPoint.x += 1; } else if (fromRoomBounds.maxY < (toRoomBounds.minY - 1)) { // fromRoom is above toRoom - do { - fromPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.maxY); - } while (!this.#canPlaceDoorAt(fromPoint)); - do { - toPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.minY); - } while (!this.#canPlaceDoorAt(toPoint)); + fromPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.maxY); + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + + for (let x of fromRoomBounds.xCoordinates()) { + fromPoint.x = x; + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + if (foundFromPoint) { + break; + } + } + + if (!foundFromPoint) { + return []; + } + + toPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.minY); + foundToPoint = this.#canPlaceDoorAt(toPoint); + + for (let x of toRoomBounds.xCoordinates()) { + toPoint.x = x; + foundToPoint = this.#canPlaceDoorAt(toPoint); + if (foundToPoint) { + break; + } + } + + if (!foundToPoint) { + return []; + } fromPoint.y += 1; toPoint.y -= 1; } else if (fromRoomBounds.minY > (toRoomBounds.maxY + 1)) { // fromRoom is below toRoom - do { - fromPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.maxY); - } while (!this.#canPlaceDoorAt(fromPoint)); - do { - toPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.minY); - } while (!this.#canPlaceDoorAt(toPoint)); + fromPoint = new Point(toRoomBounds.minX + 1 + randomInt(toRoomBounds.width - 2), toRoomBounds.maxY); + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + + for (let x of toRoomBounds.xCoordinates()) { + fromPoint.x = x; + foundFromPoint = this.#canPlaceDoorAt(fromPoint); + if (foundFromPoint) { + break; + } + } + + if (!foundFromPoint) { + return []; + } + + toPoint = new Point(fromRoomBounds.minX + 1 + randomInt(fromRoomBounds.width - 2), fromRoomBounds.minY); + foundToPoint = this.#canPlaceDoorAt(toPoint); + + for (let x of fromRoomBounds.xCoordinates()) { + toPoint.x = x; + foundToPoint = this.#canPlaceDoorAt(toPoint); + if (foundToPoint) { + break; + } + } + + if (!foundToPoint) { + return []; + } fromPoint.y += 1; toPoint.y -= 1;