mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-03 19:49:41 +00:00
thread_split: convert all tile to tileref
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerType, TerrainType, TerraNullius, Tile } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerType, TerrainType, TerraNullius } from "../game/Game";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { manhattanDist } from "../Util";
|
||||
import { MessageType } from '../game/Game';
|
||||
import { renderNumber } from "../../client/Utils";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
@@ -32,8 +31,7 @@ export class AttackExecution implements Execution {
|
||||
private troops: number | null,
|
||||
private _ownerID: PlayerID,
|
||||
private _targetID: PlayerID | null,
|
||||
private sourceCell: Cell | null,
|
||||
private targetCell: Cell | null,
|
||||
private sourceTile: TileRef | null,
|
||||
private removeTroops: boolean = true,
|
||||
) { }
|
||||
|
||||
@@ -51,8 +49,6 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
this.mg = mg
|
||||
|
||||
this.targetCell = null
|
||||
|
||||
this._owner = mg.player(this._ownerID)
|
||||
this.target = this._targetID == this.mg.terraNullius().id() ? mg.terraNullius() : mg.player(this._targetID)
|
||||
|
||||
@@ -84,7 +80,7 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
}
|
||||
// Existing attack on same target, add troops
|
||||
if (otherAttack._owner == this._owner && otherAttack._targetID == this._targetID && this.sourceCell == otherAttack.sourceCell) {
|
||||
if (otherAttack._owner == this._owner && otherAttack._targetID == this._targetID && this.sourceTile == otherAttack.sourceTile) {
|
||||
otherAttack.troops += this.troops
|
||||
otherAttack.refreshToConquer()
|
||||
this.active = false
|
||||
@@ -95,8 +91,8 @@ export class AttackExecution implements Execution {
|
||||
if (this._owner.type() != PlayerType.Bot && this.target.isPlayer() && this.target.type() == PlayerType.Human) {
|
||||
mg.displayMessage(`You are being attacked by ${this._owner.displayName()}`, MessageType.ERROR, this._targetID)
|
||||
}
|
||||
if (this.sourceCell != null) {
|
||||
this.addNeighbors(mg.tile(this.sourceCell))
|
||||
if (this.sourceTile != null) {
|
||||
this.addNeighbors(this.sourceTile)
|
||||
} else {
|
||||
this.refreshToConquer()
|
||||
}
|
||||
@@ -154,14 +150,14 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
|
||||
const tileToConquer = this.toConquer.dequeue().tile
|
||||
this.border.delete(tileToConquer.ref())
|
||||
this.border.delete(tileToConquer)
|
||||
|
||||
const onBorder = tileToConquer.neighbors().filter(t => t.owner() == this._owner).length > 0
|
||||
if (tileToConquer.owner() != this.target || !onBorder) {
|
||||
const onBorder = this.mg.neighbors(tileToConquer).filter(t => this.mg.owner(t) == this._owner).length > 0
|
||||
if (this.mg.owner(tileToConquer) != this.target || !onBorder) {
|
||||
continue
|
||||
}
|
||||
this.addNeighbors(tileToConquer)
|
||||
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg.config().attackLogic(this.troops, this._owner, this.target, tileToConquer)
|
||||
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg.config().attackLogic(this.mg, this.troops, this._owner, this.target, tileToConquer)
|
||||
numTilesPerTick -= tilesPerTickUsed
|
||||
this.troops -= attackerTroopLoss
|
||||
if (this.target.isPlayer()) {
|
||||
@@ -172,25 +168,22 @@ export class AttackExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
private addNeighbors(tile: Tile) {
|
||||
for (const neighbor of tile.neighbors()) {
|
||||
if (neighbor.terrain().isWater() || neighbor.owner() != this.target) {
|
||||
private addNeighbors(tile: TileRef) {
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
if (this.mg.isWater(neighbor) || this.mg.owner(neighbor) != this.target) {
|
||||
continue
|
||||
}
|
||||
this.border.add(neighbor.ref())
|
||||
let numOwnedByMe = neighbor.neighbors()
|
||||
.filter(t => t.terrain().isLand())
|
||||
.filter(t => t.owner() == this._owner)
|
||||
this.border.add(neighbor)
|
||||
let numOwnedByMe = this.mg.neighbors(neighbor)
|
||||
.filter(t => this.mg.isLake(t))
|
||||
.filter(t => this.mg.owner(t) == this._owner)
|
||||
.length
|
||||
let dist = 0
|
||||
if (this.targetCell != null) {
|
||||
dist = manhattanDist(tile.cell(), this.targetCell)
|
||||
}
|
||||
if (numOwnedByMe > 2) {
|
||||
numOwnedByMe = 10
|
||||
}
|
||||
let mag = 0
|
||||
switch (tile.terrain().type()) {
|
||||
switch (this.mg.terrainType(tile)) {
|
||||
case TerrainType.Plains:
|
||||
mag = 1
|
||||
break
|
||||
@@ -218,11 +211,12 @@ export class AttackExecution implements Execution {
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (const tile of this.target.tiles()) {
|
||||
if (tile.borders(this._owner)) {
|
||||
const borders = this.mg.neighbors(tile).some(t => this.mg.owner(t) == this._owner)
|
||||
if (borders) {
|
||||
this._owner.conquer(tile)
|
||||
} else {
|
||||
for (const neighbor of tile.neighbors()) {
|
||||
const no = neighbor.owner()
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
const no = this.mg.owner(neighbor)
|
||||
if (no.isPlayer() && no != this.target) {
|
||||
this.mg.player(no.id()).conquer(tile)
|
||||
break
|
||||
@@ -246,5 +240,5 @@ export class AttackExecution implements Execution {
|
||||
|
||||
|
||||
class TileContainer {
|
||||
constructor(public readonly tile: Tile, public readonly priority: number, public readonly tick: number) { }
|
||||
constructor(public readonly tile: TileRef, public readonly priority: number, public readonly tick: number) { }
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Unit, UnitType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { distSort, distSortUnit, manhattanDist } from "../Util";
|
||||
import { distSort, distSortUnit } from "../Util";
|
||||
import { ShellExecution } from "./ShellExecution";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class BattleshipExecution implements Execution {
|
||||
private random: PseudoRandom
|
||||
@@ -17,8 +17,7 @@ export class BattleshipExecution implements Execution {
|
||||
|
||||
private pathfinder: PathFinder
|
||||
|
||||
private patrolTile: Tile;
|
||||
private patrolCenterTile: Tile
|
||||
private patrolTile: TileRef;
|
||||
|
||||
// TODO: put in config
|
||||
private searchRange = 100
|
||||
@@ -29,7 +28,7 @@ export class BattleshipExecution implements Execution {
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private cell: Cell,
|
||||
private patrolCenterTile: TileRef,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -37,7 +36,6 @@ export class BattleshipExecution implements Execution {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolCenterTile = mg.tile(this.cell)
|
||||
this.patrolTile = this.patrolCenterTile
|
||||
this.random = new PseudoRandom(mg.ticks())
|
||||
}
|
||||
@@ -85,15 +83,15 @@ export class BattleshipExecution implements Execution {
|
||||
}
|
||||
|
||||
let ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||
.filter(u => manhattanDist(u.tile().cell(), this.battleship.tile().cell()) < 100)
|
||||
.filter(u => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100)
|
||||
.filter(u => u.owner() != this.battleship.owner())
|
||||
.filter(u => u != this.battleship)
|
||||
.filter(u => !u.owner().isAlliedWith(this.battleship.owner()))
|
||||
.filter(u => !this.alreadyTargeted.has(u))
|
||||
.sort(distSortUnit(this.battleship));
|
||||
.sort(distSortUnit(this.mg, this.battleship));
|
||||
|
||||
const friendlyDestroyerNearby = this.battleship.owner().units(UnitType.Destroyer)
|
||||
.filter(d => manhattanDist(d.tile().cell(), this.battleship.tile().cell()) < 120)
|
||||
.filter(d => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120)
|
||||
.length > 0
|
||||
|
||||
if (friendlyDestroyerNearby) {
|
||||
@@ -124,16 +122,15 @@ export class BattleshipExecution implements Execution {
|
||||
return false
|
||||
}
|
||||
|
||||
randomTile(): Tile {
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x = this.patrolCenterTile.cell().x + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.patrolCenterTile.cell().y + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const cell = new Cell(x, y)
|
||||
if (!this.mg.isOnMap(cell)) {
|
||||
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.mg.y(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.tile(cell)
|
||||
if (!tile.terrain().isOcean()) {
|
||||
const tile = this.mg.ref(x, y)
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
|
||||
@@ -55,8 +55,8 @@ export class BotExecution implements Execution {
|
||||
|
||||
if (this.neighborsTerraNullius) {
|
||||
for (const b of this.bot.borderTiles()) {
|
||||
for (const n of b.neighbors()) {
|
||||
if (n.owner() == this.mg.terraNullius() && n.terrain().isLand()) {
|
||||
for (const n of this.mg.neighbors(b)) {
|
||||
if (!this.mg.hasOwner(n) && this.mg.isLake(n)) {
|
||||
this.sendAttack(this.mg.terraNullius())
|
||||
return
|
||||
}
|
||||
@@ -65,14 +65,16 @@ export class BotExecution implements Execution {
|
||||
this.neighborsTerraNullius = false
|
||||
}
|
||||
|
||||
const border = Array.from(this.bot.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.hasOwner() && t.owner() != this.bot)
|
||||
const border = Array.from(this.bot.borderTiles())
|
||||
.flatMap(t => this.mg.neighbors(t))
|
||||
.filter(t => this.mg.hasOwner(t) && this.mg.owner(t) != this.bot)
|
||||
|
||||
if (border.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const toAttack = border[this.random.nextInt(0, border.length)]
|
||||
const owner = toAttack.owner()
|
||||
const owner = this.mg.owner(toAttack)
|
||||
|
||||
if (owner.isPlayer()) {
|
||||
if (this.bot.isAlliedWith(owner)) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import {Cell, Game, PlayerType, Tile} from "../game/Game";
|
||||
import {PseudoRandom} from "../PseudoRandom";
|
||||
import {GameID, SpawnIntent} from "../Schemas";
|
||||
import {bfs, dist as dist, manhattanDist, simpleHash} from "../Util";
|
||||
import {BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES} from "./utils/BotNames";
|
||||
import { Cell, Game, PlayerType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { GameID, SpawnIntent } from "../Schemas";
|
||||
import { simpleHash } from "../Util";
|
||||
import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES } from "./utils/BotNames";
|
||||
|
||||
|
||||
export class BotSpawner {
|
||||
@@ -34,11 +35,11 @@ export class BotSpawner {
|
||||
|
||||
spawnBot(botName: string): SpawnIntent | null {
|
||||
const tile = this.randTile()
|
||||
if (!tile.terrain().isLand()) {
|
||||
if (!this.gs.isLand(tile)) {
|
||||
return null
|
||||
}
|
||||
for (const spawn of this.bots) {
|
||||
if (manhattanDist(new Cell(spawn.x, spawn.y), tile.cell()) < 30) {
|
||||
if (this.gs.manhattanDist(this.gs.ref(spawn.x, spawn.y), tile) < 30) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -47,8 +48,8 @@ export class BotSpawner {
|
||||
playerID: this.random.nextID(),
|
||||
name: botName,
|
||||
playerType: PlayerType.Bot,
|
||||
x: tile.cell().x,
|
||||
y: tile.cell().y
|
||||
x: this.gs.x(tile),
|
||||
y: this.gs.y(tile)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,10 +59,10 @@ export class BotSpawner {
|
||||
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
|
||||
}
|
||||
|
||||
private randTile(): Tile {
|
||||
return this.gs.tile(new Cell(
|
||||
private randTile(): TileRef {
|
||||
return this.gs.ref(
|
||||
this.random.nextInt(0, this.gs.width()),
|
||||
this.random.nextInt(0, this.gs.height())
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
|
||||
import { bfs, dist } from "../Util";
|
||||
import { Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class CityExecution implements Execution {
|
||||
|
||||
private player: MutablePlayer
|
||||
private mg: MutableGame
|
||||
private city: MutableUnit
|
||||
private tile: Tile
|
||||
private active: boolean = true
|
||||
|
||||
constructor(private ownerId: PlayerID, private cell: Cell) { }
|
||||
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.tile = mg.tile(this.cell)
|
||||
this.player = mg.player(this.ownerId)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
|
||||
import { bfs, dist } from "../Util";
|
||||
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game";
|
||||
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||
|
||||
export class DefensePostExecution implements Execution {
|
||||
|
||||
private player: MutablePlayer
|
||||
private mg: MutableGame
|
||||
private post: MutableUnit
|
||||
private tile: Tile
|
||||
private active: boolean = true
|
||||
|
||||
private defenseBonuses: DefenseBonus[] = []
|
||||
|
||||
constructor(private ownerId: PlayerID, private cell: Cell) { }
|
||||
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||
|
||||
init(mg: MutableGame, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.tile = mg.tile(this.cell)
|
||||
this.player = mg.player(this.ownerId)
|
||||
}
|
||||
|
||||
@@ -29,9 +27,11 @@ export class DefensePostExecution implements Execution {
|
||||
return
|
||||
}
|
||||
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile)
|
||||
bfs(spawnTile, dist(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
|
||||
if (t.terrain().isLand()) {
|
||||
this.defenseBonuses.push(this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus()))
|
||||
this.mg.bfs(spawnTile, manhattanDistFN(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
|
||||
if (this.mg.isLake(t)) {
|
||||
this.defenseBonuses.push(
|
||||
this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, UnitType } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { distSort, distSortUnit, manhattanDist } from "../Util";
|
||||
import { distSort, distSortUnit } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class DestroyerExecution implements Execution {
|
||||
private random: PseudoRandom
|
||||
@@ -17,15 +17,14 @@ export class DestroyerExecution implements Execution {
|
||||
private target: MutableUnit = null
|
||||
private pathfinder: PathFinder
|
||||
|
||||
private patrolTile: Tile;
|
||||
private patrolCenterTile: Tile
|
||||
private patrolTile: TileRef;
|
||||
|
||||
// TODO: put in config
|
||||
private searchRange = 100
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private cell: Cell,
|
||||
private patrolCenterTile: TileRef,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -33,7 +32,6 @@ export class DestroyerExecution implements Execution {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolCenterTile = mg.tile(this.cell)
|
||||
this.patrolTile = this.patrolCenterTile
|
||||
this.random = new PseudoRandom(mg.ticks())
|
||||
}
|
||||
@@ -57,7 +55,7 @@ export class DestroyerExecution implements Execution {
|
||||
}
|
||||
if (this.target == null) {
|
||||
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||
.filter(u => manhattanDist(u.tile().cell(), this.destroyer.tile().cell()) < 100)
|
||||
.filter(u => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100)
|
||||
.filter(u => u.type() != UnitType.Destroyer || u.health() < this.destroyer.health()) // only attack Destroyers weaker than it.
|
||||
.filter(u => u.owner() != this.destroyer.owner())
|
||||
.filter(u => u != this.destroyer)
|
||||
@@ -80,7 +78,7 @@ export class DestroyerExecution implements Execution {
|
||||
}
|
||||
return
|
||||
}
|
||||
this.target = ships.sort(distSortUnit(this.destroyer))[0]
|
||||
this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0]
|
||||
}
|
||||
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
||||
// Incase another destroyer captured or destroyed target
|
||||
@@ -132,16 +130,15 @@ export class DestroyerExecution implements Execution {
|
||||
return false
|
||||
}
|
||||
|
||||
randomTile(): Tile {
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x = this.patrolCenterTile.cell().x + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.patrolCenterTile.cell().y + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const cell = new Cell(x, y)
|
||||
if (!this.mg.isOnMap(cell)) {
|
||||
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.mg.y(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.tile(cell)
|
||||
if (!tile.terrain().isOcean()) {
|
||||
const tile = this.mg.ref(x, y)
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, UnitType } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, PlayerType, Alliance, UnitType } from "../game/Game";
|
||||
import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
import { SpawnExecution } from "./SpawnExecution";
|
||||
@@ -21,6 +21,7 @@ import { MissileSiloExecution } from "./MissileSiloExecution";
|
||||
import { BattleshipExecution } from "./BattleshipExecution";
|
||||
import { DefensePostExecution } from "./DefensePostExecution";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +31,7 @@ export class Executor {
|
||||
// private random = new PseudoRandom(999)
|
||||
private random: PseudoRandom = null
|
||||
|
||||
constructor(private gs: Game, private gameID: GameID) {
|
||||
constructor(private mg: Game, private gameID: GameID) {
|
||||
// Add one to avoid id collisions with bots.
|
||||
this.random = new PseudoRandom(simpleHash(gameID) + 1)
|
||||
}
|
||||
@@ -42,30 +43,23 @@ export class Executor {
|
||||
createExec(intent: Intent): Execution {
|
||||
switch (intent.type) {
|
||||
case "attack": {
|
||||
const source: Cell | null = intent.sourceX != null && intent.sourceY != null
|
||||
? new Cell(intent.sourceX, intent.sourceY)
|
||||
: null;
|
||||
const target: Cell | null = intent.targetX != null && intent.targetY != null
|
||||
? new Cell(intent.targetX, intent.targetY)
|
||||
: null;
|
||||
return new AttackExecution(
|
||||
intent.troops,
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
source,
|
||||
target,
|
||||
null
|
||||
);
|
||||
}
|
||||
case "spawn":
|
||||
return new SpawnExecution(
|
||||
new PlayerInfo(sanitize(intent.name), intent.playerType, intent.clientID, intent.playerID),
|
||||
new Cell(intent.x, intent.y)
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case "boat":
|
||||
return new TransportShipExecution(
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
new Cell(intent.x, intent.y),
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
intent.troops
|
||||
);
|
||||
case "allianceRequest":
|
||||
@@ -86,19 +80,19 @@ export class Executor {
|
||||
switch (intent.unit) {
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
return new NukeExecution(intent.unit, intent.player, new Cell(intent.x, intent.y))
|
||||
return new NukeExecution(intent.unit, intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Destroyer:
|
||||
return new DestroyerExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new DestroyerExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Battleship:
|
||||
return new BattleshipExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new BattleshipExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Port:
|
||||
return new PortExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new PortExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.MissileSilo:
|
||||
return new MissileSiloExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new MissileSiloExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.DefensePost:
|
||||
return new DefensePostExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new DefensePostExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.City:
|
||||
return new CityExecution(intent.player, new Cell(intent.x, intent.y))
|
||||
return new CityExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
default:
|
||||
throw Error(`unit type ${intent.unit} not supported`)
|
||||
}
|
||||
@@ -108,12 +102,12 @@ export class Executor {
|
||||
}
|
||||
|
||||
spawnBots(numBots: number): Execution[] {
|
||||
return new BotSpawner(this.gs, this.gameID).spawnBots(numBots).map(i => this.createExec(i))
|
||||
return new BotSpawner(this.mg, this.gameID).spawnBots(numBots).map(i => this.createExec(i))
|
||||
}
|
||||
|
||||
fakeHumanExecutions(): Execution[] {
|
||||
const execs = []
|
||||
for (const nation of this.gs.nations()) {
|
||||
for (const nation of this.mg.nations()) {
|
||||
execs.push(new FakeHumanExecution(
|
||||
this.gameID,
|
||||
new PlayerInfo(
|
||||
@@ -123,7 +117,7 @@ export class Executor {
|
||||
this.random.nextID()
|
||||
),
|
||||
nation.cell,
|
||||
nation.strength * this.gs.config().difficultyModifier(this.gs.config().gameConfig().difficulty)
|
||||
nation.strength * this.mg.config().difficultyModifier(this.mg.config().gameConfig().difficulty)
|
||||
))
|
||||
}
|
||||
return execs
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AllianceRequest, Cell, Difficulty, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, Relation, TerrainType, TerraNullius, Tick, Tile, UnitType } from "../game/Game"
|
||||
import { AllianceRequest, Cell, Difficulty, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, Relation, TerrainType, TerraNullius, UnitType } from "../game/Game"
|
||||
import { PseudoRandom } from "../PseudoRandom"
|
||||
import { and, bfs, calculateBoundingBox, dist, euclDist, manhattanDist, simpleHash } from "../Util";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
import { TransportShipExecution } from "./TransportShipExecution";
|
||||
import { SpawnExecution } from "./SpawnExecution";
|
||||
@@ -15,6 +14,8 @@ import { MissileSiloExecution } from "./MissileSiloExecution";
|
||||
import { EmojiExecution } from "./EmojiExecution";
|
||||
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
|
||||
import { closestTwoTiles } from "./Util";
|
||||
import { calculateBoundingBox, simpleHash } from "../Util";
|
||||
import { andFN, manhattanDistFN, TileRef } from "../game/GameMap";
|
||||
|
||||
export class FakeHumanExecution implements Execution {
|
||||
|
||||
@@ -52,7 +53,7 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
this.mg.addExecution(new SpawnExecution(
|
||||
this.playerInfo,
|
||||
rl.cell()
|
||||
rl
|
||||
))
|
||||
}
|
||||
return
|
||||
@@ -89,7 +90,9 @@ export class FakeHumanExecution implements Execution {
|
||||
this.handleEnemies()
|
||||
this.handleUnits()
|
||||
|
||||
const enemyborder = Array.from(this.player.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.terrain().isLand() && t.owner() != this.player)
|
||||
const enemyborder = Array.from(this.player.borderTiles())
|
||||
.flatMap(t => this.mg.neighbors(t))
|
||||
.filter(t => this.mg.isLake(t) && this.mg.ownerID(t) != this.player.smallID())
|
||||
|
||||
if (enemyborder.length == 0) {
|
||||
if (this.random.chance(5)) {
|
||||
@@ -102,7 +105,7 @@ export class FakeHumanExecution implements Execution {
|
||||
return
|
||||
}
|
||||
|
||||
const enemiesWithTN = enemyborder.map(t => t.owner())
|
||||
const enemiesWithTN = enemyborder.map(t => this.mg.playerBySmallID(this.mg.ownerID(t)))
|
||||
if (enemiesWithTN.filter(o => !o.isPlayer()).length > 0) {
|
||||
this.sendAttack(this.mg.terraNullius())
|
||||
return
|
||||
@@ -224,15 +227,15 @@ export class FakeHumanExecution implements Execution {
|
||||
if (tile == null) {
|
||||
return
|
||||
}
|
||||
for (const t of bfs(tile, dist(tile, 15))) {
|
||||
for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) {
|
||||
// Make sure we nuke at least 15 tiles in border
|
||||
if (t.hasOwner() && t.owner() != other) {
|
||||
if (this.mg.owner(t) != other) {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
if (this.player.canBuild(UnitType.AtomBomb, tile)) {
|
||||
this.mg.addExecution(
|
||||
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile.cell())
|
||||
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile)
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -241,17 +244,18 @@ export class FakeHumanExecution implements Execution {
|
||||
|
||||
private maybeSendBoatAttack(other: Player) {
|
||||
const closest = closestTwoTiles(
|
||||
Array.from(this.player.borderTiles()).filter(t => t.terrain().isOceanShore()),
|
||||
Array.from(other.borderTiles()).filter(t => t.terrain().isOceanShore())
|
||||
this.mg,
|
||||
Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t)),
|
||||
Array.from(other.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||
)
|
||||
if (closest == null) {
|
||||
return
|
||||
}
|
||||
if (manhattanDist(closest.x.cell(), closest.y.cell()) < this.mg.config().boatMaxDistance()) {
|
||||
if (this.mg.manhattanDist(closest.x, closest.y) < this.mg.config().boatMaxDistance()) {
|
||||
this.mg.addExecution(new TransportShipExecution(
|
||||
this.player.id(),
|
||||
other.id(),
|
||||
closest.y.cell(),
|
||||
closest.y,
|
||||
this.player.troops() / 5
|
||||
))
|
||||
}
|
||||
@@ -260,24 +264,24 @@ export class FakeHumanExecution implements Execution {
|
||||
private handleUnits() {
|
||||
const ports = this.player.units(UnitType.Port)
|
||||
if (ports.length == 0 && this.player.gold() > this.cost(UnitType.Port)) {
|
||||
const oceanTiles = Array.from(this.player.borderTiles()).filter(t => t.terrain().isOceanShore())
|
||||
const oceanTiles = Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||
if (oceanTiles.length > 0) {
|
||||
const buildTile = this.random.randElement(oceanTiles)
|
||||
this.mg.addExecution(new PortExecution(this.player.id(), buildTile.cell()))
|
||||
this.mg.addExecution(new PortExecution(this.player.id(), buildTile))
|
||||
}
|
||||
return
|
||||
}
|
||||
this.maybeSpawnStructure(UnitType.City, 2, t => new CityExecution(this.player.id(), t.cell()))
|
||||
this.maybeSpawnStructure(UnitType.City, 2, t => new CityExecution(this.player.id(), t))
|
||||
if (this.maybeSpawnWarship(UnitType.Destroyer)) {
|
||||
return
|
||||
}
|
||||
if (this.maybeSpawnWarship(UnitType.Battleship)) {
|
||||
return
|
||||
}
|
||||
this.maybeSpawnStructure(UnitType.MissileSilo, 1, t => new MissileSiloExecution(this.player.id(), t.cell()))
|
||||
this.maybeSpawnStructure(UnitType.MissileSilo, 1, t => new MissileSiloExecution(this.player.id(), t))
|
||||
}
|
||||
|
||||
private maybeSpawnStructure(type: UnitType, maxNum: number, build: (tile: Tile) => Execution) {
|
||||
private maybeSpawnStructure(type: UnitType, maxNum: number, build: (tile: TileRef) => Execution) {
|
||||
const units = this.player.units(type)
|
||||
if (units.length >= maxNum) {
|
||||
return
|
||||
@@ -315,10 +319,10 @@ export class FakeHumanExecution implements Execution {
|
||||
}
|
||||
switch (shipType) {
|
||||
case UnitType.Destroyer:
|
||||
this.mg.addExecution(new DestroyerExecution(this.player.id(), targetTile.cell()))
|
||||
this.mg.addExecution(new DestroyerExecution(this.player.id(), targetTile))
|
||||
break
|
||||
case UnitType.Battleship:
|
||||
this.mg.addExecution(new BattleshipExecution(this.player.id(), targetTile.cell()))
|
||||
this.mg.addExecution(new BattleshipExecution(this.player.id(), targetTile))
|
||||
break
|
||||
}
|
||||
return true
|
||||
@@ -326,8 +330,8 @@ export class FakeHumanExecution implements Execution {
|
||||
return false
|
||||
}
|
||||
|
||||
private randTerritoryTile(p: Player): Tile | null {
|
||||
const boundingBox = calculateBoundingBox(p.borderTiles())
|
||||
private randTerritoryTile(p: Player): TileRef | null {
|
||||
const boundingBox = calculateBoundingBox(this.mg, p.borderTiles())
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const randX = this.random.nextInt(boundingBox.min.x, boundingBox.max.x)
|
||||
const randY = this.random.nextInt(boundingBox.min.y, boundingBox.max.y)
|
||||
@@ -335,29 +339,28 @@ export class FakeHumanExecution implements Execution {
|
||||
// Sanity check should never happen
|
||||
continue
|
||||
}
|
||||
const randTile = this.mg.tile(new Cell(randX, randY))
|
||||
if (randTile.owner() == p) {
|
||||
const randTile = this.mg.ref(randX, randY)
|
||||
if (this.mg.owner(randTile) == p) {
|
||||
return randTile
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private warshipSpawnTile(portTile: Tile): Tile | null {
|
||||
private warshipSpawnTile(portTile: TileRef): TileRef | null {
|
||||
const radius = this.mg.config().boatMaxDistance() / 2
|
||||
for (let attempts = 0; attempts < 50; attempts++) {
|
||||
const randX = this.random.nextInt(portTile.cell().x - radius, portTile.cell().x + radius)
|
||||
const randY = this.random.nextInt(portTile.cell().y - radius, portTile.cell().y + radius)
|
||||
const cell = new Cell(randX, randY)
|
||||
if (!this.mg.isOnMap(cell)) {
|
||||
const randX = this.random.nextInt(this.mg.x(portTile) - radius, this.mg.x(portTile) + radius)
|
||||
const randY = this.random.nextInt(this.mg.y(portTile) - radius, this.mg.y(portTile) + radius)
|
||||
if (!this.mg.isValidCoord(randX, randY)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.ref(randX, randY)
|
||||
// Sanity check
|
||||
if (manhattanDist(cell, portTile.cell()) >= this.mg.config().boatMaxDistance()) {
|
||||
if (this.mg.manhattanDist(tile, portTile) >= this.mg.config().boatMaxDistance()) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.tile(cell)
|
||||
if (!tile.terrain().isOcean()) {
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
@@ -394,13 +397,13 @@ export class FakeHumanExecution implements Execution {
|
||||
)
|
||||
}
|
||||
|
||||
sendBoat(tries: number = 0, oceanShore: Tile[] = null) {
|
||||
sendBoat(tries: number = 0, oceanShore: TileRef[] = null) {
|
||||
if (tries > 10) {
|
||||
return
|
||||
}
|
||||
|
||||
if (oceanShore == null) {
|
||||
oceanShore = Array.from(this.player.borderTileRefs()).filter(t => this.mg.isOceanShore(t)).map(tr => this.mg.fromRef(tr))
|
||||
oceanShore = Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||
}
|
||||
if (oceanShore.length == 0) {
|
||||
return
|
||||
@@ -408,11 +411,11 @@ export class FakeHumanExecution implements Execution {
|
||||
|
||||
const src = this.random.randElement(oceanShore)
|
||||
const otherShore = Array.from(
|
||||
bfs(
|
||||
this.mg.bfs(
|
||||
src,
|
||||
and((t) => t.terrain().isOcean() || t.terrain().isOceanShore(), dist(src, 200))
|
||||
andFN((gm, t) => gm.isOcean(t) || gm.isOceanShore(t), manhattanDistFN(src, 200))
|
||||
)
|
||||
).filter(t => t.terrain().isOceanShore() && t.owner() != this.player)
|
||||
).filter(t => this.mg.isOceanShore(t) && this.mg.owner(t) != this.player)
|
||||
|
||||
if (otherShore.length == 0) {
|
||||
return
|
||||
@@ -423,14 +426,14 @@ export class FakeHumanExecution implements Execution {
|
||||
if (this.isSmallIsland(dst)) {
|
||||
continue
|
||||
}
|
||||
if (dst.owner().isPlayer() && this.player.isAlliedWith(dst.owner() as Player)) {
|
||||
if (this.mg.owner(dst).isPlayer() && this.player.isAlliedWith(this.mg.owner(dst) as Player)) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.mg.addExecution(new TransportShipExecution(
|
||||
this.player.id(),
|
||||
dst.hasOwner() ? dst.owner().id() : null,
|
||||
dst.cell(),
|
||||
this.mg.hasOwner(dst) ? this.mg.owner(dst).id() : null,
|
||||
dst,
|
||||
this.player.troops() / 5,
|
||||
))
|
||||
return
|
||||
@@ -438,21 +441,19 @@ export class FakeHumanExecution implements Execution {
|
||||
this.sendBoat(tries + 1, oceanShore)
|
||||
}
|
||||
|
||||
randomLand(): Tile | null {
|
||||
randomLand(): TileRef | null {
|
||||
const delta = 25
|
||||
let tries = 0
|
||||
while (tries < 50) {
|
||||
tries++
|
||||
const cell = new Cell(
|
||||
this.random.nextInt(this.cell.x - delta, this.cell.x + delta),
|
||||
this.random.nextInt(this.cell.y - delta, this.cell.y + delta)
|
||||
)
|
||||
if (!this.mg.isOnMap(cell)) {
|
||||
const x = this.random.nextInt(this.cell.x - delta, this.cell.x + delta)
|
||||
const y = this.random.nextInt(this.cell.y - delta, this.cell.y + delta)
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.tile(cell)
|
||||
if (tile.terrain().isLand() && !tile.hasOwner()) {
|
||||
if (tile.terrain().type() == TerrainType.Mountain && this.random.chance(2)) {
|
||||
const tile = this.mg.ref(x, y)
|
||||
if (this.mg.isLand(tile) && !this.mg.hasOwner(tile)) {
|
||||
if (this.mg.terrainType(tile) == TerrainType.Mountain && this.random.chance(2)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
@@ -471,8 +472,8 @@ export class FakeHumanExecution implements Execution {
|
||||
))
|
||||
}
|
||||
|
||||
isSmallIsland(tile: Tile): boolean {
|
||||
return bfs(tile, and((t) => t.terrain().isLand(), dist(tile, 10))).size < 50
|
||||
isSmallIsland(tile: TileRef): boolean {
|
||||
return this.mg.bfs(tile, andFN((gm, t) => gm.isLand(t), manhattanDistFN(tile, 10))).size < 50
|
||||
}
|
||||
|
||||
owner(): MutablePlayer {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, UnitType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class MissileSiloExecution implements Execution {
|
||||
|
||||
@@ -10,7 +11,7 @@ export class MissileSiloExecution implements Execution {
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private cell: Cell
|
||||
private tile: TileRef
|
||||
) { }
|
||||
|
||||
|
||||
@@ -21,13 +22,12 @@ export class MissileSiloExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.silo == null) {
|
||||
const tile = this.mg.tile(this.cell)
|
||||
if (!this.player.canBuild(UnitType.MissileSilo, tile)) {
|
||||
consolex.warn(`player ${this.player} cannot build port at ${this.cell}`)
|
||||
if (!this.player.canBuild(UnitType.MissileSilo, this.tile)) {
|
||||
consolex.warn(`player ${this.player} cannot build port at ${this.tile}`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, tile)
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { nextTick } from "process";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile, MutableUnit, UnitType, Player, TerraNullius } from "../game/Game";
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, MutableUnit, UnitType, Player, TerraNullius } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { bfs, dist, distSortUnit, euclideanDist, manhattanDist } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class NukeExecution implements Execution {
|
||||
|
||||
@@ -15,13 +15,12 @@ export class NukeExecution implements Execution {
|
||||
private mg: MutableGame
|
||||
|
||||
private nuke: MutableUnit
|
||||
private dst: Tile
|
||||
|
||||
private pathFinder: PathFinder
|
||||
constructor(
|
||||
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
||||
private senderID: PlayerID,
|
||||
private cell: Cell,
|
||||
private dst: TileRef,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -29,11 +28,10 @@ export class NukeExecution implements Execution {
|
||||
this.mg = mg
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true)
|
||||
this.player = mg.player(this.senderID)
|
||||
this.dst = this.mg.tile(this.cell)
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
return this.dst.owner()
|
||||
return this.mg.owner(this.dst)
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
@@ -70,9 +68,8 @@ export class NukeExecution implements Execution {
|
||||
private detonate() {
|
||||
const magnitude = this.type == UnitType.AtomBomb ? { inner: 15, outer: 40 } : { inner: 140, outer: 160 }
|
||||
const rand = new PseudoRandom(this.mg.ticks())
|
||||
const tile = this.mg.tile(this.cell)
|
||||
const toDestroy = bfs(tile, (n: Tile) => {
|
||||
const d = euclideanDist(tile.cell(), n.cell())
|
||||
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||
const d = this.mg.euclideanDist(this.dst, n)
|
||||
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer
|
||||
})
|
||||
|
||||
@@ -81,7 +78,7 @@ export class NukeExecution implements Execution {
|
||||
)
|
||||
const attacked = new Map<MutablePlayer, number>()
|
||||
for (const tile of toDestroy) {
|
||||
const owner = tile.owner()
|
||||
const owner = this.mg.owner(tile)
|
||||
if (owner.isPlayer()) {
|
||||
const mp = this.mg.player(owner.id())
|
||||
mp.relinquish(tile)
|
||||
@@ -92,8 +89,8 @@ export class NukeExecution implements Execution {
|
||||
const prev = attacked.get(mp)
|
||||
attacked.set(mp, prev + 1)
|
||||
}
|
||||
if (tile.terrain().isLand()) {
|
||||
this.mg.addFallout(tile)
|
||||
if (this.mg.isLand(tile)) {
|
||||
this.mg.setFallout(tile, true)
|
||||
}
|
||||
}
|
||||
for (const [other, tilesDestroyed] of attacked) {
|
||||
@@ -110,7 +107,7 @@ export class NukeExecution implements Execution {
|
||||
|
||||
for (const unit of this.mg.units()) {
|
||||
if (unit.type() != UnitType.AtomBomb && unit.type() != UnitType.HydrogenBomb) {
|
||||
if (euclideanDist(this.cell, unit.tile().cell()) < magnitude.outer) {
|
||||
if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) {
|
||||
unit.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Config } from "../configuration/Config"
|
||||
import { Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, UnitType } from "../game/Game"
|
||||
import { bfs, calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
|
||||
import { Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, UnitType } from "../game/Game"
|
||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
|
||||
import { GameImpl } from "../game/GameImpl"
|
||||
import { consolex } from "../Consolex"
|
||||
import { TileRef } from "../game/GameMap"
|
||||
import { GameMap, TileRef } from "../game/GameMap"
|
||||
|
||||
export class PlayerExecution implements Execution {
|
||||
|
||||
@@ -37,7 +37,7 @@ export class PlayerExecution implements Execution {
|
||||
return
|
||||
}
|
||||
u.modifyHealth(1)
|
||||
const tileOwner = u.tile().owner()
|
||||
const tileOwner = this.mg.owner(u.tile())
|
||||
if (u.info().territoryBound) {
|
||||
if (tileOwner.isPlayer()) {
|
||||
if (tileOwner != this.player) {
|
||||
@@ -80,8 +80,7 @@ export class PlayerExecution implements Execution {
|
||||
if (this.player.lastTileChange() > this.lastCalc) {
|
||||
this.lastCalc = ticks
|
||||
const start = performance.now()
|
||||
// TODO
|
||||
// this.removeClusters()
|
||||
this.removeClusters()
|
||||
const end = performance.now()
|
||||
if (end - start > 1000) {
|
||||
consolex.log(`player ${this.player.name()}, took ${end - start}ms`)
|
||||
@@ -143,8 +142,8 @@ export class PlayerExecution implements Execution {
|
||||
if (enemyTiles.size == 0) {
|
||||
return false
|
||||
}
|
||||
const enemyBox = calculateBoundingBox(new Set(Array.from(enemyTiles).map(tr => this.mg.fromRef(tr))))
|
||||
const clusterBox = calculateBoundingBox(new Set(Array.from(cluster).map(tr => this.mg.fromRef(tr))))
|
||||
const enemyBox = calculateBoundingBox(this.mg, enemyTiles)
|
||||
const clusterBox = calculateBoundingBox(this.mg, cluster)
|
||||
return inscribed(enemyBox, clusterBox)
|
||||
}
|
||||
|
||||
@@ -161,8 +160,8 @@ export class PlayerExecution implements Execution {
|
||||
return
|
||||
}
|
||||
const firstTile = arr[0]
|
||||
const filter = (n: Tile): boolean => n.owner().smallID() == this.mg.ownerID(firstTile)
|
||||
const tiles = bfs(this.mg.fromRef(firstTile), filter)
|
||||
const filter = (_, t: TileRef): boolean => this.mg.ownerID(t) == this.mg.ownerID(firstTile)
|
||||
const tiles = this.mg.bfs(firstTile, filter)
|
||||
|
||||
const modePlayer = this.mg.playerBySmallID(mode)
|
||||
if (!modePlayer.isPlayer()) {
|
||||
@@ -175,7 +174,7 @@ export class PlayerExecution implements Execution {
|
||||
|
||||
private calculateClusters(): Set<TileRef>[] {
|
||||
const seen = new Set<TileRef>()
|
||||
const border = this.player.borderTileRefs()
|
||||
const border = this.player.borderTiles()
|
||||
const clusters: Set<TileRef>[] = []
|
||||
for (const tile of border) {
|
||||
if (seen.has(tile)) {
|
||||
@@ -191,12 +190,12 @@ export class PlayerExecution implements Execution {
|
||||
const curr = queue.shift()
|
||||
cluster.add(curr)
|
||||
|
||||
const neighbors = (this.mg as GameImpl).neighborsWithDiag(this.mg.fromRef(curr))
|
||||
const neighbors = (this.mg as GameImpl).neighborsWithDiag(curr)
|
||||
for (const neighbor of neighbors) {
|
||||
if (neighbor.isBorder() && border.has(neighbor.ref())) {
|
||||
if (!seen.has(neighbor.ref())) {
|
||||
queue.push(neighbor.ref())
|
||||
seen.add(neighbor.ref())
|
||||
if (this.mg.isBorder(neighbor) && border.has(neighbor)) {
|
||||
if (!seen.has(neighbor)) {
|
||||
queue.push(neighbor)
|
||||
seen.add(neighbor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
|
||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { bfs, dist, manhattanDist } from "../Util";
|
||||
import { TradeShipExecution } from "./TradeShipExecution";
|
||||
import { consolex } from "../Consolex";
|
||||
import { MiniAStar } from "../pathfinding/MiniAStar";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||
|
||||
export class PortExecution implements Execution {
|
||||
|
||||
@@ -15,12 +13,12 @@ export class PortExecution implements Execution {
|
||||
private mg: MutableGame
|
||||
private port: MutableUnit
|
||||
private random: PseudoRandom
|
||||
private portPaths = new Map<MutableUnit, Tile[]>()
|
||||
private portPaths = new Map<MutableUnit, TileRef[]>()
|
||||
private computingPaths = new Map<MutableUnit, MiniAStar>()
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private cell: Cell,
|
||||
private tile: TileRef,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -33,16 +31,16 @@ export class PortExecution implements Execution {
|
||||
|
||||
if (this.port == null) {
|
||||
// TODO: use canBuild
|
||||
const tile = this.mg.tile(this.cell)
|
||||
const tile = this.tile
|
||||
const player = this.mg.player(this._owner)
|
||||
if (!player.canBuild(UnitType.Port, tile)) {
|
||||
consolex.warn(`player ${player} cannot build port at ${this.cell}`)
|
||||
consolex.warn(`player ${player} cannot build port at ${this.tile}`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const spawns = Array.from(bfs(tile, dist(tile, 20)))
|
||||
.filter(t => t.terrain().isOceanShore() && t.owner() == player)
|
||||
.sort((a, b) => manhattanDist(a.cell(), tile.cell()) - manhattanDist(b.cell(), tile.cell()))
|
||||
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||
.filter(t => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||
.sort((a, b) => this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile))
|
||||
|
||||
if (spawns.length == 0) {
|
||||
consolex.warn(`cannot find spawn for port`)
|
||||
@@ -73,7 +71,7 @@ export class PortExecution implements Execution {
|
||||
const aStar = this.computingPaths.get(port)
|
||||
switch (aStar.compute()) {
|
||||
case PathFindResultType.Completed:
|
||||
this.portPaths.set(port, aStar.reconstructPath().map(cell => this.mg.tile(cell)))
|
||||
this.portPaths.set(port, aStar.reconstructPath())
|
||||
this.computingPaths.delete(port)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
@@ -88,8 +86,8 @@ export class PortExecution implements Execution {
|
||||
const pf = new MiniAStar(
|
||||
this.mg.map(),
|
||||
this.mg.miniMap(),
|
||||
this.port.tile().ref(),
|
||||
port.tile().ref(),
|
||||
this.port.tile(),
|
||||
port.tile(),
|
||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||
10_000,
|
||||
25
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Execution, MutableGame, MutablePlayer, MutableUnit, Tile, Unit, UnitType } from "../game/Game";
|
||||
import { Execution, MutableGame, MutablePlayer, MutableUnit, Unit, UnitType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class ShellExecution implements Execution {
|
||||
|
||||
@@ -9,7 +10,7 @@ export class ShellExecution implements Execution {
|
||||
private pathFinder: PathFinder
|
||||
private shell: MutableUnit
|
||||
|
||||
constructor(private spawn: Tile, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
|
||||
constructor(private spawn: TileRef, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Cell, Execution, MutableGame, MutablePlayer, PlayerInfo, PlayerType } from "../game/Game"
|
||||
import { TileRef } from "../game/GameMap"
|
||||
import { BotExecution } from "./BotExecution"
|
||||
import { PlayerExecution } from "./PlayerExecution"
|
||||
import { getSpawnTiles } from "./Util"
|
||||
@@ -10,7 +11,7 @@ export class SpawnExecution implements Execution {
|
||||
|
||||
constructor(
|
||||
private playerInfo: PlayerInfo,
|
||||
private cell: Cell
|
||||
private tile: TileRef
|
||||
) { }
|
||||
|
||||
init(mg: MutableGame, ticks: number) {
|
||||
@@ -25,17 +26,16 @@ export class SpawnExecution implements Execution {
|
||||
}
|
||||
|
||||
const existing = this.mg.players().find(p => p.id() == this.playerInfo.id)
|
||||
const tile = this.mg.tile(this.cell)
|
||||
if (existing) {
|
||||
existing.tiles().forEach(t => existing.relinquish(t))
|
||||
getSpawnTiles(tile).forEach(t => {
|
||||
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||
existing.conquer(t)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startManpower(this.playerInfo))
|
||||
getSpawnTiles(tile).forEach(t => {
|
||||
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||
player.conquer(t)
|
||||
})
|
||||
this.mg.addExecution(new PlayerExecution(player.id()))
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { MessageType } from '../game/Game';
|
||||
import { renderNumber } from "../../client/Utils";
|
||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game";
|
||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, UnitType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { bfs, dist, distSortUnit, manhattanDist } from "../Util";
|
||||
import { distSortUnit } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from '../game/GameMap';
|
||||
|
||||
export class TradeShipExecution implements Execution {
|
||||
|
||||
@@ -23,7 +22,7 @@ export class TradeShipExecution implements Execution {
|
||||
private dstPort: MutableUnit,
|
||||
private pathFinder: PathFinder,
|
||||
// don't modify
|
||||
private path: Tile[]
|
||||
private path: TileRef[]
|
||||
) { }
|
||||
|
||||
|
||||
@@ -60,7 +59,7 @@ export class TradeShipExecution implements Execution {
|
||||
}
|
||||
|
||||
if (this.wasCaptured) {
|
||||
const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.tradeShip))
|
||||
const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.mg, this.tradeShip))
|
||||
if (ports.length == 0) {
|
||||
this.tradeShip.delete(false)
|
||||
this.active = false
|
||||
@@ -70,7 +69,7 @@ export class TradeShipExecution implements Execution {
|
||||
const result = this.pathFinder.nextTile(this.tradeShip.tile(), dstPort.tile())
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
const gold = this.mg.config().tradeShipGold(this.srcPort, dstPort)
|
||||
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), dstPort.tile()))
|
||||
this.tradeShip.owner().addGold(gold)
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`,
|
||||
@@ -97,7 +96,7 @@ export class TradeShipExecution implements Execution {
|
||||
|
||||
if (this.index >= this.path.length) {
|
||||
this.active = false
|
||||
const gold = this.mg.config().tradeShipGold(this.srcPort, this.dstPort)
|
||||
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), this.dstPort.tile()))
|
||||
this.srcPort.owner().addGold(gold)
|
||||
this.dstPort.owner().addGold(gold)
|
||||
this.mg.displayMessage(
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, UnitType, TerrainType } from "../game/Game";
|
||||
import { and, bfs, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../Util";
|
||||
import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, UnitType, TerrainType } from "../game/Game";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
import { MessageType } from '../game/Game';
|
||||
import { DisplayMessageUpdate } from '../game/Game';
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { targetTransportTile } from "../Util";
|
||||
|
||||
export class TransportShipExecution implements Execution {
|
||||
|
||||
@@ -22,9 +21,9 @@ export class TransportShipExecution implements Execution {
|
||||
private target: MutablePlayer | TerraNullius
|
||||
|
||||
// TODO make private
|
||||
public path: Tile[]
|
||||
private src: Tile | null
|
||||
private dst: Tile | null
|
||||
public path: TileRef[]
|
||||
private src: TileRef | null
|
||||
private dst: TileRef | null
|
||||
|
||||
|
||||
private boat: MutableUnit
|
||||
@@ -34,7 +33,7 @@ export class TransportShipExecution implements Execution {
|
||||
constructor(
|
||||
private attackerID: PlayerID,
|
||||
private targetID: PlayerID | null,
|
||||
private cell: Cell,
|
||||
private ref: TileRef,
|
||||
private troops: number | null,
|
||||
) { }
|
||||
|
||||
@@ -68,7 +67,7 @@ export class TransportShipExecution implements Execution {
|
||||
|
||||
this.troops = Math.min(this.troops, this.attacker.troops())
|
||||
|
||||
this.dst = targetTransportTile(this.mg.width(), this.mg.tile(this.cell))
|
||||
this.dst = targetTransportTile(this.mg, this.ref)
|
||||
if (this.dst == null) {
|
||||
consolex.warn(`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`)
|
||||
this.active = false
|
||||
@@ -102,7 +101,7 @@ export class TransportShipExecution implements Execution {
|
||||
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
if (this.dst.owner() == this.attacker) {
|
||||
if (this.mg.owner(this.dst) == this.attacker) {
|
||||
this.attacker.addTroops(this.troops)
|
||||
this.boat.delete(false)
|
||||
this.active = false
|
||||
@@ -113,7 +112,7 @@ export class TransportShipExecution implements Execution {
|
||||
} else {
|
||||
this.attacker.conquer(this.dst)
|
||||
this.mg.addExecution(
|
||||
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst.cell(), null, false)
|
||||
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst, false)
|
||||
)
|
||||
}
|
||||
this.boat.delete(false)
|
||||
|
||||
+10
-11
@@ -1,15 +1,14 @@
|
||||
import { Game, Cell, Tile } from "../game/Game";
|
||||
import { and, bfs, euclDist } from "../Util";
|
||||
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
|
||||
|
||||
|
||||
export function getSpawnTiles(tile: Tile): Tile[] {
|
||||
return Array.from(bfs(tile, euclDist(tile, 4)))
|
||||
.filter(t => !t.hasOwner() && t.terrain().isLand())
|
||||
export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] {
|
||||
return Array.from(gm.bfs(tile, euclDistFN(tile, 4)))
|
||||
.filter(t => !gm.hasOwner(t) && gm.isLand(t))
|
||||
}
|
||||
|
||||
export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile, y: Tile } {
|
||||
const xSorted = Array.from(x).sort((a, b) => a.cell().x - b.cell().x);
|
||||
const ySorted = Array.from(y).sort((a, b) => a.cell().x - b.cell().x);
|
||||
export function closestTwoTiles(gm: GameMap, x: Iterable<TileRef>, y: Iterable<TileRef>): { x: TileRef, y: TileRef } {
|
||||
const xSorted = Array.from(x).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
const ySorted = Array.from(y).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
|
||||
if (xSorted.length == 0 || ySorted.length == 0) {
|
||||
return null;
|
||||
@@ -25,8 +24,8 @@ export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile
|
||||
const currentY = ySorted[j];
|
||||
|
||||
const distance =
|
||||
Math.abs(currentX.cell().x - currentY.cell().x) +
|
||||
Math.abs(currentX.cell().y - currentY.cell().y);
|
||||
Math.abs(gm.x(currentX) - gm.x(currentY)) +
|
||||
Math.abs(gm.y(currentX) - gm.y(currentY));
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
@@ -42,7 +41,7 @@ export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile
|
||||
i++;
|
||||
}
|
||||
// Otherwise, move whichever pointer has smaller x value
|
||||
else if (currentX.cell().x < currentY.cell().x) {
|
||||
else if (gm.x(currentX) < gm.x(currentY)) {
|
||||
i++;
|
||||
} else {
|
||||
j++;
|
||||
|
||||
Reference in New Issue
Block a user