Files
restitution/gameServer/src/systems/PathfindingService.ts
kaykayyali 3fc29f728e feat: Colyseus authoritative server + invite-code lobby
- Replace socket.io relay with Colyseus 0.15 authoritative server
- GameRoom with GameState schema (players, units, resources)
- Pure TS services: CombatResolver, EconomyService, PathfindingService, UnitManager
- POST /api/create-room → 4-char invite code
- React/MUI LobbyScreen: Create (shows code + START GAME) / Join by code
- ColyseusClient: joinOrCreate/join by room type = invite code
- Nginx: static assets direct, all else proxied to Colyseus (WS upgrade)
- Content-hashed JS bundles for Cloudflare cache-busting
- 1-player lobbies: START GAME button bypasses 2-player wait
2026-05-30 02:49:20 +00:00

90 lines
2.4 KiB
TypeScript

import EasyStar from "easystarjs";
/**
* PathfindingService — Server-side A* pathfinding service via EasyStar.js.
*
* Pure grid-based pathfinding, no Phaser dependency. Used by the
* authoritative Colyseus server to validate unit movement and compute
* tile-paths from map data.
*
* Grid semantics: 0 = walkable, 1 = blocked.
*/
export class PathfindingService {
private easystar: EasyStar.js;
private grid: number[][];
constructor(width: number, height: number) {
this.easystar = new EasyStar.js();
this.easystar.setIterationsPerCalculation(1000);
this.easystar.enableDiagonals();
this.easystar.enableCornerCutting();
// Initialize with an empty grid matching dimensions
this.grid = Array.from({ length: height }, () => Array(width).fill(0));
}
/**
* Replace the entire walkability grid and re-register acceptable tiles.
*/
setGrid(grid: number[][]): void {
this.grid = grid;
this.easystar.setGrid(grid);
this.easystar.setAcceptableTiles([0]);
}
/**
* Mark a single tile as walkable (0) or blocked (1).
* Re-registers the grid with EasyStar when the value actually changes.
*/
setWalkable(x: number, y: number, walkable: boolean): void {
if (
y < 0 ||
y >= this.grid.length ||
x < 0 ||
x >= this.grid[0].length
) {
return;
}
const newValue = walkable ? 0 : 1;
if (this.grid[y][x] === newValue) return;
this.grid[y][x] = newValue;
this.easystar.setGrid(this.grid);
}
/**
* Asynchronously find a tile-path between two tile coordinates.
* Returns the path as an array of {x, y} positions, or null if no path exists.
*/
findPath(
from: { x: number; y: number },
to: { x: number; y: number },
): Promise<{ x: number; y: number }[] | null> {
return new Promise((resolve) => {
this.easystar.findPath(
from.x,
from.y,
to.x,
to.y,
(path: { x: number; y: number }[] | null) => {
resolve(path);
},
);
this.easystar.calculate();
});
}
/**
* Validate whether a move from one tile to another is possible.
* Returns true if a path exists, false otherwise.
*/
async isValidMove(
from: { x: number; y: number },
to: { x: number; y: number },
): Promise<boolean> {
const path = await this.findPath(from, to);
return path !== null;
}
}