6f459fa88a
- Add trunk decoration layer to GameMap and mapGenerator - Load trunk.png asset in BootScene - Increase superlatives panel size and font sizes for readability - Simplify mapGenerator (remove procedural terrain features) - Add LLM integration planning documents - Remove stale day-night cycle worktree plans - Update local Claude settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.3 KiB
TypeScript
85 lines
2.3 KiB
TypeScript
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<string> = 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<string>): 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 };
|
|
}
|
|
}
|