mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-27 14:54:20 +00:00
use TileRef instead of TerrainTile for astar
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { Executor } from "../core/execution/ExecutionManager";
|
||||
import { Cell, MutableGame, PlayerID, GameMapType, Difficulty, GameType } from "../core/game/Game";
|
||||
import { createGame } from "../core/game/GameImpl";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||
import { InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent } from "./InputHandler"
|
||||
|
||||
@@ -14,7 +14,7 @@ import { GameUpdateViewData, packTileData } from "./GameView";
|
||||
export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise<GameRunner> {
|
||||
const config = getConfig(gameConfig)
|
||||
const terrainMap = await loadTerrainMap(gameConfig.gameMap);
|
||||
const game = createGame(terrainMap.map, terrainMap.miniMap, config)
|
||||
const game = createGame(terrainMap.gameMap, terrainMap.miniGameMap, terrainMap.map, terrainMap.miniMap, config)
|
||||
const gr = new GameRunner(game as MutableGame, new Executor(game, gameID), callBack)
|
||||
gr.init()
|
||||
return gr
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage
|
||||
import { ClientID } from "./Schemas";
|
||||
import { TerraNulliusImpl } from './game/TerraNulliusImpl';
|
||||
import { WorkerClient } from './worker/WorkerClient';
|
||||
import { TileRef } from './game/GameMap';
|
||||
|
||||
|
||||
export class TileView {
|
||||
@@ -12,6 +13,9 @@ export class TileView {
|
||||
|
||||
constructor(private game: GameView, public data: TileUpdate, private _terrain: TerrainTile) { }
|
||||
|
||||
ref(): TileRef {
|
||||
throw new Error('uh oh')
|
||||
}
|
||||
type(): TerrainType {
|
||||
return this._terrain.type()
|
||||
}
|
||||
@@ -32,6 +36,9 @@ export class TileView {
|
||||
}
|
||||
return false
|
||||
}
|
||||
isBorderUpdated(): boolean {
|
||||
return this.data.isBorder
|
||||
}
|
||||
cell(): Cell {
|
||||
return this._terrain.cell()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class BattleshipExecution implements Execution {
|
||||
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, t => t.type() == TerrainType.Ocean)
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolCenterTile = mg.tile(this.cell)
|
||||
|
||||
@@ -30,7 +30,7 @@ export class DestroyerExecution implements Execution {
|
||||
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, t => t.type() == TerrainType.Ocean)
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolCenterTile = mg.tile(this.cell)
|
||||
|
||||
@@ -27,7 +27,7 @@ export class NukeExecution implements Execution {
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, () => true)
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true)
|
||||
this.player = mg.player(this.senderID)
|
||||
this.dst = this.mg.tile(this.cell)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { bfs, dist, manhattanDist } from "../Util";
|
||||
import { TradeShipExecution } from "./TradeShipExecution";
|
||||
import { consolex } from "../Consolex";
|
||||
import { MiniAStar } from "../pathfinding/MiniAStar";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class PortExecution implements Execution {
|
||||
|
||||
@@ -85,10 +86,11 @@ export class PortExecution implements Execution {
|
||||
}
|
||||
|
||||
const pf = new MiniAStar(
|
||||
this.mg.terrainMap(),
|
||||
this.mg.terrainMiniMap(),
|
||||
this.port.tile().terrain(), port.tile().terrain(),
|
||||
sn => sn.type() == TerrainType.Ocean,
|
||||
this.mg.map(),
|
||||
this.mg.miniMap(),
|
||||
this.port.tile().ref(),
|
||||
port.tile().ref(),
|
||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||
10_000,
|
||||
25
|
||||
)
|
||||
@@ -108,7 +110,7 @@ export class PortExecution implements Execution {
|
||||
const port = this.random.randElement(portConnections)
|
||||
const path = this.portPaths.get(port)
|
||||
if (path != null) {
|
||||
const pf = PathFinder.Mini(this.mg, 10, (sn) => sn.type() == TerrainType.Ocean)
|
||||
const pf = PathFinder.Mini(this.mg, 10, false)
|
||||
this.mg.addExecution(new TradeShipExecution(this.player().id(), this.port, port, pf, path))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export class ShellExecution implements Execution {
|
||||
}
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.pathFinder = PathFinder.Mini(mg, 2000, () => true, 10)
|
||||
this.pathFinder = PathFinder.Mini(mg, 2000, true, 10)
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
|
||||
@@ -45,7 +45,7 @@ export class TransportShipExecution implements Execution {
|
||||
init(mg: MutableGame, ticks: number) {
|
||||
this.lastMove = ticks
|
||||
this.mg = mg
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, t => t.type() == TerrainType.Ocean, 2)
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, false, 2)
|
||||
|
||||
this.attacker = mg.player(this.attackerID)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Config } from "../configuration/Config"
|
||||
import { GameEvent } from "../EventBus"
|
||||
import { ClientID, GameConfig, GameID } from "../Schemas"
|
||||
import { GameMap, TileRef } from "./GameMap"
|
||||
|
||||
export type PlayerID = string
|
||||
export type Tick = number
|
||||
@@ -221,6 +222,7 @@ export interface Tile {
|
||||
terrain(): TerrainTile
|
||||
neighbors(): Tile[]
|
||||
hasDefenseBonus(): boolean
|
||||
ref(): TileRef
|
||||
}
|
||||
|
||||
export interface MutableTile extends Tile {
|
||||
@@ -376,6 +378,9 @@ export interface Game {
|
||||
unitInfo(type: UnitType): UnitInfo
|
||||
terrainMap(): TerrainMap
|
||||
terrainMiniMap(): TerrainMap
|
||||
|
||||
map(): GameMap
|
||||
miniMap(): GameMap
|
||||
}
|
||||
|
||||
export interface MutableGame extends Game {
|
||||
|
||||
+21
-12
@@ -11,9 +11,10 @@ import { MessageType } from './Game';
|
||||
import { UnitImpl } from "./UnitImpl";
|
||||
import { consolex } from "../Consolex";
|
||||
import { string } from "zod";
|
||||
import { GameMap } from "./GameMap";
|
||||
|
||||
export function createGame(terrainMap: TerrainMapImpl, miniMap: TerrainMap, config: Config): Game {
|
||||
return new GameImpl(terrainMap, miniMap, config)
|
||||
export function createGame(gameMap: GameMap, miniGameMap: GameMap, terrainMap: TerrainMapImpl, miniMap: TerrainMap, config: Config): Game {
|
||||
return new GameImpl(terrainMap, miniMap, gameMap, miniGameMap, config)
|
||||
}
|
||||
|
||||
export type CellString = string
|
||||
@@ -23,7 +24,7 @@ export class GameImpl implements MutableGame {
|
||||
|
||||
private unInitExecs: Execution[] = []
|
||||
|
||||
map: TileImpl[][]
|
||||
_map: TileImpl[][]
|
||||
|
||||
private nations_: Nation[] = []
|
||||
|
||||
@@ -45,17 +46,19 @@ export class GameImpl implements MutableGame {
|
||||
constructor(
|
||||
private _terrainMap: TerrainMapImpl,
|
||||
private _miniMap: TerrainMap,
|
||||
private gameMap: GameMap,
|
||||
private miniGameMap: GameMap,
|
||||
private _config: Config,
|
||||
) {
|
||||
this._terraNullius = new TerraNulliusImpl()
|
||||
this._width = _terrainMap.width();
|
||||
this._height = _terrainMap.height();
|
||||
this.map = new Array(this._width);
|
||||
this._map = new Array(this._width);
|
||||
for (let x = 0; x < this._width; x++) {
|
||||
this.map[x] = new Array(this._height);
|
||||
this._map[x] = new Array(this._height);
|
||||
for (let y = 0; y < this._height; y++) {
|
||||
let cell = new Cell(x, y);
|
||||
this.map[x][y] = new TileImpl(this, this._terraNullius, cell, _terrainMap);
|
||||
this._map[x][y] = new TileImpl(this, this._terraNullius, cell, _terrainMap);
|
||||
}
|
||||
}
|
||||
this.nations_ = _terrainMap.nationMap.nations
|
||||
@@ -65,6 +68,12 @@ export class GameImpl implements MutableGame {
|
||||
n.strength
|
||||
))
|
||||
}
|
||||
map(): GameMap {
|
||||
return this.gameMap
|
||||
}
|
||||
miniMap(): GameMap {
|
||||
return this.miniGameMap
|
||||
}
|
||||
|
||||
addUpdate(update: GameUpdate) {
|
||||
(this.updates[update.type] as any[]).push(update);
|
||||
@@ -296,7 +305,7 @@ export class GameImpl implements MutableGame {
|
||||
|
||||
tile(cell: Cell): MutableTile {
|
||||
this.assertIsOnMap(cell)
|
||||
return this.map[cell.x][cell.y] as MutableTile
|
||||
return this._map[cell.x][cell.y] as MutableTile
|
||||
}
|
||||
|
||||
isOnMap(cell: Cell): boolean {
|
||||
@@ -311,16 +320,16 @@ export class GameImpl implements MutableGame {
|
||||
const y = tile.cell().y
|
||||
const ns: TileImpl[] = []
|
||||
if (y > 0) {
|
||||
ns.push(this.map[x][y - 1])
|
||||
ns.push(this._map[x][y - 1])
|
||||
}
|
||||
if (y < this._height - 1) {
|
||||
ns.push(this.map[x][y + 1])
|
||||
ns.push(this._map[x][y + 1])
|
||||
}
|
||||
if (x > 0) {
|
||||
ns.push(this.map[x - 1][y])
|
||||
ns.push(this._map[x - 1][y])
|
||||
}
|
||||
if (x < this._width - 1) {
|
||||
ns.push(this.map[x + 1][y])
|
||||
ns.push(this._map[x + 1][y])
|
||||
}
|
||||
return ns
|
||||
}
|
||||
@@ -335,7 +344,7 @@ export class GameImpl implements MutableGame {
|
||||
const newX = x + dx
|
||||
const newY = y + dy
|
||||
if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) {
|
||||
ns.push(this.map[newX][newY])
|
||||
ns.push(this._map[newX][newY])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TerrainType } from "./Game";
|
||||
import { Cell, TerrainType } from "./Game";
|
||||
|
||||
export type TileRef = number;
|
||||
|
||||
@@ -9,10 +9,10 @@ export class GameMap {
|
||||
private readonly height_: number;
|
||||
|
||||
// Terrain bits (Uint8Array)
|
||||
private static readonly IS_LAND_BIT = 0;
|
||||
private static readonly SHORELINE_BIT = 1;
|
||||
private static readonly OCEAN_BIT = 2;
|
||||
private static readonly MAGNITUDE_OFFSET = 3; // Uses bits 3-7 (5 bits)
|
||||
private static readonly IS_LAND_BIT = 7;
|
||||
private static readonly SHORELINE_BIT = 6;
|
||||
private static readonly OCEAN_BIT = 5;
|
||||
private static readonly MAGNITUDE_OFFSET = 4; // Uses bits 3-7 (5 bits)
|
||||
private static readonly MAGNITUDE_MASK = 0x1F; // 11111 in binary
|
||||
|
||||
// State bits (Uint16Array)
|
||||
@@ -48,6 +48,10 @@ export class GameMap {
|
||||
return Math.floor(ref / this.width_);
|
||||
}
|
||||
|
||||
cell(ref: TileRef): Cell {
|
||||
return new Cell(this.x(ref), this.y(ref))
|
||||
}
|
||||
|
||||
width(): number { return this.width_; }
|
||||
height(): number { return this.height_; }
|
||||
numLandTiles(): number { return this.numLandTiles_; }
|
||||
@@ -70,7 +74,7 @@ export class GameMap {
|
||||
}
|
||||
|
||||
magnitude(ref: TileRef): number {
|
||||
return (this.terrain[ref] >> GameMap.MAGNITUDE_OFFSET) & GameMap.MAGNITUDE_MASK;
|
||||
return this.terrain[ref] & GameMap.MAGNITUDE_MASK;
|
||||
}
|
||||
|
||||
// State getters and setters (mutable)
|
||||
@@ -157,6 +161,10 @@ export class GameMap {
|
||||
if (ref % w !== 0) neighbors.push(ref - 1);
|
||||
if (ref % w !== w - 1) neighbors.push(ref + 1);
|
||||
|
||||
for (const n of neighbors) {
|
||||
(this.ref(this.x(n), this.y(n)))
|
||||
}
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { TerrainMapImpl, TerrainTileImpl } from "./TerrainMapLoader";
|
||||
import { GameImpl } from "./GameImpl";
|
||||
import { PlayerImpl } from "./PlayerImpl";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { TileRef } from "./GameMap";
|
||||
|
||||
|
||||
export class TileImpl implements MutableTile {
|
||||
@@ -21,6 +22,10 @@ export class TileImpl implements MutableTile {
|
||||
private terrainMap: TerrainMapImpl
|
||||
) { }
|
||||
|
||||
ref(): TileRef {
|
||||
return this.gs.map().ref(this._cell.x, this._cell.y)
|
||||
}
|
||||
|
||||
toUpdate(): TileUpdate {
|
||||
return {
|
||||
type: GameUpdateType.Tile,
|
||||
@@ -71,26 +76,26 @@ export class TileImpl implements MutableTile {
|
||||
|
||||
// Check top neighbor
|
||||
if (y > 0) {
|
||||
ns.push(this.gs.map[x][y - 1]);
|
||||
ns.push(this.gs._map[x][y - 1]);
|
||||
}
|
||||
|
||||
// Check bottom neighbor
|
||||
if (y < this.gs.height() - 1) {
|
||||
ns.push(this.gs.map[x][y + 1]);
|
||||
ns.push(this.gs._map[x][y + 1]);
|
||||
}
|
||||
|
||||
// Check left neighbor (wrap around)
|
||||
if (x > 0) {
|
||||
ns.push(this.gs.map[x - 1][y]);
|
||||
ns.push(this.gs._map[x - 1][y]);
|
||||
} else {
|
||||
ns.push(this.gs.map[this.gs.width() - 1][y]);
|
||||
ns.push(this.gs._map[this.gs.width() - 1][y]);
|
||||
}
|
||||
|
||||
// Check right neighbor (wrap around)
|
||||
if (x < this.gs.width() - 1) {
|
||||
ns.push(this.gs.map[x + 1][y]);
|
||||
ns.push(this.gs._map[x + 1][y]);
|
||||
} else {
|
||||
ns.push(this.gs.map[0][y]);
|
||||
ns.push(this.gs._map[0][y]);
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Cell, Game, TerrainMap, TerrainTile, TerrainType } from "../game/Game";
|
||||
import { AStar, PathFindResultType, } from "./AStar";
|
||||
import { GameManager } from "../../server/GameManager";
|
||||
import { Cell, Game, TerrainMap, TerrainType } from "../game/Game";
|
||||
import { GameMap, TileRef } from "../game/GameMap";
|
||||
import { AStar, PathFindResultType, } from "./AStar";
|
||||
import { SerialAStar } from "./SerialAStar";
|
||||
|
||||
// TODO: test this, get it work
|
||||
@@ -8,22 +10,29 @@ export class MiniAStar implements AStar {
|
||||
private aStar: SerialAStar
|
||||
|
||||
constructor(
|
||||
private terrainMap: TerrainMap,
|
||||
private miniMap: TerrainMap,
|
||||
private src: TerrainTile,
|
||||
private dst: TerrainTile,
|
||||
private canMove: (t: TerrainTile) => boolean,
|
||||
private gameMap: GameMap,
|
||||
private miniMap: GameMap,
|
||||
private src: TileRef,
|
||||
private dst: TileRef,
|
||||
private canMove: (t: TileRef) => boolean,
|
||||
private iterations: number,
|
||||
private maxTries: number
|
||||
) {
|
||||
const miniSrc = miniMap.terrain(new Cell(Math.floor(src.cell().x / 2), Math.floor(src.cell().y / 2)))
|
||||
const miniDst = miniMap.terrain(new Cell(Math.floor(dst.cell().x / 2), Math.floor(dst.cell().y / 2)))
|
||||
const miniSrc = this.miniMap.ref(
|
||||
Math.floor(gameMap.x(src) / 2),
|
||||
Math.floor(gameMap.y(src) / 2)
|
||||
)
|
||||
const miniDst = this.miniMap.ref(
|
||||
Math.floor(gameMap.x(dst) / 2),
|
||||
Math.floor(gameMap.y(dst) / 2)
|
||||
)
|
||||
this.aStar = new SerialAStar(
|
||||
miniSrc,
|
||||
miniDst,
|
||||
canMove,
|
||||
iterations,
|
||||
maxTries
|
||||
maxTries,
|
||||
this.miniMap
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +42,7 @@ export class MiniAStar implements AStar {
|
||||
|
||||
reconstructPath(): Cell[] {
|
||||
const upscaled = upscalePath(this.aStar.reconstructPath())
|
||||
upscaled.push(this.dst.cell())
|
||||
upscaled.push(new Cell(this.gameMap.x(this.dst), this.gameMap.y(this.dst)))
|
||||
return upscaled
|
||||
}
|
||||
|
||||
@@ -42,8 +51,8 @@ export class MiniAStar implements AStar {
|
||||
function upscalePath(path: Cell[], scaleFactor: number = 2): Cell[] {
|
||||
// Scale up each point
|
||||
const scaledPath = path.map(point => (new Cell(
|
||||
point.x * scaleFactor,
|
||||
point.y * scaleFactor
|
||||
point.x * scaleFactor,
|
||||
point.y * scaleFactor
|
||||
)));
|
||||
|
||||
const smoothPath: Cell[] = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AStar, PathFindResultType, TileResult } from "./AStar";
|
||||
import { SerialAStar } from "./SerialAStar";
|
||||
import { MiniAStar } from "./MiniAStar";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class PathFinder {
|
||||
|
||||
@@ -19,31 +20,23 @@ export class PathFinder {
|
||||
) { }
|
||||
|
||||
|
||||
public static Mini(game: Game, iterations: number, canMove: (s: TerrainTile) => boolean, maxTries: number = 20) {
|
||||
public static Mini(game: Game, iterations: number, canMoveOnLand: boolean, maxTries: number = 20) {
|
||||
return new PathFinder(
|
||||
game,
|
||||
(curr: Tile, dst: Tile) => {
|
||||
const currRef = game.map().ref(curr.cell().x, curr.cell().y)
|
||||
const dstRef = game.map().ref(dst.cell().x, dst.cell().y)
|
||||
return new MiniAStar(
|
||||
game.terrainMap(),
|
||||
game.terrainMiniMap(),
|
||||
curr.terrain(),
|
||||
dst.terrain(),
|
||||
canMove,
|
||||
iterations,
|
||||
maxTries
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public static Serial(game: Game, iterations: number, canMove: (t: TerrainTile) => boolean, maxTries: number = 20): PathFinder {
|
||||
return new PathFinder(
|
||||
game,
|
||||
(curr: Tile, dst: Tile) => {
|
||||
return new SerialAStar(
|
||||
curr.terrain(),
|
||||
dst.terrain(),
|
||||
canMove,
|
||||
game.map(),
|
||||
game.miniMap(),
|
||||
currRef,
|
||||
dstRef,
|
||||
(tr: TileRef): boolean => {
|
||||
if (canMoveOnLand) {
|
||||
return true
|
||||
}
|
||||
return game.miniMap().isOcean(tr)
|
||||
},
|
||||
iterations,
|
||||
maxTries
|
||||
)
|
||||
|
||||
@@ -1,46 +1,48 @@
|
||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||
import { AStar} from "./AStar";
|
||||
import { AStar } from "./AStar";
|
||||
import { PathFindResultType } from "./AStar";
|
||||
import { Cell, TerrainTile, TerrainTileKey } from "../game/Game";
|
||||
import { Cell } from "../game/Game";
|
||||
import { consolex } from "../Consolex";
|
||||
import { GameMap, TileRef } from "../game/GameMap";
|
||||
|
||||
|
||||
export class SerialAStar implements AStar {
|
||||
private fwdOpenSet: PriorityQueue<{ tile: TerrainTile; fScore: number; }>;
|
||||
private bwdOpenSet: PriorityQueue<{ tile: TerrainTile; fScore: number; }>;
|
||||
private fwdCameFrom: Map<TerrainTileKey, TerrainTile>;
|
||||
private bwdCameFrom: Map<TerrainTileKey, TerrainTile>;
|
||||
private fwdGScore: Map<TerrainTileKey, number>;
|
||||
private bwdGScore: Map<TerrainTileKey, number>;
|
||||
private meetingPoint: TerrainTile | null;
|
||||
private fwdOpenSet: PriorityQueue<{ tile: TileRef; fScore: number; }>;
|
||||
private bwdOpenSet: PriorityQueue<{ tile: TileRef; fScore: number; }>;
|
||||
private fwdCameFrom: Map<TileRef, TileRef>;
|
||||
private bwdCameFrom: Map<TileRef, TileRef>;
|
||||
private fwdGScore: Map<TileRef, number>;
|
||||
private bwdGScore: Map<TileRef, number>;
|
||||
private meetingPoint: TileRef | null;
|
||||
public completed: boolean;
|
||||
|
||||
constructor(
|
||||
private src: TerrainTile,
|
||||
private dst: TerrainTile,
|
||||
private canMove: (t: TerrainTile) => boolean,
|
||||
private src: TileRef,
|
||||
private dst: TileRef,
|
||||
private canMove: (t: TileRef) => boolean,
|
||||
private iterations: number,
|
||||
private maxTries: number
|
||||
private maxTries: number,
|
||||
private gameMap: GameMap
|
||||
) {
|
||||
this.fwdOpenSet = new PriorityQueue<{ tile: TerrainTile; fScore: number; }>(
|
||||
this.fwdOpenSet = new PriorityQueue<{ tile: TileRef; fScore: number; }>(
|
||||
(a, b) => a.fScore - b.fScore
|
||||
);
|
||||
this.bwdOpenSet = new PriorityQueue<{ tile: TerrainTile; fScore: number; }>(
|
||||
this.bwdOpenSet = new PriorityQueue<{ tile: TileRef; fScore: number; }>(
|
||||
(a, b) => a.fScore - b.fScore
|
||||
);
|
||||
this.fwdCameFrom = new Map<TerrainTileKey, TerrainTile>();
|
||||
this.bwdCameFrom = new Map<TerrainTileKey, TerrainTile>();
|
||||
this.fwdGScore = new Map<TerrainTileKey, number>();
|
||||
this.bwdGScore = new Map<TerrainTileKey, number>();
|
||||
this.fwdCameFrom = new Map<TileRef, TileRef>();
|
||||
this.bwdCameFrom = new Map<TileRef, TileRef>();
|
||||
this.fwdGScore = new Map<TileRef, number>();
|
||||
this.bwdGScore = new Map<TileRef, number>();
|
||||
this.meetingPoint = null;
|
||||
this.completed = false;
|
||||
|
||||
// Initialize forward search
|
||||
this.fwdGScore.set(src.key(), 0);
|
||||
this.fwdGScore.set(src, 0);
|
||||
this.fwdOpenSet.enqueue({ tile: src, fScore: this.heuristic(src, dst) });
|
||||
|
||||
// Initialize backward search
|
||||
this.bwdGScore.set(dst.key(), 0);
|
||||
this.bwdGScore.set(dst, 0);
|
||||
this.bwdOpenSet.enqueue({ tile: dst, fScore: this.heuristic(dst, src) });
|
||||
}
|
||||
|
||||
@@ -61,43 +63,43 @@ export class SerialAStar implements AStar {
|
||||
|
||||
// Process forward search
|
||||
const fwdCurrent = this.fwdOpenSet.dequeue()!.tile;
|
||||
if (this.bwdGScore.has(fwdCurrent.key())) {
|
||||
if (this.bwdGScore.has(fwdCurrent)) {
|
||||
// We found a meeting point!
|
||||
this.meetingPoint = fwdCurrent;
|
||||
this.completed = true;
|
||||
return PathFindResultType.Completed;
|
||||
}
|
||||
|
||||
this.expandTerrainTile(fwdCurrent, true);
|
||||
this.expandTileRef(fwdCurrent, true);
|
||||
|
||||
// Process backward search
|
||||
const bwdCurrent = this.bwdOpenSet.dequeue()!.tile;
|
||||
if (this.fwdGScore.has(bwdCurrent.key())) {
|
||||
if (this.fwdGScore.has(bwdCurrent)) {
|
||||
// We found a meeting point!
|
||||
this.meetingPoint = bwdCurrent;
|
||||
this.completed = true;
|
||||
return PathFindResultType.Completed;
|
||||
}
|
||||
|
||||
this.expandTerrainTile(bwdCurrent, false);
|
||||
this.expandTileRef(bwdCurrent, false);
|
||||
}
|
||||
|
||||
return this.completed ? PathFindResultType.Completed : PathFindResultType.PathNotFound;
|
||||
}
|
||||
|
||||
private expandTerrainTile(current: TerrainTile, isForward: boolean) {
|
||||
for (const neighbor of current.neighbors()) {
|
||||
if (!neighbor.equals(isForward ? this.dst : this.src) && !this.canMove(neighbor)) continue;
|
||||
private expandTileRef(current: TileRef, isForward: boolean) {
|
||||
for (const neighbor of this.gameMap.neighbors(current)) {
|
||||
if (neighbor != (isForward ? this.dst : this.src) && !this.canMove(neighbor)) continue;
|
||||
|
||||
const gScore = isForward ? this.fwdGScore : this.bwdGScore;
|
||||
const openSet = isForward ? this.fwdOpenSet : this.bwdOpenSet;
|
||||
const cameFrom = isForward ? this.fwdCameFrom : this.bwdCameFrom;
|
||||
|
||||
let tentativeGScore = gScore.get(current.key())! + neighbor.cost();
|
||||
let tentativeGScore = gScore.get(current)! + this.gameMap.cost(neighbor);
|
||||
|
||||
if (!gScore.has(neighbor.key()) || tentativeGScore < gScore.get(neighbor.key())!) {
|
||||
cameFrom.set(neighbor.key(), current);
|
||||
gScore.set(neighbor.key(), tentativeGScore);
|
||||
if (!gScore.has(neighbor) || tentativeGScore < gScore.get(neighbor)!) {
|
||||
cameFrom.set(neighbor, current);
|
||||
gScore.set(neighbor, tentativeGScore);
|
||||
const fScore = tentativeGScore + this.heuristic(
|
||||
neighbor,
|
||||
isForward ? this.dst : this.src
|
||||
@@ -107,10 +109,10 @@ export class SerialAStar implements AStar {
|
||||
}
|
||||
}
|
||||
|
||||
private heuristic(a: TerrainTile, b: TerrainTile): number {
|
||||
private heuristic(a: TileRef, b: TileRef): number {
|
||||
// TODO use wrapped
|
||||
try {
|
||||
return 1.1 * Math.abs(a.cell().x - b.cell().x) + Math.abs(a.cell().y - b.cell().y);
|
||||
return 1.1 * Math.abs(this.gameMap.x(a) - this.gameMap.x(b)) + Math.abs(this.gameMap.y(a) - this.gameMap.y(b));
|
||||
} catch {
|
||||
consolex.log('uh oh')
|
||||
}
|
||||
@@ -120,20 +122,20 @@ export class SerialAStar implements AStar {
|
||||
if (!this.meetingPoint) return [];
|
||||
|
||||
// Reconstruct path from start to meeting point
|
||||
const fwdPath: TerrainTile[] = [this.meetingPoint];
|
||||
const fwdPath: TileRef[] = [this.meetingPoint];
|
||||
let current = this.meetingPoint;
|
||||
while (this.fwdCameFrom.has(current.key())) {
|
||||
current = this.fwdCameFrom.get(current.key())!;
|
||||
while (this.fwdCameFrom.has(current)) {
|
||||
current = this.fwdCameFrom.get(current)!;
|
||||
fwdPath.unshift(current);
|
||||
}
|
||||
|
||||
// Reconstruct path from meeting point to goal
|
||||
current = this.meetingPoint;
|
||||
while (this.bwdCameFrom.has(current.key())) {
|
||||
current = this.bwdCameFrom.get(current.key())!;
|
||||
while (this.bwdCameFrom.has(current)) {
|
||||
current = this.bwdCameFrom.get(current)!;
|
||||
fwdPath.push(current);
|
||||
}
|
||||
|
||||
return fwdPath.map(sn => sn.cell());
|
||||
return fwdPath.map(sn => new Cell(this.gameMap.x(sn), this.gameMap.y(sn)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user