import { useState, useRef, useEffect } from "react"; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import Reward, { RewardElement } from 'react-rewards'; import Link from "../components/Link"; import { useUser } from "../user-context"; import "./timer.css"; import { getPercentageDone, } from "../lib/game-utils"; import { saveResult, postSavedResults, } from "../lib/game-results"; import { AT, getTimestamp, } from "@lingdocs/lingdocs-main"; import { randFromArray, Types, } from "@lingdocs/pashto-inflector"; import ReactGA from "react-ga"; import { isProd } from "../lib/isProd"; import autoAnimate from "@formkit/auto-animate"; const errorVibration = 200; const maxStrikes = 2; function GameCore({ questions, Display, timeLimit, Instructions, studyLink, id }:{ id: string, studyLink: string, Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element, questions: () => QuestionGenerator, Display: (props: QuestionDisplayProps) => JSX.Element, timeLimit: number; }) { // TODO: report pass with id to user info const rewardRef = useRef(null); const parent = useRef(null); const { user, pullUser, setUser } = useUser(); const [finish, setFinish] = useState(undefined); const [strikes, setStrikes] = useState(0); const [justStruck, setJustStruck] = useState(false); const [current, setCurrent] = useState | undefined>(undefined); const [questionBox, setQuestionBox] = useState>(questions()); const [timerKey, setTimerKey] = useState(1); useEffect(() => { parent.current && autoAnimate(parent.current) }, [parent]); function logGameEvent(action: string) { if (isProd && !(user?.admin)) { ReactGA.event({ category: "Game", action: `${action} - ${id}`, label: id, }); } } function handleCallback(correct: true | JSX.Element) { if (correct === true) { handleAdvance(); return; } setStrikes(s => s + 1); navigator.vibrate(errorVibration); if (strikes < maxStrikes) { setJustStruck(true); } else { logGameEvent("fail on game"); setFinish({ msg: "fail", answer: correct }); } } function handleAdvance() { setJustStruck(false); const next = questionBox.next(); if (next.done) handleFinish(); else setCurrent(next.value); } function handleResult(result: AT.TestResult) { // add the test to the user object if (!user) return; setUser((u) => { // pure type safety with the prevUser if (!u) return u; return { ...u, tests: [...u.tests, result], }; }); // save the test result in local storage saveResult(result, user.userId); // try to post the result postSavedResults(user.userId).then((r) => { if (r === "sent") pullUser(); }).catch(console.error); } function handleFinish() { logGameEvent("passed game") setFinish("pass"); rewardRef.current?.rewardMe(); if (!user) return; const result: AT.TestResult = { done: true, time: getTimestamp(), id, }; handleResult(result); } function handleQuit() { setFinish(undefined); setCurrent(undefined); } function handleRestart() { logGameEvent("started game"); const newQuestionBox = questions(); const { value } = newQuestionBox.next(); // just for type safety -- the generator will have at least one question if (!value) return; setQuestionBox(newQuestionBox); setJustStruck(false); setStrikes(0); setFinish(undefined); setCurrent(value); setTimerKey(prev => prev + 1); } function handleTimeOut() { logGameEvent("timeout on game") setFinish("time out"); navigator.vibrate(errorVibration); } function getProgressWidth(): string { const num = !current ? 0 : (finish === "pass") ? 100 : getPercentageDone(current.progress); return `${num}%`; } const progressColor = finish === "pass" ? "success" : typeof finish === "object" ? "danger" : "primary"; const gameRunning = current && finish === undefined; return <>
{current &&
}
{justStruck &&
{getStrikeMessage()}
}
{finish === undefined && (current ?
:
{/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/}
) } {finish === "pass" &&

🎉 Finished!

} {(typeof finish === "object" || finish === "time out") &&

{failMessage(current?.progress, finish)}

{typeof finish === "object" &&
The correct answer was:
{finish?.answer}
}
}
{gameRunning &&
} ; } function StrikesDisplay({ strikes }: { strikes: number }) { return
{[...Array(strikes)].map(_ => )}
; } function getStrikeMessage() { return randFromArray([ "Not quite! Try again.", "No sorry, try again", "Umm, no, try again", "Try again", "Oooooooo, sorry no...", ]); } function failMessage(progress: Progress | undefined, finish: "time out" | { msg: "fail", answer: JSX.Element }): string { const pDone = progress ? getPercentageDone(progress) : 0; const { message, face } = pDone < 20 ? { message: "No, sorry", face: "😑" } : pDone < 30 ? { message: "Oops, that's wrong", face: "😟" } : pDone < 55 ? { message: "Fail", face: "😕" } : pDone < 78 ? { message: "You almost got it!", face: "😩" } : { message: "Nooo! So close!", face: "😭" }; return typeof finish === "object" ? `${message} ${face}` : `⏳ Time's Up ${face}`; } export default GameCore;