added strikes and made games easier
This commit is contained in:
parent
c907cc0726
commit
c64e48d274
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useRef } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
||||||
import Reward, { RewardElement } from 'react-rewards';
|
import Reward, { RewardElement } from 'react-rewards';
|
||||||
import Link from "../components/Link";
|
import Link from "../components/Link";
|
||||||
|
@ -16,10 +16,12 @@ import {
|
||||||
getTimestamp,
|
getTimestamp,
|
||||||
} from "@lingdocs/lingdocs-main";
|
} from "@lingdocs/lingdocs-main";
|
||||||
import {
|
import {
|
||||||
|
randFromArray,
|
||||||
Types,
|
Types,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga";
|
||||||
import { isProd } from "../lib/isProd";
|
import { isProd } from "../lib/isProd";
|
||||||
|
import autoAnimate from "@formkit/auto-animate";
|
||||||
const errorVibration = 200;
|
const errorVibration = 200;
|
||||||
|
|
||||||
const maxStrikes = 2;
|
const maxStrikes = 2;
|
||||||
|
@ -34,6 +36,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
}) {
|
}) {
|
||||||
// TODO: report pass with id to user info
|
// TODO: report pass with id to user info
|
||||||
const rewardRef = useRef<RewardElement | null>(null);
|
const rewardRef = useRef<RewardElement | null>(null);
|
||||||
|
const parent = useRef<HTMLDivElement | null>(null);
|
||||||
const { user, pullUser, setUser } = useUser();
|
const { user, pullUser, setUser } = useUser();
|
||||||
const [finish, setFinish] = useState<undefined | "pass" | { msg: "fail", answer: JSX.Element } | "time out">(undefined);
|
const [finish, setFinish] = useState<undefined | "pass" | { msg: "fail", answer: JSX.Element } | "time out">(undefined);
|
||||||
const [strikes, setStrikes] = useState<number>(0);
|
const [strikes, setStrikes] = useState<number>(0);
|
||||||
|
@ -41,6 +44,9 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
||||||
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
||||||
const [timerKey, setTimerKey] = useState<number>(1);
|
const [timerKey, setTimerKey] = useState<number>(1);
|
||||||
|
useEffect(() => {
|
||||||
|
parent.current && autoAnimate(parent.current)
|
||||||
|
}, [parent]);
|
||||||
|
|
||||||
function logGameEvent(action: string) {
|
function logGameEvent(action: string) {
|
||||||
if (isProd && !(user?.admin)) {
|
if (isProd && !(user?.admin)) {
|
||||||
|
@ -53,15 +59,17 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCallback(correct: true | JSX.Element) {
|
function handleCallback(correct: true | JSX.Element) {
|
||||||
if (correct === true) handleAdvance();
|
if (correct === true) {
|
||||||
else if (strikes < maxStrikes) {
|
handleAdvance();
|
||||||
navigator.vibrate(errorVibration);
|
return;
|
||||||
|
}
|
||||||
setStrikes(s => s + 1);
|
setStrikes(s => s + 1);
|
||||||
setJustStruck(false);
|
navigator.vibrate(errorVibration);
|
||||||
|
if (strikes < maxStrikes) {
|
||||||
|
setJustStruck(true);
|
||||||
} else {
|
} else {
|
||||||
logGameEvent("fail on game");
|
logGameEvent("fail on game");
|
||||||
setFinish({ msg: "fail", answer: correct });
|
setFinish({ msg: "fail", answer: correct });
|
||||||
navigator.vibrate(errorVibration);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleAdvance() {
|
function handleAdvance() {
|
||||||
|
@ -111,6 +119,8 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
// just for type safety -- the generator will have at least one question
|
// just for type safety -- the generator will have at least one question
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
setQuestionBox(newQuestionBox);
|
setQuestionBox(newQuestionBox);
|
||||||
|
setJustStruck(false);
|
||||||
|
setStrikes(0);
|
||||||
setFinish(undefined);
|
setFinish(undefined);
|
||||||
setCurrent(value);
|
setCurrent(value);
|
||||||
setTimerKey(prev => prev + 1);
|
setTimerKey(prev => prev + 1);
|
||||||
|
@ -139,8 +149,9 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
<div className="progress" style={{ height: "5px" }}>
|
<div className="progress" style={{ height: "5px" }}>
|
||||||
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
||||||
</div>
|
</div>
|
||||||
<div>Strikes: {strikes}</div>
|
{current && <div className="d-flex flex-row justify-content-between mt-2">
|
||||||
{current && <div className="d-flex flex-row-reverse mt-2">
|
<StrikesDisplay strikes={strikes} />
|
||||||
|
<div className="d-flex flex-row-reverse">
|
||||||
<CountdownCircleTimer
|
<CountdownCircleTimer
|
||||||
key={timerKey}
|
key={timerKey}
|
||||||
isPlaying={!!current && !finish}
|
isPlaying={!!current && !finish}
|
||||||
|
@ -153,8 +164,13 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
onComplete={handleTimeOut}
|
onComplete={handleTimeOut}
|
||||||
/>
|
/>
|
||||||
<button onClick={handleQuit} className="btn btn-outline-secondary btn-sm mr-2">Quit</button>
|
<button onClick={handleQuit} className="btn btn-outline-secondary btn-sm mr-2">Quit</button>
|
||||||
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
<div>OOPS, NOT QUITE - TRY AGAIN</div>
|
<div ref={parent}>
|
||||||
|
{justStruck && <div className="alert alert-warning my-2" role="alert" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||||
|
{getStrikeMessage()}
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 150, zIndex: 999999999 }} type="confetti">
|
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 150, zIndex: 999999999 }} type="confetti">
|
||||||
<div>
|
<div>
|
||||||
{finish === undefined &&
|
{finish === undefined &&
|
||||||
|
@ -212,6 +228,22 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StrikesDisplay({ strikes }: { strikes: number }) {
|
||||||
|
return <div>
|
||||||
|
{[...Array(strikes)].map(_ => <span key={Math.random()} className="mr-2">❌</span>)}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
function failMessage(progress: Progress | undefined, finish: "time out" | { msg: "fail", answer: JSX.Element }): string {
|
||||||
const pDone = progress ? getPercentageDone(progress) : 0;
|
const pDone = progress ? getPercentageDone(progress) : 0;
|
||||||
const { message, face } = pDone < 20
|
const { message, face } = pDone < 20
|
||||||
|
|
|
@ -168,8 +168,8 @@ const situations: Situation[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const amount = 17;
|
const amount = 15;
|
||||||
const timeLimit = 90;
|
const timeLimit = 100;
|
||||||
|
|
||||||
type Question = {
|
type Question = {
|
||||||
EPS: T.EPSelectionComplete,
|
EPS: T.EPSelectionComplete,
|
||||||
|
|
Loading…
Reference in New Issue