feature/round-recap #11
5 changed files with 121 additions and 41 deletions
|
|
@ -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): {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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) -----------------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue