Skip to content

Commit dd6fce8

Browse files
committed
прикрутил титры
1 parent f967d80 commit dd6fce8

8 files changed

Lines changed: 398 additions & 6 deletions

File tree

artifacts/api-server/src/lib/adepts-quiz-relay-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export type AdeptsQuizRelayPayload = {
3939
donationLog?: AdeptsDonationLogEntry[];
4040
/** После бонуса ×2 с «деда» на 400 — скрыть таблицу на квиз-доске 3; сброс при входе на похороны. */
4141
hideDonationsTableOnBoard3?: boolean;
42+
/** Титры (квиз-доска 3); обновляется только если поле присутствует в relay от доски 3. */
43+
creditsRollActive?: boolean;
44+
creditsRollStartedAt?: number;
4245
};
4346

4447
const THEME_COUNT = 8;

artifacts/api-server/src/lib/adepts-quiz-room-store.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,30 @@ export function setQuizRelayFull(sessionId: string, payload: AdeptsQuizRelayPayl
115115
if (typeof payload.hideDonationsTableOnBoard3 === "boolean") {
116116
base.hideDonationsTableOnBoard3 = payload.hideDonationsTableOnBoard3;
117117
}
118+
119+
/**
120+
* Титры только на квиз-доске 3: relay с доски 1 или 2 не несёт полей титров — сбрасываем,
121+
* чтобы при переходе между досками титры не оставались «включёнными» на сервере.
122+
*/
123+
if (incomingBoardId === 1 || incomingBoardId === 2) {
124+
base.creditsRollActive = false;
125+
delete base.creditsRollStartedAt;
126+
} else {
127+
const pExtra = payload as Record<string, unknown>;
128+
if ("creditsRollActive" in pExtra) {
129+
base.creditsRollActive = pExtra["creditsRollActive"] === true;
130+
if (!base.creditsRollActive) {
131+
delete base.creditsRollStartedAt;
132+
}
133+
}
134+
if (
135+
"creditsRollStartedAt" in pExtra &&
136+
typeof pExtra["creditsRollStartedAt"] === "number" &&
137+
Number.isFinite(pExtra["creditsRollStartedAt"])
138+
) {
139+
base.creditsRollStartedAt = Math.floor(pExtra["creditsRollStartedAt"] as number);
140+
}
141+
}
118142
// (lean relay, same board) → keep existing catalog intact so clients that connect later
119143
// still receive the most recently edited catalog for this board.
120144
}
2.84 MB
Binary file not shown.

artifacts/game-client/src/apps/adepts-game/hooks/useGameState.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export type GameState = {
5151
donationLog: DonationLogEntry[];
5252
/** После ×2 с деда на 400 — скрыть таблицу на 3-й доске; сброс при входе на похороны (сервер). */
5353
hideDonationsTableOnBoard3?: boolean;
54+
/** Титры после игры (только квиз-доска 3). */
55+
creditsRollActive?: boolean;
56+
creditsRollStartedAt?: number;
5457
};
5558

5659
const DEFAULT_PLAYERS: Player[] = Array.from({ length: 5 }, (_, i) => ({
@@ -86,6 +89,8 @@ const DEFAULT_CORE: Omit<GameState, "themes" | "questions"> = {
8689
boardRoom: undefined,
8790
donationLog: [...DEFAULT_DONATION_LOG],
8891
hideDonationsTableOnBoard3: false,
92+
creditsRollActive: false,
93+
creditsRollStartedAt: undefined,
8994
};
9095

9196
const PLAYERS_KEY = "adepts-shared-players";
@@ -384,6 +389,9 @@ function loadInitialState(boardId: AdeptsBoardId): GameState {
384389
typeof parsed.hideDonationsTableOnBoard3 === "boolean"
385390
? parsed.hideDonationsTableOnBoard3
386391
: false,
392+
/** Титры не восстанавливаем из localStorage — только по кнопке «Титры». */
393+
creditsRollActive: false,
394+
creditsRollStartedAt: undefined,
387395
});
388396
}
389397
} catch (err) {
@@ -541,6 +549,30 @@ export function useGameState(boardId: AdeptsBoardId) {
541549
const nextHideDonationsTableOnBoard3 =
542550
typeof rawHide === "boolean" ? rawHide : (prev.hideDonationsTableOnBoard3 ?? false);
543551

552+
const nextCredits =
553+
boardId !== 3
554+
? { creditsRollActive: false, creditsRollStartedAt: undefined as number | undefined }
555+
: boardMismatch
556+
? {
557+
/** Relay с другой доски (1/2): титры не переносим; сервер уже сбросил credits. */
558+
creditsRollActive: false,
559+
creditsRollStartedAt: undefined,
560+
}
561+
: (() => {
562+
const active = recToUse["creditsRollActive"] === true;
563+
const rawAt = recToUse["creditsRollStartedAt"];
564+
const at =
565+
typeof rawAt === "number" && Number.isFinite(rawAt)
566+
? Math.floor(rawAt)
567+
: active
568+
? prev.creditsRollStartedAt
569+
: undefined;
570+
return {
571+
creditsRollActive: active,
572+
creditsRollStartedAt: active ? at : undefined,
573+
};
574+
})();
575+
544576
let nextState = withClosedActiveQuizIfCellUsed(
545577
migrateCatalog(boardId, {
546578
...prev,
@@ -557,6 +589,8 @@ export function useGameState(boardId: AdeptsBoardId) {
557589
dataVersion:
558590
typeof recToUse["dataVersion"] === "number" ? recToUse["dataVersion"] : prev.dataVersion,
559591
hideDonationsTableOnBoard3: nextHideDonationsTableOnBoard3,
592+
creditsRollActive: nextCredits.creditsRollActive,
593+
creditsRollStartedAt: nextCredits.creditsRollStartedAt,
560594
})
561595
);
562596

@@ -608,7 +642,11 @@ export function useGameState(boardId: AdeptsBoardId) {
608642
}, [boardId, adeptsSocketKey]);
609643

610644
useEffect(() => {
611-
localStorage.setItem(rt.storageKey, JSON.stringify(state));
645+
const persisted: GameState =
646+
boardId === 3
647+
? { ...state, creditsRollActive: false, creditsRollStartedAt: undefined }
648+
: state;
649+
localStorage.setItem(rt.storageKey, JSON.stringify(persisted));
612650
localStorage.setItem(PLAYERS_KEY, JSON.stringify(state.players));
613651
localStorage.setItem(DONATION_LOG_KEY, JSON.stringify(state.donationLog));
614652

@@ -626,15 +664,20 @@ export function useGameState(boardId: AdeptsBoardId) {
626664
type: "hostQuizRelay",
627665
payload: buildAdeptsQuizRelayPayload(state, getAdeptsSessionId(), includeCatalog, boardId),
628666
});
629-
}, [state, catalogReady, rt.storageKey]);
667+
}, [state, catalogReady, rt.storageKey, boardId]);
630668

