Double level size, fixed skitzo track path

This commit is contained in:
2026-01-31 14:17:08 +11:00
parent 43b33733e9
commit f6fc057e4f
2 changed files with 91 additions and 105 deletions

View File

@@ -43,8 +43,8 @@ export const GAME_CONFIG = {
},
map: {
width: 60,
height: 40,
width: 120,
height: 80,
minRooms: 8,
maxRooms: 13,
roomMinWidth: 5,

View File

@@ -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;