diff --git a/apps/server/src/game/engine.ts b/apps/server/src/game/engine.ts index 7b9771f..2aedfce 100644 --- a/apps/server/src/game/engine.ts +++ b/apps/server/src/game/engine.ts @@ -127,26 +127,13 @@ export class GameEngine implements RoomGameController { } this.room.status = "in_round" this.broadcastState() - // L'identité du DJ (= contributeur du titre) est privée : seul le DJ la - // reçoit, sinon "qui l'a ajouté" deviendrait trivial. - const base = { + this.io.to(this.room.code).emit("round:start", { type: round.type, + djId: this.runtime.djId ?? undefined, startsAt: startedAt, endsAt, payload: start.payload, - } - const djSocketId = this.runtime.djId - ? this.room.players.get(this.runtime.djId)?.socketId - : null - if (djSocketId) { - this.io.to(djSocketId).emit("round:start", { - ...base, - djId: this.runtime.djId ?? undefined, - }) - this.io.to(this.room.code).except(djSocketId).emit("round:start", base) - } else { - this.io.to(this.room.code).emit("round:start", base) - } + }) // Attend la première condition de fin : timer écoulé OU tous votés. await new Promise((resolve) => { @@ -190,11 +177,10 @@ export class GameEngine implements RoomGameController { } } - /** Votants éligibles : joueurs connectés, hors DJ (= contributeur, ne vote pas). */ + /** Votants éligibles : tous les joueurs connectés (le DJ neutre vote aussi). */ private eligibleVoters(): string[] { - const djId = this.runtime?.djId return [...this.room.players.values()] - .filter((p) => p.connected && p.id !== djId) + .filter((p) => p.connected) .map((p) => p.id) } diff --git a/apps/server/src/game/modes/blindtest/blindtest-round.test.ts b/apps/server/src/game/modes/blindtest/blindtest-round.test.ts index ef6f599..cdeb1ee 100644 --- a/apps/server/src/game/modes/blindtest/blindtest-round.test.ts +++ b/apps/server/src/game/modes/blindtest/blindtest-round.test.ts @@ -44,16 +44,16 @@ describe("match", () => { }) describe("BlindtestRound", () => { - test("start : le DJ est le contributeur du titre", () => { + test("start : le DJ est neutre (jamais le contributeur)", () => { const rooms = new RoomManager() const { room, player: a } = rooms.create("Alice", "sa") - rooms.join(room.code, "Bob", "sb") + const { player: b } = rooms.join(room.code, "Bob", "sb") room.blindtestTracks = [track(a.id)] prepareBlindtestForRoom(room) const round = new BlindtestRound() const start = round.start(room) - expect(start.djId).toBe(a.id) // Alice a ajouté le titre → elle est DJ + expect(start.djId).toBe(b.id) // Bob (pas Alice, la contributrice) expect(start.payload).toEqual({ trackId: "t1", youtubeId: "abc12345678" }) }) @@ -67,7 +67,7 @@ describe("BlindtestRound", () => { const ctx = ctxFor(room, t, "who_added") round.submitAnswer(ctx, b.id, { guessedPlayerId: c.id }) // faux round.submitAnswer(ctx, c.id, { guessedPlayerId: a.id }) // juste - round.submitAnswer(ctx, a.id, { guessedPlayerId: b.id }) // ignoré (contributrice) + round.submitAnswer(ctx, a.id, { guessedPlayerId: b.id }) // contributrice : non scorée const deltas = round.score(ctx) // Carol devine juste → 100 ; Alice trompe Bob (1 mauvaise devinette) → 50 expect(deltas).toContainEqual({ playerId: c.id, delta: 100 }) diff --git a/apps/server/src/game/modes/blindtest/blindtest-round.ts b/apps/server/src/game/modes/blindtest/blindtest-round.ts index 17e504f..14b61ef 100644 --- a/apps/server/src/game/modes/blindtest/blindtest-round.ts +++ b/apps/server/src/game/modes/blindtest/blindtest-round.ts @@ -26,6 +26,17 @@ interface BlindtestRoundData { track: BlindtestTrack } +/** Tire un DJ neutre : un joueur connecté qui n'a pas soumis ce titre. */ +function pickDj(room: ServerRoom, submittedBy: string): string | null { + const eligible = [...room.players.values()].filter( + (p) => p.connected && p.id !== submittedBy + ) + if (eligible.length === 0) { + return null + } + return eligible[Math.floor(Math.random() * eligible.length)].id +} + /** Résultat d'un joueur VOTANT (≠ contributeur) selon le mode. */ function voterResult( mode: BlindtestMode, @@ -60,8 +71,8 @@ export class BlindtestRound implements GameRound { start(room: ServerRoom): RoundStart { const track = takeTrack(room) - // Le DJ EST le contributeur : il lance/pilote son propre titre. - const djId = track ? track.submittedBy : null + // DJ neutre (jamais le contributeur) : il pilote la lecture et vote à l'aveugle. + const djId = track ? pickDj(room, track.submittedBy) : null const payload: BlindtestRoundPayload | null = track ? { trackId: track.id, youtubeId: track.youtubeId } : null @@ -69,9 +80,9 @@ export class BlindtestRound implements GameRound { } submitAnswer(ctx: RoundContext, playerId: string, answer: Answer): void { - const { track } = ctx.data as BlindtestRoundData - // Le contributeur (DJ) ne vote pas ; premier vote verrouillé pour les autres. - if (playerId === track.submittedBy || ctx.votes.has(playerId)) { + // Premier vote verrouillé (idempotent). Le contributeur peut voter mais son + // vote ne lui rapporte rien (voir buildResults) ; ça évite de bloquer la fin. + if (ctx.votes.has(playerId)) { return } ctx.votes.set(playerId, answer) diff --git a/apps/web/src/components/blindtest-view.tsx b/apps/web/src/components/blindtest-view.tsx index 8925185..5354eab 100644 --- a/apps/web/src/components/blindtest-view.tsx +++ b/apps/web/src/components/blindtest-view.tsx @@ -102,7 +102,7 @@ export function BlindtestView({ snapshot }: { snapshot: RoomSnapshot }) { )} - {!isDj && !showReveal && ( + {!showReveal && ( - Tu es le DJ 🎧 — c'est ton titre, fais-le deviner 🤫 + Tu es le DJ 🎧 — pilote la lecture (et vote comme les autres)