631669
useEffect(() => {
632670
const storageKey = rt.storageKey;
633671
const handler = (e: StorageEvent) => {
634672
if (e.key === storageKey && e.newValue) {
635673
try {
636674
skipEmitRef.current = true;
637-
setState(JSON.parse(e.newValue));
675+
const parsed = JSON.parse(e.newValue) as GameState;
676+
if (boardId === 3) {
677+
parsed.creditsRollActive = false;
678+
parsed.creditsRollStartedAt = undefined;
679+
}
680+
setState(parsed);
638681
} catch {}
639682
}
640683
if (e.key === PLAYERS_KEY && e.newValue) {
@@ -830,6 +873,18 @@ export function useGameState(boardId: AdeptsBoardId) {
830873
getAdeptsCommandSocket().emit("command", { type: "playerDonation", amount });
831874
}, []);
832875

876+
const setBoard3CreditsRoll = useCallback(
877+
(active: boolean) => {
878+
if (boardId !== 3 || !isHostRole()) return;
879+
setState((prev) => ({
880+
...prev,
881+
creditsRollActive: active,
882+
creditsRollStartedAt: active ? Date.now() : undefined,
883+
}));
884+
},
885+
[boardId],
886+
);
887+
833888
const emitPickCell = useCallback(
834889
(themeIndex: number, questionIndex: number, opts?: { turnSeat?: number }) => {
835890
/**
@@ -869,5 +924,6 @@ export function useGameState(boardId: AdeptsBoardId) {
869924
resetGame,
870925
emitPickCell,
871926
submitPlayerDonation,
927+
setBoard3CreditsRoll,
872928
};
873929
}

artifacts/game-client/src/apps/adepts-game/pages/Home.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { useRole } from "@/hooks/useRole";
1111
import { ChatPanel } from "@/components/ChatPanel";
1212
import { QuizBoardPandoraLottoOverlay } from "@/components/QuizBoardPandoraLottoOverlay";
1313
import { DonationsTable } from "@/components/DonationsTable";
14+
import { AdeptsCreditsRollOverlay } from "@/components/AdeptsCreditsRollOverlay";
15+
import { Button } from "@/components/ui/button";
1416
import { getAdeptsCommandSocket } from "@/lib/adeptsCommandSocket";
1517
import { getQuizNavSocket } from "@/hooks/quizNavSocket";
1618

@@ -65,6 +67,7 @@ export default function Home({ boardId }: { boardId: AdeptsBoardId }) {
6567
patchActiveQuizCard,
6668
setQuizBoardHoverCell,
6769
emitPickCell,
70+
setBoard3CreditsRoll,
6871
} = useGameState(boardId);
6972

7073
const handleAwardPoints = (playerIndex: number, points: number) => {
@@ -165,9 +168,22 @@ export default function Home({ boardId }: { boardId: AdeptsBoardId }) {
165168
<h1 className="font-display text-2xl tracking-wider text-primary glow-text">
166169
САМЫЙ ДУШНЫЙ 3.0
167170
</h1>
168-
<span className="adepts-quiz-badge text-sm font-display tracking-wider text-primary/80 border border-primary/40 px-3 py-1.5 rounded">
169-
{BADGE_LABEL[boardId]}
170-
</span>
171+
<div className="flex flex-wrap items-center gap-2">
172+
<span className="adepts-quiz-badge text-sm font-display tracking-wider text-primary/80 border border-primary/40 px-3 py-1.5 rounded">
173+
{BADGE_LABEL[boardId]}
174+
</span>
175+
{boardId === 3 && isHost ? (
176+
<Button
177+
type="button"
178+
variant="outline"
179+
size="sm"
180+
className="font-display text-xs uppercase tracking-wider border-primary/50 text-primary/90 hover:bg-primary/10"
181+
onClick={() => setBoard3CreditsRoll(true)}
182+
>
183+
Титры
184+
</Button>
185+
) : null}
186+
</div>
171187
<div className="ml-auto flex items-center gap-2">
172188
<QuizBoardReloadButton />
173189
{isHost && <GamePhaseNav />}
@@ -337,6 +353,15 @@ export default function Home({ boardId }: { boardId: AdeptsBoardId }) {
337353
)}
338354

339355
<QuizBoardPandoraLottoOverlay />
356+
357+
{boardId === 3 && state.creditsRollActive === true ? (
358+
<AdeptsCreditsRollOverlay
359+
open
360+
startedAt={state.creditsRollStartedAt}
361+
isHost={isHost}
362+
onHostClose={() => setBoard3CreditsRoll(false)}
363+
/>
364+
) : null}
340365
</div>
341366
);
342367
}

0 commit comments

Comments
 (0)