mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
sort teams based on clans (#381)
## Description: Teams are not sorted based on clans, clan members always stay on the same team. ## 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>
This commit is contained in:
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Jest Tests",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run-script", "test"],
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import { UnitGrid } from "./UnitGrid";
|
||||
import { StatsImpl } from "./StatsImpl";
|
||||
import { Stats } from "./Stats";
|
||||
import { simpleHash } from "../Util";
|
||||
import { assignTeams } from "./TeamAssignment";
|
||||
|
||||
export function createGame(
|
||||
humans: PlayerInfo[],
|
||||
@@ -89,7 +90,7 @@ export class GameImpl implements Game {
|
||||
nationMap: NationMap,
|
||||
private _config: Config,
|
||||
) {
|
||||
this._humans.forEach((p) => this.addPlayer(p, 100));
|
||||
this.addHumans();
|
||||
this._terraNullius = new TerraNulliusImpl();
|
||||
this._width = _map.width();
|
||||
this._height = _map.height();
|
||||
@@ -104,6 +105,22 @@ export class GameImpl implements Game {
|
||||
);
|
||||
this.unitGrid = new UnitGrid(this._map);
|
||||
}
|
||||
|
||||
private addHumans() {
|
||||
if (this.config().gameConfig().gameMode != GameMode.Team) {
|
||||
this._humans.forEach((p) => this.addPlayer(p, 0));
|
||||
return;
|
||||
}
|
||||
const playerToTeam = assignTeams(this._humans);
|
||||
for (const [playerInfo, team] of playerToTeam.entries()) {
|
||||
if (team == "kicked") {
|
||||
console.warn(`Player ${playerInfo.name} was kicked from team`);
|
||||
continue;
|
||||
}
|
||||
this.addPlayer(playerInfo, 0, team);
|
||||
}
|
||||
}
|
||||
|
||||
isOnEdgeOfMap(ref: TileRef): boolean {
|
||||
return this._map.isOnEdgeOfMap(ref);
|
||||
}
|
||||
@@ -330,13 +347,17 @@ export class GameImpl implements Game {
|
||||
return this.player(id);
|
||||
}
|
||||
|
||||
addPlayer(playerInfo: PlayerInfo, manpower: number): Player {
|
||||
addPlayer(
|
||||
playerInfo: PlayerInfo,
|
||||
manpower: number,
|
||||
team: Team = null,
|
||||
): Player {
|
||||
const player = new PlayerImpl(
|
||||
this,
|
||||
this.nextPlayerID,
|
||||
playerInfo,
|
||||
manpower,
|
||||
this.maybeAssignTeam(playerInfo),
|
||||
team ?? this.maybeAssignTeam(playerInfo),
|
||||
);
|
||||
this._playersBySmallID.push(player);
|
||||
this.nextPlayerID++;
|
||||
|
||||
@@ -573,7 +573,7 @@ export class PlayerImpl implements Player {
|
||||
if (this.team() == null || other.team() == null) {
|
||||
return false;
|
||||
}
|
||||
return this._team == other.team();
|
||||
return this._team.name == other.team().name;
|
||||
}
|
||||
|
||||
isFriendly(other: Player): boolean {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Player, PlayerInfo, Team, TeamName } from "./Game";
|
||||
|
||||
export function assignTeams(
|
||||
players: PlayerInfo[],
|
||||
): Map<PlayerInfo, Team | "kicked"> {
|
||||
const result = new Map<PlayerInfo, Team | "kicked">();
|
||||
let redTeamCount = 0;
|
||||
let blueTeamCount = 0;
|
||||
|
||||
// Group players by clan
|
||||
const clanGroups = new Map<string, PlayerInfo[]>();
|
||||
const noClanPlayers: PlayerInfo[] = [];
|
||||
|
||||
// Sort players into clan groups or no-clan list
|
||||
for (const player of players) {
|
||||
if (player.clan) {
|
||||
if (!clanGroups.has(player.clan)) {
|
||||
clanGroups.set(player.clan, []);
|
||||
}
|
||||
clanGroups.get(player.clan)!.push(player);
|
||||
} else {
|
||||
noClanPlayers.push(player);
|
||||
}
|
||||
}
|
||||
|
||||
const maxTeamSize = Math.ceil(players.length / 2);
|
||||
|
||||
// Sort clans by size (largest first)
|
||||
const sortedClans = Array.from(clanGroups.entries()).sort(
|
||||
(a, b) => b[1].length - a[1].length,
|
||||
);
|
||||
|
||||
// First, assign clan players
|
||||
for (const [_, clanPlayers] of sortedClans) {
|
||||
// Try to keep the clan together on the team with fewer players
|
||||
if (redTeamCount <= blueTeamCount) {
|
||||
// Assign to red team
|
||||
for (const player of clanPlayers) {
|
||||
if (redTeamCount < maxTeamSize) {
|
||||
redTeamCount++;
|
||||
result.set(player, { name: TeamName.Red });
|
||||
} else {
|
||||
result.set(player, "kicked");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Assign to blue team
|
||||
for (const player of clanPlayers) {
|
||||
if (blueTeamCount < maxTeamSize) {
|
||||
blueTeamCount++;
|
||||
result.set(player, { name: TeamName.Blue });
|
||||
} else {
|
||||
result.set(player, "kicked");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then, assign non-clan players to balance teams
|
||||
for (const player of noClanPlayers) {
|
||||
if (redTeamCount <= blueTeamCount) {
|
||||
redTeamCount++;
|
||||
result.set(player, { name: TeamName.Red });
|
||||
} else {
|
||||
blueTeamCount++;
|
||||
result.set(player, { name: TeamName.Blue });
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { TeamName, PlayerType, PlayerInfo } from "../src/core/game/Game";
|
||||
import { assignTeams } from "../src/core/game/TeamAssignment";
|
||||
|
||||
describe("assignTeams", () => {
|
||||
const createPlayer = (id: string, clan?: string): PlayerInfo => {
|
||||
const name = clan ? `[${clan}]Player ${id}` : `Player ${id}`;
|
||||
return new PlayerInfo(
|
||||
"🏳️", // flag
|
||||
name,
|
||||
PlayerType.Human,
|
||||
null, // clientID (null for testing)
|
||||
id,
|
||||
null, // nation (null for testing)
|
||||
);
|
||||
};
|
||||
|
||||
it("should assign players to teams when no clans are present", () => {
|
||||
const players = [
|
||||
createPlayer("1"),
|
||||
createPlayer("2"),
|
||||
createPlayer("3"),
|
||||
createPlayer("4"),
|
||||
];
|
||||
|
||||
const result = assignTeams(players);
|
||||
|
||||
// Check that players are assigned alternately
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[1])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[2])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[3])).toEqual({ name: TeamName.Blue });
|
||||
});
|
||||
|
||||
it("should keep clan members together on the same team", () => {
|
||||
const players = [
|
||||
createPlayer("1", "CLANA"),
|
||||
createPlayer("2", "CLANA"),
|
||||
createPlayer("3", "CLANB"),
|
||||
createPlayer("4", "CLANB"),
|
||||
];
|
||||
|
||||
const result = assignTeams(players);
|
||||
|
||||
// Check that clan members are on the same team
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[1])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[2])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[3])).toEqual({ name: TeamName.Blue });
|
||||
});
|
||||
|
||||
it("should handle mixed clan and non-clan players", () => {
|
||||
const players = [
|
||||
createPlayer("1", "CLANA"),
|
||||
createPlayer("2", "CLANA"),
|
||||
createPlayer("3"),
|
||||
createPlayer("4"),
|
||||
];
|
||||
|
||||
const result = assignTeams(players);
|
||||
|
||||
// Check that clan members are together and non-clan players balance teams
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[1])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[2])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[3])).toEqual({ name: TeamName.Blue });
|
||||
});
|
||||
|
||||
it("should kick players when teams are full", () => {
|
||||
const players = [
|
||||
createPlayer("1", "CLANA"),
|
||||
createPlayer("2", "CLANA"),
|
||||
createPlayer("3", "CLANA"),
|
||||
createPlayer("4", "CLANA"),
|
||||
createPlayer("5", "CLANB"),
|
||||
createPlayer("6", "CLANB"),
|
||||
];
|
||||
|
||||
const result = assignTeams(players);
|
||||
|
||||
// Check that players are kicked when teams are full
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[1])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[2])).toEqual({ name: TeamName.Red });
|
||||
|
||||
expect(result.get(players[3])).toEqual("kicked");
|
||||
|
||||
expect(result.get(players[4])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[5])).toEqual({ name: TeamName.Blue });
|
||||
});
|
||||
|
||||
it("should handle empty player list", () => {
|
||||
const result = assignTeams([]);
|
||||
expect(result.size).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle single player", () => {
|
||||
const players = [createPlayer("1")];
|
||||
const result = assignTeams(players);
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
});
|
||||
|
||||
it("should handle multiple clans with different sizes", () => {
|
||||
const players = [
|
||||
createPlayer("1", "CLANA"),
|
||||
createPlayer("2", "CLANA"),
|
||||
createPlayer("3", "CLANA"),
|
||||
createPlayer("4", "CLANB"),
|
||||
createPlayer("5", "CLANB"),
|
||||
createPlayer("6", "CLANC"),
|
||||
];
|
||||
|
||||
const result = assignTeams(players);
|
||||
|
||||
// Check that larger clans are assigned first
|
||||
expect(result.get(players[0])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[1])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[2])).toEqual({ name: TeamName.Red });
|
||||
expect(result.get(players[3])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[4])).toEqual({ name: TeamName.Blue });
|
||||
expect(result.get(players[5])).toEqual({ name: TeamName.Blue });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user