mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:20:46 +00:00
Random spawn. Avoid spawning near water. (#3009)
## Description: Fixing https://discord.com/channels/1359946986937258015/1360078040222142564/1463898386854973642 Now, if not all tiles on the spawn circle can be owned, the algorithm tries to select another random spawn tile. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: nikolaj_mykola --------- Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user