Add nethack-rooms-and-corridors-generator; make nethack-level-generator into a folder

This commit is contained in:
Eryn Wells 2023-02-12 10:31:48 -08:00
parent 202d8177be
commit 77106032f1
4 changed files with 412 additions and 44 deletions

View file

@ -1,43 +0,0 @@
---
title: "Nethack's Rooms & Corridors Generator"
date: 2023-02-05T09:08:07-08:00
draft: true
series: "Nethack Level Generation"
categories: Tech
tags: [Nethack, Programming]
---
## Rectangles
## Placing Rooms
[`makerooms`][makerooms_func] uses a "random rect" algorithm to create up to `MAXNROFROOMS` rooms. It iteratively
generates a random rectancle
`create_room`
`sort_rooms`
## Digging Corridors
[`makecorridors`][makecorridors_func]
[`join`][join_func]
`dig_corridor`
## Special Features
`make_niches`
`do_vault`
`create_vault`
## Special Rooms
[nh36]: https://github.com/NetHack/NetHack/tree/NetHack-3.6
[mklevc_file]: https://github.com/NetHack/NetHack/blob/NetHack-3.6/src/mklev.c
[makerooms_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L223
[makecorridors_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L319
[join_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L244

View file

@ -0,0 +1,64 @@
---
title: "Nethack's Rooms & Corridors Generator"
date: 2023-02-05T09:08:07-08:00
draft: true
series: "Nethack Level Generation"
categories: Tech
tags: [Nethack, Programming]
---
This post is one of a series of posts about Nethack's level generation
algorithms. You might want to start [at the beginning][series].
## Rectangles
Nethack builds a list of random rectangles each time it generates new level. The
list is initialized with a single rect the size of the map, 80 by 21. As new
rectangles are needed, larger ones are broken down into smaller ones by the
`split_rects` function in such a way that no rectangle overlaps any other. Up to
50 rectangeles can be generated this way, placed randomly around the map, and
each one is no smaller than 4 by 3.
## Placing Rooms
[`makerooms`][makerooms_func] uses the rectangle list above to create up to 50
rooms. If enough rooms have been generated, it will also try to generate a
[vault](#vaults).
In each turn of the loop, `makerooms` calls [`create_room`][create_room_func],
which tries to create a room.
`sort_rooms`
## Digging Corridors
After all the rooms have been placed, the level generator places corridors. It
tries to connect as many rooms to each other as possible, up to a maximum number
of doors.
[`makecorridors`][makecorridors_func]
[`join`][join_func]
`dig_corridor`
## Niches
Niches are small, one tile rooms behind locked, hidden doors. The code refers to
these as niches, though I've most often heard players refer to them as closets.
`make_niches`
## Vaults
`do_vault`
`create_vault`
## Special Rooms
[series]: {{< ref "/series/nethack-level-generation" >}}
[makerooms_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L223
[create_room_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/sp_lev.c#L1127
[makecorridors_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L319
[join_func]: https://github.com/NetHack/NetHack/blob/59b117c655731bdf1f8b92c57bdb786119927f3a/src/mklev.c#L244

View file

@ -0,0 +1,347 @@
"use strict";
/// @see ROWNO
const NUMBER_OF_ROWS = 21;
/// @see COLNO
const NUMBER_OF_COLS = 80;
/// @see XLIM
const RECT_WIDTH_LIMIT = 4;
/// @see YLIM
const RECT_HEIGHT_LIMIT = 3;
/// @see MAXRECT
const MAX_NUMBER_OF_RECTS = 50;
export class Rect {
lowX = 0;
lowY = 0;
highX = 0;
highY = 0;
constructor(lx = 0, ly = 0, hx = 0, hy = 0) {
if (lx) { this.lowX = lx; }
if (ly) { this.lowY = ly; }
if (hx) { this.highX = hx; }
if (hy) { this.highY = hy; }
}
}
/**
* A re-implementation of NetHack's rect.c, hewing as close to the implementation as possible.
*/
export class Rects {
#rects = [];
#numberOfRects = 0;
#maxNumbersOfRects;
constructor(numberOfSlots = MAX_NUMBER_OF_RECTS) {
this.#maxNumbersOfRects = numberOfSlots;
this.initialize(new Rect(0, 0, NUMBER_OF_ROWS, NUMBER_OF_COLS));
}
get numberOfRects() {
return this.#numberOfRects;
}
/// @see init_rect
initialize(baseRect) {
this.#rects = new Array(this.#maxNumbersOfRects);
if (baseRect) {
this.#rects[0] = baseRect;
this.#numberOfRects = 1;
}
}
/// @see get_rect
get(rect) {
for (let i = 0; i < this.#numberOfRects; i++) {
let storedRect = this.#rects[i];
if ( rect.lowX >= storedRect.lowX
&& rect.lowY >= storedRect.lowY
&& rect.highX <= storedRect.highX
&& rect.highY <= storedRect.highY)
{
return storedRect;
}
}
return null;
}
/// @see get_rect_ind
getIndex(rect) {
for (let i = 0; i < this.#numberOfRects; i++) {
let storedRect = this.#rects[i];
if ( rect.lowx === storedRect.lowX
&& rect.lowY === storedRect.lowY
&& rect.highX === storedRect.highX
&& rect.highY === storedRect.highY)
{
return i;
}
}
return undefined;
}
/// @see add_rect
add(rect) {
if (this.#numberOfRects > this.#maxNumbersOfRects) {
console.error("Exceeded maximum number of rects");
return;
}
if (this.get(rect)) {
return;
}
this.#rects[this.#numberOfRects] = rect;
this.#numberOfRects++;
console.debug(`Added rect (n = ${this.#numberOfRects})`, rect);
}
/// @see remove_rect
remove(rect) {
let indexOfRect = this.getIndex(rect);
if (indexOfRect < 0) {
return;
}
this.#rects[indexOfRect] = this.#rects[--this.#numberOfRects];
console.debug(`Removed rect (n = ${this.#numberOfRects})`, rect);
}
/// @see rnd_rect
random() {
assert(this.#numberOfRects > 0);
const numberOfRects = this.#numberOfRects;
return numberOfRects > 0 ? this.#rects[randomInt(numberOfRects)] : undefined;
}
/// @see intersect
intersect(rectA, rectB) {
if ( rectB.lowX > rectA.highX
|| rectB.lowY > rectA.highY
|| rectB.highX < rectA.lowX
|| rectB.highY < rectA.lowY)
{
return null;
}
let intersectingRect = new Rect(
rectB.lowX > rectA.lowX ? rectB.lowX : rectA.lowX,
rectB.lowY > rectA.lowY ? rectB.lowY : rectA.lowY,
rectB.highX > rectA.highX ? rectA.highX : rectB.highX,
rectB.highY > rectA.highY ? rectA.highY : rectB.highY
);
if ( intersectingRect.lowX > intersectingRect.highX
|| intersectingRect.lowY > intersectingRect.highY)
{
return null;
}
return intersectingRect;
}
/// @see split_rects
split(rectA, rectB) {
let outputRect;
let oldRect = rectA;
this.remove(rectA);
for (let i = this.#numberOfRects - 1; i >= 0; i--) {
let storedRect = this.#rects[i];
let intersectingRect = this.intersect(storedRect, rectB);
if (intersectingRect) {
outputRect = this.split(storedRect);
}
}
const rectHeightLimitTimes2 = 2 * RECT_HEIGHT_LIMIT;
const rectHeightLimitPlus1 = RECT_HEIGHT_LIMIT + 1;
if (rectB.lowY - oldRect.lowY - 1 > (oldRect.highY < (NUMBER_OF_ROWS - 1) ? rectHeightLimitTimes2 : rectHeightLimitPlus1) + 4) {
outputRect = oldRect;
outputRect.highY = rectB.lowY - 1;
this.add(outputRect);
}
if (rectB.lowY - oldRect.lowX - 1 > (oldRect.highX < NUMBER_OF_COLS - 1 ? rectHeightLimitTimes2 : rectHeightLimitPlus1) + 4) {
outputRect = oldRect;
outputRect.highX = rectB.lowX - 2;
this.add(outputRect);
}
if (oldRect.highY - rectB.highY - 1 > (oldRect.lowY > 0 ? rectHeightLimitTimes2 : rectHeightLimitPlus1) + 4) {
outputRect = oldRect;
outputRect.lowY = rectB.highY + 2;
this.add(outputRect);
}
if (oldRect.highX - rectB.highX - 1 > (oldRect.lowX > 0 ? rectHeightLimitTimes2 : rectHeightLimitPlus1) + 4) {
outputRect = oldRect;
outputRect.lowX = rectB.highX + 2;
this.add(outputRect);
}
}
}
export class SpecialLevel {
#rects;
#rooms = [];
// TODO: What is this?
#smeq = [];
#numberOfRooms = 0;
constructor(rects) {
this.#rects = rects;
this.#numberOfRooms = 0;
}
createRoom(x, y, width, height, xAlignment, yAlignment, roomType, isLit) {
// TODO: roomType
let isVault = false;
// TODO: isLit
let xAbs;
let yAbs;
let xTmp;
let yTmp;
let widthTmp;
let heightTmp;
let tryCount = 0;
let rectA;
let rectB;
do {
xTmp = x;
yTmp = y;
heightTmp = height;
widthTmp = width;
let xAlignmentTmp = xAlignment;
let yAlignmentTmp = yAlignment;
if ( (xTmp === null
&& yTmp === null
&& heightTmp === null
&& widthTmp === null
&& xAlignmentTmp === null
&& yAlignmentTmp === null)
|| isVault)
{
rectA = this.#rects.random();
if (!rectA) {
console.error("No more rects...");
return false;
}
let lx = rectA.lowX;
let ly = rectA.lowY;
let hx = rectA.highX;
let hy = rectA.highY;
let dx;
let dy;
if (isVault) {
dx = 1;
dy = 1;
} else {
dx = 2 + randomInt((hx - lx > 28) ? 12 : 8);
dy = 2 + randomInt(4);
if (dx * dy > 50) {
dy = 50 / dx;
}
}
let xBorder;
if (lx > 0 && hx < NUMBER_OF_COLS - 1) {
xBorder = 2 * RECT_WIDTH_LIMIT;
} else {
xBorder = RECT_WIDTH_LIMIT + 1;
}
let yBorder;
if (ly > 0 && hy < NUMBER_OF_ROWS - 1) {
yBorder = 2 * RECT_WIDTH_LIMIT;
} else {
yBorder = RECT_WIDTH_LIMIT + 1;
}
if (hx - lx < dx + 3 + xBorder || hy - ly < dy + 3 + yBorder) {
rectA = null;
continue;
}
let xAbs = lx
+ (lx > 0 ? RECT_WIDTH_LIMIT : 3)
+ randomInt(hx - (lx > 0 ? lx : 3) - dx - xBorder + 1);
let yAbs = ly
+ (ly > 0 ? RECT_HEIGHT_LIMIT : 2)
+ randomInt(hy - (ly > 0 ? ly : 2) - dy - yBorder + 1);
if (ly === 0 && hy >= (NUMBER_OF_ROWS - 1) && (!this.#numberOfRooms || !randomInt(this.#numberOfRooms)) && (yAbs + dy > NUMBER_OF_ROWS / 2)) {
yAbs = randomIntPlusOffset(3, 2);
if (this.#numberOfRooms < 4 && dy > 1) {
dy--;
}
}
if (!this.checkRoom(xAbs, dx, yAbs, dy, isVault)) {
rectA = null;
continue;
}
let widthTmp = dx + 1;
let heightTmp = dy + 1;
rectB = new Rect(xAbs - 1, yAbs - 1, xAbs + widthTmp, yAbs + heightTmp);
} else {
console.error("Not implemented yet!");
}
} while (++tryCount < 100 && !rectA);
if (!rectA) {
return false;
}
this.#rects.split(rectA, rectB);
if (!isVault) {
this.#smeq[this.#numberOfRooms] = this.#numberOfRooms;
this.addRoom(xAbs, yAbs, xAbs + widthTmp - 1, yAbs + heightTmp - 1, isLit, roomType, false);
} else {
this.#rooms[this.#numberOfRooms].lx = xAbs;
this.#rooms[this.#numberOfRooms].ly = yAbs;
}
return true;
}
checkRoom(x, dx, y, dy, isVault) {
return true;
}
addRoom(x, y, width, height, isLit, roomType, isVault) {
let room = this.#rooms[this.#numberOfRooms];
}
}
function randomInt(n) {
const max = Math.floor(n);
return Math.floor(Math.random() * max);
}
function randomIntPlusOffset(n, offset) {
return randomInt(n) + offset;
}
const randomRect = p => {
};

View file

@ -11,7 +11,7 @@ mean it's easy to understand.
I'm basing this entire series on the [Nethack 3.6][nh36] branch.
## Basics
### Basics
It's written in C, using C89 style function declarations. You see a lot of functions defined like this, with the types
of arguments defined below the function declaration: