From c01d5eef041a7350cc6717592c3fd354371f6bd8 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Fri, 23 Jan 2026 17:04:39 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B0=B8=EA=B0=80=EC=9E=90=20=EC=8B=9C?= =?UTF-8?q?=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=B2=A0=ED=8C=85=20=EC=9A=94=EC=95=BD?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-api/src/router/tournament/index.ts | 88 +++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/app/game-api/src/router/tournament/index.ts b/app/game-api/src/router/tournament/index.ts index 420dc4c..1ac3221 100644 --- a/app/game-api/src/router/tournament/index.ts +++ b/app/game-api/src/router/tournament/index.ts @@ -1,6 +1,7 @@ import { TRPCError } from '@trpc/server'; import { z } from 'zod'; +import { asRecord } from '@sammo-ts/common'; import type { TournamentType } from '@sammo-ts/logic'; import { TournamentStore } from '../../tournament/store.js'; @@ -64,6 +65,12 @@ const zBetEntry = z.object({ amount: z.number().int().positive(), }); +const zSeedParticipants = z.object({ + generalIds: z.array(z.number().int().positive()).optional(), + limit: z.number().int().min(1).max(256).optional(), + includeNpc: z.boolean().optional(), +}); + export const tournamentRouter = router({ getState: procedure.query(async ({ ctx }) => { const store = new TournamentStore(ctx.redis, buildTournamentKeys(ctx.profile.name)); @@ -116,6 +123,87 @@ export const tournamentRouter = router({ await store.setBettingEntries(input); return { ok: true, count: input.length }; }), + seedParticipants: adminProcedure.input(zSeedParticipants).mutation(async ({ ctx, input }) => { + const limit = input.limit ?? 64; + const includeNpc = input.includeNpc ?? true; + const store = new TournamentStore(ctx.redis, buildTournamentKeys(ctx.profile.name)); + + const generals = input.generalIds && input.generalIds.length > 0 + ? await ctx.db.general.findMany({ + where: { id: { in: input.generalIds } }, + select: { id: true, name: true, leadership: true, strength: true, intel: true, meta: true }, + }) + : await ctx.db.general.findMany({ + where: includeNpc ? {} : { npcState: 0 }, + orderBy: [ + { leadership: 'desc' }, + { strength: 'desc' }, + { intel: 'desc' }, + { id: 'asc' }, + ], + take: limit, + select: { id: true, name: true, leadership: true, strength: true, intel: true, meta: true }, + }); + + const participants = generals.map((general) => { + const meta = asRecord(general.meta); + const level = typeof meta.explevel === 'number' ? meta.explevel : 0; + return { + id: general.id, + name: general.name, + leadership: general.leadership, + strength: general.strength, + intel: general.intel, + level, + }; + }); + + await store.setParticipants(participants); + return { ok: true, count: participants.length }; + }), + getBettingSummary: authedProcedure.query(async ({ ctx }) => { + const store = new TournamentStore(ctx.redis, buildTournamentKeys(ctx.profile.name)); + const [state, entries, matches] = await Promise.all([ + store.getState(), + store.getBettingEntries(), + store.getMatches(), + ]); + + if (!state || state.stage < 5) { + return { state, totals: {}, myTotals: {}, totalAmount: 0, myAmount: 0 }; + } + + const candidateIds = new Set(); + for (const match of matches) { + if (match.stage === 7) { + candidateIds.add(match.attackerId); + candidateIds.add(match.defenderId); + } + } + + const totals: Record = {}; + const myTotals: Record = {}; + let totalAmount = 0; + let myAmount = 0; + const userId = ctx.auth?.user.id; + const general = userId + ? await ctx.db.general.findFirst({ where: { userId }, select: { id: true } }) + : null; + + for (const entry of entries) { + if (!candidateIds.has(entry.targetId)) { + continue; + } + totals[entry.targetId] = (totals[entry.targetId] ?? 0) + entry.amount; + totalAmount += entry.amount; + if (general && entry.generalId === general.id) { + myTotals[entry.targetId] = (myTotals[entry.targetId] ?? 0) + entry.amount; + myAmount += entry.amount; + } + } + + return { state, totals, myTotals, totalAmount, myAmount }; + }), placeBet: authedProcedure .input( z.object({