|
|
|
@@ -307,20 +307,19 @@ const shouldReplaceMergedQuestion = (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const hashString = (value: string): number => {
|
|
|
|
|
let hash = 2166136261
|
|
|
|
|
let hash = 2_166_136_261
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < value.length; ++i)
|
|
|
|
|
{
|
|
|
|
|
hash ^= value.charCodeAt (i)
|
|
|
|
|
hash = Math.imul (hash, 16777619)
|
|
|
|
|
hash = Math.imul (hash, 16_777_619)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hash >>> 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const deterministicUnitFloat = (seed: string): number =>
|
|
|
|
|
hashString (seed) / 4294967295
|
|
|
|
|
const deterministicUnitFloat = (seed: string): number => hashString (seed) / 4_294_967_295
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clearStoredGame = (): void => {
|
|
|
|
@@ -366,16 +365,17 @@ const loadRecentGames = (): RecentGameSummary[] => {
|
|
|
|
|
if (!(Array.isArray (parsed)))
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
return parsed
|
|
|
|
|
.filter ((item): item is RecentGameSummary =>
|
|
|
|
|
return (
|
|
|
|
|
parsed
|
|
|
|
|
.filter ((item): item is RecentGameSummary => (
|
|
|
|
|
typeof item === 'object'
|
|
|
|
|
&& item !== null
|
|
|
|
|
&& item != null
|
|
|
|
|
&& Number.isInteger ((item as RecentGameSummary).correctPostId)
|
|
|
|
|
&& (((item as RecentGameSummary).firstQuestionId === null)
|
|
|
|
|
&& (((item as RecentGameSummary).firstQuestionId == null)
|
|
|
|
|
|| typeof (item as RecentGameSummary).firstQuestionId === 'string')
|
|
|
|
|
&& Number.isFinite ((item as RecentGameSummary).savedAt))
|
|
|
|
|
&& Number.isFinite ((item as RecentGameSummary).savedAt)))
|
|
|
|
|
.sort ((a, b) => b.savedAt - a.savedAt)
|
|
|
|
|
.slice (0, maxStoredRecentGames)
|
|
|
|
|
.slice (0, maxStoredRecentGames))
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
@@ -387,13 +387,12 @@ const loadRecentGames = (): RecentGameSummary[] => {
|
|
|
|
|
const storeRecentGameSummary = (
|
|
|
|
|
summary: RecentGameSummary,
|
|
|
|
|
): RecentGameSummary[] => {
|
|
|
|
|
const next = [
|
|
|
|
|
summary,
|
|
|
|
|
...loadRecentGames ().filter (item =>
|
|
|
|
|
item.savedAt !== summary.savedAt
|
|
|
|
|
&& !(
|
|
|
|
|
item.correctPostId === summary.correctPostId
|
|
|
|
|
&& item.firstQuestionId === summary.firstQuestionId))]
|
|
|
|
|
const next =
|
|
|
|
|
[summary,
|
|
|
|
|
...loadRecentGames ().filter (item => (item.savedAt !== summary.savedAt
|
|
|
|
|
&& !(item.correctPostId === summary.correctPostId
|
|
|
|
|
&& (item.firstQuestionId
|
|
|
|
|
=== summary.firstQuestionId))))]
|
|
|
|
|
.slice (0, maxStoredRecentGames)
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
@@ -429,11 +428,10 @@ const loadBackgroundMotionMode = (): BackgroundMotionMode => {
|
|
|
|
|
const resettableExtraQuestionState = (): {
|
|
|
|
|
extraQuestions: GekanatorExtraQuestion[]
|
|
|
|
|
extraQuestionAnswers: Record<string, GekanatorAnswerValue>
|
|
|
|
|
extraQuestionState: 'idle'
|
|
|
|
|
} => ({
|
|
|
|
|
extraQuestions: [],
|
|
|
|
|
extraQuestionAnswers: { },
|
|
|
|
|
extraQuestionState: 'idle' })
|
|
|
|
|
extraQuestionState: 'idle' } => (
|
|
|
|
|
{ extraQuestions: [],
|
|
|
|
|
extraQuestionAnswers: { },
|
|
|
|
|
extraQuestionState: 'idle' })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const recoveredCandidateMapFromStored = (
|
|
|
|
@@ -445,9 +443,8 @@ const recoveredCandidateMapFromStored = (
|
|
|
|
|
const storedRecoveredCandidatesFromMap = (
|
|
|
|
|
recoveredCandidatePosts: Map<number, number>,
|
|
|
|
|
): RecoveredCandidatePost[] =>
|
|
|
|
|
[...recoveredCandidatePosts.entries ()].map (([postId, answerCountAtRecovery]) => ({
|
|
|
|
|
postId,
|
|
|
|
|
answerCountAtRecovery }))
|
|
|
|
|
[...recoveredCandidatePosts.entries ()]
|
|
|
|
|
.map (([postId, answerCountAtRecovery]) => ({ postId, answerCountAtRecovery }))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const baseDeltaForAnswer = (answer: GekanatorAnswerValue): number => {
|
|
|
|
@@ -468,10 +465,7 @@ const baseDeltaForAnswer = (answer: GekanatorAnswerValue): number => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const distributionEntropy = (weights: number[]): number =>
|
|
|
|
|
weights.reduce ((sum, weight) =>
|
|
|
|
|
weight <= 0
|
|
|
|
|
? sum
|
|
|
|
|
: sum - weight * Math.log2 (weight), 0)
|
|
|
|
|
weights.reduce ((sum, weight) => weight <= 0 ? sum : sum - weight * Math.log2 (weight), 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const questionCategoryPenalty = (
|
|
|
|
@@ -481,9 +475,9 @@ const questionCategoryPenalty = (
|
|
|
|
|
): number => {
|
|
|
|
|
const earlyFactor = Math.max (0, (3 - answerCount) / 3)
|
|
|
|
|
const titleLengthPenalty =
|
|
|
|
|
titleLengthMinimumForCondition (question.condition) === null
|
|
|
|
|
? 0
|
|
|
|
|
: (answerCount === 0 ? 8 : 3.5) * earlyFactor
|
|
|
|
|
titleLengthMinimumForCondition (question.condition) == null
|
|
|
|
|
? 0
|
|
|
|
|
: (answerCount === 0 ? 8 : 3.5) * earlyFactor
|
|
|
|
|
|
|
|
|
|
switch (question.kind)
|
|
|
|
|
{
|
|
|
|
@@ -673,11 +667,11 @@ const buildMaterialIndex = (
|
|
|
|
|
originalYearByPostId.set (post.id, originalYear)
|
|
|
|
|
originalMonthByPostId.set (post.id, originalMonth)
|
|
|
|
|
originalMonthDayByPostId.set (post.id, originalMonthDay)
|
|
|
|
|
if (originalYear !== null)
|
|
|
|
|
if (originalYear != null)
|
|
|
|
|
addPostIdToIndex (postIdsByOriginalYear, originalYear, post.id)
|
|
|
|
|
if (originalMonth !== null)
|
|
|
|
|
if (originalMonth != null)
|
|
|
|
|
addPostIdToIndex (postIdsByOriginalMonth, originalMonth, post.id)
|
|
|
|
|
if (originalMonthDay !== null)
|
|
|
|
|
if (originalMonthDay != null)
|
|
|
|
|
addPostIdToIndex (postIdsByOriginalMonthDay, originalMonthDay, post.id)
|
|
|
|
|
|
|
|
|
|
const titleLength = post.title?.length ?? 0
|
|
|
|
@@ -878,7 +872,7 @@ const matchingPostIdsForCondition = ({
|
|
|
|
|
case 'title-length-greater-than': {
|
|
|
|
|
const threshold =
|
|
|
|
|
titleLengthMinimumForCondition (condition)
|
|
|
|
|
if (threshold === null)
|
|
|
|
|
if (threshold == null)
|
|
|
|
|
return new Set<number> ()
|
|
|
|
|
|
|
|
|
|
const cached = materialIndex.titleLengthThresholdCache.get (threshold)
|
|
|
|
@@ -941,7 +935,7 @@ const matchingPostIdsForQuestion = ({
|
|
|
|
|
const byCondition = matchingPostIdsForCondition ({
|
|
|
|
|
condition: question.condition,
|
|
|
|
|
materialIndex })
|
|
|
|
|
if (byCondition !== null)
|
|
|
|
|
if (byCondition != null)
|
|
|
|
|
return byCondition
|
|
|
|
|
|
|
|
|
|
const matched = matchIndex.get (question.id) ?? dynamicMatchIndex?.get (question.id)
|
|
|
|
@@ -1146,7 +1140,7 @@ const applyQuestionAnswerDeltaToScores = ({
|
|
|
|
|
|
|
|
|
|
const propagatedDelta = baseDelta * edge.cos
|
|
|
|
|
const current = propagatedDeltaByPostId.get (edge.targetPostId)
|
|
|
|
|
if (current === undefined || Math.abs (propagatedDelta) > Math.abs (current))
|
|
|
|
|
if (current == null || Math.abs (propagatedDelta) > Math.abs (current))
|
|
|
|
|
propagatedDeltaByPostId.set (edge.targetPostId, propagatedDelta)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
@@ -1204,13 +1198,13 @@ const buildQuestionsForCandidateIds = (
|
|
|
|
|
): GekanatorQuestion[] => {
|
|
|
|
|
const total = candidateIds.length
|
|
|
|
|
const confirmationPost =
|
|
|
|
|
confirmationPostId === null
|
|
|
|
|
confirmationPostId == null
|
|
|
|
|
? null
|
|
|
|
|
: materialIndex.postById.get (confirmationPostId) ?? null
|
|
|
|
|
|
|
|
|
|
if (mode === 'split' && total === 0)
|
|
|
|
|
return acceptedQuestions
|
|
|
|
|
if (mode === 'confirmation' && confirmationPost === null)
|
|
|
|
|
if (mode === 'confirmation' && confirmationPost == null)
|
|
|
|
|
return acceptedQuestions
|
|
|
|
|
|
|
|
|
|
const tagCounts = new Map<string, number> ()
|
|
|
|
@@ -1228,7 +1222,7 @@ const buildQuestionsForCandidateIds = (
|
|
|
|
|
if (host)
|
|
|
|
|
hostCounts.set (host, (hostCounts.get (host) ?? 0) + 1)
|
|
|
|
|
const year = materialIndex.originalYearByPostId.get (postId)
|
|
|
|
|
if (year !== null && year !== undefined)
|
|
|
|
|
if (year != null)
|
|
|
|
|
yearCounts.set (year, (yearCounts.get (year) ?? 0) + 1)
|
|
|
|
|
const monthDay = materialIndex.originalMonthDayByPostId.get (postId)
|
|
|
|
|
if (monthDay)
|
|
|
|
@@ -1272,7 +1266,7 @@ const buildQuestionsForCandidateIds = (
|
|
|
|
|
counts: monthDayCounts,
|
|
|
|
|
total,
|
|
|
|
|
cap: factCap
|
|
|
|
|
}).filter (([monthDay]) => specialOriginalMonthDayLabelFor (String (monthDay)) !== null)
|
|
|
|
|
}).filter (([monthDay]) => specialOriginalMonthDayLabelFor (String (monthDay)) != null)
|
|
|
|
|
|
|
|
|
|
if (mode === 'split')
|
|
|
|
|
{
|
|
|
|
@@ -1358,7 +1352,7 @@ const buildQuestionsForCandidateIds = (
|
|
|
|
|
materialIndex })
|
|
|
|
|
: null)
|
|
|
|
|
addQuestion (
|
|
|
|
|
year !== null && year !== undefined
|
|
|
|
|
year != null && year != undefined
|
|
|
|
|
? buildDateQuestion ({
|
|
|
|
|
type: 'original-year',
|
|
|
|
|
year })
|
|
|
|
@@ -1445,7 +1439,7 @@ const candidatePostsForState = ({
|
|
|
|
|
const answerCountAtRecovery = recoveredCandidatePosts.get (post.id)
|
|
|
|
|
|
|
|
|
|
return answers.every ((answer, index) => {
|
|
|
|
|
if (answerCountAtRecovery !== undefined && index < answerCountAtRecovery)
|
|
|
|
|
if (answerCountAtRecovery != null && index < answerCountAtRecovery)
|
|
|
|
|
return true
|
|
|
|
|
if (softenedQuestionIds.has (answer.questionId))
|
|
|
|
|
return true
|
|
|
|
@@ -1468,17 +1462,17 @@ const candidatePostsForState = ({
|
|
|
|
|
condition,
|
|
|
|
|
materialIndex })
|
|
|
|
|
const useExpectedAnswer =
|
|
|
|
|
question !== undefined
|
|
|
|
|
question != null
|
|
|
|
|
&& usesLearnedTagExamples (question)
|
|
|
|
|
if (question && !(questionIsFactLikeForHardFiltering (question)))
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
if (matched !== null)
|
|
|
|
|
if (matched != null)
|
|
|
|
|
{
|
|
|
|
|
if (useExpectedAnswer)
|
|
|
|
|
{
|
|
|
|
|
const expected = expectedAnswerForQuestion (question, post)
|
|
|
|
|
return expected === null
|
|
|
|
|
return expected == null
|
|
|
|
|
|| expected === 'unknown'
|
|
|
|
|
|| expected === answer.answer
|
|
|
|
|
}
|
|
|
|
@@ -1492,7 +1486,7 @@ const candidatePostsForState = ({
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
const expected = expectedAnswerForQuestion (question, post)
|
|
|
|
|
return expected === null || expected === 'unknown' || expected === answer.answer
|
|
|
|
|
return expected == null || expected === 'unknown' || expected === answer.answer
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
@@ -1648,7 +1642,7 @@ const previewAnswer = ({
|
|
|
|
|
dynamicMatchIndex })
|
|
|
|
|
const nextPosts = nextPostIds
|
|
|
|
|
.map (postId => postById.get (postId))
|
|
|
|
|
.filter ((post): post is Post => post !== undefined)
|
|
|
|
|
.filter ((post): post is Post => post != null)
|
|
|
|
|
if (nextPosts.length === 0)
|
|
|
|
|
return {
|
|
|
|
|
answer,
|
|
|
|
@@ -1709,7 +1703,7 @@ const softenNextQuestionIds = ({
|
|
|
|
|
.filter ((item): item is {
|
|
|
|
|
answer: GekanatorAnswerLog
|
|
|
|
|
question: GekanatorQuestion } =>
|
|
|
|
|
item.question !== undefined
|
|
|
|
|
item.question != null
|
|
|
|
|
&& item.answer.answer !== 'unknown'
|
|
|
|
|
&& !(softenedQuestionIds.has (item.answer.questionId)))
|
|
|
|
|
.sort ((a, b) => questionDifficulty (b.question) - questionDifficulty (a.question))[0]
|
|
|
|
@@ -1753,9 +1747,9 @@ const sameConditionValue = (
|
|
|
|
|
): boolean => {
|
|
|
|
|
const leftTitleLength = titleLengthMinimumForCondition (left)
|
|
|
|
|
const rightTitleLength = titleLengthMinimumForCondition (right)
|
|
|
|
|
if (leftTitleLength !== null || rightTitleLength !== null)
|
|
|
|
|
return leftTitleLength !== null
|
|
|
|
|
&& rightTitleLength !== null
|
|
|
|
|
if (leftTitleLength != null || rightTitleLength != null)
|
|
|
|
|
return leftTitleLength != null
|
|
|
|
|
&& rightTitleLength != null
|
|
|
|
|
&& leftTitleLength === rightTitleLength
|
|
|
|
|
|
|
|
|
|
if (left.type !== right.type)
|
|
|
|
@@ -1796,7 +1790,7 @@ const isMonthCrossMatch = (
|
|
|
|
|
): boolean => {
|
|
|
|
|
const candidateMonth = monthForCondition (candidate)
|
|
|
|
|
const previousMonth = monthForCondition (previous)
|
|
|
|
|
if (candidateMonth === null || previousMonth === null)
|
|
|
|
|
if (candidateMonth == null || previousMonth == null)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
const sameType = candidate.type === previous.type
|
|
|
|
@@ -1814,12 +1808,12 @@ const isExclusiveContradiction = (
|
|
|
|
|
const candidateGroup = exclusiveConditionGroupFor (candidate)
|
|
|
|
|
const previousGroup = exclusiveConditionGroupFor (previous)
|
|
|
|
|
|
|
|
|
|
if (candidateGroup !== null && candidateGroup === previousGroup)
|
|
|
|
|
if (candidateGroup != null && candidateGroup === previousGroup)
|
|
|
|
|
return !(sameConditionValue (candidate, previous))
|
|
|
|
|
|
|
|
|
|
const candidateMonth = monthForCondition (candidate)
|
|
|
|
|
const previousMonth = monthForCondition (previous)
|
|
|
|
|
if (candidateMonth !== null && previousMonth !== null)
|
|
|
|
|
if (candidateMonth != null && previousMonth != null)
|
|
|
|
|
return candidateMonth !== previousMonth
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
@@ -2028,7 +2022,7 @@ const chooseQuestion = (
|
|
|
|
|
},
|
|
|
|
|
0) / priorWeightTotal))
|
|
|
|
|
const priorBonus =
|
|
|
|
|
priorSplitScore === null
|
|
|
|
|
priorSplitScore == null
|
|
|
|
|
? 0
|
|
|
|
|
: Math.max (0, .22 - priorSplitScore) * -18
|
|
|
|
|
const infoGainBonus = -Math.min (1.2, infoGain) * 4
|
|
|
|
@@ -2050,7 +2044,7 @@ const chooseQuestion = (
|
|
|
|
|
.filter ((item): item is {
|
|
|
|
|
question: GekanatorQuestion
|
|
|
|
|
score: number
|
|
|
|
|
narrow: boolean } => item !== null && Number.isFinite (item.score))
|
|
|
|
|
narrow: boolean } => item != null && Number.isFinite (item.score))
|
|
|
|
|
.sort ((a, b) => a.score - b.score)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -2135,15 +2129,15 @@ const chooseWinningRunQuestion = ({
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
const expected = expectedAnswerForQuestion (question, targetPost)
|
|
|
|
|
return expected !== null && expected !== 'unknown'
|
|
|
|
|
return expected != null && expected !== 'unknown'
|
|
|
|
|
})
|
|
|
|
|
.map (question => {
|
|
|
|
|
const expected = expectedAnswerForQuestion (question, targetPost)
|
|
|
|
|
const priority =
|
|
|
|
|
expected === null
|
|
|
|
|
expected == null
|
|
|
|
|
? null
|
|
|
|
|
: winningRunPriorityFor (expected)
|
|
|
|
|
if (priority === null)
|
|
|
|
|
if (priority == null)
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
const yesCount = matchingPostCountInIds ({
|
|
|
|
@@ -2168,7 +2162,7 @@ const chooseWinningRunQuestion = ({
|
|
|
|
|
question: GekanatorQuestion
|
|
|
|
|
priority: number
|
|
|
|
|
humanOffset: number
|
|
|
|
|
matchingCount: number } => item !== null)
|
|
|
|
|
matchingCount: number } => item != null)
|
|
|
|
|
.sort ((a, b) => {
|
|
|
|
|
if (a.priority !== b.priority)
|
|
|
|
|
return a.priority - b.priority
|
|
|
|
@@ -2258,7 +2252,7 @@ const chooseFallbackQuestion = ({
|
|
|
|
|
question: GekanatorQuestion
|
|
|
|
|
knownCount: number
|
|
|
|
|
balance: number
|
|
|
|
|
humanOffset: number } => item !== null)
|
|
|
|
|
humanOffset: number } => item != null)
|
|
|
|
|
.sort ((a, b) => {
|
|
|
|
|
if (a.humanOffset !== b.humanOffset)
|
|
|
|
|
return a.humanOffset - b.humanOffset
|
|
|
|
@@ -2291,13 +2285,13 @@ const isWinningRunActive = (
|
|
|
|
|
winningRunTargetId: number | null,
|
|
|
|
|
winningRunStartAnswerCount: number | null,
|
|
|
|
|
): boolean =>
|
|
|
|
|
winningRunTargetId !== null && winningRunStartAnswerCount !== null
|
|
|
|
|
winningRunTargetId != null && winningRunStartAnswerCount != null
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const winningRunQuestionCount = (
|
|
|
|
|
answers: GekanatorAnswerLog[],
|
|
|
|
|
winningRunStartAnswerCount: number | null,
|
|
|
|
|
): number => winningRunStartAnswerCount === null
|
|
|
|
|
): number => winningRunStartAnswerCount == null
|
|
|
|
|
? 0
|
|
|
|
|
: answers
|
|
|
|
|
.slice (winningRunStartAnswerCount)
|
|
|
|
@@ -2377,15 +2371,15 @@ const nextQuestionPlanFor = (
|
|
|
|
|
? eligiblePosts[0]?.id ?? null
|
|
|
|
|
: null
|
|
|
|
|
const nextWinningRunStartAnswerCount =
|
|
|
|
|
nextWinningRunTargetId === null
|
|
|
|
|
nextWinningRunTargetId == null
|
|
|
|
|
? null
|
|
|
|
|
: ((isWinningRunActive (winningRunTargetId, winningRunStartAnswerCount)
|
|
|
|
|
&& winningRunTargetId === nextWinningRunTargetId
|
|
|
|
|
&& winningRunStartAnswerCount !== null)
|
|
|
|
|
&& winningRunStartAnswerCount != null)
|
|
|
|
|
? winningRunStartAnswerCount
|
|
|
|
|
: answers.length)
|
|
|
|
|
const nextWinningRunTargetPost =
|
|
|
|
|
nextWinningRunTargetId === null
|
|
|
|
|
nextWinningRunTargetId == null
|
|
|
|
|
? null
|
|
|
|
|
: posts.find (post => post.id === nextWinningRunTargetId) ?? null
|
|
|
|
|
const buildQuestionsForPosts = (scopePosts: Post[]): GekanatorQuestion[] =>
|
|
|
|
@@ -2398,8 +2392,8 @@ const nextQuestionPlanFor = (
|
|
|
|
|
if (eligiblePosts.length === 1)
|
|
|
|
|
{
|
|
|
|
|
const winningRunFinished =
|
|
|
|
|
nextWinningRunTargetId !== null
|
|
|
|
|
&& nextWinningRunStartAnswerCount !== null
|
|
|
|
|
nextWinningRunTargetId != null
|
|
|
|
|
&& nextWinningRunStartAnswerCount != null
|
|
|
|
|
&& eligiblePosts[0]?.id === nextWinningRunTargetId
|
|
|
|
|
&& winningRunQuestionCount (
|
|
|
|
|
answers,
|
|
|
|
@@ -2412,7 +2406,7 @@ const nextQuestionPlanFor = (
|
|
|
|
|
questionMode: null,
|
|
|
|
|
winningRunTargetId: nextWinningRunTargetId,
|
|
|
|
|
winningRunStartAnswerCount: nextWinningRunStartAnswerCount }
|
|
|
|
|
if (!(nextWinningRunTargetPost) || nextWinningRunStartAnswerCount === null)
|
|
|
|
|
if (!(nextWinningRunTargetPost) || nextWinningRunStartAnswerCount == null)
|
|
|
|
|
return {
|
|
|
|
|
question: null,
|
|
|
|
|
guess: null,
|
|
|
|
@@ -2586,9 +2580,9 @@ const backgroundPostsFor = ({
|
|
|
|
|
}): Post[] => {
|
|
|
|
|
const focusPosts =
|
|
|
|
|
phase === 'end' || phase === 'review' || phase === 'learned'
|
|
|
|
|
? [reviewCorrectPost, reviewGuessedPost].filter ((post): post is Post => post !== null)
|
|
|
|
|
? [reviewCorrectPost, reviewGuessedPost].filter ((post): post is Post => post != null)
|
|
|
|
|
: phase === 'guess'
|
|
|
|
|
? [displayedGuess, ...eligiblePosts].filter ((post): post is Post => post !== null)
|
|
|
|
|
? [displayedGuess, ...eligiblePosts].filter ((post): post is Post => post != null)
|
|
|
|
|
: eligiblePosts.length > 0
|
|
|
|
|
? eligiblePosts
|
|
|
|
|
: availablePosts
|
|
|
|
@@ -2646,7 +2640,7 @@ const GekanatorBackdrop: FC<{
|
|
|
|
|
const isWinningRunBackdrop =
|
|
|
|
|
!(guessThumbnail)
|
|
|
|
|
&& phase === 'question'
|
|
|
|
|
&& winningRunTargetPost !== null
|
|
|
|
|
&& winningRunTargetPost != null
|
|
|
|
|
&& Boolean (backgroundThumbnailUrl (winningRunTargetPost))
|
|
|
|
|
const backdropMode =
|
|
|
|
|
guessThumbnail
|
|
|
|
@@ -2843,7 +2837,7 @@ const GekanatorBackdrop: FC<{
|
|
|
|
|
setActiveDirection (nextDirection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (flipTimerRef.current !== null)
|
|
|
|
|
if (flipTimerRef.current != null)
|
|
|
|
|
{
|
|
|
|
|
window.clearTimeout (flipTimerRef.current)
|
|
|
|
|
flipTimerRef.current = null
|
|
|
|
@@ -2924,7 +2918,7 @@ const GekanatorBackdrop: FC<{
|
|
|
|
|
}, tileFlipDuration * 1000)
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (flipTimerRef.current !== null)
|
|
|
|
|
if (flipTimerRef.current != null)
|
|
|
|
|
{
|
|
|
|
|
window.clearTimeout (flipTimerRef.current)
|
|
|
|
|
flipTimerRef.current = null
|
|
|
|
@@ -3072,10 +3066,10 @@ const expectedAnswerFor = (
|
|
|
|
|
|
|
|
|
|
const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
const storedGame = useMemo (loadStoredGame, [])
|
|
|
|
|
const hasStoredRestore = storedGame !== null && isStoredPhase (storedGame.phase)
|
|
|
|
|
const hasStoredRestore = storedGame != null && isStoredPhase (storedGame.phase)
|
|
|
|
|
const queryClient = useQueryClient ()
|
|
|
|
|
const isAdmin = user?.role === 'admin'
|
|
|
|
|
const canPersistGame = user !== null
|
|
|
|
|
const canPersistGame = user != null
|
|
|
|
|
const [recentGames, setRecentGames] = useState<RecentGameSummary[]> (
|
|
|
|
|
() => loadRecentGames ())
|
|
|
|
|
const [backgroundMotionMode, setBackgroundMotionMode] = useState<BackgroundMotionMode> (
|
|
|
|
@@ -3188,7 +3182,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
setAskedQuestionBank (
|
|
|
|
|
storedAskedQuestionBankIds
|
|
|
|
|
.map (questionId => questionById.get (questionId))
|
|
|
|
|
.filter ((question): question is GekanatorQuestion => question !== undefined))
|
|
|
|
|
.filter ((question): question is GekanatorQuestion => question != null))
|
|
|
|
|
setStoredAskedQuestionBankIds ([])
|
|
|
|
|
}, [posts,
|
|
|
|
|
storedAskedQuestionBankIds,
|
|
|
|
@@ -3340,11 +3334,11 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
?? null,
|
|
|
|
|
[acceptedQuestions, questionSuggestionSelectedId])
|
|
|
|
|
const canSubmitQuestionSuggestion = useMemo (() => {
|
|
|
|
|
if (!(canPersistGame) || reviewCorrectPostId === null)
|
|
|
|
|
if (!(canPersistGame) || reviewCorrectPostId == null)
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
if (questionSuggestionEntryMode === 'search')
|
|
|
|
|
return selectedSuggestedQuestion !== null
|
|
|
|
|
return selectedSuggestedQuestion != null
|
|
|
|
|
|
|
|
|
|
return questionSuggestion.trim () !== ''
|
|
|
|
|
}, [
|
|
|
|
@@ -3403,7 +3397,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
userPriorWeights, materialIndex, acceptedQuestionMatchIndex,
|
|
|
|
|
lastGuessQuestionCount, winningRunTargetId, winningRunStartAnswerCount])
|
|
|
|
|
const winningRunTargetPost = useMemo (
|
|
|
|
|
() => questionPlan.winningRunTargetId === null
|
|
|
|
|
() => questionPlan.winningRunTargetId == null
|
|
|
|
|
? null
|
|
|
|
|
: posts.find (post => post.id === questionPlan.winningRunTargetId) ?? null,
|
|
|
|
|
[posts, questionPlan.winningRunTargetId])
|
|
|
|
@@ -3417,7 +3411,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
&& winningRunQuestionsAsked < winningRunQuestionLimit
|
|
|
|
|
&& eligiblePosts.length === 1
|
|
|
|
|
&& eligiblePosts[0]?.id === questionPlan.winningRunTargetId
|
|
|
|
|
&& winningRunTargetPost !== null
|
|
|
|
|
&& winningRunTargetPost != null
|
|
|
|
|
const topScoredPosts = useMemo (
|
|
|
|
|
() => eligiblePosts
|
|
|
|
|
.map (post => ({ post, score: scores.get (post.id) ?? 0 }))
|
|
|
|
@@ -3454,7 +3448,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
posts.find (post => post.id === reviewCorrectPostId) ?? null
|
|
|
|
|
const effectiveResultWon =
|
|
|
|
|
resultWon
|
|
|
|
|
?? ((reviewGuessedPostId !== null && reviewCorrectPostId !== null)
|
|
|
|
|
?? ((reviewGuessedPostId != null && reviewCorrectPostId != null)
|
|
|
|
|
? reviewGuessedPostId === reviewCorrectPostId
|
|
|
|
|
: null)
|
|
|
|
|
const effectiveBackgroundMotionMode =
|
|
|
|
@@ -3895,7 +3889,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startReview = () => {
|
|
|
|
|
if (reviewGuessedPostId === null || reviewCorrectPostId === null)
|
|
|
|
|
if (reviewGuessedPostId == null || reviewCorrectPostId == null)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
saveMutation.reset ()
|
|
|
|
@@ -3915,13 +3909,13 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
const saveReviewedResult = (onSuccess: (gameId: number) => void) => {
|
|
|
|
|
if (
|
|
|
|
|
!(canPersistGame)
|
|
|
|
|
|| reviewGuessedPostId === null
|
|
|
|
|
|| reviewCorrectPostId === null
|
|
|
|
|
|| reviewGuessedPostId == null
|
|
|
|
|
|| reviewCorrectPostId == null
|
|
|
|
|
|| saveMutation.isPending
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if (savedGameId !== null)
|
|
|
|
|
if (savedGameId != null)
|
|
|
|
|
{
|
|
|
|
|
onSuccess (savedGameId)
|
|
|
|
|
return
|
|
|
|
@@ -3979,7 +3973,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
const saveExtraQuestions = () => {
|
|
|
|
|
if (
|
|
|
|
|
!(canPersistGame)
|
|
|
|
|
|| savedGameId === null
|
|
|
|
|
|| savedGameId == null
|
|
|
|
|
|| extraQuestionAnswersMutation.isPending
|
|
|
|
|
|| extraQuestions.some (question => !(extraQuestionAnswers[String (question.id)]))
|
|
|
|
|
)
|
|
|
|
@@ -4175,7 +4169,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const startExtraQuestions = () => {
|
|
|
|
|
if (reviewCorrectPostId === null || saveMutation.isPending)
|
|
|
|
|
if (reviewCorrectPostId == null || saveMutation.isPending)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
saveReviewedResult (gameId => {
|
|
|
|
@@ -4206,7 +4200,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
const dialogue = phase === 'learned' ? resultDialogue : introDialogue
|
|
|
|
|
const saveStatusMessage =
|
|
|
|
|
saved
|
|
|
|
|
&& learnedExampleCount !== null
|
|
|
|
|
&& learnedExampleCount != null
|
|
|
|
|
? learnedExampleCount > 0
|
|
|
|
|
? `${ learnedExampleCount }件の回答を学習しました`
|
|
|
|
|
: null
|
|
|
|
@@ -4436,7 +4430,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
{' / '}
|
|
|
|
|
recoveryStepCount: {recoveryStepCount}
|
|
|
|
|
{' / '}
|
|
|
|
|
currentQuestion===null: {String (currentQuestion === null)}
|
|
|
|
|
currentQuestion===null: {String (currentQuestion == null)}
|
|
|
|
|
</div>
|
|
|
|
|
{topScoredPosts.length > 0 && (
|
|
|
|
|
<div className="mt-1 flex flex-wrap gap-x-4 gap-y-1">
|
|
|
|
@@ -4525,7 +4519,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
{' / '}
|
|
|
|
|
recoveryStepCount: {recoveryStepCount}
|
|
|
|
|
{' / '}
|
|
|
|
|
currentQuestion===null: {String (currentQuestion === null)}
|
|
|
|
|
currentQuestion===null: {String (currentQuestion == null)}
|
|
|
|
|
</div>)}
|
|
|
|
|
<PostMiniCard post={displayedGuess}/>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
@@ -4621,7 +4615,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{reviewGuessedPostId !== null && reviewCorrectPostId !== null && (
|
|
|
|
|
{reviewGuessedPostId != null && reviewCorrectPostId != null && (
|
|
|
|
|
<p className="text-sm text-neutral-600 dark:text-neutral-300">
|
|
|
|
|
判定: {reviewGuessedPostId === reviewCorrectPostId ? '当たり' : 'はずれ'}
|
|
|
|
|
</p>)}
|
|
|
|
@@ -4648,7 +4642,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
type="button"
|
|
|
|
|
className="rounded bg-pink-600 px-4 py-2 font-bold text-white
|
|
|
|
|
hover:bg-pink-500 disabled:opacity-50"
|
|
|
|
|
disabled={reviewCorrectPostId === null || saveMutation.isPending}
|
|
|
|
|
disabled={reviewCorrectPostId == null || saveMutation.isPending}
|
|
|
|
|
onClick={saveAndReset}>
|
|
|
|
|
もう一度
|
|
|
|
|
</button>
|
|
|
|
@@ -4659,7 +4653,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
dark:hover:bg-red-900 disabled:opacity-50"
|
|
|
|
|
disabled={
|
|
|
|
|
!(canPersistGame)
|
|
|
|
|
|| reviewCorrectPostId === null
|
|
|
|
|
|| reviewCorrectPostId == null
|
|
|
|
|
|| saveMutation.isPending
|
|
|
|
|
}
|
|
|
|
|
onClick={startReview}>
|
|
|
|
@@ -4682,7 +4676,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
hover:bg-yellow-100 dark:border-red-700
|
|
|
|
|
dark:hover:bg-red-900 disabled:opacity-50"
|
|
|
|
|
disabled={!(canPersistGame)
|
|
|
|
|
|| reviewCorrectPostId === null
|
|
|
|
|
|| reviewCorrectPostId == null
|
|
|
|
|
|| saveMutation.isPending
|
|
|
|
|
|| extraQuestionState === 'loading'
|
|
|
|
|
|| extraQuestionAnswersMutation.isPending}
|
|
|
|
@@ -4768,7 +4762,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{reviewGuessedPostId !== null && reviewCorrectPostId !== null && (
|
|
|
|
|
{reviewGuessedPostId != null && reviewCorrectPostId != null && (
|
|
|
|
|
<p className="text-sm text-neutral-600 dark:text-neutral-300">
|
|
|
|
|
判定:
|
|
|
|
|
{reviewGuessedPostId === reviewCorrectPostId ? '当たり' : 'はずれ'}
|
|
|
|
@@ -4798,7 +4792,7 @@ const GekanatorPage: FC<{ user: User | null }> = ({ user }) => {
|
|
|
|
|
hover:bg-pink-500 disabled:opacity-50"
|
|
|
|
|
disabled={
|
|
|
|
|
!(canPersistGame)
|
|
|
|
|
|| reviewCorrectPostId === null
|
|
|
|
|
|| reviewCorrectPostId == null
|
|
|
|
|
|| saveMutation.isPending
|
|
|
|
|
|| questionSuggestionMutation.isPending
|
|
|
|
|
}
|
|
|
|
|