133 lines
3.7 KiB
TypeScript
133 lines
3.7 KiB
TypeScript
import type { db as Db } from "../db";
|
|
import type { Question, QuizState } from "../party-types";
|
|
import {
|
|
buildMemberOptions,
|
|
buildMemberPairOptions,
|
|
buildQuestionWindow,
|
|
getCurrentLeader,
|
|
getMostDiverseMember,
|
|
getTopClusterTracks,
|
|
getTopTrackListener,
|
|
hasClearLeader,
|
|
type PartyAnalytics,
|
|
type PartyQuestionMember,
|
|
pickQuestionCandidate,
|
|
pickRandom,
|
|
type QuestionCandidate,
|
|
resolveQuestionSong,
|
|
} from "./question-utils";
|
|
|
|
export async function buildSocialQuestion(
|
|
dbClient: typeof Db,
|
|
quizState: QuizState,
|
|
analytics: PartyAnalytics,
|
|
members: PartyQuestionMember[],
|
|
index: number,
|
|
): Promise<Question | null> {
|
|
type ChoiceQuestion = Extract<Question, { type: "choice" }>;
|
|
const questions: Array<
|
|
QuestionCandidate<Omit<ChoiceQuestion, "startTimestamp" | "endTimestamp">>
|
|
> = [];
|
|
const topSong = await resolveQuestionSong(dbClient, analytics);
|
|
|
|
const hasMultipleMembers = members.length >= 2;
|
|
if (hasMultipleMembers && hasClearLeader(quizState)) {
|
|
const leader = getCurrentLeader(quizState, members);
|
|
const options = buildMemberOptions(leader, members);
|
|
if (options) {
|
|
questions.push({
|
|
key: "social:leader",
|
|
subjectKey: `member:${leader.userId}`,
|
|
question: {
|
|
type: "choice",
|
|
text: "Who is leading the quiz right now?",
|
|
options,
|
|
correct: 0,
|
|
points: 10,
|
|
song: topSong ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
if (hasMultipleMembers) {
|
|
const diverse = getMostDiverseMember(analytics, members);
|
|
if (diverse) {
|
|
const options = buildMemberOptions(diverse, members);
|
|
if (options) {
|
|
questions.push({
|
|
key: "social:diverse",
|
|
subjectKey: `member:${diverse.userId}`,
|
|
question: {
|
|
type: "choice",
|
|
text: "Who looks like the most diverse listener in the party?",
|
|
options,
|
|
correct: 0,
|
|
points: 10,
|
|
song: topSong ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const topTracks = getTopClusterTracks(analytics);
|
|
const randomTrack = pickRandom(topTracks);
|
|
if (randomTrack && hasMultipleMembers) {
|
|
const topListener = getTopTrackListener(randomTrack, members);
|
|
if (topListener) {
|
|
const randomTrackSong = await resolveQuestionSong(dbClient, analytics, {
|
|
trackName: randomTrack.name,
|
|
artistNames: randomTrack.artists?.map((artist) => artist.name),
|
|
albumName: randomTrack.albumName,
|
|
});
|
|
const options = buildMemberOptions(topListener, members);
|
|
if (options) {
|
|
questions.push({
|
|
key: `social:track-listener:${randomTrack.name}`,
|
|
subjectKey: `track:${randomTrack.name}`,
|
|
question: {
|
|
type: "choice",
|
|
text: `Who listens the most to "${randomTrack.name}"?`,
|
|
options,
|
|
correct: 0,
|
|
points: 10,
|
|
song: randomTrackSong ?? topSong ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (members.length >= 3 && analytics?.groupSummary?.mostAlignedPair) {
|
|
const topPair = analytics.groupSummary.mostAlignedPair;
|
|
const memberA = members.find((m) => m.userId === topPair.userIdA);
|
|
const memberB = members.find((m) => m.userId === topPair.userIdB);
|
|
if (memberA && memberB) {
|
|
const correctPair = `${memberA.name} & ${memberB.name}`;
|
|
const pairOptions = buildMemberPairOptions(members, correctPair);
|
|
if (pairOptions) {
|
|
questions.push({
|
|
key: `social:pair:${memberA.userId}:${memberB.userId}`,
|
|
subjectKey: `pair:${[memberA.userId, memberB.userId].sort().join("|")}`,
|
|
question: {
|
|
type: "choice",
|
|
text: "Which two players share the most musical taste?",
|
|
options: pairOptions,
|
|
correct: 0,
|
|
points: 10,
|
|
song: topSong ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (questions.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const question = pickQuestionCandidate(questions, quizState.history, index);
|
|
if (!question) return null;
|
|
return buildQuestionWindow(question);
|
|
}
|