Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions src/shared/game/systems/MovementSystem.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Unit } from '../entities/Unit';
import type { Tile } from '../entities/Tile';
import { TerrainType, UnitType } from '../entities/types';
import { isSame, toKey, type Position, getNeighbors } from '../entities/Position';
import { isSame, type Position } from '../entities/Position';

/* eslint-disable-next-line @typescript-eslint/no-extraneous-class */
export class MovementSystem {
Expand All @@ -11,17 +11,23 @@ export class MovementSystem {

static getReachableTiles(unit: Unit, map: Tile[][]): (Position & { cost: number })[] {
const reachable: (Position & { cost: number })[] = [];
const visited = new Map<string, number>();
const height = map.length;
const width = map[0]?.length ?? 0;

if (width === 0 || height === 0) return [];

// ⚡ Turbo: Use Map with numeric keys (y * width + x) to avoid string concatenation
// We use a Map instead of a full-map TypedArray to stay memory-efficient for large maps
// where units only explore a small local area.
const visited = new Map<number, number>();
const queue: (Position & { cost: number })[] = [];
let head = 0;

const startIdx = unit.position.y * width + unit.position.x;
queue.push({ ...unit.position, cost: 0 });
visited.set(toKey(unit.position), 0);

const height = map.length;
const width = map[0]?.length ?? 0;
visited.set(startIdx, 0);

// ⚡ Turbo: Use index-based queue to avoid O(N) shift() operations
// Use index-based queue to avoid O(N) shift() operations
while (head < queue.length) {
const current = queue[head++];
if (!current) continue;
Expand All @@ -31,23 +37,38 @@ export class MovementSystem {
reachable.push(current);
}

const neighbors = getNeighbors(current, width, height);
for (const neighbor of neighbors) {
const row = map[neighbor.y];
const targetTile = row?.[neighbor.x];
if (!targetTile) continue;
const { x, y, cost } = current;

// ⚡ Turbo: Inline neighbor search to avoid array and object allocations from getNeighbors
// Note: This 8-way traversal matches the getNeighbors implementation in Position.ts
for (let dy = -1; dy <= 1; dy++) {
const ny = y + dy;
if (ny < 0 || ny >= height) continue;

const row = map[ny];
if (!row) continue;

for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;

const nx = x + dx;
if (nx < 0 || nx >= width) continue;

const targetTile = row[nx];
if (!targetTile) continue;

const moveCost = this.getMovementCost(unit, targetTile);
const moveCost = this.getMovementCost(unit, targetTile);

if (moveCost !== Infinity) {
const totalCost = current.cost + moveCost;
if (moveCost !== Infinity) {
const totalCost = cost + moveCost;

if (totalCost <= unit.movesRemaining) {
const key = toKey(neighbor);
const prevCost = visited.get(key);
if (prevCost === undefined || prevCost > totalCost) {
visited.set(key, totalCost);
queue.push({ ...neighbor, cost: totalCost });
if (totalCost <= unit.movesRemaining) {
const idx = ny * width + nx;
const prevCost = visited.get(idx);
if (prevCost === undefined || prevCost > totalCost) {
visited.set(idx, totalCost);
queue.push({ x: nx, y: ny, cost: totalCost });
}
}
}
}
Expand Down