mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 09:14:09 +00:00
fixed units() returning empty when no units specified, moved canBuild logic to PlayerImpl
This commit is contained in:
+24
-9
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user