import { randInt } from "@common/utils"; import { Color, Direction, DIRECTION_OFFSETS, getOppositeDirection, ROOM_AREA_HEIGHT, ROOM_AREA_WIDTH, ROOM_AREA_X, ROOM_AREA_Y } from "./const"; import { drawBox } from "./display"; import { Drawable } from "./drawable"; import { Figure } from "./figure"; import { DOOR_SPRITE } from "./images"; import { getItemsCount, Item } from "./item"; type IDoors = [Door | null, Door | null, Door | null, Door | null, Door | null, Door | null]; export class Door extends Figure { constructor( x: number, y: number, direction: Direction, public worldX: number, public worldY: number, public worldZ: number, ) { super(x, y, DOOR_SPRITE); this.frame = direction; } isActivated(x: number, y: number): boolean { const cx = Math.floor(this.x + this.width / 2); const cy = Math.floor(this.y + this.height / 2); return (cx === x && cy === y); } get direction(): Direction { return this.frame as Direction; } } export class Room extends Drawable { constructor( public readonly x: number, public readonly y: number, public readonly width: number, public readonly height: number, public readonly worldX: number, public readonly worldY: number, public readonly worldZ: number, public readonly doors: IDoors = [null, null, null, null, null, null], public readonly items: Item[] = [], ) { super(); } override doDraw() { drawBox(ROOM_AREA_X, ROOM_AREA_Y, ROOM_AREA_WIDTH - 2, ROOM_AREA_HEIGHT - 2, { fill: [' '] }); drawBox(this.x, this.y, this.width, this.height, { fill: ['.', Color.GRAY] }); this.doors.forEach((door) => { if (door) { door.doDraw(); } }); this.items.forEach((item) => item.doDraw()); } getActivatedDoor(x: number, y: number): Door | null { return this.doors.find((door) => door?.isActivated(x, y)) ?? null; } pickItem(x: number, y: number): Item | null { const itemIndex = this.items.findIndex((item) => item.x === x && item.y === y); if (itemIndex >= 0) { const [item] = this.items.splice(itemIndex, 1); this.invalidate(); return item; } return null; } } type IRoomBlank = { -readonly [P in keyof Room]?: Room[P] }; const rooms = new Map(); const generateRoomId = (worldX: number, worldY: number, worldZ: number) => `${worldX},${worldY},${worldZ}`; const generateDoors = ({ x, y, width, height, worldX, worldY, worldZ }: IRoomBlank): IDoors => { const doors: IDoors = [null, null, null, null, null, null]; if (typeof worldX === 'undefined' || typeof worldY === 'undefined' || typeof worldZ === 'undefined') { console.error('World coordinates not defined for doors generation'); return doors; } if (typeof x === 'undefined' || typeof y === 'undefined' || typeof width === 'undefined' || typeof height === 'undefined') { console.error('Screen coordinates not defined for doors generation'); return doors; } for (let i = 0; i < 4; i++) { const [xoff, yoff] = DIRECTION_OFFSETS[i]; const doorWorldX = worldX + xoff; const doorWorldY = worldY + yoff; const doorId = generateRoomId(doorWorldX, doorWorldY, worldZ); const doorRoom = rooms.get(doorId); if (doorRoom && !doorRoom.doors[getOppositeDirection(i)]) { continue; } if ((worldX === 0 && worldY === 0) || doorRoom || Math.random() < 0.3) { const doorX = randInt(x + 1, x + width - 2); const doorY = randInt(y + 1, y + height - 2); let dx = 0; let dy = 0; switch (i) { case Direction.NORTH: dx = doorX; dy = y; break; case Direction.SOUTH: dx = doorX; dy = y + height + 1; break; case Direction.EAST: dx = x + width + 1; dy = doorY; break; case Direction.WEST: dx = x; dy = doorY; break; } doors[i] = new Door(dx, dy, i as Direction, doorWorldX, doorWorldY, worldZ); } } if (worldX === 0 && worldY === 0) { const doorX = randInt(x + 1, x + width - 2); const doorY = randInt(y + 1, y + height - 2); const [, , zoff] = DIRECTION_OFFSETS[Direction.DOWN]; doors[Direction.DOWN] = new Door(doorX, doorY, Direction.DOWN, 0, 0, worldZ + zoff); if (worldZ !== 0) { let upDoorX: number; let upDoorY: number; do { upDoorX = randInt(x + 1, x + width - 2); upDoorY = randInt(y + 1, y + height - 2); } while (doorX === upDoorX && doorY === upDoorY); const [, , upZoff] = DIRECTION_OFFSETS[Direction.UP]; doors[Direction.UP] = new Door(upDoorX, upDoorY, Direction.UP, 0, 0, worldZ + upZoff); } } return doors; }; const generatedItems = new Set(); const generateItems = ({ x, y, width, height, doors }: IRoomBlank): Item[] => { const items: Item[] = []; if (typeof x === 'undefined' || typeof y === 'undefined' || typeof width === 'undefined' || typeof height === 'undefined') { console.error('Screen coordinates not defined for items generation'); return items; } if (typeof doors === 'undefined') { console.error('Doors not defined for items generation'); return items; } const hasObjectAt = (x: number, y: number) => doors.some((d) => d?.x === x && d?.y === y) || items.some((i) => i.x === x && i.y === y); const oneDoor = doors.filter((d) => Boolean(d)).length === 1; const id = randInt(0, getItemsCount()); if (Math.random() < 0.4 && oneDoor && !generatedItems.has(id)) { let newItemX: number; let newItemY: number; do { newItemX = randInt(x + 1, x + width - 2); newItemY = randInt(y + 1, y + height - 2); } while (hasObjectAt(newItemX, newItemY)); const item = new Item(id, 1, newItemX, newItemY); items.push(item); generatedItems.add(id); } return items; }; export function getRoom(worldX: number, worldY: number, worldZ: number) { const id = generateRoomId(worldX, worldY, worldZ); const existingRoom = rooms.get(id); if (existingRoom) { return existingRoom; } const width = randInt(5, ROOM_AREA_WIDTH - 5); const height = randInt(5, ROOM_AREA_HEIGHT - 5); const x = Math.max(ROOM_AREA_X + randInt(2, ROOM_AREA_WIDTH - width - 2), ROOM_AREA_X + 2); const y = Math.max(ROOM_AREA_Y + randInt(2, ROOM_AREA_HEIGHT - height - 2), ROOM_AREA_Y + 2); const roomBlank: IRoomBlank = { x, y, width, height, worldX, worldY, worldZ }; roomBlank.doors = generateDoors(roomBlank); roomBlank.items = generateItems(roomBlank); const room = new Room(x, y, width, height, worldX, worldY, worldZ, roomBlank.doors, roomBlank.items); rooms.set(id, room); return room; } export const getRoomsCount = () => rooms.size; export const getPossibleRoomsCount = () => { const roomsSet = new Set(rooms.keys()); for (const room of rooms.values()) { room.doors.forEach((d) => { if (d) { const id = generateRoomId(d.worldX, d.worldY, d.worldZ); roomsSet.add(id); } }); } return roomsSet.size; }; export const getRoomsForLayer = (z: number): Room[] => Array.from(rooms.values()).filter((r) => r.worldZ === z);