From 0f28a2212ec886f506c6aa179ebbbdf2ad81afd0 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Sun, 4 Jan 2026 10:13:44 +1100 Subject: [PATCH] Improve look of generated map --- src/game/generator.ts | 161 ++++++++++++++++++++++++++++++++---------- 1 file changed, 124 insertions(+), 37 deletions(-) diff --git a/src/game/generator.ts b/src/game/generator.ts index b381dc9..25f040e 100644 --- a/src/game/generator.ts +++ b/src/game/generator.ts @@ -1,31 +1,105 @@ import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "./types"; import { idx } from "./world"; +interface Room { + x: number; + y: number; + width: number; + height: number; +} + +function seededRandom(seed: number): () => number { + let state = seed; + return () => { + state = (state * 1103515245 + 12345) & 0x7fffffff; + return state / 0x7fffffff; + }; +} + export function makeTestWorld(level: number, runState: RunState): { world: World; playerId: EntityId } { - const width = 30; - const height = 18; - const tiles: Tile[] = new Array(width * height).fill(0); + const width = 60; + const height = 40; + const tiles: Tile[] = new Array(width * height).fill(1); // Start with all walls const fakeWorldForIdx: World = { width, height, tiles, actors: new Map(), exit: { x: 0, y: 0 } }; - - // Border walls - for (let x = 0; x < width; x++) { - tiles[idx(fakeWorldForIdx, x, 0)] = 1; - tiles[idx(fakeWorldForIdx, x, height - 1)] = 1; - } - for (let y = 0; y < height; y++) { - tiles[idx(fakeWorldForIdx, 0, y)] = 1; - tiles[idx(fakeWorldForIdx, width - 1, y)] = 1; + const random = seededRandom(level * 12345); + + // Generate rooms + const rooms: Room[] = []; + const numRooms = 8 + Math.floor(random() * 6); // 8-13 rooms + + for (let i = 0; i < numRooms; i++) { + const roomWidth = 5 + Math.floor(random() * 8); // 5-12 + const roomHeight = 4 + Math.floor(random() * 7); // 4-10 + 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 }; + + // Check if room overlaps with existing rooms + let overlaps = false; + 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 + ) { + overlaps = true; + break; + } + } + + if (!overlaps) { + // Carve out the room + for (let x = newRoom.x; x < newRoom.x + newRoom.width; x++) { + for (let y = newRoom.y; y < newRoom.y + newRoom.height; y++) { + tiles[idx(fakeWorldForIdx, x, y)] = 0; + } + } + + // Connect to previous room with a corridor + if (rooms.length > 0) { + const prevRoom = rooms[rooms.length - 1]; + const prevCenterX = Math.floor(prevRoom.x + prevRoom.width / 2); + const prevCenterY = Math.floor(prevRoom.y + prevRoom.height / 2); + const newCenterX = Math.floor(newRoom.x + newRoom.width / 2); + const newCenterY = Math.floor(newRoom.y + newRoom.height / 2); + + // Create L-shaped corridor + if (random() < 0.5) { + // Horizontal then vertical + for (let x = Math.min(prevCenterX, newCenterX); x <= Math.max(prevCenterX, newCenterX); x++) { + tiles[idx(fakeWorldForIdx, x, prevCenterY)] = 0; + } + for (let y = Math.min(prevCenterY, newCenterY); y <= Math.max(prevCenterY, newCenterY); y++) { + tiles[idx(fakeWorldForIdx, newCenterX, y)] = 0; + } + } else { + // Vertical then horizontal + for (let y = Math.min(prevCenterY, newCenterY); y <= Math.max(prevCenterY, newCenterY); y++) { + tiles[idx(fakeWorldForIdx, prevCenterX, y)] = 0; + } + for (let x = Math.min(prevCenterX, newCenterX); x <= Math.max(prevCenterX, newCenterX); x++) { + tiles[idx(fakeWorldForIdx, x, newCenterY)] = 0; + } + } + } + + rooms.push(newRoom); + } } - // Internal walls (vary slightly with level so it feels different) - const shift = level % 4; - for (let x = 6; x < 22; x++) tiles[idx(fakeWorldForIdx, x, 7 + (shift % 2))] = 1; - for (let y = 4; y < 14; y++) tiles[idx(fakeWorldForIdx, 14 + ((shift + 1) % 2), y)] = 1; + // Place player in first room + const firstRoom = rooms[0]; + const playerX = firstRoom.x + Math.floor(firstRoom.width / 2); + const playerY = firstRoom.y + Math.floor(firstRoom.height / 2); - // Exit (stairs) - const exit: Vec2 = { x: width - 3, y: height - 3 }; - tiles[idx(fakeWorldForIdx, exit.x, exit.y)] = 0; + // Place exit in last room + const lastRoom = rooms[rooms.length - 1]; + const exitX = lastRoom.x + Math.floor(lastRoom.width / 2); + const exitY = lastRoom.y + Math.floor(lastRoom.height / 2); + const exit: Vec2 = { x: exitX, y: exitY }; const actors = new Map(); @@ -33,30 +107,43 @@ export function makeTestWorld(level: number, runState: RunState): { world: World actors.set(playerId, { id: playerId, isPlayer: true, - pos: { x: 3, y: 3 }, + pos: { x: playerX, y: playerY }, speed: 100, energy: 0, stats: { ...runState.stats }, inventory: { gold: runState.inventory.gold, items: [...runState.inventory.items] } }); - // Enemies - actors.set(2, { - id: 2, - isPlayer: false, - pos: { x: 24, y: 13 }, - speed: 90, - energy: 0, - stats: { maxHp: 10, hp: 10, attack: 3, defense: 1 } - }); - actors.set(3, { - id: 3, - isPlayer: false, - pos: { x: 20, y: 4 }, - speed: 130, - energy: 0, - stats: { maxHp: 8, hp: 8, attack: 4, defense: 0 } - }); + // Place enemies in random rooms (skip first room with player) + let enemyId = 2; + const numEnemies = 3 + level + Math.floor(random() * 4); // Increases with level + + for (let i = 0; i < numEnemies && i < rooms.length - 1; i++) { + const roomIdx = 1 + Math.floor(random() * (rooms.length - 1)); + const room = rooms[roomIdx]; + + const enemyX = room.x + 1 + Math.floor(random() * (room.width - 2)); + const enemyY = room.y + 1 + Math.floor(random() * (room.height - 2)); + + // Vary enemy stats by level + const baseHp = 8 + level * 2; + const baseAttack = 3 + Math.floor(level / 2); + + actors.set(enemyId, { + id: enemyId, + isPlayer: false, + pos: { x: enemyX, y: enemyY }, + speed: 80 + Math.floor(random() * 50), // 80-130 speed + energy: 0, + stats: { + maxHp: baseHp + Math.floor(random() * 4), + hp: baseHp + Math.floor(random() * 4), + attack: baseAttack + Math.floor(random() * 2), + defense: Math.floor(random() * 3) + } + }); + enemyId++; + } return { world: { width, height, tiles, actors, exit }, playerId }; }