From f466e2505838386f976e8f1f851dc6bf7fe6eaec Mon Sep 17 00:00:00 2001 From: Hide_D Date: Fri, 23 Jan 2026 17:49:47 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=B0=B8=EA=B0=80=20=EC=8B=A0=EC=B2=AD?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-api/src/router/tournament/index.ts | 44 ++++++++++++++ app/game-api/src/tournament/worker.ts | 60 +------------------ .../src/views/TournamentView.vue | 2 +- 3 files changed, 46 insertions(+), 60 deletions(-) diff --git a/app/game-api/src/router/tournament/index.ts b/app/game-api/src/router/tournament/index.ts index 7944e58..3e2d08f 100644 --- a/app/game-api/src/router/tournament/index.ts +++ b/app/game-api/src/router/tournament/index.ts @@ -223,6 +223,50 @@ export const tournamentRouter = router({ return { state, totals, myTotals, totalAmount, myAmount }; }), + join: authedProcedure.mutation(async ({ ctx }) => { + const userId = ctx.auth?.user.id; + if (!userId) { + throw new TRPCError({ code: 'UNAUTHORIZED' }); + } + const store = new TournamentStore(ctx.redis, buildTournamentKeys(ctx.profile.name)); + const state = await store.getState(); + if (!state || state.stage !== 1) { + throw new TRPCError({ code: 'BAD_REQUEST', message: '참가 신청 기간이 아닙니다.' }); + } + + const participants = await store.getParticipants(); + + const general = await ctx.db.general.findFirst({ + where: { userId }, + select: { id: true, name: true, leadership: true, strength: true, intel: true, meta: true }, + }); + if (!general) { + throw new TRPCError({ code: 'NOT_FOUND', message: '장수 정보를 찾을 수 없습니다.' }); + } + + const already = participants.find((entry) => entry.id === general.id); + if (already) { + return { ok: true, count: participants.length }; + } + + if (participants.length >= 64) { + throw new TRPCError({ code: 'BAD_REQUEST', message: '참가 인원이 가득 찼습니다.' }); + } + + const meta = asRecord(general.meta); + const level = typeof meta.explevel === 'number' ? meta.explevel : 0; + const next = participants.concat({ + id: general.id, + name: general.name, + leadership: general.leadership, + strength: general.strength, + intel: general.intel, + level, + }); + + await store.setParticipants(next); + return { ok: true, count: next.length }; + }), placeBet: authedProcedure .input( z.object({ diff --git a/app/game-api/src/tournament/worker.ts b/app/game-api/src/tournament/worker.ts index 62ecbf4..93bbf27 100644 --- a/app/game-api/src/tournament/worker.ts +++ b/app/game-api/src/tournament/worker.ts @@ -1,4 +1,4 @@ -import { asRecord, createTournamentRng } from '@sammo-ts/common'; +import { createTournamentRng } from '@sammo-ts/common'; import { resolveTournamentBattle, TournamentType } from '@sammo-ts/logic'; import { createGamePostgresConnector, @@ -419,61 +419,6 @@ const applyPreBattleStage = async ( return state; }; -const resolveParticipantOrder = (type: TournamentType): Array< - { leadership: 'desc' } | { strength: 'desc' } | { intel: 'desc' } | { id: 'asc' } -> => { - switch (type) { - case TournamentType.LEADERSHIP: - return [{ leadership: 'desc' }, { id: 'asc' }]; - case TournamentType.STRENGTH: - return [{ strength: 'desc' }, { id: 'asc' }]; - case TournamentType.INTEL: - return [{ intel: 'desc' }, { id: 'asc' }]; - case TournamentType.TOTAL: - default: - return [{ leadership: 'desc' }, { strength: 'desc' }, { intel: 'desc' }, { id: 'asc' }]; - } -}; - -const ensureParticipants = async ( - store: TournamentStore, - prisma: ReturnType['prisma'], - state: TournamentState, - limit: number -): Promise => { - const participants = await store.getParticipants(); - if (participants.length > 0) { - return; - } - - const generals = await prisma.general.findMany({ - orderBy: resolveParticipantOrder(state.type), - take: limit, - select: { - id: true, - name: true, - leadership: true, - strength: true, - intel: true, - meta: true, - }, - }); - - const snapshot = 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(snapshot); -}; export const runTournamentWorker = async (): Promise => { const config = resolveGameApiConfigFromEnv(); @@ -514,9 +459,6 @@ export const runTournamentWorker = async (): Promise => { const worldState = await postgres.prisma.worldState.findFirst(); const baseSeed = (worldState?.meta as Record | null)?.hiddenSeed ?? 'tournament'; let nextState = state; - if (isPreBattleStage(state.stage) && (state.stage === 1 || state.stage === 3 || state.stage === 4)) { - await ensureParticipants(store, postgres.prisma, state, 64); - } if (isBattleStage(state.stage)) { nextState = await applyBattle(store, state, String(baseSeed)); } else if (isPreBattleStage(state.stage)) { diff --git a/app/game-frontend/src/views/TournamentView.vue b/app/game-frontend/src/views/TournamentView.vue index edb9042..ef58b25 100644 --- a/app/game-frontend/src/views/TournamentView.vue +++ b/app/game-frontend/src/views/TournamentView.vue @@ -255,7 +255,7 @@ const placeBet = async () => { const toggleJoin = async () => { try { - await trpc.general.setMySetting.mutate({ tnmt: 1 }); + await trpc.tournament.join.mutate(); lastActionMessage.value = '참가 신청이 반영되었습니다.'; await loadTournament(); } catch (err) {