diff --git a/app/game-api/src/router/tournament/index.ts b/app/game-api/src/router/tournament/index.ts index 181e2cfc..9aa986e9 100644 --- a/app/game-api/src/router/tournament/index.ts +++ b/app/game-api/src/router/tournament/index.ts @@ -71,6 +71,13 @@ const zParticipant = z.object({ gl: z.number().int().optional(), seedRank: z.number().int().optional(), finalRank: z.number().int().optional(), + preliminaryGroupId: z.number().int().min(0).max(7).optional(), + preliminaryGroupNo: z.number().int().min(0).max(7).optional(), + preliminaryRank: z.number().int().min(1).max(8).optional(), + preliminaryWin: z.number().int().min(0).optional(), + preliminaryDraw: z.number().int().min(0).optional(), + preliminaryLose: z.number().int().min(0).optional(), + preliminaryGl: z.number().int().optional(), }); const zMatch = z.object({ diff --git a/app/game-api/src/tournament/types.ts b/app/game-api/src/tournament/types.ts index e05e478e..c0a4ab9c 100644 --- a/app/game-api/src/tournament/types.ts +++ b/app/game-api/src/tournament/types.ts @@ -34,6 +34,13 @@ export interface TournamentParticipantEntry { gl?: number; seedRank?: number; finalRank?: number; + preliminaryGroupId?: number; + preliminaryGroupNo?: number; + preliminaryRank?: number; + preliminaryWin?: number; + preliminaryDraw?: number; + preliminaryLose?: number; + preliminaryGl?: number; } export interface TournamentMatchEntry { diff --git a/app/game-api/src/tournament/worker.ts b/app/game-api/src/tournament/worker.ts index 7fb6c2fd..5ee85766 100644 --- a/app/game-api/src/tournament/worker.ts +++ b/app/game-api/src/tournament/worker.ts @@ -186,6 +186,8 @@ export const applyPreBattleStage = async ( gl: 0, seedRank: 0, finalRank: 0, + preliminaryGroupId: entry.groupId, + preliminaryGroupNo: entry.groupNo, })); await store.setParticipants(grouped); const nextState: TournamentState = { @@ -244,10 +246,17 @@ export const applyPreBattleStage = async ( for (let groupId = 0; groupId < 8; groupId += 1) { const groupEntries = ranked.filter((entry) => entry.groupId === groupId); const ordered = sortByRanking(groupEntries); - ordered.slice(0, 4).forEach((entry, idx) => { + ordered.forEach((entry, idx) => { const target = ranked.find((item) => item.id === entry.id); if (target) { - target.seedRank = idx + 1; + target.preliminaryGroupId = groupId; + target.preliminaryGroupNo = entry.groupNo; + target.preliminaryRank = idx + 1; + target.preliminaryWin = entry.win ?? 0; + target.preliminaryDraw = entry.draw ?? 0; + target.preliminaryLose = entry.lose ?? 0; + target.preliminaryGl = entry.gl ?? 0; + target.seedRank = idx < 4 ? idx + 1 : 0; } }); } diff --git a/app/game-api/src/tournament/workerHelpers.ts b/app/game-api/src/tournament/workerHelpers.ts index a10400cc..7d71272a 100644 --- a/app/game-api/src/tournament/workerHelpers.ts +++ b/app/game-api/src/tournament/workerHelpers.ts @@ -195,17 +195,20 @@ export const assignManualApplicantGroup = (options: { extraSeed: `manual-group:${options.current.map((entry) => entry.id).join('-')}:${openGroupIds.join('-')}`, }); const groupId = rng.choice(openGroupIds); + const groupNo = groupCounts[groupId] ?? 0; return { ...options.applicant, groupId, - groupNo: groupCounts[groupId] ?? 0, + groupNo, win: 0, draw: 0, lose: 0, gl: 0, seedRank: 0, finalRank: 0, + preliminaryGroupId: groupId, + preliminaryGroupNo: groupNo, }; }; diff --git a/app/game-api/test/tournamentRouter.test.ts b/app/game-api/test/tournamentRouter.test.ts index 3b7a8148..b3bd1b4b 100644 --- a/app/game-api/test/tournamentRouter.test.ts +++ b/app/game-api/test/tournamentRouter.test.ts @@ -249,6 +249,7 @@ describe('tournament router permissions and mutations', () => { expect(results.filter((result) => result.status === 'rejected')).toHaveLength(1); const summary = await caller.tournament.getBettingSummary(); expect(summary.myAmount).toBe(600); + expect(Object.values(summary.myTotals)).toEqual([600]); expect(summary.totalAmount).toBe(600); expect(transport.gold.get(general.id)).toBe(2_400); }); diff --git a/app/game-api/test/tournamentWorker.test.ts b/app/game-api/test/tournamentWorker.test.ts index a0ce15b5..1b779111 100644 --- a/app/game-api/test/tournamentWorker.test.ts +++ b/app/game-api/test/tournamentWorker.test.ts @@ -246,8 +246,9 @@ describe('tournament worker schedule compatibility', () => { groupNo, }; }); - const groupCounts = Array.from({ length: 8 }, (_, groupId) => - current.filter((entry) => entry.groupId === groupId).length + const groupCounts = Array.from( + { length: 8 }, + (_, groupId) => current.filter((entry) => entry.groupId === groupId).length ); const openGroupId = groupCounts.findIndex((count) => count === 7); expect(openGroupId).toBeGreaterThanOrEqual(0); @@ -267,7 +268,16 @@ describe('tournament worker schedule compatibility', () => { }, }); - expect(applicant).toMatchObject({ groupId: openGroupId, groupNo: 7, win: 0, draw: 0, lose: 0, gl: 0 }); + expect(applicant).toMatchObject({ + groupId: openGroupId, + groupNo: 7, + preliminaryGroupId: openGroupId, + preliminaryGroupNo: 7, + win: 0, + draw: 0, + lose: 0, + gl: 0, + }); }); it('catches up from the stored schedule instead of discarding elapsed legacy phases', () => { @@ -334,6 +344,7 @@ describe('tournament worker (in-memory)', () => { expect(entries.map((entry) => entry.groupNo).sort((a, b) => Number(a) - Number(b))).toEqual([ 0, 1, 2, 3, 4, 5, 6, 7, ]); + expect(entries.every((entry) => entry.preliminaryGroupId === groupId)).toBe(true); } }); @@ -648,14 +659,30 @@ describe('tournament worker (in-memory)', () => { expect(participants.find((entry) => entry.id === 1)).toMatchObject({ groupId: expect.any(Number) }); expect(participants.find((entry) => entry.id === 1001)).toMatchObject({ groupId: expect.any(Number) }); expect( - Array.from({ length: 8 }, (_, groupId) => - participants.filter((entry) => entry.groupId === groupId).length - ) + Array.from({ length: 8 }, (_, groupId) => participants.filter((entry) => entry.groupId === groupId).length) ).toEqual(Array.from({ length: 8 }, () => 8)); await store.setState(afterJoin); const finalState = await runTournamentToCompletion({ store, prisma, baseSeed: 'seed' }); + const finalParticipants = await store.getParticipants(); expect(finalState.stage).toBe(0); expect(finalState.winnerId).toBeDefined(); + for (let groupId = 0; groupId < 8; groupId += 1) { + const preliminaryEntries = finalParticipants + .filter((entry) => entry.preliminaryGroupId === groupId) + .sort((lhs, rhs) => (lhs.preliminaryRank ?? 99) - (rhs.preliminaryRank ?? 99)); + expect(preliminaryEntries).toHaveLength(8); + expect(preliminaryEntries.map((entry) => entry.preliminaryRank)).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + expect( + preliminaryEntries.every( + (entry) => + entry.preliminaryGroupNo !== undefined && + entry.preliminaryWin !== undefined && + entry.preliminaryDraw !== undefined && + entry.preliminaryLose !== undefined && + entry.preliminaryGl !== undefined + ) + ).toBe(true); + } }); });