Improve look of generated map
This commit is contained in:
@@ -1,31 +1,105 @@
|
|||||||
import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "./types";
|
import { type World, type EntityId, type RunState, type Tile, type Actor, type Vec2 } from "./types";
|
||||||
import { idx } from "./world";
|
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 } {
|
export function makeTestWorld(level: number, runState: RunState): { world: World; playerId: EntityId } {
|
||||||
const width = 30;
|
const width = 60;
|
||||||
const height = 18;
|
const height = 40;
|
||||||
const tiles: Tile[] = new Array(width * height).fill(0);
|
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 } };
|
const fakeWorldForIdx: World = { width, height, tiles, actors: new Map(), exit: { x: 0, y: 0 } };
|
||||||
|
const random = seededRandom(level * 12345);
|
||||||
|
|
||||||
// Border walls
|
// Generate rooms
|
||||||
for (let x = 0; x < width; x++) {
|
const rooms: Room[] = [];
|
||||||
tiles[idx(fakeWorldForIdx, x, 0)] = 1;
|
const numRooms = 8 + Math.floor(random() * 6); // 8-13 rooms
|
||||||
tiles[idx(fakeWorldForIdx, x, height - 1)] = 1;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
tiles[idx(fakeWorldForIdx, 0, y)] = 1;
|
|
||||||
tiles[idx(fakeWorldForIdx, width - 1, y)] = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal walls (vary slightly with level so it feels different)
|
if (!overlaps) {
|
||||||
const shift = level % 4;
|
// Carve out the room
|
||||||
for (let x = 6; x < 22; x++) tiles[idx(fakeWorldForIdx, x, 7 + (shift % 2))] = 1;
|
for (let x = newRoom.x; x < newRoom.x + newRoom.width; x++) {
|
||||||
for (let y = 4; y < 14; y++) tiles[idx(fakeWorldForIdx, 14 + ((shift + 1) % 2), y)] = 1;
|
for (let y = newRoom.y; y < newRoom.y + newRoom.height; y++) {
|
||||||
|
tiles[idx(fakeWorldForIdx, x, y)] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Exit (stairs)
|
// Connect to previous room with a corridor
|
||||||
const exit: Vec2 = { x: width - 3, y: height - 3 };
|
if (rooms.length > 0) {
|
||||||
tiles[idx(fakeWorldForIdx, exit.x, exit.y)] = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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<EntityId, Actor>();
|
const actors = new Map<EntityId, Actor>();
|
||||||
|
|
||||||
@@ -33,30 +107,43 @@ export function makeTestWorld(level: number, runState: RunState): { world: World
|
|||||||
actors.set(playerId, {
|
actors.set(playerId, {
|
||||||
id: playerId,
|
id: playerId,
|
||||||
isPlayer: true,
|
isPlayer: true,
|
||||||
pos: { x: 3, y: 3 },
|
pos: { x: playerX, y: playerY },
|
||||||
speed: 100,
|
speed: 100,
|
||||||
energy: 0,
|
energy: 0,
|
||||||
stats: { ...runState.stats },
|
stats: { ...runState.stats },
|
||||||
inventory: { gold: runState.inventory.gold, items: [...runState.inventory.items] }
|
inventory: { gold: runState.inventory.gold, items: [...runState.inventory.items] }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enemies
|
// Place enemies in random rooms (skip first room with player)
|
||||||
actors.set(2, {
|
let enemyId = 2;
|
||||||
id: 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,
|
isPlayer: false,
|
||||||
pos: { x: 24, y: 13 },
|
pos: { x: enemyX, y: enemyY },
|
||||||
speed: 90,
|
speed: 80 + Math.floor(random() * 50), // 80-130 speed
|
||||||
energy: 0,
|
energy: 0,
|
||||||
stats: { maxHp: 10, hp: 10, attack: 3, defense: 1 }
|
stats: {
|
||||||
});
|
maxHp: baseHp + Math.floor(random() * 4),
|
||||||
actors.set(3, {
|
hp: baseHp + Math.floor(random() * 4),
|
||||||
id: 3,
|
attack: baseAttack + Math.floor(random() * 2),
|
||||||
isPlayer: false,
|
defense: Math.floor(random() * 3)
|
||||||
pos: { x: 20, y: 4 },
|
}
|
||||||
speed: 130,
|
|
||||||
energy: 0,
|
|
||||||
stats: { maxHp: 8, hp: 8, attack: 4, defense: 0 }
|
|
||||||
});
|
});
|
||||||
|
enemyId++;
|
||||||
|
}
|
||||||
|
|
||||||
return { world: { width, height, tiles, actors, exit }, playerId };
|
return { world: { width, height, tiles, actors, exit }, playerId };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user