thread_split: convert all tile to tileref

This commit is contained in:
Evan
2025-01-17 20:13:26 -08:00
parent c42cc2a9b4
commit f0f5bae79f
53 changed files with 1104 additions and 1405 deletions
+22 -28
View File
@@ -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) { }
}
+14 -17
View File
@@ -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
+6 -4
View File
@@ -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)) {
+13 -12
View File
@@ -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())
))
)
}
}
+3 -5
View File
@@ -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)
}
+8 -8
View File
@@ -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())
)
}
})
}
+13 -16
View File
@@ -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
+16 -22
View File
@@ -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
+51 -50
View File
@@ -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 {
+6 -6
View File
@@ -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)
}
}
+10 -13
View File
@@ -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()
}
}
+15 -16
View File
@@ -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)
}
}
}
+12 -14
View File
@@ -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
+3 -2
View File
@@ -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) {
}
+4 -4
View File
@@ -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()))
+7 -8
View File
@@ -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(
+10 -11
View File
@@ -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
View File
@@ -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++;