Double level size, fixed skitzo track path
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user