implement duos (#630)

## Description:
Implement Duos team mode.
Also assign teams to nations at the start instead of assigning them
randomly on spawn. This gives more consistent team sizes

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

<DISCORD USERNAME>

Co-authored-by: evan <openfrontio@gmail.com>
This commit is contained in:
evanpelle
2025-04-30 13:47:35 -07:00
committed by GitHub
parent 997bdca02a
commit d6a412aa50
18 changed files with 212 additions and 171 deletions
+6 -5
View File
@@ -6,6 +6,7 @@ import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { consolex } from "../core/Consolex";
import {
Difficulty,
Duos,
GameMapType,
GameMode,
mapCategories,
@@ -28,7 +29,7 @@ export class HostLobbyModal extends LitElement {
@state() private selectedDifficulty: Difficulty = Difficulty.Medium;
@state() private disableNPCs = false;
@state() private gameMode: GameMode = GameMode.FFA;
@state() private teamCount: number = 2;
@state() private teamCount: number | typeof Duos = 2;
@state() private disableNukes: boolean = false;
@state() private bots: number = 400;
@state() private infiniteGold: boolean = false;
@@ -194,7 +195,7 @@ export class HostLobbyModal extends LitElement {
${translateText("host_modal.team_count")}
</div>
<div class="option-cards">
${[2, 3, 4, 5, 6, 7].map(
${[Duos, 2, 3, 4, 5, 6, 7].map(
(o) => html`
<div
class="option-card ${this.teamCount === o
@@ -465,8 +466,8 @@ export class HostLobbyModal extends LitElement {
this.putGameConfig();
}
private async handleTeamCountSelection(value: number) {
this.teamCount = value;
private async handleTeamCountSelection(value: number | typeof Duos) {
this.teamCount = value === Duos ? Duos : Number(value);
this.putGameConfig();
}
@@ -489,7 +490,7 @@ export class HostLobbyModal extends LitElement {
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
gameMode: this.gameMode,
numPlayerTeams: this.teamCount,
playerTeams: this.teamCount,
} as GameConfig),
},
);
+6 -5
View File
@@ -5,6 +5,7 @@ import { translateText } from "../client/Utils";
import { consolex } from "../core/Consolex";
import {
Difficulty,
Duos,
GameMapType,
GameMode,
GameType,
@@ -36,7 +37,7 @@ export class SinglePlayerModal extends LitElement {
@state() private instantBuild: boolean = false;
@state() private useRandomMap: boolean = false;
@state() private gameMode: GameMode = GameMode.FFA;
@state() private teamCount: number = 2;
@state() private teamCount: number | typeof Duos = 2;
render() {
return html`
@@ -165,7 +166,7 @@ export class SinglePlayerModal extends LitElement {
${translateText("host_modal.team_count")}
</div>
<div class="option-cards">
${[2, 3, 4, 5, 6, 7].map(
${["Duos", 2, 3, 4, 5, 6, 7].map(
(o) => html`
<div
class="option-card ${this.teamCount === o
@@ -355,8 +356,8 @@ export class SinglePlayerModal extends LitElement {
this.gameMode = value;
}
private handleTeamCountSelection(value: number) {
this.teamCount = value;
private handleTeamCountSelection(value: number | string) {
this.teamCount = value === "Duos" ? Duos : Number(value);
}
private getRandomMap(): GameMapType {
@@ -410,7 +411,7 @@ export class SinglePlayerModal extends LitElement {
gameMap: this.selectedMap,
gameType: GameType.Singleplayer,
gameMode: this.gameMode,
numPlayerTeams: this.teamCount,
playerTeams: this.teamCount,
difficulty: this.selectedDifficulty,
disableNPCs: this.disableNPCs,
disableNukes: this.disableNukes,
+41 -16
View File
@@ -4,9 +4,11 @@ import { Executor } from "./execution/ExecutionManager";
import { WinCheckExecution } from "./execution/WinCheckExecution";
import {
AllPlayers,
Cell,
Game,
GameUpdates,
NameViewData,
Nation,
Player,
PlayerActions,
PlayerBorderTiles,
@@ -23,8 +25,9 @@ import {
GameUpdateViewData,
} from "./game/GameUpdates";
import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
import { PseudoRandom } from "./PseudoRandom";
import { ClientID, GameStartInfo, Turn } from "./Schemas";
import { sanitize } from "./Util";
import { sanitize, simpleHash } from "./Util";
import { fixProfaneUsername } from "./validations/username";
export async function createGameRunner(
@@ -34,26 +37,48 @@ export async function createGameRunner(
): Promise<GameRunner> {
const config = await getConfig(gameStart.config, null);
const gameMap = await loadGameMap(gameStart.config.gameMap);
const game = createGame(
gameStart.players.map(
(p) =>
new PlayerInfo(
p.flag,
p.clientID == clientID
? sanitize(p.username)
: fixProfaneUsername(sanitize(p.username)),
PlayerType.Human,
p.clientID,
p.playerID,
),
),
const random = new PseudoRandom(simpleHash(gameStart.gameID));
const humans = gameStart.players.map(
(p) =>
new PlayerInfo(
p.flag,
p.clientID == clientID
? sanitize(p.username)
: fixProfaneUsername(sanitize(p.username)),
PlayerType.Human,
p.clientID,
p.playerID,
),
);
const nations = gameStart.config.disableNPCs
? []
: gameMap.nationMap.nations.map(
(n) =>
new Nation(
new Cell(n.coordinates[0], n.coordinates[1]),
n.strength,
new PlayerInfo(
n.flag || "",
n.name,
PlayerType.FakeHuman,
null,
random.nextID(),
),
),
);
const game: Game = createGame(
humans,
nations,
gameMap.gameMap,
gameMap.miniGameMap,
gameMap.nationMap,
config,
);
const gr = new GameRunner(
game as Game,
game,
new Executor(game, gameStart.gameID, clientID),
callBack,
);
+6 -7
View File
@@ -2,11 +2,11 @@ import { z } from "zod";
import {
AllPlayers,
Difficulty,
Duos,
GameMapType,
GameMode,
GameType,
PlayerType,
Team,
UnitType,
} from "./game/Game";
@@ -121,9 +121,11 @@ const GameConfigSchema = z.object({
infiniteTroops: z.boolean(),
instantBuild: z.boolean(),
maxPlayers: z.number().optional(),
numPlayerTeams: z.number().optional(),
playerTeams: z.union([z.number().optional(), z.literal(Duos)]),
});
export const TeamSchema = z.string();
const SafeString = z
.string()
.regex(
@@ -364,7 +366,7 @@ const ClientBaseMessageSchema = z.object({
export const ClientSendWinnerSchema = ClientBaseMessageSchema.extend({
type: z.literal("winner"),
winner: ID.or(z.nativeEnum(Team)).nullable(),
winner: z.union([ID, TeamSchema]).nullable(),
allPlayersStats: AllPlayersStatsSchema,
winnerType: z.enum(["player", "team"]),
});
@@ -425,10 +427,7 @@ export const GameRecordSchema = z.object({
date: SafeString,
num_turns: z.number(),
turns: z.array(TurnSchema),
winner: z
.union([ID, z.nativeEnum(Team)])
.nullable()
.optional(),
winner: z.union([ID, SafeString]).nullable().optional(),
winnerType: z.enum(["player", "team"]).nullable().optional(),
allPlayersStats: z.record(ID, PlayerStatsSchema),
version: z.enum(["v0.0.1"]),
+2 -1
View File
@@ -2,6 +2,7 @@ import { Colord } from "colord";
import { GameConfig, GameID } from "../Schemas";
import {
Difficulty,
Duos,
Game,
GameMapType,
Gold,
@@ -67,7 +68,7 @@ export interface Config {
instantBuild(): boolean;
numSpawnPhaseTurns(): number;
userSettings(): UserSettings;
numPlayerTeams(): number;
playerTeams(): number | typeof Duos;
startManpower(playerInfo: PlayerInfo): number;
populationIncreaseRate(player: Player | PlayerView): number;
+5 -2
View File
@@ -1,5 +1,6 @@
import {
Difficulty,
Duos,
Game,
GameMapType,
GameMode,
@@ -203,12 +204,14 @@ export class DefaultConfig implements Config {
defensePostDefenseBonus(): number {
return 5;
}
numPlayerTeams(): number {
return this._gameConfig.numPlayerTeams ?? 0;
playerTeams(): number | typeof Duos {
return this._gameConfig.playerTeams ?? 0;
}
spawnNPCs(): boolean {
return !this._gameConfig.disableNPCs;
}
disableNukes(): boolean {
return this._gameConfig.disableNukes;
}
+11 -10
View File
@@ -1,7 +1,7 @@
import { Colord, colord } from "colord";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { PlayerType, Team, TerrainType } from "../game/Game";
import { ColoredTeams, PlayerType, Team, TerrainType } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import {
@@ -43,24 +43,25 @@ export const pastelTheme = new (class implements Theme {
teamColor(team: Team): Colord {
switch (team) {
case Team.Blue:
case ColoredTeams.Blue:
return blue;
case Team.Red:
case ColoredTeams.Red:
return red;
case Team.Teal:
case ColoredTeams.Teal:
return teal;
case Team.Purple:
case ColoredTeams.Purple:
return purple;
case Team.Yellow:
case ColoredTeams.Yellow:
return yellow;
case Team.Orange:
case ColoredTeams.Orange:
return orange;
case Team.Green:
case ColoredTeams.Green:
return green;
case Team.Bot:
case ColoredTeams.Bot:
return botColor;
default:
return humanColors[simpleHash(team) % humanColors.length];
}
throw new Error(`Missing color for ${team}`);
}
territoryColor(player: PlayerView): Colord {
+11 -10
View File
@@ -1,7 +1,7 @@
import { Colord, colord } from "colord";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { PlayerType, Team, TerrainType } from "../game/Game";
import { ColoredTeams, PlayerType, Team, TerrainType } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import {
@@ -43,24 +43,25 @@ export const pastelThemeDark = new (class implements Theme {
teamColor(team: Team): Colord {
switch (team) {
case Team.Blue:
case ColoredTeams.Blue:
return blue;
case Team.Red:
case ColoredTeams.Red:
return red;
case Team.Teal:
case ColoredTeams.Teal:
return teal;
case Team.Purple:
case ColoredTeams.Purple:
return purple;
case Team.Yellow:
case ColoredTeams.Yellow:
return yellow;
case Team.Orange:
case ColoredTeams.Orange:
return orange;
case Team.Green:
case ColoredTeams.Green:
return green;
case Team.Bot:
case ColoredTeams.Bot:
return botColor;
default:
return humanColors[simpleHash(team) % humanColors.length];
}
throw new Error(`Missing color for ${team}`);
}
territoryColor(player: PlayerView): Colord {
+2 -14
View File
@@ -1,4 +1,4 @@
import { Execution, Game, PlayerInfo, PlayerType } from "../game/Game";
import { Execution, Game } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { ClientID, GameID, Intent, Turn } from "../Schemas";
import { simpleHash } from "../Util";
@@ -120,19 +120,7 @@ export class Executor {
fakeHumanExecutions(): Execution[] {
const execs = [];
for (const nation of this.mg.nations()) {
execs.push(
new FakeHumanExecution(
this.gameID,
new PlayerInfo(
nation.flag || "",
nation.name,
PlayerType.FakeHuman,
null,
this.random.nextID(),
nation,
),
),
);
execs.push(new FakeHumanExecution(this.gameID, nation));
}
return execs;
}
+9 -7
View File
@@ -4,9 +4,9 @@ import {
Difficulty,
Execution,
Game,
Nation,
Player,
PlayerID,
PlayerInfo,
PlayerType,
Relation,
TerrainType,
@@ -46,10 +46,10 @@ export class FakeHumanExecution implements Execution {
constructor(
gameID: GameID,
private playerInfo: PlayerInfo,
private nation: Nation,
) {
this.random = new PseudoRandom(
simpleHash(playerInfo.id) + simpleHash(gameID),
simpleHash(nation.playerInfo.id) + simpleHash(gameID),
);
this.attackRate = this.random.nextInt(40, 80);
this.attackTick = this.random.nextInt(0, this.attackRate);
@@ -110,15 +110,17 @@ export class FakeHumanExecution implements Execution {
if (this.mg.inSpawnPhase()) {
const rl = this.randomLand();
if (rl == null) {
consolex.warn(`cannot spawn ${this.playerInfo.name}`);
consolex.warn(`cannot spawn ${this.nation.playerInfo.name}`);
return;
}
this.mg.addExecution(new SpawnExecution(this.playerInfo, rl));
this.mg.addExecution(new SpawnExecution(this.nation.playerInfo, rl));
return;
}
if (this.player == null) {
this.player = this.mg.players().find((p) => p.id() == this.playerInfo.id);
this.player = this.mg
.players()
.find((p) => p.id() == this.nation.playerInfo.id);
if (this.player == null) {
return;
}
@@ -553,7 +555,7 @@ export class FakeHumanExecution implements Execution {
let tries = 0;
while (tries < 50) {
tries++;
const cell = this.playerInfo.nation.cell;
const cell = this.nation.spawnCell;
const x = this.random.nextInt(cell.x - delta, cell.x + delta);
const y = this.random.nextInt(cell.y - delta, cell.y + delta);
if (!this.mg.isValidCoord(x, y)) {
+9 -2
View File
@@ -1,5 +1,12 @@
import { GameEvent } from "../EventBus";
import { Execution, Game, GameMode, Player, Team } from "../game/Game";
import {
ColoredTeams,
Execution,
Game,
GameMode,
Player,
Team,
} from "../game/Game";
export class WinEvent implements GameEvent {
constructor(public readonly winner: Player) {}
@@ -66,7 +73,7 @@ export class WinCheckExecution implements Execution {
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
const percentage = (max[1] / numTilesWithoutFallout) * 100;
if (percentage > this.mg.config().percentageTilesOwnedToWin()) {
if (max[0] == Team.Bot) return;
if (max[0] == ColoredTeams.Bot) return;
this.mg.setWinner(max[0], this.mg.stats().stats());
console.log(`${max[0]} has won the game`);
this.active = false;
+16 -13
View File
@@ -37,16 +37,20 @@ export enum Difficulty {
Impossible = "Impossible",
}
export enum Team {
Red = "Red",
Blue = "Blue",
Teal = "Teal",
Purple = "Purple",
Yellow = "Yellow",
Orange = "Orange",
Green = "Green",
Bot = "Bot",
}
export type Team = string;
export const Duos = "Duos" as const;
export const ColoredTeams: Record<string, Team> = {
Red: "Red",
Blue: "Blue",
Teal: "Teal",
Purple: "Purple",
Yellow: "Yellow",
Orange: "Orange",
Green: "Green",
Bot: "Bot",
} as const;
export enum GameMapType {
World = "World",
@@ -158,10 +162,9 @@ export enum Relation {
export class Nation {
constructor(
public readonly flag: string,
public readonly name: string,
public readonly cell: Cell,
public readonly spawnCell: Cell,
public readonly strength: number,
public readonly playerInfo: PlayerInfo,
) {}
}
+37 -27
View File
@@ -8,6 +8,8 @@ import {
Alliance,
AllianceRequest,
Cell,
ColoredTeams,
Duos,
EmojiMessage,
Execution,
Game,
@@ -32,19 +34,18 @@ import { PlayerImpl } from "./PlayerImpl";
import { Stats } from "./Stats";
import { StatsImpl } from "./StatsImpl";
import { assignTeams } from "./TeamAssignment";
import { NationMap } from "./TerrainMapLoader";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { UnitGrid } from "./UnitGrid";
import { UnitImpl } from "./UnitImpl";
export function createGame(
humans: PlayerInfo[],
nations: Nation[],
gameMap: GameMap,
miniGameMap: GameMap,
nationMap: NationMap,
config: Config,
): Game {
return new GameImpl(humans, gameMap, miniGameMap, nationMap, config);
return new GameImpl(humans, nations, gameMap, miniGameMap, config);
}
export type CellString = string;
@@ -54,8 +55,6 @@ export class GameImpl implements Game {
private unInitExecs: Execution[] = [];
private nations_: Nation[] = [];
_players: Map<PlayerID, PlayerImpl> = new Map<PlayerID, PlayerImpl>();
_playersBySmallID = [];
@@ -75,51 +74,62 @@ export class GameImpl implements Game {
private _stats: StatsImpl = new StatsImpl();
private playerTeams: Team[] = [Team.Red, Team.Blue];
private botTeam: Team = Team.Bot;
private playerTeams: Team[] = [ColoredTeams.Red, ColoredTeams.Blue];
private botTeam: Team = ColoredTeams.Bot;
constructor(
private _humans: PlayerInfo[],
private _nations: Nation[],
private _map: GameMap,
private miniGameMap: GameMap,
nationMap: NationMap,
private _config: Config,
) {
this._terraNullius = new TerraNulliusImpl();
this._width = _map.width();
this._height = _map.height();
this.nations_ = nationMap.nations.map(
(n) =>
new Nation(
n.flag || "",
n.name,
new Cell(n.coordinates[0], n.coordinates[1]),
n.strength,
),
);
this.unitGrid = new UnitGrid(this._map);
if (_config.gameConfig().gameMode === GameMode.Team) {
const numPlayerTeams = _config.numPlayerTeams();
this.populateTeams();
}
this.addPlayers();
}
private populateTeams() {
if (this._config.playerTeams() === Duos) {
this.playerTeams = [];
const numTeams = Math.ceil(
(this._humans.length + this._nations.length) / 2,
);
for (let i = 0; i < numTeams; i++) {
this.playerTeams.push("Team " + (i + 1));
}
} else {
const numPlayerTeams = this._config.playerTeams() as number;
if (numPlayerTeams < 2)
throw new Error(`Too few teams: ${numPlayerTeams}`);
if (numPlayerTeams >= 3) this.playerTeams.push(Team.Teal);
if (numPlayerTeams >= 4) this.playerTeams.push(Team.Purple);
if (numPlayerTeams >= 5) this.playerTeams.push(Team.Yellow);
if (numPlayerTeams >= 6) this.playerTeams.push(Team.Orange);
if (numPlayerTeams >= 7) this.playerTeams.push(Team.Green);
if (numPlayerTeams >= 3) this.playerTeams.push(ColoredTeams.Teal);
if (numPlayerTeams >= 4) this.playerTeams.push(ColoredTeams.Purple);
if (numPlayerTeams >= 5) this.playerTeams.push(ColoredTeams.Yellow);
if (numPlayerTeams >= 6) this.playerTeams.push(ColoredTeams.Orange);
if (numPlayerTeams >= 7) this.playerTeams.push(ColoredTeams.Green);
if (numPlayerTeams >= 8)
throw new Error(`Too many teams: ${numPlayerTeams}`);
}
this.addHumans();
}
private addHumans() {
private addPlayers() {
if (this.config().gameConfig().gameMode != GameMode.Team) {
this._humans.forEach((p) => this.addPlayer(p));
this._nations.forEach((n) => this.addPlayer(n.playerInfo));
return;
}
const playerToTeam = assignTeams(this._humans, this.playerTeams);
const isDuos = this.config().gameConfig().playerTeams === Duos;
const allPlayers = [
...this._humans,
...this._nations.map((n) => n.playerInfo),
];
const playerToTeam = assignTeams(allPlayers, this.playerTeams);
for (const [playerInfo, team] of playerToTeam.entries()) {
if (team == "kicked") {
console.warn(`Player ${playerInfo.name} was kicked from team`);
@@ -180,7 +190,7 @@ export class GameImpl implements Game {
return this.config().unitInfo(type);
}
nations(): Nation[] {
return this.nations_;
return this._nations;
}
createAllianceRequest(requestor: Player, recipient: Player): AllianceRequest {
+2 -2
View File
@@ -95,8 +95,8 @@ export class GameServer {
if (gameConfig.gameMode != null) {
this.gameConfig.gameMode = gameConfig.gameMode;
}
if (gameConfig.numPlayerTeams != null) {
this.gameConfig.numPlayerTeams = gameConfig.numPlayerTeams;
if (gameConfig.playerTeams != null) {
this.gameConfig.playerTeams = gameConfig.playerTeams;
}
}
+1 -1
View File
@@ -252,7 +252,7 @@ async function schedulePublicGame(playlist: MapPlaylist) {
disableNPCs: gameMode == GameMode.Team,
disableNukes: false,
gameMode,
numPlayerTeams,
playerTeams: numPlayerTeams,
bots: 400,
};
+1 -1
View File
@@ -165,7 +165,7 @@ export function startWorker() {
disableNPCs: req.body.disableNPCs,
disableNukes: req.body.disableNukes,
gameMode: req.body.gameMode,
numPlayerTeams: req.body.numPlayerTeams,
playerTeams: req.body.playerTeams,
});
res.status(200).json({ success: true });
}),
+46 -46
View File
@@ -1,7 +1,7 @@
import { PlayerInfo, PlayerType, Team } from "../src/core/game/Game";
import { ColoredTeams, PlayerInfo, PlayerType } from "../src/core/game/Game";
import { assignTeams } from "../src/core/game/TeamAssignment";
const teams = [Team.Red, Team.Blue];
const teams = [ColoredTeams.Red, ColoredTeams.Blue];
describe("assignTeams", () => {
const createPlayer = (id: string, clan?: string): PlayerInfo => {
@@ -27,10 +27,10 @@ describe("assignTeams", () => {
const result = assignTeams(players, teams);
// Check that players are assigned alternately
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Blue);
expect(result.get(players[2])).toEqual(Team.Red);
expect(result.get(players[3])).toEqual(Team.Blue);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Blue);
expect(result.get(players[2])).toEqual(ColoredTeams.Red);
expect(result.get(players[3])).toEqual(ColoredTeams.Blue);
});
it("should keep clan members together on the same team", () => {
@@ -44,10 +44,10 @@ describe("assignTeams", () => {
const result = assignTeams(players, teams);
// Check that clan members are on the same team
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Red);
expect(result.get(players[2])).toEqual(Team.Blue);
expect(result.get(players[3])).toEqual(Team.Blue);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Red);
expect(result.get(players[2])).toEqual(ColoredTeams.Blue);
expect(result.get(players[3])).toEqual(ColoredTeams.Blue);
});
it("should handle mixed clan and non-clan players", () => {
@@ -61,10 +61,10 @@ describe("assignTeams", () => {
const result = assignTeams(players, teams);
// Check that clan members are together and non-clan players balance teams
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Red);
expect(result.get(players[2])).toEqual(Team.Blue);
expect(result.get(players[3])).toEqual(Team.Blue);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Red);
expect(result.get(players[2])).toEqual(ColoredTeams.Blue);
expect(result.get(players[3])).toEqual(ColoredTeams.Blue);
});
it("should kick players when teams are full", () => {
@@ -80,14 +80,14 @@ describe("assignTeams", () => {
const result = assignTeams(players, teams);
// Check that players are kicked when teams are full
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Red);
expect(result.get(players[2])).toEqual(Team.Red);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Red);
expect(result.get(players[2])).toEqual(ColoredTeams.Red);
expect(result.get(players[3])).toEqual("kicked");
expect(result.get(players[4])).toEqual(Team.Blue);
expect(result.get(players[5])).toEqual(Team.Blue);
expect(result.get(players[4])).toEqual(ColoredTeams.Blue);
expect(result.get(players[5])).toEqual(ColoredTeams.Blue);
});
it("should handle empty player list", () => {
@@ -98,7 +98,7 @@ describe("assignTeams", () => {
it("should handle single player", () => {
const players = [createPlayer("1")];
const result = assignTeams(players, teams);
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
});
it("should handle multiple clans with different sizes", () => {
@@ -114,12 +114,12 @@ describe("assignTeams", () => {
const result = assignTeams(players, teams);
// Check that larger clans are assigned first
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Red);
expect(result.get(players[2])).toEqual(Team.Red);
expect(result.get(players[3])).toEqual(Team.Blue);
expect(result.get(players[4])).toEqual(Team.Blue);
expect(result.get(players[5])).toEqual(Team.Blue);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Red);
expect(result.get(players[2])).toEqual(ColoredTeams.Red);
expect(result.get(players[3])).toEqual(ColoredTeams.Blue);
expect(result.get(players[4])).toEqual(ColoredTeams.Blue);
expect(result.get(players[5])).toEqual(ColoredTeams.Blue);
});
it("should distribute players among a larger number of teams", () => {
@@ -141,28 +141,28 @@ describe("assignTeams", () => {
];
const result = assignTeams(players, [
Team.Red,
Team.Blue,
Team.Teal,
Team.Purple,
Team.Yellow,
Team.Orange,
Team.Green,
ColoredTeams.Red,
ColoredTeams.Blue,
ColoredTeams.Teal,
ColoredTeams.Purple,
ColoredTeams.Yellow,
ColoredTeams.Orange,
ColoredTeams.Green,
]);
expect(result.get(players[0])).toEqual(Team.Red);
expect(result.get(players[1])).toEqual(Team.Red);
expect(result.get(players[0])).toEqual(ColoredTeams.Red);
expect(result.get(players[1])).toEqual(ColoredTeams.Red);
expect(result.get(players[2])).toEqual("kicked");
expect(result.get(players[3])).toEqual(Team.Blue);
expect(result.get(players[4])).toEqual(Team.Blue);
expect(result.get(players[5])).toEqual(Team.Teal);
expect(result.get(players[6])).toEqual(Team.Purple);
expect(result.get(players[7])).toEqual(Team.Yellow);
expect(result.get(players[8])).toEqual(Team.Orange);
expect(result.get(players[9])).toEqual(Team.Green);
expect(result.get(players[10])).toEqual(Team.Teal);
expect(result.get(players[11])).toEqual(Team.Purple);
expect(result.get(players[12])).toEqual(Team.Yellow);
expect(result.get(players[13])).toEqual(Team.Orange);
expect(result.get(players[3])).toEqual(ColoredTeams.Blue);
expect(result.get(players[4])).toEqual(ColoredTeams.Blue);
expect(result.get(players[5])).toEqual(ColoredTeams.Teal);
expect(result.get(players[6])).toEqual(ColoredTeams.Purple);
expect(result.get(players[7])).toEqual(ColoredTeams.Yellow);
expect(result.get(players[8])).toEqual(ColoredTeams.Orange);
expect(result.get(players[9])).toEqual(ColoredTeams.Green);
expect(result.get(players[10])).toEqual(ColoredTeams.Teal);
expect(result.get(players[11])).toEqual(ColoredTeams.Purple);
expect(result.get(players[12])).toEqual(ColoredTeams.Yellow);
expect(result.get(players[13])).toEqual(ColoredTeams.Orange);
});
});
+1 -2
View File
@@ -18,7 +18,6 @@ export async function setup(mapName: string, _gameConfig: GameConfig = {}) {
const miniGameMap = await genTerrainFromBin(
String.fromCharCode.apply(null, miniMap),
);
const nationMap = { nations: [] };
// Configure the game
const serverConfig = new TestServerConfig();
@@ -36,5 +35,5 @@ export async function setup(mapName: string, _gameConfig: GameConfig = {}) {
const config = new TestConfig(serverConfig, gameConfig, new UserSettings());
// Create and return the game
return createGame([], gameMap, miniGameMap, nationMap, config); // TODO: !!!
return createGame([], [], gameMap, miniGameMap, config);
}