mirror of
https://github.com/priyanshujain/bertsatlas.git
synced 2026-10-02 19:06:59 +00:00
refine home options and trivia pacing
This commit is contained in:
1 parent
c6b2eb3cce
commit
598c5f9ef3
30 files changed
+493
-187
No files matched your search
@@ -28,6 +28,7 @@ interface ProgressWriter {
|
||||
attempt: Attempt,
|
||||
updatedMastery: List<Mastery>,
|
||||
activeRunSnapshot: ActiveRunSnapshot,
|
||||
streak: Streak,
|
||||
)
|
||||
|
||||
suspend fun recordCompletedRun(
|
||||
@@ -36,7 +37,7 @@ interface ProgressWriter {
|
||||
newlyEarnedStamps: List<PassportStamp>,
|
||||
)
|
||||
|
||||
suspend fun recordAbandonedRun(runId: String)
|
||||
suspend fun recordAbandonedRun(runId: String, streak: Streak? = null)
|
||||
suspend fun resetProgress()
|
||||
}
|
||||
|
||||
|
||||
@@ -76,11 +76,11 @@ object MasteryRules {
|
||||
}
|
||||
|
||||
object StreakRules {
|
||||
fun recordCompletedRun(current: Streak, completedEpochDay: Int): Streak {
|
||||
val lastDay = current.lastCompletedEpochDay
|
||||
if (lastDay != null && completedEpochDay <= lastDay) return current
|
||||
fun recordActivity(current: Streak, activityEpochDay: Int): Streak {
|
||||
val lastDay = current.lastActivityEpochDay
|
||||
if (lastDay != null && activityEpochDay <= lastDay) return current
|
||||
|
||||
val currentDays = if (lastDay != null && completedEpochDay == lastDay + 1) {
|
||||
val currentDays = if (lastDay != null && activityEpochDay == lastDay + 1) {
|
||||
current.currentDays + 1
|
||||
} else {
|
||||
1
|
||||
@@ -88,9 +88,12 @@ object StreakRules {
|
||||
return Streak(
|
||||
currentDays = currentDays,
|
||||
bestDays = maxOf(current.bestDays, currentDays),
|
||||
lastCompletedEpochDay = completedEpochDay,
|
||||
lastCompletedEpochDay = activityEpochDay,
|
||||
)
|
||||
}
|
||||
|
||||
fun recordCompletedRun(current: Streak, completedEpochDay: Int): Streak =
|
||||
recordActivity(current, completedEpochDay)
|
||||
}
|
||||
|
||||
data class PassportEvidence(
|
||||
|
||||
@@ -109,7 +109,8 @@ class QuizEngine(
|
||||
}
|
||||
val completionPending = state.runCompletionPending ||
|
||||
state.sessionExpired ||
|
||||
(state.configuration.duration != QuizDuration.Unlimited && state.sessionRemainingMillis == 0L)
|
||||
(state.configuration.duration != QuizDuration.Unlimited && state.sessionRemainingMillis == 0L) ||
|
||||
state.hasReachedAnsweredQuestionLimit()
|
||||
if (state.phase == RunPhase.Paused) {
|
||||
require(state.pausedFromPhase in PAUSABLE_PHASES) { "A paused snapshot has no resumable phase" }
|
||||
return state.copy(
|
||||
@@ -161,13 +162,17 @@ class QuizEngine(
|
||||
hintTypes = timed.activeQuestion.usedHintTypes,
|
||||
)
|
||||
val correctStreak = if (evaluation.correct) timed.currentCorrectStreak + 1 else 0
|
||||
val attempts = timed.attempts + attempt
|
||||
val answeredQuestionLimitReached = timed.configuration.duration.answeredQuestionLimit
|
||||
?.let { attempts.size >= it }
|
||||
?: false
|
||||
return timed.copy(
|
||||
phase = RunPhase.Feedback,
|
||||
score = timed.score + points,
|
||||
answeredQuestionIds = timed.answeredQuestionIds + timed.activeQuestion.question.id,
|
||||
recentQuestionIds = (timed.recentQuestionIds + timed.activeQuestion.question.id).takeLast(10),
|
||||
answeredCountryIds = (timed.answeredCountryIds + timed.activeQuestion.question.countryIds).takeLast(10),
|
||||
attempts = timed.attempts + attempt,
|
||||
attempts = attempts,
|
||||
feedback = AnswerFeedback(
|
||||
kind = when {
|
||||
!evaluation.correct -> FeedbackKind.Incorrect
|
||||
@@ -182,6 +187,7 @@ class QuizEngine(
|
||||
adaptive = AdaptiveDifficulty.afterAnswer(timed.adaptive, evaluation.correct, hinted),
|
||||
currentCorrectStreak = correctStreak,
|
||||
bestCorrectStreak = maxOf(timed.bestCorrectStreak, correctStreak),
|
||||
runCompletionPending = timed.runCompletionPending || answeredQuestionLimitReached,
|
||||
tickingSinceMillis = null,
|
||||
)
|
||||
}
|
||||
@@ -592,6 +598,9 @@ class QuizEngine(
|
||||
private fun ActiveQuestion.revealedTileIds(): Set<String> =
|
||||
appliedHints.mapNotNullTo(linkedSetOf()) { it.revealedTileId }
|
||||
|
||||
private fun QuizRunState.hasReachedAnsweredQuestionLimit(): Boolean =
|
||||
configuration.duration.answeredQuestionLimit?.let { attempts.size >= it } == true
|
||||
|
||||
private fun correctAnswer(question: Question): String = when (question) {
|
||||
is Question.MultipleChoice -> question.choices.first { it.id == question.correctChoiceId }.label
|
||||
is Question.LetterBank -> question.displayAnswer
|
||||
@@ -667,8 +676,8 @@ class QuizEngine(
|
||||
private data class AnswerEvaluation(val correct: Boolean, val submittedText: String)
|
||||
|
||||
companion object {
|
||||
const val MULTIPLE_CHOICE_WINDOW_MILLIS = 10_000L
|
||||
const val LETTER_BANK_WINDOW_MILLIS = 20_000L
|
||||
const val MULTIPLE_CHOICE_WINDOW_MILLIS = 30_000L
|
||||
const val LETTER_BANK_WINDOW_MILLIS = 30_000L
|
||||
const val LETTER_BANK_DECOY_COUNT = 3
|
||||
const val MINIMUM_UNLIMITED_ANSWERS = 5
|
||||
const val ACTIVE_RUN_SNAPSHOT_SCHEMA_VERSION = 1
|
||||
|
||||
@@ -9,11 +9,14 @@ data class QuizConfiguration(
|
||||
)
|
||||
|
||||
@Serializable
|
||||
enum class QuizDuration(val activeDurationMillis: Long?) {
|
||||
FiveMinutes(5 * 60 * 1_000L),
|
||||
TenMinutes(10 * 60 * 1_000L),
|
||||
TwentyMinutes(20 * 60 * 1_000L),
|
||||
Unlimited(null),
|
||||
enum class QuizDuration(
|
||||
val activeDurationMillis: Long?,
|
||||
val answeredQuestionLimit: Int?,
|
||||
) {
|
||||
FiveMinutes(5 * 60 * 1_000L, 10),
|
||||
TenMinutes(10 * 60 * 1_000L, 20),
|
||||
TwentyMinutes(20 * 60 * 1_000L, 40),
|
||||
Unlimited(null, null),
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -60,7 +60,10 @@ data class Streak(
|
||||
val currentDays: Int = 0,
|
||||
val bestDays: Int = 0,
|
||||
val lastCompletedEpochDay: Int? = null,
|
||||
)
|
||||
) {
|
||||
val lastActivityEpochDay: Int?
|
||||
get() = lastCompletedEpochDay
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class PassportStampKind {
|
||||
|
||||
Reference in new issue
Block a user