Merge branch 'nethack-background'
This commit is contained in:
commit
4254726189
8 changed files with 889 additions and 4 deletions
4
Makefile
4
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
|
||||
|
||||
|
|
753
assets/scripts/nethack/dungeon.js
Normal file
753
assets/scripts/nethack/dungeon.js
Normal file
|
@ -0,0 +1,753 @@
|
|||
const NUMBER_OF_ROOMS = 12;
|
||||
const TUNNEL_PASSES = 3;
|
||||
|
||||
class Cell {
|
||||
static CORRIDOR = "#";
|
||||
static DOOR_CLOSED = "+";
|
||||
|
||||
character;
|
||||
characterColor;
|
||||
backgroundColor;
|
||||
|
||||
constructor(char, charColor) {
|
||||
this.character = char;
|
||||
}
|
||||
|
||||
empty() { this.character = " "; }
|
||||
floor() { this.character = "."; }
|
||||
upStair() { this.character = "<"; }
|
||||
downStair() { 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 {
|
||||
x = 0;
|
||||
y = 0;
|
||||
|
||||
constructor(x, 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 {
|
||||
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 width() { return this.size.width; }
|
||||
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;
|
||||
|
||||
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 {
|
||||
#size;
|
||||
#cells = [];
|
||||
#rooms = [];
|
||||
|
||||
constructor(width, 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(" ");
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
#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 {
|
||||
#rect;
|
||||
|
||||
constructor(rect) {
|
||||
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; }
|
||||
get maxY() { return this.#rect.maxY; }
|
||||
|
||||
randomPoint() {
|
||||
return new Point(
|
||||
this.#rect.minX + 1 + randomInt(this.#rect.size.width - 2),
|
||||
this.#rect.minY + 1 + randomInt(this.#rect.size.height - 2)
|
||||
);
|
||||
}
|
||||
|
||||
transformCellAt(pt, cell) {
|
||||
const minX = this.minX;
|
||||
const minY = this.minY;
|
||||
const maxX = this.maxX;
|
||||
const maxY = this.maxY;
|
||||
|
||||
const x = pt.x;
|
||||
const y = pt.y;
|
||||
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
|
||||
class NRandomRoomsGenerator {
|
||||
static MIN_ROOM_DIMENSION = 7;
|
||||
static MAX_ROOM_DIMENSION = 12;
|
||||
|
||||
#numberOfRooms = 12;
|
||||
#rooms;
|
||||
#grid;
|
||||
|
||||
constructor(grid, numberOfRooms) {
|
||||
this.#grid = grid;
|
||||
|
||||
if (numberOfRooms) {
|
||||
this.#numberOfRooms = numberOfRooms;
|
||||
}
|
||||
}
|
||||
|
||||
get rooms() {
|
||||
if (!this.#rooms) {
|
||||
this.#generateRooms();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
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);
|
||||
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();
|
||||
this.#numberOfDoorsPlaced++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const successfullyFoundTargetPoint = this.#digCorridorFromPointToPoint(p, fromPoint, toPoint);
|
||||
|
||||
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();
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 foundFromPoint = false;
|
||||
let foundToPoint = false;
|
||||
|
||||
let fromPoint;
|
||||
let toPoint;
|
||||
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);
|
||||
|
||||
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) {
|
||||
// fromRoom is farther right than toRoomBounds.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let grid;
|
||||
|
||||
new p5(p => {
|
||||
const CELL_WIDTH = 20;
|
||||
const CELL_HEIGHT = Math.floor(CELL_WIDTH * 1.3);
|
||||
|
||||
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");
|
||||
|
||||
const gridBounds = Rect.fromCoordinates(
|
||||
0, 0,
|
||||
Math.ceil(canvasWidth / CELL_WIDTH) - 1, Math.ceil(canvasHeight / CELL_HEIGHT) - 1
|
||||
);
|
||||
|
||||
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, NRandomRoomsGenerator, TunnelGenerator);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
: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;
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<body>
|
||||
{{ block "body" . -}}
|
||||
{{ block "before" . }}{{ end }}
|
||||
{{ block "header" . }}{{ partial "header.html" .}}{{ end }}
|
||||
<main class="{{ .Type }} {{ .Kind }}{{ if gt (len .Pages) 0 }} list{{ 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 }}
|
|
@ -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 }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue