diff --git a/src/core/types.ts b/src/core/types.ts index c3bc8fc..b96f21b 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -177,6 +177,7 @@ export type Inventory = { export type RunState = { stats: Stats; inventory: Inventory; + seed: number; lastReloadableWeaponId?: string | null; }; diff --git a/src/engine/world/generator.ts b/src/engine/world/generator.ts index ecdcc2a..7ffb2a8 100644 --- a/src/engine/world/generator.ts +++ b/src/engine/world/generator.ts @@ -38,13 +38,13 @@ export function generateWorld(floor: number, runState: RunState): { world: World const height = GAME_CONFIG.map.height; const tiles: Tile[] = new Array(width * height).fill(TileType.WALL); - const random = seededRandom(floor * 12345); + const random = seededRandom(runState.seed + floor * 12345); // Create ECS World first const ecsWorld = new ECSWorld(); // Starts at ID 1 by default // Set ROT's RNG seed for consistent dungeon generation - ROT.RNG.setSeed(floor * 12345); + ROT.RNG.setSeed(runState.seed + floor * 12345); // Replace generateRooms call with track-first logic for mine cart mechanic const { rooms, trackPath } = generateTrackLevel(width, height, tiles, floor, random); @@ -156,19 +156,52 @@ function generateTrackLevel(width: number, height: number, tiles: Tile[], _floor // 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 }); + const startDir = Math.floor(random() * 4); // 0: East, 1: West, 2: South, 3: North - const targetX = width - 10; + let currA: Vec2; + const margin = 10; const stepSize = 12; - 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); + if (startDir === 0) { // East (Left to Right) + currA = { x: margin, y: margin + Math.floor(random() * (height - margin * 2)) }; + } else if (startDir === 1) { // West (Right to Left) + currA = { x: width - margin, y: margin + Math.floor(random() * (height - margin * 2)) }; + } else if (startDir === 2) { // South (Top to Bottom) + currA = { x: margin + Math.floor(random() * (width - margin * 2)), y: margin }; + } else { // North (Bottom to Top) + currA = { x: margin + Math.floor(random() * (width - margin * 2)), y: height - margin }; + } + + anchors.push({ ...currA }); + + const isFinished = () => { + if (startDir === 0) return currA.x >= width - margin; + if (startDir === 1) return currA.x <= margin; + if (startDir === 2) return currA.y >= height - margin; + return currA.y <= margin; + }; + + while (!isFinished()) { + let nextX = currA.x; + let nextY = currA.y; + + if (startDir === 0) { // East + nextX += Math.floor(stepSize * (0.8 + random() * 0.4)); + nextY += Math.floor((random() - 0.5) * height * 0.4); + } else if (startDir === 1) { // West + nextX -= Math.floor(stepSize * (0.8 + random() * 0.4)); + nextY += Math.floor((random() - 0.5) * height * 0.4); + } else if (startDir === 2) { // South + nextY += Math.floor(stepSize * (0.8 + random() * 0.4)); + nextX += Math.floor((random() - 0.5) * width * 0.4); + } else { // North + nextY -= Math.floor(stepSize * (0.8 + random() * 0.4)); + nextX += Math.floor((random() - 0.5) * width * 0.4); + } currA = { - x: Math.min(width - 5, nextX), - y: Math.max(5, Math.min(height - 5, nextY)) + x: Math.max(margin / 2, Math.min(width - margin / 2, nextX)), + y: Math.max(margin / 2, Math.min(height - margin / 2, nextY)) }; anchors.push({ ...currA }); } diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index d636b5f..8b85fd0 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -50,6 +50,7 @@ export class GameScene extends Phaser.Scene { public runState: RunState = { stats: { ...GAME_CONFIG.player.initialStats }, inventory: { gold: 0, items: [] }, + seed: Math.floor(Math.random() * 1000000), lastReloadableWeaponId: null }; @@ -478,6 +479,7 @@ export class GameScene extends Phaser.Scene { this.runState = { stats: { ...p.stats }, inventory: { gold: p.inventory.gold, items: [...p.inventory.items] }, + seed: this.runState.seed, lastReloadableWeaponId: this.runState.lastReloadableWeaponId }; } @@ -486,6 +488,7 @@ export class GameScene extends Phaser.Scene { this.runState = { stats: { ...GAME_CONFIG.player.initialStats }, inventory: { gold: 0, items: [] }, + seed: Math.floor(Math.random() * 1000000), lastReloadableWeaponId: null }; this.floorIndex = 1;