Use rot-js to create dungeon layout
This commit is contained in:
@@ -85,7 +85,16 @@ describe('Combat Simulation', () => {
|
||||
it("should attack if player is adjacent", () => {
|
||||
const actors = new Map<EntityId, Actor>();
|
||||
const player = { id: 1, category: "combatant", isPlayer: true, pos: { x: 4, y: 3 }, stats: createTestStats() } as any;
|
||||
const enemy = { id: 2, category: "combatant", isPlayer: false, pos: { x: 3, y: 3 }, stats: createTestStats() } as any;
|
||||
const enemy = {
|
||||
id: 2,
|
||||
category: "combatant",
|
||||
isPlayer: false,
|
||||
pos: { x: 3, y: 3 },
|
||||
stats: createTestStats(),
|
||||
// Set AI state to pursuing so the enemy will attack when adjacent
|
||||
aiState: "pursuing",
|
||||
lastKnownPlayerPos: { x: 4, y: 3 }
|
||||
} as any;
|
||||
actors.set(1, player);
|
||||
actors.set(2, enemy);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { type World, type EntityId, type RunState, type Tile, type Actor, type V
|
||||
import { idx } from "./world-logic";
|
||||
import { GAME_CONFIG } from "../../core/config/GameConfig";
|
||||
import { seededRandom } from "../../core/math";
|
||||
import * as ROT from "rot-js";
|
||||
|
||||
interface Room {
|
||||
x: number;
|
||||
@@ -11,7 +12,7 @@ interface Room {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a procedural dungeon world with rooms and corridors
|
||||
* Generates a procedural dungeon world with rooms and corridors using rot-js Uniform algorithm
|
||||
* @param floor The floor number (affects difficulty)
|
||||
* @param runState Player's persistent state across floors
|
||||
* @returns Generated world and player ID
|
||||
@@ -23,7 +24,10 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
||||
|
||||
const random = seededRandom(floor * 12345);
|
||||
|
||||
const rooms = generateRooms(width, height, tiles, random);
|
||||
// Set ROT's RNG seed for consistent dungeon generation
|
||||
ROT.RNG.setSeed(floor * 12345);
|
||||
|
||||
const rooms = generateRooms(width, height, tiles);
|
||||
|
||||
// Place player in first room
|
||||
const firstRoom = rooms[0];
|
||||
@@ -62,80 +66,39 @@ export function generateWorld(floor: number, runState: RunState): { world: World
|
||||
}
|
||||
|
||||
|
||||
function generateRooms(width: number, height: number, tiles: Tile[], random: () => number): Room[] {
|
||||
function generateRooms(width: number, height: number, tiles: Tile[]): Room[] {
|
||||
const rooms: Room[] = [];
|
||||
const numRooms = GAME_CONFIG.map.minRooms + Math.floor(random() * (GAME_CONFIG.map.maxRooms - GAME_CONFIG.map.minRooms + 1));
|
||||
|
||||
const fakeWorldForIdx = { width, height };
|
||||
// Create rot-js Uniform dungeon generator
|
||||
const dungeon = new ROT.Map.Uniform(width, height, {
|
||||
roomWidth: [GAME_CONFIG.map.roomMinWidth, GAME_CONFIG.map.roomMaxWidth],
|
||||
roomHeight: [GAME_CONFIG.map.roomMinHeight, GAME_CONFIG.map.roomMaxHeight],
|
||||
roomDugPercentage: 0.3, // 30% of the map should be rooms/corridors
|
||||
});
|
||||
|
||||
for (let i = 0; i < numRooms; i++) {
|
||||
const roomWidth = GAME_CONFIG.map.roomMinWidth + Math.floor(random() * (GAME_CONFIG.map.roomMaxWidth - GAME_CONFIG.map.roomMinWidth + 1));
|
||||
const roomHeight = GAME_CONFIG.map.roomMinHeight + Math.floor(random() * (GAME_CONFIG.map.roomMaxHeight - GAME_CONFIG.map.roomMinHeight + 1));
|
||||
const roomX = 1 + Math.floor(random() * (width - roomWidth - 2));
|
||||
const roomY = 1 + Math.floor(random() * (height - roomHeight - 2));
|
||||
|
||||
const newRoom: Room = { x: roomX, y: roomY, width: roomWidth, height: roomHeight };
|
||||
|
||||
if (!doesOverlap(newRoom, rooms)) {
|
||||
carveRoom(newRoom, tiles, fakeWorldForIdx);
|
||||
|
||||
if (rooms.length > 0) {
|
||||
carveCorridor(rooms[rooms.length - 1], newRoom, tiles, fakeWorldForIdx, random);
|
||||
}
|
||||
|
||||
rooms.push(newRoom);
|
||||
// Generate the dungeon
|
||||
dungeon.create((x, y, value) => {
|
||||
if (value === 0) {
|
||||
// 0 = floor, 1 = wall
|
||||
tiles[y * width + x] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
});
|
||||
|
||||
// Extract room information from the generated dungeon
|
||||
const roomData = (dungeon as any).getRooms();
|
||||
|
||||
for (const room of roomData) {
|
||||
rooms.push({
|
||||
x: room.getLeft(),
|
||||
y: room.getTop(),
|
||||
width: room.getRight() - room.getLeft() + 1,
|
||||
height: room.getBottom() - room.getTop() + 1
|
||||
});
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
function doesOverlap(newRoom: Room, rooms: Room[]): boolean {
|
||||
for (const room of rooms) {
|
||||
if (
|
||||
newRoom.x < room.x + room.width + 1 &&
|
||||
newRoom.x + newRoom.width + 1 > room.x &&
|
||||
newRoom.y < room.y + room.height + 1 &&
|
||||
newRoom.y + newRoom.height + 1 > room.y
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function carveRoom(room: Room, tiles: Tile[], world: any): void {
|
||||
for (let x = room.x; x < room.x + room.width; x++) {
|
||||
for (let y = room.y; y < room.y + room.height; y++) {
|
||||
tiles[idx(world, x, y)] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function carveCorridor(room1: Room, room2: Room, tiles: Tile[], world: any, random: () => number): void {
|
||||
const x1 = Math.floor(room1.x + room1.width / 2);
|
||||
const y1 = Math.floor(room1.y + room1.height / 2);
|
||||
const x2 = Math.floor(room2.x + room2.width / 2);
|
||||
const y2 = Math.floor(room2.y + room2.height / 2);
|
||||
|
||||
if (random() < 0.5) {
|
||||
// Horizontal then vertical
|
||||
for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
|
||||
tiles[idx(world, x, y1)] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) {
|
||||
tiles[idx(world, x2, y)] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
} else {
|
||||
// Vertical then horizontal
|
||||
for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) {
|
||||
tiles[idx(world, x1, y)] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
|
||||
tiles[idx(world, x, y2)] = GAME_CONFIG.terrain.empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decorate(width: number, height: number, tiles: Tile[], random: () => number, exit: Vec2): void {
|
||||
const world = { width, height };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user