diff --git a/TODO.txt b/TODO.txt index 22f221e1e..1d4b71b63 100644 --- a/TODO.txt +++ b/TODO.txt @@ -184,6 +184,7 @@ * trade ship gives gold when completes route DONE 11/15/2024 * add missile silo DONE 11/15/2024 * nuke spawns from missile silo +* BUG: can build destroyer on land * destroyer can capture trade ships * add battleship * add defense post diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts index c6b93aa69..6ea3ab1ee 100644 --- a/src/client/graphics/layers/UnitLayer.ts +++ b/src/client/graphics/layers/UnitLayer.ts @@ -80,6 +80,8 @@ export class UnitLayer implements Layer { break; case UnitType.TradeShip: this.handleTradeShipEvent(event) + case UnitType.Nuke: + this.handleNuke(event) } } @@ -93,6 +95,17 @@ export class UnitLayer implements Layer { .forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.unit.owner().info()), 180)); } + private handleNuke(event: UnitEvent) { + bfs(event.oldTile, euclDist(event.oldTile, 2)).forEach(t => { + this.clearCell(t.cell()); + }); + if (event.unit.isActive()) { + bfs(event.unit.tile(), euclDist(event.unit.tile(), 2)) + .forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.unit.owner().info()), 255)); + } + + } + private handleTradeShipEvent(event: UnitEvent) { bfs(event.oldTile, euclDist(event.oldTile, 1)).forEach(t => { this.clearCell(t.cell()); @@ -130,6 +143,7 @@ export class UnitLayer implements Layer { } } + paintCell(cell: Cell, color: Colord, alpha: number) { const index = (cell.y * this.game.width()) + cell.x; const offset = index * 4; diff --git a/src/core/PathFinding.ts b/src/core/PathFinding.ts index d1aecd9ed..e8dd5a6fc 100644 --- a/src/core/PathFinding.ts +++ b/src/core/PathFinding.ts @@ -12,7 +12,11 @@ export class AStar { private meetingPoint: Tile | null; public completed: boolean; - constructor(private src: Tile, private dst: Tile) { + constructor( + private src: Tile, + private dst: Tile, + private canMove: (t: Tile) => boolean + ) { this.fwdOpenSet = new PriorityQueue<{ tile: Tile; fScore: number; }>( (a, b) => a.fScore - b.fScore ); @@ -70,7 +74,7 @@ export class AStar { private expandNode(current: Tile, isForward: boolean) { for (const neighbor of current.neighborsWrapped()) { - if (neighbor !== (isForward ? this.dst : this.src) && neighbor.isLand()) continue; + 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; @@ -128,7 +132,7 @@ export class PathFinder { private aStar: AStar private inProgress = false - constructor(private iterations: number) { + constructor(private iterations: number, private canMove: (t: Tile) => boolean) { } @@ -145,7 +149,7 @@ export class PathFinder { this.curr = curr this.dst = dst this.path = null - this.aStar = new AStar(curr, dst) + this.aStar = new AStar(curr, dst, this.canMove) if (this.aStar.compute(this.iterations)) { this.inProgress = false this.path = this.aStar.reconstructPath() diff --git a/src/core/execution/DestroyerExecution.ts b/src/core/execution/DestroyerExecution.ts index 99e508201..ec58a3b3c 100644 --- a/src/core/execution/DestroyerExecution.ts +++ b/src/core/execution/DestroyerExecution.ts @@ -1,4 +1,4 @@ -import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game"; +import { BuildItems, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game"; import { AStar, PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { distSort, distSortUnit, manhattanDist } from "../Util"; @@ -12,7 +12,7 @@ export class DestroyerExecution implements Execution { private mg: MutableGame = null private target: MutableUnit = null - private pathfinder = new PathFinder(5000) + private pathfinder = new PathFinder(5000, t => t.isWater()) private patrolTile: Tile; private patrolCenterTile: Tile @@ -37,6 +37,7 @@ export class DestroyerExecution implements Execution { tick(ticks: number): void { // TODO: remove gold from player if (this.destroyer == null) { + // TODO validate can build const spawns = this._owner.units(UnitType.Port).map(u => u.tile()).sort(distSort(this.patrolTile)) if (spawns.length == 0) { console.warn(`no ports found for destoryer for player ${this._owner}`) @@ -44,6 +45,7 @@ export class DestroyerExecution implements Execution { return } this.destroyer = this._owner.addUnit(UnitType.Destroyer, 0, spawns[0]) + this._owner.removeGold(BuildItems.Destroyer.cost) return } if (!this.destroyer.isActive()) { diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 5eea88886..07ff36c9e 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -1,15 +1,22 @@ -import { Cell, Execution, BuildItems, MutableGame, MutablePlayer, PlayerID, Tile } from "../game/Game"; +import { BuildValidator } from "../game/BuildValidator"; +import { Cell, Execution, BuildItems, MutableGame, MutablePlayer, PlayerID, Tile, MutableUnit, UnitType } from "../game/Game"; +import { PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; -import { bfs, dist, euclideanDist, manhattanDist } from "../Util"; +import { bfs, dist, distSortUnit, euclideanDist, manhattanDist } from "../Util"; export class NukeExecution implements Execution { - private sender: MutablePlayer + private player: MutablePlayer private active = true private mg: MutableGame + private nuke: MutableUnit + private dst: Tile + + private pathFinder: PathFinder = null + constructor( private senderID: PlayerID, private cell: Cell, @@ -19,20 +26,41 @@ export class NukeExecution implements Execution { init(mg: MutableGame, ticks: number): void { this.mg = mg - this.sender = mg.player(this.senderID) + this.player = mg.player(this.senderID) if (this.magnitude == null) { this.magnitude = 50 } + this.dst = this.mg.tile(this.cell) } tick(ticks: number): void { - if (this.sender.gold() < BuildItems.Nuke.cost) { - console.warn(`player ${this.sender} insufficient gold for nuke`) - this.active = false + if (this.nuke == null) { + if (new BuildValidator(this.mg).canBuild(this.player, this.dst, BuildItems.Nuke)) { + const spawn = this.player.units(UnitType.MissileSilo) + .sort((a, b) => manhattanDist(a.tile().cell(), this.cell) - manhattanDist(b.tile().cell(), this.cell))[0] + this.nuke = this.player.addUnit(UnitType.Nuke, 0, spawn.tile()) + this.player.removeGold(BuildItems.Nuke.cost) + this.pathFinder = new PathFinder(10_000, t => true) + } else { + console.warn(`cannot build Nuke`) + this.active = false + return + } + } + if (this.nuke.tile() == this.dst) { + this.detonate() return } - this.sender.removeGold(BuildItems.Nuke.cost) + for (let i = 0; i < 4; i++) { + const nextTile = this.pathFinder.nextTile(this.nuke.tile(), this.dst) + if (nextTile == null) { + return + } + this.nuke.move(nextTile) + } + } + private detonate() { const rand = new PseudoRandom(this.mg.ticks()) const tile = this.mg.tile(this.cell) const toDestroy = bfs(tile, (n: Tile) => { @@ -52,6 +80,7 @@ export class NukeExecution implements Execution { .filter(b => euclideanDist(this.cell, b.tile().cell()) < this.magnitude + 50) .forEach(b => b.delete()) this.active = false + this.nuke.delete() } owner(): MutablePlayer { diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index c4a5d199f..c798cc30e 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -45,6 +45,7 @@ export class PortExecution implements Execution { return } this.port = this.player.addUnit(UnitType.Port, 0, spawns[0]) + this.player.removeGold(BuildItems.Port.cost) } @@ -73,7 +74,7 @@ export class PortExecution implements Execution { continue } if (!this.portPaths.has(port)) { - this.computingPaths.set(port, new AStar(this.port.tile(), port.tile())) + this.computingPaths.set(port, new AStar(this.port.tile(), port.tile(), t => t.isWater())) continue } } diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index 58eec10e3..b7634b91b 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -25,7 +25,7 @@ export class TransportShipExecution implements Execution { private boat: MutableUnit - private pathFinder: PathFinder = new PathFinder(10_000) + private pathFinder: PathFinder = new PathFinder(10_000, t => t.isWater()) constructor( private attackerID: PlayerID, diff --git a/src/core/game/BuildValidator.ts b/src/core/game/BuildValidator.ts index c27cf87fa..7312b6353 100644 --- a/src/core/game/BuildValidator.ts +++ b/src/core/game/BuildValidator.ts @@ -10,7 +10,7 @@ export class BuildValidator { } switch (item) { case BuildItems.Nuke: - return true + return player.units(UnitType.MissileSilo).length > 0 case BuildItems.Port: return this.canBuildPort(player, tile) case BuildItems.Destroyer: diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 936473d26..9f5f60e57 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -39,11 +39,10 @@ export class BuildItem { } export const BuildItems = { - // Nuke: new BuildItem(UnitType.Nuke, 1_000_000), - Nuke: new BuildItem(UnitType.Nuke, 10), - Destroyer: new BuildItem(UnitType.Destroyer, 10), - Port: new BuildItem(UnitType.Port, 0), - MissileSilo: new BuildItem(UnitType.MissileSilo, 10), + Nuke: new BuildItem(UnitType.Nuke, 1_000_000), + Destroyer: new BuildItem(UnitType.Destroyer, 100_000), + Port: new BuildItem(UnitType.Port, 300_000), + MissileSilo: new BuildItem(UnitType.MissileSilo, 1_000_000), } as const; export class Nation { @@ -156,6 +155,8 @@ export interface Tile { neighbors(): Tile[] neighborsWrapped(): Tile[] onShore(): boolean + x(): number + y(): number } export interface Unit { @@ -252,6 +253,7 @@ export interface MutablePlayer extends Player { addTroops(troops: number): void removeTroops(troops: number): number + // TODO: make addUnit require gold addUnit(type: UnitType, troops: number, tile: Tile): MutableUnit } diff --git a/src/core/game/TileImpl.ts b/src/core/game/TileImpl.ts index e60838d40..e7f989f7c 100644 --- a/src/core/game/TileImpl.ts +++ b/src/core/game/TileImpl.ts @@ -95,6 +95,12 @@ export class TileImpl implements Tile { isBorder(): boolean { return this._isBorder; } isInterior(): boolean { return this.hasOwner() && !this.isBorder(); } cell(): Cell { return this._cell; } + x(): number { + return this._cell.x + } + y(): number { + return this._cell.y + } neighbors(): Tile[] { if (this._neighbors == null) {