diff --git a/src/server/MapPlaylist.ts b/src/server/MapPlaylist.ts new file mode 100644 index 000000000..488d48aea --- /dev/null +++ b/src/server/MapPlaylist.ts @@ -0,0 +1,107 @@ +import { GameMapType } from "../core/game/Game"; +import { PseudoRandom } from "../core/PseudoRandom"; + +enum PlaylistType { + BigMaps, + SmallMaps, +} + +const random = new PseudoRandom(123); + +export class MapPlaylist { + private mapsPlaylistBig: GameMapType[] = []; + private mapsPlaylistSmall: GameMapType[] = []; + private currentPlaylistCounter = 0; + + // Get the next map in rotation + public getNextMap(): GameMapType { + const playlistType: PlaylistType = this.getNextPlaylistType(); + const mapsPlaylist: GameMapType[] = this.getNextMapsPlayList(playlistType); + return mapsPlaylist.shift()!; + } + + private getNextMapsPlayList(playlistType: PlaylistType): GameMapType[] { + switch (playlistType) { + case PlaylistType.BigMaps: + if (!(this.mapsPlaylistBig.length > 0)) { + this.fillMapsPlaylist(playlistType, this.mapsPlaylistBig); + } + return this.mapsPlaylistBig; + + case PlaylistType.SmallMaps: + if (!(this.mapsPlaylistSmall.length > 0)) { + this.fillMapsPlaylist(playlistType, this.mapsPlaylistSmall); + } + return this.mapsPlaylistSmall; + } + } + + private fillMapsPlaylist( + playlistType: PlaylistType, + mapsPlaylist: GameMapType[], + ): void { + const frequency = this.getFrequency(playlistType); + Object.keys(GameMapType).forEach((key) => { + let count = parseInt(frequency[key]); + while (count > 0) { + mapsPlaylist.push(GameMapType[key]); + count--; + } + }); + while (!this.allNonConsecutive(mapsPlaylist)) { + random.shuffleArray(mapsPlaylist); + } + } + + // Specifically controls how the playlists rotate. + private getNextPlaylistType(): PlaylistType { + switch (this.currentPlaylistCounter) { + case 0: + case 1: + this.currentPlaylistCounter++; + return PlaylistType.BigMaps; + case 2: + this.currentPlaylistCounter = 0; + return PlaylistType.SmallMaps; + } + } + + private getFrequency(playlistType: PlaylistType) { + switch (playlistType) { + // Big Maps are those larger than ~2.5 mil pixels + case PlaylistType.BigMaps: + return { + Europe: 3, + NorthAmerica: 2, + Africa: 2, + Britannia: 1, + GatewayToTheAtlantic: 2, + Australia: 1, + Iceland: 1, + SouthAmerica: 3, + KnownWorld: 2, + }; + case PlaylistType.SmallMaps: + return { + World: 1, + Mena: 2, + Pangaea: 1, + Asia: 1, + Mars: 1, + TwoSeas: 3, + Japan: 3, + BlackSea: 1, + }; + } + } + + // Check for consecutive duplicates in the maps array + private allNonConsecutive(maps: GameMapType[]): boolean { + for (let i = 0; i < maps.length - 1; i++) { + if (maps[i] === maps[i + 1]) { + return false; + } + } + return true; + } +} diff --git a/src/server/Master.ts b/src/server/Master.ts index c2a00d42b..a371bfb9c 100644 --- a/src/server/Master.ts +++ b/src/server/Master.ts @@ -5,15 +5,16 @@ import http from "http"; import path from "path"; import { fileURLToPath } from "url"; import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; -import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game"; -import { PseudoRandom } from "../core/PseudoRandom"; +import { Difficulty, GameMode, GameType } from "../core/game/Game"; import { GameConfig, GameInfo } from "../core/Schemas"; import { generateID } from "../core/Util"; import { gatekeeper, LimiterType } from "./Gatekeeper"; import { logger } from "./Logger"; +import { MapPlaylist } from "./MapPlaylist"; import { setupMetricsServer } from "./MasterMetrics"; const config = getServerConfigFromServer(); +const playlist = new MapPlaylist(); const readyWorkers = new Set(); const app = express(); @@ -100,7 +101,7 @@ export async function startMaster() { log.info("All workers ready, starting game scheduling"); const scheduleLobbies = () => { - schedulePublicGame().catch((error) => { + schedulePublicGame(playlist).catch((error) => { log.error("Error scheduling public game:", error); }); }; @@ -222,9 +223,9 @@ async function fetchLobbies(): Promise { } // Function to schedule a new public game -async function schedulePublicGame() { +async function schedulePublicGame(playlist: MapPlaylist) { const gameID = generateID(); - const map = getNextMap(); + const map = playlist.getNextMap(); publicLobbyIDs.add(gameID); // Create the default public game config (from your GameManager) const defaultGameConfig = { @@ -270,62 +271,6 @@ async function schedulePublicGame() { } } -// Map rotation management (moved from GameManager) -const mapsPlaylist: GameMapType[] = []; -const random = new PseudoRandom(123); - -// Get the next map in rotation -function getNextMap(): GameMapType { - if (mapsPlaylist.length > 0) { - return mapsPlaylist.shift()!; - } - - const frequency = { - World: 1, - Europe: 3, - Mena: 2, - NorthAmerica: 2, - BlackSea: 1, - Pangaea: 1, - Africa: 2, - Asia: 1, - Mars: 1, - Britannia: 2, - GatewayToTheAtlantic: 2, - Australia: 2, - Iceland: 2, - SouthAmerica: 3, - Japan: 3, - TwoSeas: 3, - }; - - Object.keys(GameMapType).forEach((key) => { - let count = parseInt(frequency[key]); - - while (count > 0) { - mapsPlaylist.push(GameMapType[key]); - count--; - } - }); - - while (true) { - random.shuffleArray(mapsPlaylist); - if (allNonConsecutive(mapsPlaylist)) { - return mapsPlaylist.shift()!; - } - } -} - -// Check for consecutive duplicates in the maps array -function allNonConsecutive(maps: GameMapType[]): boolean { - for (let i = 0; i < maps.length - 1; i++) { - if (maps[i] === maps[i + 1]) { - return false; - } - } - return true; -} - function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); }