improved social questions

This commit is contained in:
Daniel Bulant 2026-05-04 11:51:41 +02:00
parent 834db89c9e
commit e4e392de51
No known key found for this signature in database
4 changed files with 79 additions and 18 deletions

View file

@ -83,7 +83,11 @@ export type QuizState = {
}; };
export type PartySocketEvent = export type PartySocketEvent =
| { type: "party_status"; party: Party|null; members: PartyMemberWithUser[] } | {
type: "party_status";
party: Party | null;
members: PartyMemberWithUser[];
}
| { type: "member_payload"; fromUserId: string; payload: unknown } | { type: "member_payload"; fromUserId: string; payload: unknown }
| { type: "error"; message: string } | { type: "error"; message: string }
| { type: "pong" }; | { type: "pong" };

View file

@ -14,6 +14,7 @@ export type PartyAnalytics = {
name: string; name: string;
artists?: { name: string }[]; artists?: { name: string }[];
albumName?: string; albumName?: string;
memberScores?: { userId: string; score: number }[];
}[]; }[];
artists?: { name: string }[]; artists?: { name: string }[];
genres?: { name: string }[]; genres?: { name: string }[];
@ -95,12 +96,28 @@ export function getTopClusterArtists(analytics: PartyAnalytics): string[] {
); );
} }
export function getTopClusterTracks( export function getTopClusterTracks(analytics: PartyAnalytics): Array<{
analytics: PartyAnalytics, name: string;
): Array<{ name: string; artists?: { name: string }[]; albumName?: string }> { artists?: { name: string }[];
albumName?: string;
memberScores?: { userId: string; score: number }[];
}> {
return analytics?.storyClusters?.[0]?.tracks ?? []; return analytics?.storyClusters?.[0]?.tracks ?? [];
} }
export function getTopTrackListener(
track: { memberScores?: { userId: string; score: number }[] },
members: PartyQuestionMember[],
): PartyQuestionMember | null {
const topMember = (track.memberScores ?? [])
.slice()
.sort((a, b) => b.score - a.score)
.at(0);
if (!topMember) return null;
return members.find((member) => member.userId === topMember.userId) ?? null;
}
export function buildOrderedOptions( export function buildOrderedOptions(
values: Array<string | undefined>, values: Array<string | undefined>,
desiredCount: number, desiredCount: number,
@ -143,6 +160,17 @@ export function getCurrentLeader(
); );
} }
export function hasClearLeader(quizState: {
scores: Record<string, number>;
}): boolean {
const scores = Object.values(quizState.scores);
if (scores.length < 2) return false;
const ordered = scores.slice().sort((a, b) => b - a);
const [first, second] = ordered;
if (first === undefined || second === undefined) return false;
return first > second;
}
export function getMostDiverseMember( export function getMostDiverseMember(
analytics: PartyAnalytics, analytics: PartyAnalytics,
members: PartyQuestionMember[], members: PartyQuestionMember[],

View file

@ -5,8 +5,12 @@ import {
getCurrentLeader, getCurrentLeader,
getMostAlignedMember, getMostAlignedMember,
getMostDiverseMember, getMostDiverseMember,
getTopClusterTracks,
getTopTrackListener,
hasClearLeader,
type PartyAnalytics, type PartyAnalytics,
type PartyQuestionMember, type PartyQuestionMember,
pickRandom,
} from "./question-utils"; } from "./question-utils";
export function buildSocialQuestion( export function buildSocialQuestion(
@ -16,35 +20,60 @@ export function buildSocialQuestion(
index: number, index: number,
): Question { ): Question {
type ChoiceQuestion = Extract<Question, { type: "choice" }>; type ChoiceQuestion = Extract<Question, { type: "choice" }>;
const leader = getCurrentLeader(quizState, members);
const diverse = getMostDiverseMember(analytics, members);
const aligned = getMostAlignedMember(analytics, members);
const questions: Array< const questions: Array<
Omit<ChoiceQuestion, "startTimestamp" | "endTimestamp"> Omit<ChoiceQuestion, "startTimestamp" | "endTimestamp">
> = [ > = [];
{
const hasMultipleMembers = members.length >= 2;
if (hasMultipleMembers && hasClearLeader(quizState)) {
const leader = getCurrentLeader(quizState, members);
questions.push({
type: "choice", type: "choice",
text: "Who is leading the quiz right now?", text: "Who is leading the quiz right now?",
options: buildMemberOptions(leader, members), options: buildMemberOptions(leader, members),
correct: 0, correct: 0,
points: 10, points: 10,
}, });
{ }
if (hasMultipleMembers) {
const diverse = getMostDiverseMember(analytics, members);
questions.push({
type: "choice", type: "choice",
text: "Who looks like the most diverse listener in the party?", text: "Who looks like the most diverse listener in the party?",
options: buildMemberOptions(diverse, members), options: buildMemberOptions(diverse, members),
correct: 0, correct: 0,
points: 10, points: 10,
}, });
{
const aligned = getMostAlignedMember(analytics, members);
questions.push({
type: "choice", type: "choice",
text: "Which member seems most aligned with the rest of the party?", text: "Which member seems most aligned with the rest of the party?",
options: buildMemberOptions(aligned, members), options: buildMemberOptions(aligned, members),
correct: 0, correct: 0,
points: 10, points: 10,
}, });
]; }
const topTracks = getTopClusterTracks(analytics);
const randomTrack = pickRandom(topTracks);
if (randomTrack && hasMultipleMembers) {
const topListener = getTopTrackListener(randomTrack, members);
if (topListener) {
questions.push({
type: "choice",
text: `Who listens the most to "${randomTrack.name}"?`,
options: buildMemberOptions(topListener, members),
correct: 0,
points: 10,
});
}
}
if (questions.length === 0) {
throw new Error("Question not found");
}
const question = questions[index % questions.length]; const question = questions[index % questions.length];
if (!question) throw new Error("Question not found"); if (!question) throw new Error("Question not found");

View file

@ -101,9 +101,9 @@ export class QuizWorkflow extends ConfiguredInstance {
} }
} }
break; break;
} }
if (receivedPlayers.has(response.playerId)) continue; if (receivedPlayers.has(response.playerId)) continue;
receivedPlayers.add(response.playerId); receivedPlayers.add(response.playerId);
const answeredAt = Date.now(); const answeredAt = Date.now();