import { Execution, Game, Player, PlayerInfo, PlayerType } from "../game/Game"; import { TileRef } from "../game/GameMap"; import { PseudoRandom } from "../PseudoRandom"; import { GameID } from "../Schemas"; import { simpleHash } from "../Util"; import { BotExecution } from "./BotExecution"; import { PlayerExecution } from "./PlayerExecution"; import { getSpawnTiles } from "./Util"; export class SpawnExecution implements Execution { private random: PseudoRandom; active: boolean = true; private mg: Game; private static readonly MAX_SPAWN_TRIES = 1_000; constructor( gameID: GameID, private playerInfo: PlayerInfo, public tile?: TileRef, ) { this.random = new PseudoRandom( simpleHash(playerInfo.id) + simpleHash(gameID), ); } init(mg: Game, ticks: number) { this.mg = mg; } tick(ticks: number) { this.active = false; if (!this.mg.inSpawnPhase()) { this.active = false; return; } let player: Player | null = null; if (this.mg.hasPlayer(this.playerInfo.id)) { player = this.mg.player(this.playerInfo.id); } else { player = this.mg.addPlayer(this.playerInfo); } // Security: If random spawn is enabled, prevent players from re-rolling their spawn location if (this.mg.config().isRandomSpawn() && player.hasSpawned()) { return; } this.tile ??= this.randomSpawnLand(); if (this.tile === undefined) { console.warn(`SpawnExecution: cannot spawn ${this.playerInfo.name}`); return; } player.tiles().forEach((t) => player.relinquish(t)); getSpawnTiles(this.mg, this.tile).forEach((t) => { player.conquer(t); }); if (!player.hasSpawned()) { this.mg.addExecution(new PlayerExecution(player)); if (player.type() === PlayerType.Bot) { this.mg.addExecution(new BotExecution(player)); } } player.setSpawnTile(this.tile); } isActive(): boolean { return this.active; } activeDuringSpawnPhase(): boolean { return true; } private randomSpawnLand(): TileRef | undefined { let tries = 0; while (tries < SpawnExecution.MAX_SPAWN_TRIES) { tries++; const tile = this.randTile(); if ( !this.mg.isLand(tile) || this.mg.hasOwner(tile) || this.mg.isBorder(tile) ) { continue; } const isOtherPlayerSpawnedNearby = this.mg .allPlayers() .filter((player) => player.id() !== this.playerInfo.id) .some((player) => { const spawnTile = player.spawnTile(); if (spawnTile === undefined) { return false; } return ( this.mg.manhattanDist(spawnTile, tile) < this.mg.config().minDistanceBetweenPlayers() ); }); if (isOtherPlayerSpawnedNearby) { continue; } return tile; } return; } private randTile(): TileRef { const x = this.random.nextInt(0, this.mg.width()); const y = this.random.nextInt(0, this.mg.height()); return this.mg.ref(x, y); } }