import type { Cell } from './Cell'; import { cellKey } from './Cell'; import type { EntityId } from '../ecs/EntityId'; /** 单格状态 */ export interface GridCellState { /** 行 */ row: number; /** 列 */ col: number; /** 是否可行走 */ walkable: boolean; /** 是否可建造 */ buildable: boolean; /** 占用该格的实体 ID */ occupant: EntityId | null; } /** 战场格子地图(纯逻辑,不依赖引擎) */ export class GridMap { /** 行数 */ readonly rows: number; /** 列数 */ readonly cols: number; /** 格子尺寸(逻辑像素) */ readonly cellSize: number; private _cells: GridCellState[][]; /** 可走性版本:仅 walkable 真变化时递增;供 Pathfinder rebuild early-out */ private _walkRevision = 0; /** * @param rows 行数 * @param cols 列数 * @param cellSize 格子尺寸 */ constructor(rows: number, cols: number, cellSize: number) { this.rows = rows; this.cols = cols; this.cellSize = cellSize; this._cells = []; for (let r = 0; r < rows; r++) { const row: GridCellState[] = []; for (let c = 0; c < cols; c++) { row.push({ row: r, col: c, walkable: true, buildable: true, occupant: null }); } this._cells.push(row); } } /** * @param cell 格子坐标 * @returns 是否在地图范围内 */ inBounds(cell: Cell): boolean { return cell.row >= 0 && cell.row < this.rows && cell.col >= 0 && cell.col < this.cols; } /** * @param cell 格子坐标 * @returns 格子状态,越界返回 undefined */ getCell(cell: Cell): GridCellState | undefined { if (!this.inBounds(cell)) return undefined; return this._cells[cell.row][cell.col]; } /** * @param cell 格子坐标 * @returns 是否可走 */ isWalkable(cell: Cell): boolean { return this.getCell(cell)?.walkable ?? false; } /** 设置格子是否可建造 */ setBuildable(cell: Cell, buildable: boolean): void { const s = this.getCell(cell); if (s) s.buildable = buildable; } /** 可走性版本号(walkable 真变化时 +1) */ get walkRevision(): number { return this._walkRevision; } /** 设置格子是否可行走 */ setWalkable(cell: Cell, walkable: boolean): void { const s = this.getCell(cell); if (!s || s.walkable === walkable) return; s.walkable = walkable; this._walkRevision++; } /** * @param cells 占用格子列表 * @param entity 占用实体 ID */ occupy(cells: Cell[], entity: EntityId): void { for (const c of cells) { const s = this.getCell(c); if (s) s.occupant = entity; } } /** 清除指定实体的所有占地 */ clearOccupant(entity: EntityId): void { for (let r = 0; r < this.rows; r++) { for (let c = 0; c < this.cols; c++) { if (this._cells[r][c].occupant === entity) { this._cells[r][c].occupant = null; } } } } /** * @param cells 待检测格子列表 * @returns 所有格均可建造且未被占用 */ isEmptyForDeploy(cells: Cell[]): boolean { for (const c of cells) { const s = this.getCell(c); if (!s || !s.buildable || s.occupant != null) return false; } return true; } /** * 写入格子中心世界坐标,避免热路径分配 {x,y} * @param cell 格子坐标 * @param out 输出 * @returns out */ cellToWorldInto(cell: Cell, out: { x: number; y: number }): { x: number; y: number } { out.x = (cell.col + 0.5) * this.cellSize; out.y = (cell.row + 0.5) * this.cellSize; return out; } /** * @param cell 格子坐标 * @returns 格子中心世界坐标(逻辑坐标,Y 向下为正) */ cellToWorld(cell: Cell): { x: number; y: number } { return this.cellToWorldInto(cell, { x: 0, y: 0 }); } /** * @param x 世界 X * @param y 世界 Y * @returns 对应格子坐标 */ worldToCell(x: number, y: number): Cell { return { row: Math.floor(y / this.cellSize), col: Math.floor(x / this.cellSize), }; } /** * @param a 格子 A * @param b 格子 B * @returns 两格中心欧氏距离(格) */ distanceCells(a: Cell, b: Cell): number { const dr = a.row - b.row; const dc = a.col - b.col; return Math.sqrt(dr * dr + dc * dc); } /** * @param cell 格子坐标 * @returns 一维索引(row * cols + col) */ indexOf(cell: Cell): number { return cell.row * this.cols + cell.col; } /** * @param index 一维索引 * @returns 格子坐标 */ cellFromIndex(index: number): Cell { return { row: Math.floor(index / this.cols), col: index % this.cols }; } /** * @param cells 格子列表 * @returns 各格 cellKey 字符串数组 */ keysOf(cells: Cell[]): string[] { return cells.map(cellKey); } }