more stuff on game format
This commit is contained in:
parent
09c0958328
commit
16066df914
|
@ -1,133 +0,0 @@
|
|||
import React, { useState, useRef } from "react";
|
||||
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
||||
import Reward, { RewardElement } from 'react-rewards';
|
||||
import "./timer.css";
|
||||
import {
|
||||
getPercentageDone,
|
||||
} from "../lib/game-utils";
|
||||
const errorVibration = 200;
|
||||
|
||||
function Game<T>({ questions, Display, timeLimit, Instructions }: GameInput<T>) {
|
||||
const rewardRef = useRef<RewardElement | null>(null);
|
||||
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
|
||||
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
||||
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
||||
const [timerKey, setTimerKey] = useState<number>(1);
|
||||
|
||||
function handleCallback(correct: boolean) {
|
||||
if (correct) handleAdvance();
|
||||
else handleFailure();
|
||||
}
|
||||
function handleFailure() {
|
||||
// rewardRef.current?.punishMe();
|
||||
setFinish("fail");
|
||||
navigator.vibrate(errorVibration);
|
||||
}
|
||||
function handleAdvance() {
|
||||
const next = questionBox.next();
|
||||
if (next.done) handleFinish();
|
||||
else setCurrent(next.value);
|
||||
}
|
||||
function handleFinish() {
|
||||
// post results
|
||||
setFinish("pass");
|
||||
rewardRef.current?.rewardMe();
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleQuit() {
|
||||
setFinish(null);
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleRestart() {
|
||||
const newQuestionBox = questions();
|
||||
const { value } = newQuestionBox.next();
|
||||
// just for type safety -- the generator will have at least one question
|
||||
if (!value) return;
|
||||
setQuestionBox(newQuestionBox);
|
||||
setFinish(null);
|
||||
setCurrent(value);
|
||||
setTimerKey(prev => prev + 1);
|
||||
}
|
||||
function handleTimeOut() {
|
||||
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"
|
||||
: finish === "fail"
|
||||
? "danger"
|
||||
: "primary";
|
||||
return <div className="text-center" style={{ minHeight: "200px" }}>
|
||||
<div className="progress" style={{ height: "5px" }}>
|
||||
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
||||
</div>
|
||||
{current && <div className="d-flex flex-row-reverse mt-2">
|
||||
<CountdownCircleTimer
|
||||
key={timerKey}
|
||||
isPlaying={!!current && !finish}
|
||||
size={30}
|
||||
strokeWidth={3}
|
||||
strokeLinecap="square"
|
||||
duration={timeLimit}
|
||||
colors="#555555"
|
||||
onComplete={handleTimeOut}
|
||||
/>
|
||||
{!finish && <button onClick={handleQuit} className="btn btn-outline-secondary btn-sm mr-2">Quit</button>}
|
||||
</div>}
|
||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 125 }} type="confetti">
|
||||
<div className="py-3">
|
||||
{finish === null &&
|
||||
(current
|
||||
? <div>
|
||||
<Display question={current.question} callback={handleCallback} />
|
||||
</div>
|
||||
: <div>
|
||||
<div className="pt-3">
|
||||
{/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/}
|
||||
<Instructions />
|
||||
</div>
|
||||
<div>
|
||||
<button className="btn btn-primary mt-4" onClick={handleRestart}>Start</button>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
{finish === "pass" && <div>
|
||||
<h4 className="mt-4">
|
||||
<span role="img" aria-label="celebration">🎉</span> Finished!
|
||||
</h4>
|
||||
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>}
|
||||
{(finish === "fail" || finish === "time out") && <div>
|
||||
<h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>
|
||||
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>}
|
||||
</div>
|
||||
</Reward>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function failMessage(progress: Progress | undefined, finish: "time out" | "fail"): 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 finish === "fail"
|
||||
? `${message} ${face}`
|
||||
: `⏳ Time's Up ${face}`;
|
||||
}
|
||||
|
||||
export default Game;
|
|
@ -8,7 +8,7 @@ import {
|
|||
Examples,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import genderColors from "../../lib/gender-colors";
|
||||
import GenderGame from "../../components/GenderGame";
|
||||
import GenderGame from "../../games/GenderGame";
|
||||
import { firstVariation } from "../../lib/text-tools";
|
||||
import GenderTable from "../../components/GenderTable";
|
||||
import Link from "../../components/Link";
|
||||
|
@ -109,8 +109,6 @@ All nouns in Pashto are either <Masc /> or <Fem />. Thankfully, you can pretty m
|
|||
- Words ending in <InlinePs opts={opts} ps={{p:"و", f:"oo"}} /> can be either <Masc /> or <Fem />.
|
||||
- Words ending in <InlinePs opts={opts} ps={{ p: "ـه", f: "u" }} /> can also be <Fem /> plural, as in <InlinePs opts={opts} ps={{ p: "اوبه", f: "oobu", e: "water" }} />
|
||||
|
||||
**🕹 Here's a little game to practice identifying the genders of words:**
|
||||
|
||||
<GenderGame level={1} />
|
||||
|
||||
## Exceptions
|
||||
|
@ -166,6 +164,4 @@ Some words are used to describe people who obviously have a gender and they *tot
|
|||
},
|
||||
]} />
|
||||
|
||||
**🕹 Let's try the game again, this time with the exceptions included:**
|
||||
|
||||
<GenderGame level={2} />
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
import Table from "../../components/Table";
|
||||
import Link from "../../components/Link";
|
||||
import GenderTable from "../../components/GenderTable";
|
||||
import UnisexNounGame from "../../components/UnisexNounGame";
|
||||
import UnisexNounGame from "../../games/UnisexNounGame";
|
||||
|
||||
There are many words for people and animals in Pashto that can be used in both masculine and feminine forms.
|
||||
|
||||
|
@ -64,10 +64,10 @@ With the feminine form the <InlinePs opts={opts} ps={{ p: "ی", f: "ey" }} /> on
|
|||
},
|
||||
{
|
||||
masc: {
|
||||
ex: {p: "ښووونکی", f:"xowóonkey", e: "male teacher 👨🏫" },
|
||||
ex: {p: "ښوونکی", f:"xUwóonkey", e: "male teacher 👨🏫" },
|
||||
},
|
||||
fem: {
|
||||
ex: { p: "ښووونکې", f: "xowóonke", e: "female teacher 👩🏫" },
|
||||
ex: { p: "ښوونکې", f: "xUwóonke", e: "female teacher 👩🏫" },
|
||||
},
|
||||
},
|
||||
]} />
|
||||
|
@ -209,8 +209,6 @@ If the accent comes on the end of the word, the femine form is a little differen
|
|||
}
|
||||
]} />
|
||||
|
||||
**🕹 Here's a game to practice changing the genders of nouns**
|
||||
|
||||
<UnisexNounGame />
|
||||
|
||||
<!--
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
import React, { useState, useRef } from "react";
|
||||
import { CountdownCircleTimer } from "react-countdown-circle-timer";
|
||||
import Reward, { RewardElement } from 'react-rewards';
|
||||
import Link from "../components/Link";
|
||||
import "./timer.css";
|
||||
import {
|
||||
getPercentageDone,
|
||||
} from "../lib/game-utils";
|
||||
const errorVibration = 200;
|
||||
|
||||
function Game<T>({ questions, Display, timeLimit, Instructions, studyLink, label }: GameInput<T>) {
|
||||
const rewardRef = useRef<RewardElement | null>(null);
|
||||
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
|
||||
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
||||
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
||||
const [timerKey, setTimerKey] = useState<number>(1);
|
||||
|
||||
function handleCallback(correct: boolean) {
|
||||
if (correct) handleAdvance();
|
||||
else handleFailure();
|
||||
}
|
||||
function handleFailure() {
|
||||
// rewardRef.current?.punishMe();
|
||||
setFinish("fail");
|
||||
navigator.vibrate(errorVibration);
|
||||
}
|
||||
function handleAdvance() {
|
||||
const next = questionBox.next();
|
||||
if (next.done) handleFinish();
|
||||
else setCurrent(next.value);
|
||||
}
|
||||
function handleFinish() {
|
||||
// post results
|
||||
setFinish("pass");
|
||||
rewardRef.current?.rewardMe();
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleQuit() {
|
||||
setFinish(null);
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleRestart() {
|
||||
const newQuestionBox = questions();
|
||||
const { value } = newQuestionBox.next();
|
||||
// just for type safety -- the generator will have at least one question
|
||||
if (!value) return;
|
||||
setQuestionBox(newQuestionBox);
|
||||
setFinish(null);
|
||||
setCurrent(value);
|
||||
setTimerKey(prev => prev + 1);
|
||||
}
|
||||
function handleTimeOut() {
|
||||
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"
|
||||
: finish === "fail"
|
||||
? "danger"
|
||||
: "primary";
|
||||
return <div>
|
||||
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {label}</h4>
|
||||
<div className="text-center" style={{ minHeight: "200px" }}>
|
||||
<div className="progress" style={{ height: "5px" }}>
|
||||
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
||||
</div>
|
||||
{current && <div className="d-flex flex-row-reverse mt-2">
|
||||
<CountdownCircleTimer
|
||||
key={timerKey}
|
||||
isPlaying={!!current && !finish}
|
||||
size={30}
|
||||
strokeWidth={3}
|
||||
strokeLinecap="square"
|
||||
duration={timeLimit}
|
||||
colors="#555555"
|
||||
onComplete={handleTimeOut}
|
||||
/>
|
||||
{!finish && <button onClick={handleQuit} className="btn btn-outline-secondary btn-sm mr-2">Quit</button>}
|
||||
</div>}
|
||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 125 }} type="confetti">
|
||||
<div className="py-3">
|
||||
{finish === null &&
|
||||
(current
|
||||
? <div>
|
||||
<Display question={current.question} callback={handleCallback} />
|
||||
</div>
|
||||
: <div>
|
||||
<div className="pt-3">
|
||||
{/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/}
|
||||
<Instructions />
|
||||
</div>
|
||||
<div>
|
||||
<button className="btn btn-primary mt-4" onClick={handleRestart}>Start</button>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
{finish === "pass" && <div>
|
||||
<h4 className="mt-4">
|
||||
<span role="img" aria-label="celebration">🎉</span> Finished!
|
||||
</h4>
|
||||
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>}
|
||||
{(finish === "fail" || finish === "time out") && <div>
|
||||
<h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>
|
||||
<div>
|
||||
<button className="btn btn-secondary my-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>
|
||||
<div>
|
||||
<Link to={studyLink}>
|
||||
<button className="btn btn-outline-secondary"><span role="img" aria-label="">📚</span> Study more</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</Reward>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function failMessage(progress: Progress | undefined, finish: "time out" | "fail"): 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 finish === "fail"
|
||||
? `${message} ${face}`
|
||||
: `⏳ Time's Up ${face}`;
|
||||
}
|
||||
|
||||
export default Game;
|
|
@ -101,6 +101,8 @@ export default function({level}: { level: 1 | 2 }) {
|
|||
}
|
||||
|
||||
return <Game
|
||||
label={level === 1 ? "Choose the right gender - Level 1" : "Choose the right gender - Level 2"}
|
||||
studyLink={level === 1 ? "/nouns/nouns-gender#gender-by-ending" : "/nouns/nouns-gender#exceptions"}
|
||||
questions={questions}
|
||||
Display={Display}
|
||||
timeLimit={level === 1 ? 65 : 85}
|
|
@ -120,6 +120,8 @@ export default function() {
|
|||
}
|
||||
|
||||
return <Game
|
||||
label="Changing genders on unisex nouns"
|
||||
studyLink="/nouns/nouns-unisex#"
|
||||
questions={questions}
|
||||
Display={Display}
|
||||
timeLimit={130}
|
|
@ -16,6 +16,8 @@ type QuestionDisplayProps<T> = {
|
|||
};
|
||||
|
||||
type GameInput<T> = {
|
||||
label: string,
|
||||
studyLink: string,
|
||||
Instructions: (props: { opts?: import("@lingdocs/pashto-inflector").Types.TextOptions }) => JSX.Element,
|
||||
questions: () => QuestionGenerator<T>,
|
||||
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
||||
|
|
Loading…
Reference in New Issue