diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index c509403db..74b6554f6 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -8,7 +8,6 @@ import { UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; -import { PathFinder } from "../pathfinding/PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { TradeShipExecution } from "./TradeShipExecution"; @@ -79,9 +78,8 @@ export class PortExecution implements Execution { } const port = this.random.randElement(ports); - const pf = PathFinder.Mini(this.mg, 2500); this.mg.addExecution( - new TradeShipExecution(this.player().id(), this.port, port, pf), + new TradeShipExecution(this.player().id(), this.port, port), ); } diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 3272d2a2c..e25647544 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -10,8 +10,8 @@ import { UnitType, } from "../game/Game"; import { TileRef } from "../game/GameMap"; -import { PathFindResultType } from "../pathfinding/AStar"; -import { PathFinder } from "../pathfinding/PathFinding"; +import { AStar, PathFindResultType } from "../pathfinding/AStar"; +import { MiniAStar } from "../pathfinding/MiniAStar"; import { distSortUnit } from "../Util"; export class TradeShipExecution implements Execution { @@ -19,15 +19,14 @@ export class TradeShipExecution implements Execution { private mg: Game | null = null; private origOwner: Player | null = null; private tradeShip: Unit | null = null; - private index = 0; private wasCaptured = false; private tilesTraveled = 0; + private aStar: AStar | null = null; constructor( private _owner: PlayerID, private srcPort: Unit, private _dstPort: Unit, - private pathFinder: PathFinder, ) {} init(mg: Game, ticks: number): void { @@ -103,46 +102,82 @@ export class TradeShipExecution implements Execution { const cachedNextTile = this._dstPort.cacheGet(this.tradeShip.tile()); if (cachedNextTile !== undefined) { - if ( - this.mg.isWater(cachedNextTile) && - this.mg.isShoreline(cachedNextTile) - ) { - this.tradeShip.setSafeFromPirates(); - } - this.tradeShip.move(cachedNextTile); - this.tilesTraveled++; + this.moveTradeShip(cachedNextTile); return; } - const result = this.pathFinder.nextTile( - this.tradeShip.tile(), - this._dstPort.tile(), - ); + this.computeNewPath(); + } - switch (result.type) { - case PathFindResultType.Completed: - this.complete(); - break; + private fillCachePath(port: Unit, path: TileRef[]): void { + if (path.length < 2) { + throw new Error("path must have at least 2 points"); + } + for (let i = 0; i < path.length - 1; i++) { + if (port.cacheGet(path[i]) !== undefined) { + continue; + } + const from = path[i]; + const to = path[i + 1]; + port.cachePut(from, to); + } + } + + private moveTradeShip(nextTile?: TileRef): void { + if (nextTile === undefined) { + throw new Error("missing tile"); + } + + if (nextTile === this._dstPort.tile()) { + this.complete(); + return; + } + // Update safeFromPirates status + if (this.mg!.isWater(nextTile) && this.mg!.isShoreline(nextTile)) { + this.tradeShip!.setSafeFromPirates(); + } + this.tradeShip!.move(nextTile); + this.tilesTraveled++; + } + + private computeNewPath(): void { + if (this.aStar === null) { + this.aStar = new MiniAStar( + this.mg!, + this.mg!.miniMap(), + this.tradeShip!.tile(), + this._dstPort.tile(), + 2500, + 20, + ); + } + + switch (this.aStar.compute()) { case PathFindResultType.Pending: // Fire unit event to rerender. - this.tradeShip.touch(); + this.tradeShip!.touch(); break; - case PathFindResultType.NextTile: - this._dstPort.cachePut(this.tradeShip.tile(), result.tile); - // Update safeFromPirates status - if (this.mg.isWater(result.tile) && this.mg.isShoreline(result.tile)) { - this.tradeShip.setSafeFromPirates(); + case PathFindResultType.Completed: { + const fullPath = this.aStar.reconstructPath(); + if (fullPath === null || fullPath.length === 0) { + throw new Error("missing path"); } - this.tradeShip.move(result.tile); - this.tilesTraveled++; + this.fillCachePath(this._dstPort, fullPath); + if (!this.wasCaptured) { + this.fillCachePath(this.srcPort, fullPath.slice().reverse()); + } + this.moveTradeShip(fullPath.shift()); break; + } case PathFindResultType.PathNotFound: - consolex.warn("captured trade ship cannot find route"); - if (this.tradeShip.isActive()) { - this.tradeShip.delete(false); + consolex.warn("trade ship cannot find route"); + if (this.tradeShip!.isActive()) { + this.tradeShip!.delete(false); } this.active = false; break; + default: + throw new Error("unexpected path finding compute result"); } } diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index a1223d8be..8d59d34d9 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -215,7 +215,8 @@ export class TransportShipExecution implements Execution { break; case PathFindResultType.PathNotFound: // TODO: add to poisoned port list - consolex.warn(`path not found tot dst`); + consolex.warn(`path not found to dst`); + this.attacker.addTroops(this.troops); this.boat.delete(false); this.active = false; return; diff --git a/src/core/pathfinding/MiniAStar.ts b/src/core/pathfinding/MiniAStar.ts index 2c51a7a38..d85d1b2d0 100644 --- a/src/core/pathfinding/MiniAStar.ts +++ b/src/core/pathfinding/MiniAStar.ts @@ -9,7 +9,7 @@ export class MiniAStar implements AStar { constructor( private gameMap: GameMap, private miniMap: GameMap, - src: TileRef | TileRef[], + private src: TileRef | TileRef[], private dst: TileRef, iterations: number, maxTries: number, @@ -41,16 +41,52 @@ export class MiniAStar implements AStar { } reconstructPath(): TileRef[] { - const upscaled = upscalePath( - this.aStar - .reconstructPath() - .map((tr) => new Cell(this.miniMap.x(tr), this.miniMap.y(tr))), + let cellSrc: Cell | undefined; + if (!Array.isArray(this.src)) { + cellSrc = new Cell(this.gameMap.x(this.src), this.gameMap.y(this.src)); + } + const cellDst = new Cell( + this.gameMap.x(this.dst), + this.gameMap.y(this.dst), + ); + const upscaled = fixExtremes( + upscalePath( + this.aStar + .reconstructPath() + .map((tr) => new Cell(this.miniMap.x(tr), this.miniMap.y(tr))), + ), + cellDst, + cellSrc, ); - upscaled.push(new Cell(this.gameMap.x(this.dst), this.gameMap.y(this.dst))); return upscaled.map((c) => this.gameMap.ref(c.x, c.y)); } } +function fixExtremes(upscaled: Cell[], cellDst: Cell, cellSrc?: Cell): Cell[] { + if (cellSrc !== undefined) { + const srcIndex = findCell(upscaled, cellSrc); + if (srcIndex === -1) { + // didnt find the start tile in the path + upscaled.unshift(cellSrc); + } else if (srcIndex !== 0) { + // found start tile but not at the start + // remove all tiles before the start tile + upscaled = upscaled.slice(srcIndex); + } + } + + const dstIndex = findCell(upscaled, cellDst); + if (dstIndex === -1) { + // didnt find the dst tile in the path + upscaled.push(cellDst); + } else if (dstIndex !== upscaled.length - 1) { + // found dst tile but not at the end + // remove all tiles after the dst tile + upscaled = upscaled.slice(0, dstIndex + 1); + } + return upscaled; +} + function upscalePath(path: Cell[], scaleFactor: number = 2): Cell[] { // Scale up each point const scaledPath = path.map( @@ -92,3 +128,12 @@ function upscalePath(path: Cell[], scaleFactor: number = 2): Cell[] { return smoothPath; } + +function findCell(upscaled: Cell[], cellDst: Cell): number { + for (let i = 0; i < upscaled.length; i++) { + if (upscaled[i].x === cellDst.x && upscaled[i].y === cellDst.y) { + return i; + } + } + return -1; +}