diff --git a/src/core/execution/SpawnExecution.ts b/src/core/execution/SpawnExecution.ts index 4162e85fc..d8e5586ab 100644 --- a/src/core/execution/SpawnExecution.ts +++ b/src/core/execution/SpawnExecution.ts @@ -7,6 +7,8 @@ import { BotExecution } from "./BotExecution"; import { PlayerExecution } from "./PlayerExecution"; import { getSpawnTiles } from "./Util"; +type Spawn = { center: TileRef; tiles: TileRef[] }; + export class SpawnExecution implements Execution { private random: PseudoRandom; active: boolean = true; @@ -47,15 +49,15 @@ export class SpawnExecution implements Execution { return; } - this.tile ??= this.randomSpawnLand(); + const spawn = this.getSpawn(this.tile); - if (this.tile === undefined) { + if (!spawn) { console.warn(`SpawnExecution: cannot spawn ${this.playerInfo.name}`); return; } player.tiles().forEach((t) => player.relinquish(t)); - getSpawnTiles(this.mg, this.tile).forEach((t) => { + spawn.tiles.forEach((t) => { player.conquer(t); }); @@ -66,7 +68,7 @@ export class SpawnExecution implements Execution { } } - player.setSpawnTile(this.tile); + player.setSpawnTile(spawn.center); } isActive(): boolean { @@ -77,18 +79,28 @@ export class SpawnExecution implements Execution { return true; } - private randomSpawnLand(): TileRef | undefined { + private getSpawn(center?: TileRef): Spawn | undefined { + if (center !== undefined) { + const tiles = getSpawnTiles(this.mg, center, false); + + if (!tiles.length) { + return; + } + + return { center, tiles }; + } + let tries = 0; while (tries < SpawnExecution.MAX_SPAWN_TRIES) { tries++; - const tile = this.randTile(); + const center = this.randTile(); if ( - !this.mg.isLand(tile) || - this.mg.hasOwner(tile) || - this.mg.isBorder(tile) + !this.mg.isLand(center) || + this.mg.hasOwner(center) || + this.mg.isBorder(center) ) { continue; } @@ -104,7 +116,7 @@ export class SpawnExecution implements Execution { } return ( - this.mg.manhattanDist(spawnTile, tile) < + this.mg.manhattanDist(spawnTile, center) < this.mg.config().minDistanceBetweenPlayers() ); }); @@ -113,7 +125,13 @@ export class SpawnExecution implements Execution { continue; } - return tile; + const tiles = getSpawnTiles(this.mg, center, true); + if (!tiles) { + // if some of the spawn tile is outside of the land, we want to find another spawn tile + continue; + } + + return { center, tiles }; } return; diff --git a/src/core/execution/Util.ts b/src/core/execution/Util.ts index ed33836f5..790be42c9 100644 --- a/src/core/execution/Util.ts +++ b/src/core/execution/Util.ts @@ -126,11 +126,34 @@ export function listNukeBreakAlliance( return playersToBreakAllianceWith; } +export function getSpawnTiles( + gm: GameMap, + tile: TileRef, + requireAllValid: true, +): TileRef[] | null; +export function getSpawnTiles( + gm: GameMap, + tile: TileRef, + requireAllValid?: false, +): TileRef[]; +export function getSpawnTiles( + gm: GameMap, + tile: TileRef, + requireAllValid = false, +): TileRef[] | null { + const spawnTiles = Array.from(gm.bfs(tile, euclDistFN(tile, 4, true))); -export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] { - return Array.from(gm.bfs(tile, euclDistFN(tile, 4, true))).filter( - (t) => !gm.hasOwner(t) && gm.isLand(t), - ); + const isInvalid = (t: TileRef) => gm.hasOwner(t) || !gm.isLand(t); + + if (!requireAllValid) { + return spawnTiles.filter((t) => !isInvalid(t)); + } + + if (spawnTiles.some(isInvalid)) { + return null; + } + + return spawnTiles; } export function closestTile( diff --git a/tests/core/execution/SpawnExecution.test.ts b/tests/core/execution/SpawnExecution.test.ts index d9108acea..aa9508495 100644 --- a/tests/core/execution/SpawnExecution.test.ts +++ b/tests/core/execution/SpawnExecution.test.ts @@ -98,15 +98,15 @@ describe("Spawn execution", () => { const game = await setup("half_land_half_ocean", undefined, [playerInfo]); - game.addExecution(new SpawnExecution("game_id", playerInfo, 50)); - game.addExecution(new SpawnExecution("game_id", playerInfo, 60)); + game.addExecution(new SpawnExecution("game_id", playerInfo, 10)); + game.addExecution(new SpawnExecution("game_id", playerInfo, 20)); while (game.inSpawnPhase()) { game.executeNextTick(); } - expect(game.playerByClientID("client_id")?.spawnTile()).toBe(60); + expect(game.playerByClientID("client_id")?.spawnTile()).toBe(20); // Previous territory from first spawn should be relinquished - expect(game.owner(50).isPlayer()).toBe(false); + expect(game.owner(10).isPlayer()).toBe(false); }); });