Files
Binding-Fignyaac/src/room/RoomMap.ts
T
Volodia b947fbb7fb initial: dungeon crawler with Bun + TypeScript modular architecture
- 19 TypeScript modules under src/ (constants, entities, room, game, render)
- Canvas 2D rendering with dark fantasy palette
- Room-based navigation, random 7x7 map generation
- Two combat modes: ranged (pistol) and melee (knife)
- Wall collision with door opening support
- Minimap, HP bar, enemy AI
- Bun build pipeline: src/main.ts -> dist/main.js + dist/index.html
2026-06-18 12:55:17 +03:00

90 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { MAP_RADIUS, MIN_ROOMS, EXTRA_ROOMS } from '../constants';
import { Room } from './Room';
import type { RoomType } from '../types';
import { shuffle, ri } from '../math';
import { OPP } from '../doors';
export class RoomMap {
rooms: Map<string, Room> = new Map();
key(c: number, r: number): string {
return c + ',' + r;
}
get(c: number, r: number): Room | undefined {
return this.rooms.get(this.key(c, r));
}
has(c: number, r: number): boolean {
return this.rooms.has(this.key(c, r));
}
private add(c: number, r: number, type: RoomType): Room {
const room = new Room(c, r, type);
this.rooms.set(this.key(c, r), room);
return room;
}
hasBoss(): boolean {
for (const room of this.rooms.values()) {
if (room.type === 'boss') return true;
}
return false;
}
/** Generate a connected 7×7 grid of rooms using a random walk */
generate(): void {
this.add(0, 0, 'spawn');
const frontier: [number, number][] = [[0, 0]];
let count = 1;
const target = MIN_ROOMS + ri(0, EXTRA_ROOMS);
const dirs: [string, number, number][] = [
['up', 0, -1],
['down', 0, 1],
['left', -1, 0],
['right', 1, 0],
];
while (frontier.length > 0 && count < target) {
const idx = ri(0, frontier.length - 1);
const [cr, cc] = frontier[idx];
shuffle(dirs);
let added = false;
for (const [_d, dc, dr] of dirs) {
if (count >= target) break;
const nc = cr + dc;
const nr = cc + dr;
if (Math.abs(nc) > MAP_RADIUS || Math.abs(nr) > MAP_RADIUS) continue;
if (this.has(nc, nr)) continue;
let type: RoomType = 'normal';
if (!this.hasBoss() && (count === target - 1 || (Math.random() < 0.2 && count >= 3))) {
type = 'boss';
} else if (Math.random() < 0.12 && count >= 2) {
type = 'treasure';
}
this.add(nc, nr, type);
const dir = _d as keyof typeof OPP;
this.get(cr, cc)!.doors[dir] = true;
this.get(nc, nr)!.doors[OPP[dir] as keyof typeof OPP] = true;
frontier.push([nc, nr]);
count++;
added = true;
}
if (!added) frontier.splice(idx, 1);
}
// Ensure at least one boss room exists
if (!this.hasBoss()) {
const normals = [...this.rooms.values()].filter(r => r.type === 'normal');
if (normals.length > 0) {
normals[ri(0, normals.length - 1)].type = 'boss';
}
}
}
}