more stuff on game format

This commit is contained in:
lingdocs 2021-09-11 03:23:29 +04:00
parent 09c0958328
commit 16066df914
8 changed files with 154 additions and 143 deletions

View File

@ -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;

View File

@ -8,7 +8,7 @@ import {
Examples, Examples,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import genderColors from "../../lib/gender-colors"; import genderColors from "../../lib/gender-colors";
import GenderGame from "../../components/GenderGame"; import GenderGame from "../../games/GenderGame";
import { firstVariation } from "../../lib/text-tools"; import { firstVariation } from "../../lib/text-tools";
import GenderTable from "../../components/GenderTable"; import GenderTable from "../../components/GenderTable";
import Link from "../../components/Link"; 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:"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" }} /> - 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} /> <GenderGame level={1} />
## Exceptions ## 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} /> <GenderGame level={2} />

View File

@ -10,7 +10,7 @@ import {
import Table from "../../components/Table"; import Table from "../../components/Table";
import Link from "../../components/Link"; import Link from "../../components/Link";
import GenderTable from "../../components/GenderTable"; 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. 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: { masc: {
ex: {p: "ښووونکی", f:"xowóonkey", e: "male teacher 👨‍🏫" }, ex: {p: "ښوونکی", f:"xUwóonkey", e: "male teacher 👨‍🏫" },
}, },
fem: { 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 /> <UnisexNounGame />
<!-- <!--

144
src/games/Game.tsx Normal file
View File

@ -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;

View File

@ -101,6 +101,8 @@ export default function({level}: { level: 1 | 2 }) {
} }
return <Game 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} questions={questions}
Display={Display} Display={Display}
timeLimit={level === 1 ? 65 : 85} timeLimit={level === 1 ? 65 : 85}

View File

@ -120,6 +120,8 @@ export default function() {
} }
return <Game return <Game
label="Changing genders on unisex nouns"
studyLink="/nouns/nouns-unisex#"
questions={questions} questions={questions}
Display={Display} Display={Display}
timeLimit={130} timeLimit={130}

2
src/types.d.ts vendored
View File

@ -16,6 +16,8 @@ type QuestionDisplayProps<T> = {
}; };
type GameInput<T> = { type GameInput<T> = {
label: string,
studyLink: string,
Instructions: (props: { opts?: import("@lingdocs/pashto-inflector").Types.TextOptions }) => JSX.Element, Instructions: (props: { opts?: import("@lingdocs/pashto-inflector").Types.TextOptions }) => JSX.Element,
questions: () => QuestionGenerator<T>, questions: () => QuestionGenerator<T>,
Display: (props: QuestionDisplayProps<T>) => JSX.Element, Display: (props: QuestionDisplayProps<T>) => JSX.Element,