fixed units() returning empty when no units specified, moved canBuild logic to PlayerImpl

This commit is contained in:
Evan
2024-11-17 12:38:54 -08:00
parent 8f8de97d9b
commit 371a33b406
11 changed files with 145 additions and 70 deletions
+24 -9
View File
@@ -45,36 +45,51 @@ export function distSort(target: Tile): (a: Tile, b: Tile) => number {
}
}
export function distSortUnit(target: Unit): (a: Unit, b: Unit) => number {
export function distSortUnit(target: Unit | Tile): (a: Unit, b: Unit) => number {
const targetCell = ('tile' in target) ? target.tile().cell() : target.cell();
return (a: Unit, b: Unit) => {
return manhattanDist(a.tile().cell(), target.tile().cell()) - manhattanDist(b.tile().cell(), target.tile().cell());
return manhattanDist(a.tile().cell(), targetCell) - manhattanDist(b.tile().cell(), targetCell);
}
}
export function and(x: (tile: Tile) => boolean, y: (tile: Tile) => boolean): (tile: Tile) => boolean {
return (tile: Tile) => x(tile) && y(tile)
}
// TODO: refactor to new file
export function sourceDstOceanShore(game: Game, src: Player, dst: Player | TerraNullius, cell: Cell): [Tile | null, Tile | null] {
let srcTile = closestOceanShoreFromPlayer(src, cell, game.width())
export function sourceDstOceanShore(game: Game, src: Player, tile: Tile): [Tile | null, Tile | null] {
const dst = tile.owner()
let srcTile = closestOceanShoreFromPlayer(src, tile, game.width())
let dstTile: Tile | null = null
if (dst.isPlayer()) {
dstTile = closestOceanShoreFromPlayer(dst as Player, cell, game.width())
dstTile = closestOceanShoreFromPlayer(dst as Player, tile, game.width())
} else {
dstTile = closestOceanShoreTN(game.tile(cell), 300)
dstTile = closestOceanShoreTN(tile, 300)
}
return [srcTile, dstTile]
}
function closestOceanShoreFromPlayer(player: Player, target: Cell, width: number): Tile | null {
export function targetTransportTile(game: Game, tile: Tile): Tile | null {
const dst = tile.owner()
let dstTile: Tile | null = null
if (dst.isPlayer()) {
dstTile = closestOceanShoreFromPlayer(dst as Player, tile, game.width())
} else {
dstTile = closestOceanShoreTN(tile, 300)
}
return dstTile
}
export function closestOceanShoreFromPlayer(player: Player, target: Tile, width: number): Tile | null {
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.isOceanShore())
if (shoreTiles.length == 0) {
return null
}
return shoreTiles.reduce((closest, current) => {
const closestDistance = manhattanDistWrapped(target, closest.cell(), width);
const currentDistance = manhattanDistWrapped(target, current.cell(), width);
const closestDistance = manhattanDistWrapped(target.cell(), closest.cell(), width);
const currentDistance = manhattanDistWrapped(target.cell(), current.cell(), width);
return currentDistance < closestDistance ? current : closest;
});
}
+3 -6
View File
@@ -35,16 +35,13 @@ 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}`)
const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile)
if (spawn == false) {
this.active = false
return
}
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawns[0])
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn)
return
}
if (!this.destroyer.isActive()) {
+12 -11
View File
@@ -14,8 +14,7 @@ export class NukeExecution implements Execution {
private nuke: MutableUnit
private dst: Tile
private pathFinder: PathFinder = null
private pathFinder: PathFinder = new PathFinder(10_000, () => true)
constructor(
private senderID: PlayerID,
private cell: Cell,
@@ -34,16 +33,13 @@ export class NukeExecution implements Execution {
tick(ticks: number): void {
if (this.nuke == null) {
if (this.player.canBuild(UnitType.Nuke, this.dst)) {
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.buildUnit(UnitType.Nuke, 0, spawn.tile())
this.pathFinder = new PathFinder(10_000, t => true)
} else {
const spawn = this.player.canBuild(UnitType.Nuke, this.dst)
if (spawn == false) {
console.warn(`cannot build Nuke`)
this.active = false
return
}
this.nuke = this.player.buildUnit(UnitType.Nuke, 0, spawn)
}
if (this.nuke.tile() == this.dst) {
this.detonate()
@@ -79,9 +75,14 @@ export class NukeExecution implements Execution {
mp.removeTroops(ratio[mp.id()])
}
}
this.mg.units()
.filter(b => euclideanDist(this.cell, b.tile().cell()) < this.magnitude + 50)
.forEach(b => b.delete())
for (const unit of this.mg.units()) {
if (euclideanDist(this.cell, unit.tile().cell()) < this.magnitude + 50) {
unit.delete()
}
}
// this.mg.units()
// .filter(b => euclideanDist(this.cell, b.tile().cell()) < this.magnitude + 50)
// .forEach(b => b.delete())
this.active = false
this.nuke.delete()
}
+7 -1
View File
@@ -27,7 +27,13 @@ export class TradeShipExecution implements Execution {
tick(ticks: number): void {
if (this.tradeShip == null) {
this.tradeShip = this.player.buildUnit(UnitType.TradeShip, 0, this.srcPort.tile())
const spawn = this.player.canBuild(UnitType.TradeShip, this.srcPort.tile())
if (spawn == false) {
console.warn(`cannot build trade ship`)
this.active = false
return
}
this.tradeShip = this.player.buildUnit(UnitType.TradeShip, 0, spawn)
}
if (this.index >= this.path.length) {
this.active = false
+10 -15
View File
@@ -1,5 +1,5 @@
import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent, UnitType } from "../game/Game";
import { and, bfs, manhattanDistWrapped, sourceDstOceanShore } from "../Util";
import { and, bfs, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../Util";
import { AttackExecution } from "./AttackExecution";
import { DisplayMessageEvent, MessageType } from "../../client/graphics/layers/EventsDisplay";
import { AStar, PathFinder } from "../PathFinding";
@@ -62,23 +62,18 @@ export class TransportShipExecution implements Execution {
}
this.troops = Math.min(this.troops, this.attacker.troops())
this.attacker.removeTroops(this.troops)
const [srcTile, dstTile]: [Tile | null, Tile | null] = sourceDstOceanShore(this.mg, this.attacker, this.target, this.cell);
this.src = srcTile
const dstTile = targetTransportTile(this.mg, this.mg.tile(this.cell))
const src = this.attacker.canBuild(UnitType.TransportShip, dstTile)
if (src == false) {
console.warn(`can't build transport ship`)
this.active = false
return
}
this.src = src
this.dst = dstTile
if (this.src == null || this.dst == null) {
this.active = false
return
}
if (manhattanDistWrapped(this.src.cell(), this.dst.cell(), mg.width()) > mg.config().boatMaxDistance()) {
mg.displayMessage(`Cannot send boat: destination is too far away`, MessageType.WARN, this.attackerID)
this.active = false
return
}
this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.troops, this.src)
}
+2 -1
View File
@@ -214,7 +214,8 @@ export interface Player {
targetTroopRatio(): number
troops(): number
canBuild(type: UnitType, tile: Tile): boolean
// If can build returns the spawn tile, false otherwise
canBuild(type: UnitType, targetTile: Tile): Tile | false
}
export interface MutablePlayer extends Player {
+78 -21
View File
@@ -1,6 +1,6 @@
import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, EmojiMessage, EmojiMessageEvent, AllPlayers, Gold, UnitType } from "./Game";
import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, EmojiMessage, EmojiMessageEvent, AllPlayers, Gold, UnitType, Unit } from "./Game";
import { ClientID } from "../Schemas";
import { bfs, dist, manhattanDist, processName, simpleHash } from "../Util";
import { assertNever, bfs, closestOceanShoreFromPlayer, dist, distSortUnit, manhattanDist, manhattanDistWrapped, processName, simpleHash, sourceDstOceanShore } from "../Util";
import { CellString, GameImpl } from "./GameImpl";
import { UnitImpl } from "./UnitImpl";
import { TileImpl } from "./TileImpl";
@@ -73,15 +73,10 @@ export class PlayerImpl implements MutablePlayer {
}
buildUnit(type: UnitType, troops: number, tile: Tile): UnitImpl {
const b = new UnitImpl(type, this.gs, tile, troops, this);
this._units.push(b);
this.removeGold(this.gs.unitInfo(type).cost)
this.gs.fireUnitUpdateEvent(b, b.tile());
return b;
}
units(...types: UnitType[]): UnitImpl[] {
if (types.length == 0) {
return this._units
}
const ts = new Set(types)
return this._units.filter(u => ts.has(u.type()));
}
@@ -324,31 +319,93 @@ export class PlayerImpl implements MutablePlayer {
return toRemove
}
canBuild(unitType: UnitType, tile: Tile): boolean {
buildUnit(type: UnitType, troops: number, spawnTile: Tile): UnitImpl {
const b = new UnitImpl(type, this.gs, spawnTile, troops, this);
this._units.push(b);
this.removeGold(this.gs.unitInfo(type).cost)
this.removeTroops(troops)
this.gs.fireUnitUpdateEvent(b, b.tile());
return b;
}
canBuild(unitType: UnitType, targetTile: Tile): Tile | false {
const cost = this.gs.unitInfo(unitType).cost
if (!this.isAlive() || this.gold() < cost) {
return false
}
switch (unitType) {
case UnitType.Nuke:
return this.units(UnitType.MissileSilo).length > 0
return this.nukeSpawn(targetTile)
case UnitType.Port:
return this.canBuildPort(tile)
return this.portSpawn(targetTile)
case UnitType.Destroyer:
return this.canBuildDestroyer(tile)
return this.destroyerSpawn(targetTile)
case UnitType.MissileSilo:
return tile.owner() == this
return this.missileSiloSpawn(targetTile)
case UnitType.TransportShip:
return this.transportShipSpawn(targetTile)
case UnitType.TradeShip:
return this.tradeShipSpawn(targetTile)
default:
assertNever(unitType)
}
}
canBuildPort(tile: Tile): boolean {
return Array.from(bfs(tile, dist(tile, 20)))
.filter(t => t.owner() == this && t.isOceanShore()).length > 0
nukeSpawn(tile: Tile): Tile | false {
const spawns = this.units(UnitType.MissileSilo).map(u => u as Unit).sort(distSortUnit(tile))
if (spawns.length == 0) {
return false
}
return spawns[0].tile()
}
canBuildDestroyer(tile: Tile): boolean {
return this.units(UnitType.Port)
.filter(u => manhattanDist(u.tile().cell(), tile.cell()) < this.gs.config().boatMaxDistance()).length > 0
portSpawn(tile: Tile): Tile | false {
const spawns = Array.from(bfs(tile, dist(tile, 20)))
.filter(t => t.owner() == this && t.isOceanShore())
.sort((a, b) => manhattanDist(a.cell(), tile.cell()) - manhattanDist(b.cell(), tile.cell()))
if (spawns.length == 0) {
return false
}
return spawns[0]
}
destroyerSpawn(tile: Tile): Tile | false {
if (!tile.isOcean()) {
return false
}
const spawns = this.units(UnitType.Port)
.filter(u => manhattanDist(u.tile().cell(), tile.cell()) < this.gs.config().boatMaxDistance())
.sort((a, b) => manhattanDist(a.tile().cell(), tile.cell()) - manhattanDist(b.tile().cell(), tile.cell()))
if (spawns.length == 0) {
return false
}
return spawns[0].tile()
}
missileSiloSpawn(tile: Tile): Tile | false {
if (tile.owner() != this) {
return false
}
return tile
}
transportShipSpawn(targetTile: Tile): Tile | false {
if (!targetTile.isOceanShore()) {
return false
}
const spawn = closestOceanShoreFromPlayer(this, targetTile, this.gs.width())
if (spawn == null) {
return false
}
return spawn
}
tradeShipSpawn(targetTile: Tile): Tile | false {
const spawns = this.units(UnitType.Port).filter(u => u.tile() == targetTile)
if (spawns.length == 0) {
return false
}
return spawns[0].tile()
}
hash(): number {