import { WORLD_WIDTH, WORLD_HEIGHT, type Position } from '@dflike/shared'; export interface PointOfInterest { type: 'food' | 'rest'; position: Position; } export class GameMap { readonly width: number; readonly height: number; private obstacles: Set = new Set(); private pois: PointOfInterest[] = []; terrain: number[] = []; // flat array of Terrain values decorations: number[] = []; // flat array of canopy tile indices (-1 = none) trunkDecorations: number[] = []; // flat array of trunk tile indices (-1 = none) constructor(width = WORLD_WIDTH, height = WORLD_HEIGHT) { this.width = width; this.height = height; } private key(x: number, y: number): string { return `${x},${y}`; } setObstacle(x: number, y: number): void { this.obstacles.add(this.key(x, y)); } loadObstacles(obstacleSet: Set): void { for (const key of obstacleSet) { this.obstacles.add(key); } } isWalkable(x: number, y: number): boolean { if (x < 0 || y < 0 || x >= this.width || y >= this.height) return false; return !this.obstacles.has(this.key(x, y)); } addPointOfInterest(poi: PointOfInterest): void { this.pois.push(poi); } getPointsOfInterest(type?: 'food' | 'rest'): PointOfInterest[] { if (type) return this.pois.filter(p => p.type === type); return this.pois; } getObstacles(): Position[] { return [...this.obstacles].map(k => { const [x, y] = k.split(',').map(Number); return { x, y }; }); } findNearestWalkable(centerX: number, centerY: number, radius: number): Position | null { if (this.isWalkable(centerX, centerY)) { return { x: centerX, y: centerY }; } for (let dist = 1; dist <= radius; dist++) { for (let dx = -dist; dx <= dist; dx++) { const dyRange = dist - Math.abs(dx); for (const dy of [-dyRange, dyRange]) { const x = centerX + dx; const y = centerY + dy; if (this.isWalkable(x, y)) { return { x, y }; } } } } return null; } getRandomWalkable(): Position { let x: number, y: number; do { x = Math.floor(Math.random() * this.width); y = Math.floor(Math.random() * this.height); } while (!this.isWalkable(x, y)); return { x, y }; } }