From f6fc057e4fc8d119b289f4324a6fa61bc9c5cee1 Mon Sep 17 00:00:00 2001 From: Kyle Banicevic Date: Sat, 31 Jan 2026 14:17:08 +1100 Subject: [PATCH] Double level size, fixed skitzo track path --- src/core/config/GameConfig.ts | 4 +- src/engine/world/generator.ts | 192 ++++++++++++++++------------------ 2 files changed, 91 insertions(+), 105 deletions(-) diff --git a/src/core/config/GameConfig.ts b/src/core/config/GameConfig.ts index 215b653..af679ab 100644 --- a/src/core/config/GameConfig.ts +++ b/src/core/config/GameConfig.ts @@ -43,8 +43,8 @@ export const GAME_CONFIG = { }, map: { - width: 60, - height: 40, + width: 120, + height: 80, minRooms: 8, maxRooms: 13, roomMinWidth: 5, diff --git a/src/engine/world/generator.ts b/src/engine/world/generator.ts index e36a0dd..e2c1282 100644 --- a/src/engine/world/generator.ts +++ b/src/engine/world/generator.ts @@ -135,146 +135,131 @@ export function generateWorld(floor: number, runState: RunState): { world: World */ function generateTrackLevel(width: number, height: number, tiles: Tile[], _floor: number, random: () => number): { rooms: Room[], trackPath: Vec2[] } { const rooms: Room[] = []; - - // 1. Generate Start and End points (further apart) - const start: Vec2 = { x: 3, y: 5 + Math.floor(random() * (height - 10)) }; - const end: Vec2 = { x: width - 4, y: 5 + Math.floor(random() * (height - 10)) }; - - // 2. Generate Track Path (Winding random walk) const trackPath: Vec2[] = []; - let curr = { ...start }; - trackPath.push(curr); - // Bias weights - const targetBias = 0.6; - const straightBias = 0.2; + // 1. Generate a winding path of "Anchor Points" for rooms + const anchors: Vec2[] = []; + let currA = { x: 10, y: Math.floor(height / 2) }; + anchors.push({ ...currA }); - let iter = 0; - const maxIter = width * height; + const targetX = width - 10; + const stepSize = 12; - let lastDir = { dx: 1, dy: 0 }; + while (currA.x < targetX) { + const nextX = currA.x + Math.floor(stepSize * (0.8 + random() * 0.4)); + const nextY = currA.y + Math.floor((random() - 0.5) * height * 0.6); - while ((curr.x !== end.x || curr.y !== end.y) && iter < maxIter) { - iter++; - // Determine possible directions - const dirs = [ - { dx: 1, dy: 0 }, - { dx: 0, dy: 1 }, - { dx: 0, dy: -1 }, - { dx: -1, dy: 0 } - ]; - - // Score directions - const scores = dirs.map(d => { - let score = 0; - - // Target bias (distance reduction) - const distCurr = Math.abs(curr.x - end.x) + Math.abs(curr.y - end.y); - const distNext = Math.abs((curr.x + d.dx) - end.x) + Math.abs((curr.y + d.dy) - end.y); - if (distNext < distCurr) score += targetBias; - - // Straight bias - if (d.dx === lastDir.dx && d.dy === lastDir.dy) score += straightBias; - - // Randomness - score += random() * 0.3; - - // Boundary check - const nx = curr.x + d.dx; - const ny = curr.y + d.dy; - if (nx < 2 || nx >= width - 2 || ny < 2 || ny >= height - 2) score = -100; - - return { d, score }; - }); - - // scores already sorted - scores.sort((a, b) => b.score - a.score); - const best = scores[0]; - - const nextX = curr.x + best.d.dx; - const nextY = curr.y + best.d.dy; - - // Create NEW position object to avoid mutation bugs - curr = { x: nextX, y: nextY }; - lastDir = best.d; - - // Avoid double-back if possible - const existing = trackPath.find(p => p.x === curr.x && p.y === curr.y); - if (!existing) { - trackPath.push({ ...curr }); - } + currA = { + x: Math.min(width - 5, nextX), + y: Math.max(5, Math.min(height - 5, nextY)) + }; + anchors.push({ ...currA }); } - console.log(`[generator] Track walker finished at (${curr.x}, ${curr.y}) after ${iter} iterations. Path length: ${trackPath.length}`); + // 2. Place Primary Rooms at anchors and connect them + let prevCenter: Vec2 | null = null; - // 3. Dig out the track path (Narrower 2x2 tunnel) - for (const pos of trackPath) { - for (let dy = 0; dy <= 1; dy++) { - for (let dx = 0; dx <= 1; dx++) { - const nx = pos.x + dx; - const ny = pos.y + dy; - if (nx >= 0 && nx < width && ny >= 0 && ny < height) { - tiles[ny * width + nx] = TileType.EMPTY; + for (const anchor of anchors) { + const rw = 7 + Math.floor(random() * 6); + const rh = 6 + Math.floor(random() * 6); + const rx = Math.floor(anchor.x - rw / 2); + const ry = Math.floor(anchor.y - rh / 2); + + const room: Room = { x: rx, y: ry, width: rw, height: rh }; + + // Dig room interior + for (let y = ry + 1; y < ry + rh - 1; y++) { + for (let x = rx + 1; x < rx + rw - 1; x++) { + if (x >= 0 && x < width && y >= 0 && y < height) { + tiles[y * width + x] = TileType.EMPTY; } } } + rooms.push(room); + + const currCenter = { x: rx + Math.floor(rw / 2), y: ry + Math.floor(rh / 2) }; + + // 3. Connect to previous room and lay track + if (prevCenter) { + // Connect path + const segment: Vec2[] = []; + let tx = prevCenter.x; + let ty = prevCenter.y; + + const dig = (x: number, y: number) => { + for (let dy = 0; dy <= 1; dy++) { + for (let dx = 0; dx <= 1; dx++) { + const nx = x + dx; + const ny = y + dy; + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + tiles[ny * width + nx] = TileType.EMPTY; + } + } + } + if (!segment.find(p => p.x === x && p.y === y)) { + segment.push({ x, y }); + } + }; + + // Simple L-shape for tracks within/between rooms + while (tx !== currCenter.x) { + tx += currCenter.x > tx ? 1 : -1; + dig(tx, ty); + } + while (ty !== currCenter.y) { + ty += currCenter.y > ty ? 1 : -1; + dig(tx, ty); + } + trackPath.push(...segment); + } else { + trackPath.push(currCenter); + } + + prevCenter = currCenter; } - // 4. Generate rooms branching off the track - const numRooms = 12 + Math.floor(random() * 6); - for (let i = 0; i < numRooms; i++) { - const pathIdx = Math.floor(random() * trackPath.length); - const pathNode = trackPath[pathIdx]; + // 4. Branch Side Rooms off the main path + const numSideRooms = 25 + Math.floor(random() * 15); + for (let i = 0; i < numSideRooms; i++) { + const sourcePathIdx = Math.floor(random() * trackPath.length); + const source = trackPath[sourcePathIdx]; - const rw = 6 + Math.floor(random() * 6); - const rh = 5 + Math.floor(random() * 6); + const rw = 5 + Math.floor(random() * 7); + const rh = 4 + Math.floor(random() * 7); - // Random side offset const side = random() < 0.5 ? -1 : 1; let rx, ry; - if (random() < 0.5) { // Horizontal branch - rx = pathNode.x + (side * Math.floor(rw / 2 + 2)); - ry = pathNode.y - Math.floor(rh / 2); - } else { // Vertical branch - rx = pathNode.x - Math.floor(rw / 2); - ry = pathNode.y + (side * Math.floor(rh / 2 + 2)); + if (random() < 0.5) { + rx = source.x + (side * Math.floor(rw / 2 + 3)); + ry = source.y - Math.floor(rh / 2); + } else { + rx = source.x - Math.floor(rw / 2); + ry = source.y + (side * Math.floor(rh / 2 + 3)); } rx = Math.max(1, Math.min(width - rw - 1, rx)); ry = Math.max(1, Math.min(height - rh - 1, ry)); const room = { x: rx, y: ry, width: rw, height: rh }; - - // Overlap check - const overlap = rooms.some(r => { - return !(room.x + room.width < r.x - 1 || - room.x > r.x + r.width + 1 || - room.y + room.height < r.y - 1 || - room.y > r.y + r.height + 1); - }); - + const overlap = rooms.some(r => !(room.x + room.width < r.x - 1 || room.x > r.x + r.width + 1 || room.y + room.height < r.y - 1 || room.y > r.y + r.height + 1)); if (overlap) continue; - // Dig room interior for (let y = ry + 1; y < ry + rh - 1; y++) { for (let x = rx + 1; x < rx + rw - 1; x++) { tiles[y * width + x] = TileType.EMPTY; } } - // Connect room to path node - digCorridor(width, tiles, pathNode.x, pathNode.y, rx + Math.floor(rw / 2), ry + Math.floor(rh / 2)); + digCorridor(width, tiles, source.x, source.y, rx + Math.floor(rw / 2), ry + Math.floor(rh / 2)); - // Door at entrance let ex = rx + Math.floor(rw / 2); - let ey = ry + (pathNode.y <= ry ? 0 : rh - 1); - if (Math.abs(pathNode.x - (rx + rw / 2)) > Math.abs(pathNode.y - (ry + rh / 2))) { - ex = (pathNode.x <= rx ? 0 : rw - 1) + rx; + let ey = ry + (source.y <= ry ? 0 : rh - 1); + if (Math.abs(source.x - (rx + rw / 2)) > Math.abs(source.y - (ry + rh / 2))) { + ex = (source.x <= rx ? 0 : rw - 1) + rx; ey = ry + Math.floor(rh / 2); } tiles[ey * width + ex] = TileType.DOOR_CLOSED; - rooms.push(room); } @@ -285,6 +270,7 @@ function generateTrackLevel(width: number, height: number, tiles: Tile[], _floor return { rooms, trackPath }; } + function digCorridor(width: number, tiles: Tile[], x1: number, y1: number, x2: number, y2: number) { let currX = x1; let currY = y1;