Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

quiz infinite with stats #875

Merged
merged 55 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3c67048
quiz infinite with stats
kavigupta Jan 18, 2025
88d746f
updates and fixes to test
kavigupta Jan 18, 2025
c6b9ca2
update
kavigupta Jan 18, 2025
af21a2a
fix
kavigupta Jan 18, 2025
e6054e8
update
kavigupta Jan 18, 2025
7675adc
faster technique
kavigupta Jan 18, 2025
9b6f4ac
update
kavigupta Jan 18, 2025
fac2d0d
add best scores display
kavigupta Jan 18, 2025
39f8cd1
update
kavigupta Jan 18, 2025
6745bda
delete console
kavigupta Jan 19, 2025
5b421df
Merge remote-tracking branch 'origin/master' into quiz-infinite-stats
kavigupta Jan 19, 2025
4077659
fix version
kavigupta Jan 19, 2025
63a5a2f
add test for coming back to a partially completed quiz
kavigupta Jan 19, 2025
12ee8ea
add test for going back to a completed results page
kavigupta Jan 19, 2025
ccc383e
make sure to screencap
kavigupta Jan 19, 2025
14310ff
add prints
kavigupta Jan 19, 2025
404adb0
fix test
kavigupta Jan 19, 2025
5826fbf
fix test
kavigupta Jan 19, 2025
5fb174a
add default to unfinished quiz
kavigupta Jan 19, 2025
ad708ad
seed string
kavigupta Jan 19, 2025
ebbf96b
shorter id
kavigupta Jan 19, 2025
4f47a21
multi-line pattern
kavigupta Jan 30, 2025
4c210f6
update
kavigupta Jan 30, 2025
b87048d
fix
kavigupta Jan 30, 2025
4e695b5
several fixes
kavigupta Jan 30, 2025
e8695a3
update
kavigupta Jan 30, 2025
2651105
Merge remote-tracking branch 'origin/master' into quiz-infinite-stats
kavigupta Jan 30, 2025
dc4edb3
Merge remote-tracking branch 'origin/master' into quiz-infinite-stats
kavigupta Jan 31, 2025
9c3dd73
add medaling
kavigupta Jan 31, 2025
9511967
update
kavigupta Jan 31, 2025
7920b77
update to include second best
kavigupta Jan 31, 2025
567c97a
add more tests
kavigupta Jan 31, 2025
4d24060
add more tests for clicking around
kavigupta Jan 31, 2025
c9cad34
more scores
kavigupta Jan 31, 2025
40f7b4d
update
kavigupta Jan 31, 2025
06d7b65
generalize the friend results
kavigupta Jan 31, 2025
24396c6
Merge branch 'generalized-friend-results' into quiz-infinite-stats
kavigupta Jan 31, 2025
f30407d
add infinite
kavigupta Jan 31, 2025
45cfc32
generalize the friend display
kavigupta Jan 31, 2025
4beb4ec
delete import
kavigupta Jan 31, 2025
2badd52
add result indirection
kavigupta Jan 31, 2025
44f8baf
Merge branch 'generalized-friend-display' into quiz-infinite-stats
kavigupta Jan 31, 2025
e22876b
Merge remote-tracking branch 'origin/master' into quiz-infinite-stats
kavigupta Jan 31, 2025
95fd145
add infinite friends
kavigupta Jan 31, 2025
29ce9a9
extract friends quiz utils
kavigupta Jan 31, 2025
c23b8f6
Merge branch 'extract-friends-experiments' into quiz-infinite-stats
kavigupta Jan 31, 2025
76931e1
add friends test
kavigupta Feb 1, 2025
68894fb
several fixes, implement things properly
kavigupta Feb 1, 2025
20779ae
add friends tests etc
kavigupta Feb 1, 2025
cbcfd59
remove comment
kavigupta Feb 1, 2025
3d8398a
fix imports
kavigupta Feb 1, 2025
e3fd456
add link to juxtastat infinite
kavigupta Feb 1, 2025
14ac0eb
Revert "add link to juxtastat infinite"
kavigupta Feb 1, 2025
0ba94d1
add screencaps
kavigupta Feb 1, 2025
0cb7dac
Merge remote-tracking branch 'origin/master' into quiz-infinite-stats
kavigupta Feb 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions react/src/components/quiz-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ function QuizPanelNoResets(props: { quizDescriptor: QuizDescriptor, todayName?:
switch (props.quizDescriptor.kind) {
case 'juxtastat':
case 'retrostat':
case 'infinite':
quizHistory = persistentQuizHistory
setQuizHistory = newHistory => QuizLocalStorage.shared.history.value = newHistory
break
case 'custom':
// TODO stats for infinite quiz
case 'infinite':
quizHistory = transientQuizHistory
setQuizHistory = (newHistory) => { setTransientQuizHistory(newHistory) }
break
Expand Down
20 changes: 16 additions & 4 deletions react/src/navigation/PageDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ import { StatGroupSettings } from '../page_template/statistic-settings'
import { allGroups, CategoryIdentifier, StatName, StatPath, statsTree } from '../page_template/statistic-tree'
import { getDailyOffsetNumber, getRetrostatOffsetNumber } from '../quiz/dates'
import { validQuizInfiniteVersions } from '../quiz/infinite'
import { QuizQuestionsModel, wrapQuestionsModel, addFriendFromLink, CustomQuizContent, JuxtaQuestionJSON, loadJuxta, loadRetro, QuizDescriptor, RetroQuestionJSON, infiniteQuiz } from '../quiz/quiz'
import {
QuizQuestionsModel, wrapQuestionsModel, addFriendFromLink, CustomQuizContent, JuxtaQuestionJSON,
loadJuxta, loadRetro, QuizDescriptor, RetroQuestionJSON, infiniteQuiz, QuizHistory,
} from '../quiz/quiz'
import { getInfiniteQuizzes } from '../quiz/statistics'
import { defaultArticleUniverse, defaultComparisonUniverse } from '../universe'
import { Article, IDataList } from '../utils/protos'
import { randomID } from '../utils/random'
import { randomBase62ID } from '../utils/random'
import { followSymlink, followSymlinks } from '../utils/symlinks'
import { NormalizeProto } from '../utils/types'
import { base64Gunzip } from '../utils/urlParamShort'
Expand Down Expand Up @@ -458,12 +462,20 @@ export async function loadPageDescriptor(newDescriptor: PageDescriptor, settings
break
case 'infinite':
if (updatedDescriptor.seed === undefined) {
updatedDescriptor.seed = randomID(10)
const [seedVersions] = getInfiniteQuizzes(JSON.parse(localStorage.quiz_history as string) as QuizHistory, false)
if (seedVersions.length > 0) {
const [seed, version] = seedVersions[0]
updatedDescriptor.seed = seed
updatedDescriptor.v = version
}
else {
updatedDescriptor.seed = randomBase62ID(7)
}
}
if (updatedDescriptor.v === undefined) {
updatedDescriptor.v = Math.max(...validQuizInfiniteVersions)
}
quizDescriptor = { kind: 'infinite', name: updatedDescriptor.seed, seed: updatedDescriptor.seed, version: updatedDescriptor.v }
quizDescriptor = { kind: 'infinite', name: `I_${updatedDescriptor.seed}_${updatedDescriptor.v}`, seed: updatedDescriptor.seed, version: updatedDescriptor.v }
quiz = infiniteQuiz(updatedDescriptor.seed, updatedDescriptor.v)
todayName = undefined
break
Expand Down
104 changes: 93 additions & 11 deletions react/src/quiz/quiz-friends.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React, { CSSProperties, ReactNode, useEffect, useState } from 'react'
import React, { CSSProperties, ReactNode, useContext, useEffect, useState } from 'react'
import { GridLoader, MoonLoader } from 'react-spinners'

import { EditableString } from '../components/table'
import { Navigator } from '../navigation/Navigator'
import { urlFromPageDescriptor } from '../navigation/PageDescriptor'
import { useColors, useJuxtastatColors } from '../page_template/colors'
import { mixWithBackground } from '../utils/color'

import { endpoint, QuizDescriptorWithTime, QuizFriends, QuizLocalStorage } from './quiz'
import { endpoint, QuizDescriptorWithTime, QuizDescriptorWithStats, QuizFriends, QuizLocalStorage, QuizDescriptor } from './quiz'
import { CorrectPattern } from './quiz-result'
import { parseTimeIdentifier } from './statistics'

interface ResultToDisplayForFriends { corrects: CorrectPattern }
export type ResultToDisplayForFriends = { corrects: CorrectPattern } | { forThisSeed: number | null, maxScore: number | null, maxScoreSeed: string | null, maxScoreVersion: number | null }

interface FriendResponse { result: ResultToDisplayForFriends | null, friends: boolean, idError?: string }
type FriendScore = { name?: string } & FriendResponse
Expand Down Expand Up @@ -39,10 +40,34 @@ async function juxtaRetroResponse(
}))
}

async function infiniteResponse(
user: string,
secureID: string,
quizDescriptor: QuizDescriptor & { kind: 'infinite' },
requesters: string[],
): Promise<FriendResponse[] | undefined> {
const friendScoresResponse = await fetch(`${endpoint}/juxtastat/infinite_results`, {
method: 'POST',
body: JSON.stringify({ user, secureID, requesters, seed: quizDescriptor.seed, version: quizDescriptor.version }),
headers: {
'Content-Type': 'application/json',
},
}).then(x => x.json()) as { results: { forThisSeed: number | null, maxScore: number | null, maxScoreSeed: string | null, maxScoreVersion: number | null, friends: boolean, idError?: string }[] } | { error: string }
if ('error' in friendScoresResponse) {
// probably some kind of auth error. Handled elsewhere
return undefined
}
return friendScoresResponse.results.map(x => ({
result: { forThisSeed: x.forThisSeed, maxScore: x.maxScore, maxScoreSeed: x.maxScoreSeed, maxScoreVersion: x.maxScoreVersion },
friends: x.friends,
idError: x.idError,
}))
}

export function QuizFriendsPanel(props: {
quizFriends: QuizFriends
setQuizFriends: (quizFriends: QuizFriends) => void
quizDescriptor: QuizDescriptorWithTime
quizDescriptor: QuizDescriptorWithStats
myResult: ResultToDisplayForFriends
}): ReactNode {
const colors = useColors()
Expand All @@ -62,7 +87,10 @@ export function QuizFriendsPanel(props: {
// map name to id for quizFriends
const quizIDtoName = Object.fromEntries(props.quizFriends.map(x => [x[1], x[0]]))
const requesters = props.quizFriends.map(x => x[1])
const friendScoresResponse = await juxtaRetroResponse(user, secureID, props.quizDescriptor, requesters)
const friendScoresResponse
= props.quizDescriptor.kind === 'infinite'
? await infiniteResponse(user, secureID, props.quizDescriptor, requesters)
: await juxtaRetroResponse(user, secureID, props.quizDescriptor, requesters)
if (friendScoresResponse === undefined) {
return
}
Expand All @@ -79,13 +107,16 @@ export function QuizFriendsPanel(props: {
})()
}, [props.quizDescriptor, props.quizFriends, user, secureID])

const allResults = [props.myResult, ...friendScores.map(x => x.result)].filter(x => x !== null)

const content = (
<div>
<div style={{ margin: 'auto', width: '100%' }}>
<div className="quiz_summary">Friends</div>
</div>
<>
<PlayerScore result={props.myResult} />
{props.quizDescriptor.kind === 'infinite' ? <InfiniteHeader /> : undefined}
<PlayerScore result={props.myResult} otherResults={allResults} />

{
friendScores.map((friendScore, idx) => (
Expand All @@ -106,6 +137,7 @@ export function QuizFriendsPanel(props: {
}}
quizFriends={props.quizFriends}
setQuizFriends={props.setQuizFriends}
otherResults={allResults}
/>
),
)
Expand All @@ -128,15 +160,35 @@ export function QuizFriendsPanel(props: {
<div style={{ opacity: isLoading ? 0.5 : 1, pointerEvents: isLoading ? 'none' : undefined }}>
<WithError content={content} error={error} />
</div>
{ isLoading ? <MoonLoader size={spinnerSize} color={colors.textMain} cssOverride={spinnerStyle} /> : null}
{isLoading ? <MoonLoader size={spinnerSize} color={colors.textMain} cssOverride={spinnerStyle} /> : null}
</div>
)
}

function InfiniteHeader(): ReactNode {
return (
<div
style={{ display: 'flex', flexDirection: 'row', height: scoreCorrectHeight, alignItems: 'center' }}
className="testing-friends-section"
>
<div style={{ width: '25%' }} />
<div style={{ width: '50%', display: 'flex', flexDirection: 'row' }}>
<div style={{ width: '50%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
On This Seed
</div>
<div style={{ width: '50%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
Overall Best
</div>
</div>
<div style={{ width: '25%' }} />
</div>
)
}

const scoreCorrectHeight = '2em'
const addFriendHeight = '1.5em'

function PlayerScore(props: { result: ResultToDisplayForFriends }): ReactNode {
function PlayerScore(props: { result: ResultToDisplayForFriends, otherResults: ResultToDisplayForFriends[] }): ReactNode {
const copyFriendLink = async (): Promise<void> => {
const playerName = prompt('Enter your name:')

Expand All @@ -161,7 +213,7 @@ function PlayerScore(props: { result: ResultToDisplayForFriends }): ReactNode {
You
</div>
<div style={{ width: '50%' }}>
<FriendScoreCorrects result={props.result} friends={true} />
<FriendScoreCorrects result={props.result} friends={true} otherResults={props.otherResults} />
</div>
<div style={{ width: '25%', display: 'flex', height: addFriendHeight }}>
<button
Expand All @@ -182,6 +234,7 @@ function FriendScore(props: {
removeFriend: () => Promise<void>
quizFriends: QuizFriends
setQuizFriends: (x: QuizFriends) => void
otherResults: ResultToDisplayForFriends[]
}): ReactNode {
const colors = useColors()

Expand Down Expand Up @@ -230,7 +283,7 @@ function FriendScore(props: {
/>
</div>
<div style={{ width: '50%' }}>
<FriendScoreCorrects {...props.friendScore} />
<FriendScoreCorrects {...props.friendScore} otherResults={props.otherResults} />
</div>
<div style={{ width: '25%', display: 'flex', height: addFriendHeight }}>
<button
Expand All @@ -248,9 +301,10 @@ function FriendScore(props: {
return <WithError error={error} content={row} />
}

function FriendScoreCorrects(props: FriendScore): ReactNode {
function FriendScoreCorrects(props: FriendScore & { otherResults: ResultToDisplayForFriends[] }): ReactNode {
const colors = useColors()
const juxtaColors = useJuxtastatColors()
const navContext = useContext(Navigator.Context)
const border = `1px solid ${colors.background}`
const greyedOut = {
backgroundColor: mixWithBackground(colors.hueColors.orange, 0.5, colors.background),
Expand Down Expand Up @@ -282,6 +336,34 @@ function FriendScoreCorrects(props: FriendScore): ReactNode {
<div style={greyedOut}>Not Done Yet</div>
)
}
if ('forThisSeed' in props.result) {
const link
= props.result.maxScoreSeed === null || props.result.maxScoreVersion === null
// eslint-disable-next-line @typescript-eslint/no-empty-function -- this is a dummy onClick function for when there is no link
? { href: undefined, onClick: () => { } }
: navContext.link({
kind: 'quiz', mode: 'infinite', seed: props.result.maxScoreSeed, v: props.result.maxScoreVersion,
}, { scroll: { kind: 'position', top: 0 } })
const relevantOtherResults = props.otherResults.filter(
x => 'forThisSeed' in x,
)
const baseStyle = { width: '50%', border, display: 'flex', justifyContent: 'center', alignItems: 'center', color: '#fff', fontWeight: 'bold' }
const maxMaxScore = Math.max(...relevantOtherResults.map(x => x.maxScore ?? 0)) === props.result.maxScore
const maxForThisSeed = Math.max(...relevantOtherResults.map(x => x.forThisSeed ?? 0)) === props.result.forThisSeed
return (
<div style={{ display: 'flex', flexDirection: 'row', height: scoreCorrectHeight }}>
<div style={{ ...baseStyle, backgroundColor: maxForThisSeed ? colors.hueColors.green : colors.hueColors.blue }}>
{props.result.forThisSeed ?? '-'}
</div>
<div
style={{ ...baseStyle, backgroundColor: maxMaxScore ? colors.hueColors.green : colors.hueColors.blue }}
onClick={link.onClick}
>
<a style={{ textDecoration: 'none', color: '#fff' }} href={link.href}>{props.result.maxScore ?? '-'}</a>
</div>
</div>
)
}
const corrects = props.result.corrects
return (
<div
Expand Down
Loading
Loading