diff --git a/src/core/PathFinding.ts b/src/core/PathFinding.ts new file mode 100644 index 000000000..0cfc2b8ba --- /dev/null +++ b/src/core/PathFinding.ts @@ -0,0 +1,71 @@ +import { PriorityQueue } from "@datastructures-js/priority-queue"; +import { Tile } from "./game/Game"; + + +export class AStar { + private openSet: PriorityQueue<{ tile: Tile; fScore: number; }>; + private cameFrom: Map; + private gScore: Map; + private current: Tile | null; + public completed: boolean; + + constructor(private src: Tile, private dst: Tile) { + this.openSet = new PriorityQueue<{ tile: Tile; fScore: number; }>( + (a, b) => a.fScore - b.fScore + ); + this.cameFrom = new Map(); + this.gScore = new Map(); + this.current = null; + this.completed = false; + + this.gScore.set(src, 0); + this.openSet.enqueue({ tile: src, fScore: this.heuristic(src, dst) }); + } + + compute(iterations: number): boolean { + if (this.completed) return true; + + while (!this.openSet.isEmpty()) { + iterations--; + this.current = this.openSet.dequeue()!.tile; + if (iterations <= 0) { + return false; + } + + if (this.current === this.dst) { + this.completed = true; + return true; + } + + for (const neighbor of this.current.neighborsWrapped()) { + if (neighbor != this.dst && neighbor.isLand()) continue; // Skip non-water tiles + + const tentativeGScore = this.gScore.get(this.current)! + 100 - neighbor.magnitude(); + + if (!this.gScore.has(neighbor) || tentativeGScore < this.gScore.get(neighbor)!) { + this.cameFrom.set(neighbor, this.current); + this.gScore.set(neighbor, tentativeGScore); + const fScore = tentativeGScore + this.heuristic(neighbor, this.dst); + + this.openSet.enqueue({ tile: neighbor, fScore: fScore }); + } + } + } + + return this.completed; + } + + private heuristic(a: Tile, b: Tile): number { + // Manhattan distance + return Math.abs(a.cell().x - b.cell().x) + Math.abs(a.cell().y - b.cell().y); + } + + public reconstructPath(): Tile[] { + const path = [this.current!]; + while (this.cameFrom.has(this.current!)) { + this.current = this.cameFrom.get(this.current!)!; + path.unshift(this.current); + } + return path; + } +} diff --git a/src/core/execution/DestroyerExecution.ts b/src/core/execution/DestroyerExecution.ts index 22267293a..d25ddfd8e 100644 --- a/src/core/execution/DestroyerExecution.ts +++ b/src/core/execution/DestroyerExecution.ts @@ -1,4 +1,4 @@ -import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game"; +import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game"; export class DestroyerExecution implements Execution { diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 32c3b8320..94dc04a5b 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -1,8 +1,8 @@ -import { PriorityQueue } from "@datastructures-js/priority-queue"; import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent, UnitType } from "../game/Game"; import { and, bfs, manhattanDistWrapped, sourceDstOceanShore } from "../Util"; import { AttackExecution } from "./AttackExecution"; import { DisplayMessageEvent, MessageType } from "../../client/graphics/layers/EventsDisplay"; +import { AStar } from "../PathFinding"; export class TransportShipExecution implements Execution { @@ -150,70 +150,3 @@ export class TransportShipExecution implements Execution { } -export class AStar { - private openSet: PriorityQueue<{ tile: Tile, fScore: number }>; - private cameFrom: Map; - private gScore: Map; - private current: Tile | null; - public completed: boolean; - - constructor(private src: Tile, private dst: Tile) { - this.openSet = new PriorityQueue<{ tile: Tile, fScore: number }>( - (a, b) => a.fScore - b.fScore - ); - this.cameFrom = new Map(); - this.gScore = new Map(); - this.current = null; - this.completed = false; - - this.gScore.set(src, 0); - this.openSet.enqueue({ tile: src, fScore: this.heuristic(src, dst) }); - } - - compute(iterations: number): boolean { - if (this.completed) return true; - - while (!this.openSet.isEmpty()) { - iterations-- - this.current = this.openSet.dequeue()!.tile; - if (iterations <= 0) { - return false - } - - if (this.current === this.dst) { - this.completed = true; - return true; - } - - for (const neighbor of this.current.neighborsWrapped()) { - if (neighbor != this.dst && neighbor.isLand()) continue; // Skip non-water tiles - - const tentativeGScore = this.gScore.get(this.current)! + 100 - neighbor.magnitude(); - - if (!this.gScore.has(neighbor) || tentativeGScore < this.gScore.get(neighbor)!) { - this.cameFrom.set(neighbor, this.current); - this.gScore.set(neighbor, tentativeGScore); - const fScore = tentativeGScore + this.heuristic(neighbor, this.dst); - - this.openSet.enqueue({ tile: neighbor, fScore: fScore }); - } - } - } - - return this.completed; - } - - private heuristic(a: Tile, b: Tile): number { - // Manhattan distance - return Math.abs(a.cell().x - b.cell().x) + Math.abs(a.cell().y - b.cell().y); - } - - public reconstructPath(): Tile[] { - const path = [this.current!]; - while (this.cameFrom.has(this.current!)) { - this.current = this.cameFrom.get(this.current!)!; - path.unshift(this.current); - } - return path; - } -} \ No newline at end of file