feature/round-recap #11

Merged
ayoub merged 4 commits from feature/round-recap into dev 2026-06-10 23:14:59 +00:00
5 changed files with 121 additions and 41 deletions
Showing only changes of commit f7ee2204d1 - Show all commits

View file

@ -46,7 +46,7 @@ const dummyRound: GameRound = {
} }
return deltas return deltas
}, },
recap: () => ({ label: "2+2 ?", answer: "4" }), recap: () => ({ label: "2+2 ?", answer: "4", answers: [] }),
} }
function setup(roundDurationSec: number): { function setup(roundDurationSec: number): {

View file

@ -160,13 +160,43 @@ export class BlindtestRound implements GameRound {
recap(ctx: RoundContext): RoundRecapInfo { recap(ctx: RoundContext): RoundRecapInfo {
const { track } = ctx.data as BlindtestRoundData const { track } = ctx.data as BlindtestRoundData
const mode = ctx.room.settings.blindtestMode
const submitter = ctx.room.players.get(track.submittedBy) const submitter = ctx.room.players.get(track.submittedBy)
const nameOf = (id?: string) =>
(id && ctx.room.players.get(id)?.name) || "?"
const answers = []
for (const [playerId, vote] of ctx.votes) {
if (playerId === track.submittedBy) {
continue
}
const a = vote as {
title?: string
artist?: string
guessedPlayerId?: string
}
let text = ""
if (mode === "who_added") {
text = nameOf(a.guessedPlayerId)
} else if (mode === "title_artist") {
text = `${a.title ?? ""} / ${a.artist ?? ""}`
} else {
text = `${a.title ?? ""} / ${a.artist ?? ""}${nameOf(a.guessedPlayerId)}`
}
answers.push({
playerId,
answer: text,
correct: voterResult(mode, vote, track).points > 0,
})
}
return { return {
label: "Blindtest", label: "Blindtest",
answer: `${track.title}${track.artist}`, answer: `${track.title}${track.artist}`,
youtubeId: track.youtubeId, youtubeId: track.youtubeId,
url: track.url, url: track.url,
addedBy: submitter?.name ?? "?", addedBy: submitter?.name ?? "?",
answers,
} }
} }
} }

View file

@ -152,12 +152,28 @@ export class QuizRound implements GameRound {
const answer = isTextFormat(question) const answer = isTextFormat(question)
? (question.acceptedAnswers?.[0] ?? "") ? (question.acceptedAnswers?.[0] ?? "")
: (question.choices?.[question.correctIndex ?? 0] ?? "") : (question.choices?.[question.correctIndex ?? 0] ?? "")
const answers = []
for (const player of ctx.room.players.values()) {
const vote = ctx.votes.get(player.id)
if (!vote) {
continue
}
const text = isTextFormat(question)
? (asText(vote) ?? "")
: (question.choices?.[asChoice(vote) ?? -1] ?? "?")
answers.push({
playerId: player.id,
answer: text,
correct: isCorrect(question, vote),
})
}
return { return {
label: question.prompt, label: question.prompt,
answer, answer,
category: question.category, category: question.category,
imageUrl: imageUrl:
question.format === "image_reveal" ? question.imageUrl : undefined, question.format === "image_reveal" ? question.imageUrl : undefined,
answers,
} }
} }
} }

View file

@ -332,7 +332,9 @@ function RoundsRecap({
<ListChecks className="size-4" /> Récapitulatif des manches ({recap.length}) <ListChecks className="size-4" /> Récapitulatif des manches ({recap.length})
</summary> </summary>
<ul className="flex flex-col gap-2 p-3 pt-0"> <ul className="flex flex-col gap-2 p-3 pt-0">
{recap.map((r) => ( {recap.map((r) => {
const pointsBy = new Map(r.scorers.map((s) => [s.playerId, s.points]))
return (
<li key={r.index} className="bg-muted/40 flex gap-3 rounded-lg p-2"> <li key={r.index} className="bg-muted/40 flex gap-3 rounded-lg p-2">
{r.youtubeId ? ( {r.youtubeId ? (
<img <img
@ -349,7 +351,8 @@ function RoundsRecap({
) : null} ) : null}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="text-muted-foreground text-[10px] uppercase"> <p className="text-muted-foreground text-[10px] uppercase">
{r.index + 1} · {r.category ?? (r.type === "blindtest" ? "Blindtest" : "Quiz")} {r.index + 1} ·{" "}
{r.category ?? (r.type === "blindtest" ? "Blindtest" : "Quiz")}
</p> </p>
{r.type !== "blindtest" && ( {r.type !== "blindtest" && (
<p className="line-clamp-1 text-xs">{r.label}</p> <p className="line-clamp-1 text-xs">{r.label}</p>
@ -363,16 +366,38 @@ function RoundsRecap({
</span> </span>
)} )}
</p> </p>
<p className="text-muted-foreground text-[10px]"> <ul className="mt-1 flex flex-col gap-0.5">
{r.scorers.length > 0 {r.answers.length === 0 ? (
? r.scorers <li className="text-muted-foreground text-[10px]">
.map((s) => `${nameOf(s.playerId)} +${s.points}`) Aucune réponse
.join(" · ") </li>
: "Personne n'a marqué"} ) : (
</p> r.answers.map((a) => (
<li key={a.playerId} className="text-[10px]">
<span className="text-muted-foreground">
{nameOf(a.playerId)} :{" "}
</span>
<span
className={
a.correct ? "text-green-500" : "text-red-400"
}
>
{a.answer || "—"}
</span>
{pointsBy.get(a.playerId) ? (
<span className="text-muted-foreground">
{" "}
+{pointsBy.get(a.playerId)}
</span>
) : null}
</li>
))
)}
</ul>
</div> </div>
</li> </li>
))} )
})}
</ul> </ul>
</details> </details>
) )

View file

@ -90,6 +90,13 @@ export interface RoundRecapScorer {
points: number points: number
} }
/** Réponse d'un joueur sur une manche (récap). */
export interface RoundPlayerAnswer {
playerId: string
answer: string
correct: boolean
}
/** Récapitulatif d'une manche jouée (affiché en fin de partie). */ /** Récapitulatif d'une manche jouée (affiché en fin de partie). */
export interface RoundRecap { export interface RoundRecap {
index: number index: number
@ -106,6 +113,8 @@ export interface RoundRecap {
addedBy?: string addedBy?: string
/** Joueurs ayant marqué sur cette manche. */ /** Joueurs ayant marqué sur cette manche. */
scorers: RoundRecapScorer[] scorers: RoundRecapScorer[]
/** Réponse de chaque joueur ayant répondu. */
answers: RoundPlayerAnswer[]
} }
// --- Snapshot diffusé au client (room:state) ----------------------------- // --- Snapshot diffusé au client (room:state) -----------------------------