nukes spawn from missile silos

This commit is contained in:
Evan
2024-11-16 13:48:33 -08:00
parent e26230275b
commit 05a3bed093
10 changed files with 81 additions and 22 deletions
+1
View File
@@ -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
+14
View File
@@ -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;
+8 -4
View File
@@ -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()
+4 -2
View File
@@ -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()) {
+37 -8
View File
@@ -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 {
+2 -1
View File
@@ -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
}
}
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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:
+7 -5
View File
@@ -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
}
+6
View File
@@ -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) {