Files
OpenFrontIO/src/core/execution/utils/PlayerSpawner.ts
T
Mykola 25ea11114e Random spawn (#2375)
## Description:

https://github.com/openfrontio/OpenFrontIO/issues/2352

This is my first PR in this project, and I’ll continue refining it based
on feedback.

<img width="1088" height="859" alt="image"
src="https://github.com/user-attachments/assets/07f4f8b1-52fa-4136-add4-19b00aefd963"
/>

<img width="1157" height="783" alt="image"
src="https://github.com/user-attachments/assets/1c5be80d-72f8-4ead-8d4b-706a3a04fd73"
/>

<img width="1488" height="777" alt="image"
src="https://github.com/user-attachments/assets/4d743548-f0c3-4579-963b-43676f68fab1"
/>

<img width="1499" height="778" alt="image"
src="https://github.com/user-attachments/assets/f808e44f-ef97-467f-9e41-812e2857c36e"
/>


## 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: Evan <evanpelle@gmail.com>
2025-11-06 15:49:37 -08:00

84 lines
1.9 KiB
TypeScript

import { Game, PlayerType } from "../../game/Game";
import { TileRef } from "../../game/GameMap";
import { PseudoRandom } from "../../PseudoRandom";
import { GameID } from "../../Schemas";
import { simpleHash } from "../../Util";
import { SpawnExecution } from "../SpawnExecution";
export class PlayerSpawner {
private random: PseudoRandom;
private players: SpawnExecution[] = [];
private static readonly MAX_SPAWN_TRIES = 10_000;
private static readonly MIN_SPAWN_DISTANCE = 30;
constructor(
private gm: Game,
gameID: GameID,
) {
this.random = new PseudoRandom(simpleHash(gameID));
}
private randTile(): TileRef {
const x = this.random.nextInt(0, this.gm.width());
const y = this.random.nextInt(0, this.gm.height());
return this.gm.ref(x, y);
}
private randomSpawnLand(): TileRef | null {
let tries = 0;
while (tries < PlayerSpawner.MAX_SPAWN_TRIES) {
tries++;
const tile = this.randTile();
if (
!this.gm.isLand(tile) ||
this.gm.hasOwner(tile) ||
this.gm.isBorder(tile)
) {
continue;
}
let tooCloseToOtherPlayer = false;
for (const spawn of this.players) {
if (
this.gm.manhattanDist(spawn.tile, tile) <
PlayerSpawner.MIN_SPAWN_DISTANCE
) {
tooCloseToOtherPlayer = true;
break;
}
}
if (tooCloseToOtherPlayer) {
continue;
}
return tile;
}
return null;
}
spawnPlayers(): SpawnExecution[] {
for (const player of this.gm.allPlayers()) {
if (player.type() !== PlayerType.Human) {
continue;
}
const spawnLand = this.randomSpawnLand();
if (spawnLand === null) {
// TODO: this should normally not happen, additional logic may be needed, if this occurs
continue;
}
this.players.push(new SpawnExecution(player.info(), spawnLand));
}
return this.players;
}
}