import { Ranking, RankType, } from "../src/client/components/baseComponents/ranking/GameInfoRanking"; import { Difficulty, GameMapSize, GameMapType, GameMode, GameType, } from "../src/core/game/Game"; import { AnalyticsRecord, GameConfig } from "../src/core/Schemas"; import { GOLD_INDEX_STEAL, GOLD_INDEX_TRADE, GOLD_INDEX_TRAIN_OTHER, GOLD_INDEX_TRAIN_SELF, GOLD_INDEX_WAR, } from "../src/core/StatsSchemas"; describe("Ranking class", () => { const mockConfig: GameConfig = { gameMap: GameMapType.Montreal, difficulty: Difficulty.Medium, donateGold: false, donateTroops: false, gameType: GameType.Public, gameMode: GameMode.FFA, gameMapSize: GameMapSize.Normal, nations: "disabled", bots: 0, infiniteGold: false, infiniteTroops: false, instantBuild: false, maxPlayers: 40, disabledUnits: [], randomSpawn: false, }; const gameTickDuration = 1000; const gameDuration = gameTickDuration / 10; function makeSession( overrides: Partial = {}, ): AnalyticsRecord { return { version: "v0.0.2", info: { duration: gameTickDuration, winner: ["player", "p2"], players: [ { clientID: "p1", username: "Alice", clanTag: "X", cosmetics: { flag: "USA" }, stats: { units: { port: [2n, 0n, 0n, 2n] }, conquests: [5n], gold: [0n, 100n, 20n, 0n, 15n, 5n], // total 140 bombs: { abomb: [1n], hbomb: [1n], mirv: [2n], }, }, persistentID: null, }, { clientID: "p2", username: "Bob", clanTag: null, stats: { units: { city: [2n, 0n, 0n, 2n] }, conquests: [8n], gold: [0n, 50n, 10n, 5n], // total 65, no train trade bombs: { abomb: [0n], hbomb: [2n], mirv: [0n], }, }, persistentID: null, }, { clientID: "p3", username: "Charlie", clanTag: null, stats: { // no units, but has conquests/killedAt to count as played conquests: [8n], killedAt: BigInt(600), gold: [0n, 10n, 2n, 10n, 0n, 5n], // total 27 bombs: {}, }, persistentID: null, }, ], gameID: "", lobbyCreatedAt: 0, config: { ...mockConfig }, start: 0, end: 0, num_turns: 0, lobbyFillTime: 0, }, gitCommit: "DEV", subdomain: "", domain: "", }; } test("summarizes players correctly", () => { const r = new Ranking(makeSession()); const players = r.sortedBy(RankType.ConquestHumans); expect(players.length).toBe(3); const p1 = players.find((p) => p.id === "p1")!; expect(p1.username).toBe("Alice"); expect(p1.flag).toBe("USA"); expect(p1.conquests).toStrictEqual([5n]); expect(p1.atoms).toBe(1); expect(p1.mirv).toBe(2); }); test("correctly identifies winner", () => { const r = new Ranking(makeSession()); const p2 = r.sortedBy(RankType.ConquestHumans).find((p) => p.id === "p2")!; expect(p2.winner).toBe(true); }); test("rank by total gold", () => { const r = new Ranking(makeSession()); const rankedPlayers = r.sortedBy(RankType.TotalGold); expect(rankedPlayers.length).toBe(3); expect(rankedPlayers[0].id).toBe("p1"); expect(rankedPlayers[1].id).toBe("p2"); expect(rankedPlayers[2].id).toBe("p3"); }); test("rank by stolen gold", () => { const r = new Ranking(makeSession()); const rankedPlayers = r.sortedBy(RankType.StolenGold); expect(rankedPlayers.length).toBe(3); expect(rankedPlayers[0].id).toBe("p3"); expect(rankedPlayers[1].id).toBe("p2"); expect(rankedPlayers[2].id).toBe("p1"); }); test("rank by hydros", () => { const r = new Ranking(makeSession()); const rankedPlayers = r.sortedBy(RankType.Hydros); expect(rankedPlayers.length).toBe(3); expect(rankedPlayers[0].id).toBe("p2"); expect(rankedPlayers[1].id).toBe("p1"); expect(rankedPlayers[2].id).toBe("p3"); }); test("lifetime score is percentage of duration", () => { const r = new Ranking(makeSession()); const p3 = r.sortedBy(RankType.ConquestHumans).find((p) => p.id === "p3")!; const expected = Number(BigInt(600)) / gameDuration; expect(r.score(p3, RankType.Lifetime)).toBe(expected); }); test("lifetime score gives 100 when alive", () => { const r = new Ranking(makeSession()); const p1 = r.allPlayers.find((p) => p.id === "p1")!; expect(r.score(p1, RankType.Lifetime)).toBe(100); }); test("winners should be ahead of players with same score", () => { const r = new Ranking(makeSession()); const sortedPlayers = r.sortedBy(RankType.ConquestHumans); expect(sortedPlayers[0].id).toBe("p2"); // p2 & p3 same score but winner first }); test("gold scores work correctly", () => { const r = new Ranking(makeSession()); const p1 = r.sortedBy(RankType.TotalGold).find((p) => p.id === "p1")!; expect(r.score(p1, RankType.StolenGold)).toBe( Number(p1.gold[GOLD_INDEX_STEAL] ?? 0n), ); expect(r.score(p1, RankType.NavalTrade)).toBe( Number(p1.gold[GOLD_INDEX_TRADE] ?? 0n), ); const ownTrain = p1.gold[GOLD_INDEX_TRAIN_SELF] ?? 0n; const otherTrain = p1.gold[GOLD_INDEX_TRAIN_OTHER] ?? 0n; expect(r.score(p1, RankType.TrainTrade)).toBe( Number(ownTrain + otherTrain), ); expect(r.score(p1, RankType.ConqueredGold)).toBe( Number(p1.gold[GOLD_INDEX_WAR] ?? 0n), ); }); });