some rough equative games
This commit is contained in:
parent
77b500693b
commit
2bd4a09f43
|
@ -5,7 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||||
"@lingdocs/lingdocs-main": "^0.2.0",
|
"@lingdocs/lingdocs-main": "^0.2.0",
|
||||||
"@lingdocs/pashto-inflector": "^2.4.3",
|
"@lingdocs/pashto-inflector": "^2.4.9",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
|
|
@ -24,6 +24,10 @@ import {
|
||||||
addToForm,
|
addToForm,
|
||||||
InlinePs,
|
InlinePs,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
import {
|
||||||
|
equativeGamePresent,
|
||||||
|
} from "../../games/games";
|
||||||
|
import GameDisplay from "../../games/GameDisplay";
|
||||||
|
|
||||||
The [equative](https://en.wikipedia.org/wiki/Equative) might be the most basic way of joining words together. We use it to say that something *is/equals* something else. It's kind of like an equals "=" sign in math.
|
The [equative](https://en.wikipedia.org/wiki/Equative) might be the most basic way of joining words together. We use it to say that something *is/equals* something else. It's kind of like an equals "=" sign in math.
|
||||||
|
|
||||||
|
@ -212,3 +216,5 @@ When you're asking a question, all you have to do is change the intonation. You
|
||||||
<Examples opts={defaultTextOptions}>{[
|
<Examples opts={defaultTextOptions}>{[
|
||||||
{ p: "خو ته یې افعان", f: "kho tu ye afgháan", e: "But you are Afghan!" }
|
{ p: "خو ته یې افعان", f: "kho tu ye afgháan", e: "But you are Afghan!" }
|
||||||
]}</Examples>
|
]}</Examples>
|
||||||
|
|
||||||
|
<GameDisplay record={equativeGamePresent} />
|
||||||
|
|
|
@ -20,31 +20,30 @@ import {
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
const errorVibration = 200;
|
const errorVibration = 200;
|
||||||
|
|
||||||
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id }:{
|
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id, onStartStop }:{
|
||||||
id: string,
|
id: string,
|
||||||
studyLink: string,
|
studyLink: string,
|
||||||
Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element,
|
Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element,
|
||||||
questions: () => QuestionGenerator<T>,
|
questions: () => QuestionGenerator<T>,
|
||||||
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
||||||
timeLimit: number;
|
timeLimit: number;
|
||||||
|
onStartStop: (a: "start" | "stop") => void,
|
||||||
}) {
|
}) {
|
||||||
// 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 { user, pullUser, setUser } = useUser();
|
const { user, pullUser, setUser } = useUser();
|
||||||
const [finish, setFinish] = useState<null | "pass" | "fail" | "time out">(null);
|
const [finish, setFinish] = useState<undefined | "pass" | { msg: "fail", answer: JSX.Element } | "time out">(undefined);
|
||||||
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);
|
||||||
|
|
||||||
function handleCallback(correct: boolean) {
|
function handleCallback(correct: true | JSX.Element) {
|
||||||
if (correct) handleAdvance();
|
if (correct === true) handleAdvance();
|
||||||
else handleFailure();
|
else {
|
||||||
}
|
setFinish({ msg: "fail", answer: correct });
|
||||||
function handleFailure() {
|
|
||||||
// rewardRef.current?.punishMe();
|
|
||||||
setFinish("fail");
|
|
||||||
navigator.vibrate(errorVibration);
|
navigator.vibrate(errorVibration);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function handleAdvance() {
|
function handleAdvance() {
|
||||||
const next = questionBox.next();
|
const next = questionBox.next();
|
||||||
if (next.done) handleFinish();
|
if (next.done) handleFinish();
|
||||||
|
@ -69,6 +68,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
function handleFinish() {
|
function handleFinish() {
|
||||||
|
onStartStop("stop");
|
||||||
setFinish("pass");
|
setFinish("pass");
|
||||||
rewardRef.current?.rewardMe();
|
rewardRef.current?.rewardMe();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
@ -80,20 +80,23 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
handleResult(result);
|
handleResult(result);
|
||||||
}
|
}
|
||||||
function handleQuit() {
|
function handleQuit() {
|
||||||
setFinish(null);
|
onStartStop("stop");
|
||||||
|
setFinish(undefined);
|
||||||
setCurrent(undefined);
|
setCurrent(undefined);
|
||||||
}
|
}
|
||||||
function handleRestart() {
|
function handleRestart() {
|
||||||
|
onStartStop("start");
|
||||||
const newQuestionBox = questions();
|
const newQuestionBox = questions();
|
||||||
const { value } = newQuestionBox.next();
|
const { value } = newQuestionBox.next();
|
||||||
// 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);
|
||||||
setFinish(null);
|
setFinish(undefined);
|
||||||
setCurrent(value);
|
setCurrent(value);
|
||||||
setTimerKey(prev => prev + 1);
|
setTimerKey(prev => prev + 1);
|
||||||
}
|
}
|
||||||
function handleTimeOut() {
|
function handleTimeOut() {
|
||||||
|
onStartStop("stop");
|
||||||
setFinish("time out");
|
setFinish("time out");
|
||||||
navigator.vibrate(errorVibration);
|
navigator.vibrate(errorVibration);
|
||||||
}
|
}
|
||||||
|
@ -107,7 +110,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
}
|
}
|
||||||
const progressColor = finish === "pass"
|
const progressColor = finish === "pass"
|
||||||
? "success"
|
? "success"
|
||||||
: finish === "fail"
|
: typeof finish === "object"
|
||||||
? "danger"
|
? "danger"
|
||||||
: "primary";
|
: "primary";
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -130,7 +133,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
</div>}
|
</div>}
|
||||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 125 }} type="confetti">
|
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 125 }} type="confetti">
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
{finish === null &&
|
{finish === undefined &&
|
||||||
(current
|
(current
|
||||||
? <div>
|
? <div>
|
||||||
<Display question={current.question} callback={handleCallback} />
|
<Display question={current.question} callback={handleCallback} />
|
||||||
|
@ -151,8 +154,12 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
</h4>
|
</h4>
|
||||||
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
||||||
</div>}
|
</div>}
|
||||||
{(finish === "fail" || finish === "time out") && <div>
|
{(typeof finish === "object" || finish === "time out") && <div>
|
||||||
<h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>
|
<h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>
|
||||||
|
{typeof finish === "object" && <div>
|
||||||
|
<div>The correct answer was:</div>
|
||||||
|
{finish?.answer}
|
||||||
|
</div>}
|
||||||
<div>
|
<div>
|
||||||
<button className="btn btn-secondary my-4" onClick={handleRestart}>Try Again</button>
|
<button className="btn btn-secondary my-4" onClick={handleRestart}>Try Again</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -168,7 +175,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function failMessage(progress: Progress | undefined, finish: "time out" | "fail"): 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
|
||||||
? { message: "No, sorry", face: "😑" }
|
? { message: "No, sorry", face: "😑" }
|
||||||
|
@ -179,7 +186,7 @@ function failMessage(progress: Progress | undefined, finish: "time out" | "fail"
|
||||||
: pDone < 78
|
: pDone < 78
|
||||||
? { message: "You almost got it!", face: "😩" }
|
? { message: "You almost got it!", face: "😩" }
|
||||||
: { message: "Nooo! So close!", face: "😭" };
|
: { message: "Nooo! So close!", face: "😭" };
|
||||||
return finish === "fail"
|
return typeof finish === "object"
|
||||||
? `${message} ${face}`
|
? `${message} ${face}`
|
||||||
: `⏳ Time's Up ${face}`;
|
: `⏳ Time's Up ${face}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,33 @@
|
||||||
import { useUser } from "../user-context";
|
import { useUser } from "../user-context";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
function GameDisplay({ record: { title, Game, id } }: { record: GameRecord }) {
|
function GameDisplay({ record: { title, Game, id } }: { record: GameRecord }) {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
const [running, setRunning] = useState<boolean>(false);
|
||||||
const completed = user?.tests.some((t) => (
|
const completed = user?.tests.some((t) => (
|
||||||
// TODO: Or if it's in the locally stored (unposted test results)
|
// TODO: Or if it's in the locally stored (unposted test results)
|
||||||
(t.done === true) && (t.id === id)
|
(t.done === true) && (t.id === id)
|
||||||
));
|
));
|
||||||
return <div>
|
function onStartStop(a: "start" | "stop") {
|
||||||
|
if (a === "start" && !running) {
|
||||||
|
setRunning(true);
|
||||||
|
}
|
||||||
|
if (a === "stop") {
|
||||||
|
setRunning(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
{running && <div style={{
|
||||||
|
position: "absolute",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 0.3)",
|
||||||
|
backdropFilter: "blur(7px)",
|
||||||
|
top: "0px",
|
||||||
|
left: "0px",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
zIndex: 2,
|
||||||
|
}}></div>}
|
||||||
|
<div style={{ position: "relative", zIndex: 9999 }}>
|
||||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
|
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
|
||||||
|
@ -15,8 +36,9 @@ function GameDisplay({ record: { title, Game, id } }: { record: GameRecord }) {
|
||||||
<h4>{completed ? "✅" : ""}</h4>
|
<h4>{completed ? "✅" : ""}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Game />
|
{Game(onStartStop)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GameDisplay;
|
export default GameDisplay;
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import games from "./games";
|
import games from "./games";
|
||||||
import { useUser } from "../user-context";
|
import { useUser } from "../user-context";
|
||||||
import Link from "../components/Link";
|
import Link from "../components/Link";
|
||||||
|
@ -37,7 +37,7 @@ function GamesBrowser() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SmoothCollapse expanded={open}>
|
<SmoothCollapse expanded={open}>
|
||||||
<Game />
|
{Game(() => null)}
|
||||||
</SmoothCollapse>
|
</SmoothCollapse>
|
||||||
</div>
|
</div>
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import EquativeGame from "./sub-cores/EquativeGame";
|
||||||
import GenderGame from "./sub-cores/GenderGame";
|
import GenderGame from "./sub-cores/GenderGame";
|
||||||
import UnisexNounGame from "./sub-cores/UnisexNounGame";
|
import UnisexNounGame from "./sub-cores/UnisexNounGame";
|
||||||
|
|
||||||
|
@ -5,7 +6,7 @@ function makeGameRecord(
|
||||||
title: string,
|
title: string,
|
||||||
id: string,
|
id: string,
|
||||||
studyLink: string,
|
studyLink: string,
|
||||||
game: (id: string, link: string) => (() => JSX.Element),
|
game: (id: string, link: string) => ((s: (a: "start" | "stop") => void) => JSX.Element),
|
||||||
): GameRecord {
|
): GameRecord {
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
|
@ -19,19 +20,68 @@ export const nounGenderGame1 = makeGameRecord(
|
||||||
"Identify Noun Genders - Level 1",
|
"Identify Noun Genders - Level 1",
|
||||||
"gender-nouns-1",
|
"gender-nouns-1",
|
||||||
"/nouns/nouns-gender#gender-by-ending",
|
"/nouns/nouns-gender#gender-by-ending",
|
||||||
(id, link) => () => <GenderGame id={id} level={1} link={link} />,
|
(id, link) => (s: (a: "start" | "stop") => void) => <GenderGame id={id} level={1} link={link} onStartStop={s} />,
|
||||||
);
|
);
|
||||||
export const nounGenderGame2 = makeGameRecord(
|
export const nounGenderGame2 = makeGameRecord(
|
||||||
"Identify Noun Genders - Level 2",
|
"Identify Noun Genders - Level 2",
|
||||||
"gender-nouns-2",
|
"gender-nouns-2",
|
||||||
"/nouns/nouns-gender#exceptions",
|
"/nouns/nouns-gender#exceptions",
|
||||||
(id, link) => () => <GenderGame id={id} level={2} link={link} />,
|
(id, link) => (s: (a: "start" | "stop") => void) => <GenderGame id={id} level={2} link={link} onStartStop={s} />,
|
||||||
);
|
);
|
||||||
export const unisexNounGame = makeGameRecord(
|
export const unisexNounGame = makeGameRecord(
|
||||||
"Changing genders on unisex nouns",
|
"Changing genders on unisex nouns",
|
||||||
"unisex-nouns-1",
|
"unisex-nouns-1",
|
||||||
"/nouns/nouns-unisex/",
|
"/nouns/nouns-unisex/",
|
||||||
(id, link) => () => <UnisexNounGame id={id} link={link} />,
|
(id, link) => (s: (a: "start" | "stop") => void) => <UnisexNounGame id={id} link={link} onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGamePresent = makeGameRecord(
|
||||||
|
"Write the present equative",
|
||||||
|
"equative-present",
|
||||||
|
"/equatives/present-equative/",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="present" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGameHabitual = makeGameRecord(
|
||||||
|
"Write the habitual equative",
|
||||||
|
"equative-habitual",
|
||||||
|
"/equatives/habitual-equative/",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="habitual" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGameSubjunctive = makeGameRecord(
|
||||||
|
"Write the subjunctive equative",
|
||||||
|
"equative-subjunctive",
|
||||||
|
"/equatives/other-equatives/#subjunctive-equative",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="subjunctive" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGameFuture = makeGameRecord(
|
||||||
|
"Write the future equative",
|
||||||
|
"equative-future",
|
||||||
|
"/equatives/other-equatives/#future-equative",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="future" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGamePast = makeGameRecord(
|
||||||
|
"Write the past equative",
|
||||||
|
"equative-past",
|
||||||
|
"/equatives/other-equatives/#past-equative",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="past" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGameWouldBe = makeGameRecord(
|
||||||
|
'Write the "would be" equative',
|
||||||
|
"equative-would-be",
|
||||||
|
"equatives/other-equatives/#would-be-equative",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="wouldBe" onStartStop={s} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const equativeGamePastSubjunctive = makeGameRecord(
|
||||||
|
'Write the past subjunctive equative',
|
||||||
|
"equative-past-subjunctive",
|
||||||
|
"/equatives/other-equatives/#past-subjunctive",
|
||||||
|
(id, link) => (s: (a: "start" | "stop") => void) => <EquativeGame id={id} link={link} tense="pastSubjunctive" onStartStop={s} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
const games: { chapter: string, items: GameRecord[] }[] = [
|
const games: { chapter: string, items: GameRecord[] }[] = [
|
||||||
|
@ -43,6 +93,18 @@ const games: { chapter: string, items: GameRecord[] }[] = [
|
||||||
unisexNounGame,
|
unisexNounGame,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
chapter: "Equatives",
|
||||||
|
items: [
|
||||||
|
equativeGamePresent,
|
||||||
|
equativeGameHabitual,
|
||||||
|
equativeGameSubjunctive,
|
||||||
|
equativeGameFuture,
|
||||||
|
equativeGamePast,
|
||||||
|
equativeGameWouldBe,
|
||||||
|
equativeGamePastSubjunctive,
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default games;
|
export default games;
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
makeProgress,
|
||||||
|
} from "../../lib/game-utils";
|
||||||
|
import GameCore from "../GameCore";
|
||||||
|
import {
|
||||||
|
Types as T,
|
||||||
|
Examples,
|
||||||
|
defaultTextOptions as opts,
|
||||||
|
standardizePashto,
|
||||||
|
typePredicates as tp,
|
||||||
|
makeNounSelection,
|
||||||
|
randFromArray,
|
||||||
|
renderEP,
|
||||||
|
compileEP,
|
||||||
|
flattenLengths,
|
||||||
|
randomPerson,
|
||||||
|
InlinePs,
|
||||||
|
grammarUnits,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
|
||||||
|
const kidsColor = "#017BFE";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const nouns: T.NounEntry[] = [
|
||||||
|
{"ts":1527815251,"i":7790,"p":"سړی","f":"saRéy","g":"saRey","e":"man","c":"n. m.","ec":"man","ep":"men"},
|
||||||
|
{"ts":1527812797,"i":8605,"p":"ښځه","f":"xúdza","g":"xudza","e":"woman, wife","c":"n. f.","ec":"woman","ep":"women"},
|
||||||
|
{"ts":1527812881,"i":11691,"p":"ماشوم","f":"maashoom","g":"maashoom","e":"child, kid","c":"n. m. anim. unisex","ec":"child","ep":"children"},
|
||||||
|
{"ts":1527815197,"i":2503,"p":"پښتون","f":"puxtoon","g":"puxtoon","e":"Pashtun","c":"n. m. anim. unisex / adj.","infap":"پښتانه","infaf":"puxtaanu","infbp":"پښتن","infbf":"puxtan"},
|
||||||
|
{"ts":1527815737,"i":484,"p":"استاذ","f":"Ustaaz","g":"Ustaaz","e":"teacher, professor, expert, master (in a field)","c":"n. m. anim. unisex anim.","ec":"teacher"},
|
||||||
|
].filter(tp.isNounEntry);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const adjectives: T.AdjectiveEntry[] = [
|
||||||
|
{"ts":1527815306,"i":7582,"p":"ستړی","f":"stúRey","g":"stuRey","e":"tired","c":"adj."},
|
||||||
|
{"ts":1527812625,"i":9116,"p":"غټ","f":"ghuT, ghaT","g":"ghuT,ghaT","e":"big, fat","c":"adj."},
|
||||||
|
{"ts":1527812792,"i":5817,"p":"خوشاله","f":"khoshaala","g":"khoshaala","e":"happy, glad","c":"adj."},
|
||||||
|
{"ts":1527812796,"i":8641,"p":"ښه","f":"xu","g":"xu","e":"good","c":"adj."},
|
||||||
|
{"ts":1527812798,"i":5636,"p":"خفه","f":"khúfa","g":"khufa","e":"sad, upset, angry; choked, suffocated","c":"adj."},
|
||||||
|
].filter(tp.isAdjectiveEntry);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const locAdverbs: T.LocativeAdverbEntry[] = [
|
||||||
|
{"ts":1527812558,"i":6241,"p":"دلته","f":"dălta","g":"dalta","e":"here","c":"loc. adv."},
|
||||||
|
{"ts":1527812449,"i":13937,"p":"هلته","f":"hálta, álta","g":"halta,alta","e":"there","c":"loc. adv."},
|
||||||
|
].filter(tp.isLocativeAdverbEntry);
|
||||||
|
|
||||||
|
const amount = 20;
|
||||||
|
const timeLimit = 55;
|
||||||
|
|
||||||
|
type Question = {
|
||||||
|
phrase: { ps: T.PsString, e?: string[] },
|
||||||
|
equative: T.EquativeRendered,
|
||||||
|
};
|
||||||
|
|
||||||
|
const pronounTypes = [
|
||||||
|
[T.Person.FirstSingMale, T.Person.FirstSingFemale],
|
||||||
|
[T.Person.SecondSingMale, T.Person.SecondSingFemale],
|
||||||
|
[T.Person.ThirdSingMale],
|
||||||
|
[T.Person.ThirdSingFemale],
|
||||||
|
[T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
|
||||||
|
[T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
|
||||||
|
[T.Person.ThirdPlurMale, T.Person.ThirdPlurFemale],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EquativeGame({ id, link, tense, onStartStop }: { id: string, link: string, tense: T.EquativeTense, onStartStop: (a: "start" | "stop") => void }) {
|
||||||
|
function* questions (): Generator<Current<Question>> {
|
||||||
|
let pool = [...pronounTypes];
|
||||||
|
function makeRandPronoun(): T.PronounSelection {
|
||||||
|
let person: T.Person;
|
||||||
|
console.log(pool);
|
||||||
|
do {
|
||||||
|
person = randomPerson();
|
||||||
|
// eslint-disable-next-line
|
||||||
|
} while (!pool.some(p => p.includes(person)));
|
||||||
|
pool = pool.filter(p => !p.includes(person));
|
||||||
|
if (pool.length === 0) {
|
||||||
|
pool = pronounTypes;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: "pronoun",
|
||||||
|
distance: "far",
|
||||||
|
person,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function makeRandomNoun(): T.NounSelection {
|
||||||
|
const n = makeNounSelection(randFromArray(nouns), undefined);
|
||||||
|
return {
|
||||||
|
...n,
|
||||||
|
gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender,
|
||||||
|
number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (let i = 0; i < amount; i++) {
|
||||||
|
console.log("one factory call");
|
||||||
|
const subj = randFromArray([
|
||||||
|
makeRandPronoun,
|
||||||
|
makeRandPronoun,
|
||||||
|
makeRandomNoun,
|
||||||
|
makeRandPronoun,
|
||||||
|
])();
|
||||||
|
const pred = randFromArray([...adjectives, ...locAdverbs]);
|
||||||
|
const EPS = makeEPS(subj, pred, tense);
|
||||||
|
const rendered = renderEP(EPS);
|
||||||
|
const compiled = compileEP(rendered, true, { equative: true, ba: false, kidsSection: true });
|
||||||
|
const phrase = {
|
||||||
|
ps: compiled.ps[0],
|
||||||
|
e: compiled.e,
|
||||||
|
};
|
||||||
|
yield {
|
||||||
|
progress: makeProgress(i, amount),
|
||||||
|
question: {
|
||||||
|
phrase,
|
||||||
|
equative: rendered.equative,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||||
|
const [answer, setAnswer] = useState<string>("");
|
||||||
|
const [withBa, setWithBa] = useState<boolean>(false);
|
||||||
|
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setAnswer(value);
|
||||||
|
}
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const given = standardizePashto(answer.trim());
|
||||||
|
const correct = checkAnswer(given, question.equative.ps)
|
||||||
|
&& withBa === question.equative.hasBa;
|
||||||
|
if (correct) {
|
||||||
|
setAnswer("");
|
||||||
|
}
|
||||||
|
callback(!correct
|
||||||
|
? <div>
|
||||||
|
<div className="my-2">
|
||||||
|
{flattenLengths(question.equative.ps).reduce(((accum, curr, i): JSX.Element[] => (
|
||||||
|
[
|
||||||
|
...accum,
|
||||||
|
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||||
|
<span>{curr.p}</span>,
|
||||||
|
]
|
||||||
|
)), [] as JSX.Element[])}
|
||||||
|
</div>
|
||||||
|
<div><strong>{question.equative.hasBa ? "with" : "without"}</strong> a <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids' section.</div>
|
||||||
|
</div>
|
||||||
|
: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<div className="pt-2 pb-1 mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||||
|
{/*
|
||||||
|
@ts-ignore */}
|
||||||
|
<Examples opts={opts} ex={modExs([question.phrase.ps])}></Examples>
|
||||||
|
{question.phrase.e && question.phrase.e.map(e => (
|
||||||
|
<div className="text-muted mb-1">{e}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="my-3" style={{ maxWidth: "200px", margin: "0 auto" }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
dir="auto"
|
||||||
|
value={answer}
|
||||||
|
onChange={handleInput}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="form-check mt-1">
|
||||||
|
<input
|
||||||
|
className="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
checked={withBa}
|
||||||
|
onChange={e => setWithBa(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label text-muted" htmlFor="OSVCheckbox">
|
||||||
|
with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the <span style={{ color: kidsColor }}>kids' section</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="text-center my-2">
|
||||||
|
{/* <div> */}
|
||||||
|
<button className="btn btn-primary" type="submit">check</button>
|
||||||
|
{/* </div> */}
|
||||||
|
{/* <div className="text-muted small text-center mt-2">
|
||||||
|
Type <kbd>Enter</kbd> to check
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Instructions() {
|
||||||
|
return <div>
|
||||||
|
<p className="lead">Fill in the blank with the correct <strong>{humanReadableTense(tense)} equative</strong> <strong>in Pashto script</strong></p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <GameCore
|
||||||
|
onStartStop={onStartStop}
|
||||||
|
studyLink={link}
|
||||||
|
questions={questions}
|
||||||
|
id={id}
|
||||||
|
Display={Display}
|
||||||
|
timeLimit={timeLimit}
|
||||||
|
Instructions={Instructions}
|
||||||
|
/>
|
||||||
|
};
|
||||||
|
|
||||||
|
function modExs(exs: T.PsString[]): { p: JSX.Element, f: JSX.Element }[] {
|
||||||
|
return exs.map(ps => {
|
||||||
|
if (!ps.p.includes(" ___ ")) {
|
||||||
|
return {
|
||||||
|
p: <>{ps.p}</>,
|
||||||
|
f: <>{ps.f}</>,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const splitP = ps.p.split(" ___ ");
|
||||||
|
const splitF = ps.f.split(" ___ ");
|
||||||
|
return {
|
||||||
|
p: <>{splitP[0]} <span style={{ color: kidsColor }}>___</span> {splitP[1]}</>,
|
||||||
|
f: <>{splitF[0]} <span style={{ color: kidsColor }}>___</span> {splitF[1]}</>,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function humanReadableTense(tense: T.EquativeTense): string {
|
||||||
|
return tense === "pastSubjunctive"
|
||||||
|
? "past subjunctive"
|
||||||
|
: tense === "wouldBe"
|
||||||
|
? `"would be"`
|
||||||
|
: tense;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAnswer(given: string, answer: T.SingleOrLengthOpts<T.PsString[]>): boolean {
|
||||||
|
const possible = flattenLengths(answer);
|
||||||
|
return possible.some(x => given === x.p);
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeEPS(subject: T.NPSelection, predicate: T.AdjectiveEntry | T.LocativeAdverbEntry, tense: T.EquativeTense): T.EPSelectionComplete {
|
||||||
|
return {
|
||||||
|
subject,
|
||||||
|
predicate: {
|
||||||
|
type: "Complement",
|
||||||
|
selection: tp.isAdjectiveEntry(predicate) ? {
|
||||||
|
type: "adjective",
|
||||||
|
entry: predicate,
|
||||||
|
} : {
|
||||||
|
type: "loc. adv.",
|
||||||
|
entry: predicate,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
equative: {
|
||||||
|
tense,
|
||||||
|
negative: false,
|
||||||
|
},
|
||||||
|
omitSubject: false,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
getRandomFromList,
|
|
||||||
makeProgress,
|
makeProgress,
|
||||||
} from "../../lib/game-utils";
|
} from "../../lib/game-utils";
|
||||||
import genderColors from "../../lib/gender-colors";
|
import genderColors from "../../lib/gender-colors";
|
||||||
|
@ -14,6 +13,7 @@ import {
|
||||||
isUnisexSet,
|
isUnisexSet,
|
||||||
typePredicates as tp,
|
typePredicates as tp,
|
||||||
firstVariation,
|
firstVariation,
|
||||||
|
randFromArray,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { nouns } from "../../words/words";
|
import { nouns } from "../../words/words";
|
||||||
import { categorize } from "../../lib/categorize";
|
import { categorize } from "../../lib/categorize";
|
||||||
|
@ -86,7 +86,7 @@ const exceptions: Record<string, CategorySet> = {
|
||||||
|
|
||||||
const amount = 35;
|
const amount = 35;
|
||||||
|
|
||||||
export default function GenderGame({level, id, link}: { level: 1 | 2, id: string, link: string }) {
|
export default function GenderGame({level, id, link, onStartStop }: { level: 1 | 2, id: string, link: string, onStartStop: (a: "start" | "stop") => void }) {
|
||||||
function* questions () {
|
function* questions () {
|
||||||
const wordPool = {...types};
|
const wordPool = {...types};
|
||||||
const exceptionsPool = {...exceptions};
|
const exceptionsPool = {...exceptions};
|
||||||
|
@ -94,13 +94,13 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
||||||
for (let i = 0; i < amount; i++) {
|
for (let i = 0; i < amount; i++) {
|
||||||
const base = level === 1
|
const base = level === 1
|
||||||
? wordPool
|
? wordPool
|
||||||
: getRandomFromList([wordPool, exceptionsPool]);
|
: randFromArray([wordPool, exceptionsPool]);
|
||||||
const gender = getRandomFromList(genders);
|
const gender = randFromArray(genders);
|
||||||
let typeToUse: string;
|
let typeToUse: string;
|
||||||
do {
|
do {
|
||||||
typeToUse = getRandomFromList(Object.keys(base[gender]));
|
typeToUse = randFromArray(Object.keys(base[gender]));
|
||||||
} while (!base[gender][typeToUse].length);
|
} while (!base[gender][typeToUse].length);
|
||||||
const question = getRandomFromList(base[gender][typeToUse]);
|
const question = randFromArray(base[gender][typeToUse]);
|
||||||
base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts);
|
base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts);
|
||||||
yield {
|
yield {
|
||||||
progress: makeProgress(i, amount),
|
progress: makeProgress(i, amount),
|
||||||
|
@ -111,7 +111,10 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
||||||
|
|
||||||
function Display({ question, callback }: QuestionDisplayProps<T.DictionaryEntry>) {
|
function Display({ question, callback }: QuestionDisplayProps<T.DictionaryEntry>) {
|
||||||
function check(gender: "m" | "f") {
|
function check(gender: "m" | "f") {
|
||||||
callback(!nounNotIn(gender === "m" ? mascNouns : femNouns)(question));
|
const correct = !nounNotIn(gender === "m" ? mascNouns : femNouns)(question);
|
||||||
|
callback(!correct
|
||||||
|
? <div>ANSWER HERE</div>
|
||||||
|
: true);
|
||||||
}
|
}
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mb-4" style={{ fontSize: "larger" }}>
|
<div className="mb-4" style={{ fontSize: "larger" }}>
|
||||||
|
@ -132,12 +135,13 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
|
||||||
|
|
||||||
function Instructions() {
|
function Instructions() {
|
||||||
return <div>
|
return <div>
|
||||||
<h4>Choose the right gender for each word</h4>
|
<h5>Choose the right gender for each word</h5>
|
||||||
{level === 2 && <div>⚠ Exceptions included...</div>}
|
{level === 2 && <div>⚠ Exceptions included...</div>}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <GameCore
|
return <GameCore
|
||||||
|
onStartStop={onStartStop}
|
||||||
studyLink={link}
|
studyLink={link}
|
||||||
questions={questions}
|
questions={questions}
|
||||||
id={id}
|
id={id}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
getRandomFromList,
|
|
||||||
makeProgress,
|
makeProgress,
|
||||||
compareF,
|
compareF,
|
||||||
} from "../../lib/game-utils";
|
} from "../../lib/game-utils";
|
||||||
|
@ -14,6 +13,7 @@ import {
|
||||||
standardizePashto,
|
standardizePashto,
|
||||||
firstVariation,
|
firstVariation,
|
||||||
typePredicates as tp,
|
typePredicates as tp,
|
||||||
|
randFromArray,
|
||||||
} from "@lingdocs/pashto-inflector";
|
} from "@lingdocs/pashto-inflector";
|
||||||
import { nouns } from "../../words/words";
|
import { nouns } from "../../words/words";
|
||||||
import { intoPatterns } from "../../lib/categorize";
|
import { intoPatterns } from "../../lib/categorize";
|
||||||
|
@ -28,20 +28,20 @@ const amount = 20;
|
||||||
|
|
||||||
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
|
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
|
||||||
|
|
||||||
export default function UnisexNounGame({ id, link }: { id: string, link: string }) {
|
export default function UnisexNounGame({ id, link, onStartStop }: { id: string, link: string, onStartStop: (a: "start" | "stop") => void }) {
|
||||||
function* questions (): Generator<Current<Question>> {
|
function* questions (): Generator<Current<Question>> {
|
||||||
let pool = { ...types };
|
let pool = { ...types };
|
||||||
for (let i = 0; i < amount; i++) {
|
for (let i = 0; i < amount; i++) {
|
||||||
const keys = Object.keys(types) as NType[];
|
const keys = Object.keys(types) as NType[];
|
||||||
let type: NType
|
let type: NType
|
||||||
do {
|
do {
|
||||||
type = getRandomFromList(keys);
|
type = randFromArray(keys);
|
||||||
} while (!pool[type].length);
|
} while (!pool[type].length);
|
||||||
const entry = getRandomFromList<T.UnisexNounEntry>(
|
const entry = randFromArray<T.UnisexNounEntry>(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
pool[type]
|
pool[type]
|
||||||
);
|
);
|
||||||
const gender = getRandomFromList(genders) as T.Gender;
|
const gender = randFromArray(genders) as T.Gender;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
pool[type] = pool[type].filter((x) => x.ts !== entry.ts);
|
pool[type] = pool[type].filter((x) => x.ts !== entry.ts);
|
||||||
yield {
|
yield {
|
||||||
|
@ -84,7 +84,9 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
||||||
if (correct) {
|
if (correct) {
|
||||||
setAnswer("");
|
setAnswer("");
|
||||||
}
|
}
|
||||||
callback(correct);
|
callback(!correct
|
||||||
|
? <div>CORRECT ANSWER HERE</div>
|
||||||
|
: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -120,11 +122,12 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
||||||
|
|
||||||
function Instructions() {
|
function Instructions() {
|
||||||
return <div>
|
return <div>
|
||||||
<h4>Change the gender of a given noun</h4>
|
<h5>Change the gender of a given noun</h5>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <GameCore
|
return <GameCore
|
||||||
|
onStartStop={onStartStop}
|
||||||
studyLink={link}
|
studyLink={link}
|
||||||
questions={questions}
|
questions={questions}
|
||||||
id={id}
|
id={id}
|
||||||
|
|
|
@ -26,10 +26,6 @@ export function getPercentageDone(progress: Progress): number {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomFromList<T>(list: T[]): T {
|
|
||||||
return list[Math.floor((Math.random()*list.length))];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeProgress(i: number, total: number): Progress {
|
export function makeProgress(i: number, total: number): Progress {
|
||||||
return { current: i + 1, total };
|
return { current: i + 1, total };
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ type QuestionGenerator<T> = Generator<Current<T>, void, unknown>;
|
||||||
|
|
||||||
type QuestionDisplayProps<T> = {
|
type QuestionDisplayProps<T> = {
|
||||||
question: T,
|
question: T,
|
||||||
callback: (correct: boolean) => void,
|
callback: (correct: true | JSX.Element) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type GameRecord = {
|
type GameRecord = {
|
||||||
title: string,
|
title: string,
|
||||||
id: string,
|
id: string,
|
||||||
studyLink: string,
|
studyLink: string,
|
||||||
Game: () => JSX.Element,
|
Game: (onStartStop: (a: "start" | "stop") => void) => JSX.Element,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1684,10 +1684,10 @@
|
||||||
pbf "^3.2.1"
|
pbf "^3.2.1"
|
||||||
rambda "^6.7.0"
|
rambda "^6.7.0"
|
||||||
|
|
||||||
"@lingdocs/pashto-inflector@^2.4.2":
|
"@lingdocs/pashto-inflector@^2.4.9":
|
||||||
version "2.4.2"
|
version "2.5.0"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-2.4.2.tgz#584be92d04051a4b3f4e557fa4473671570ed24d"
|
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-2.5.0.tgz#4f90a1d2db15e8389cd84f0a576c37b814f303fd"
|
||||||
integrity sha512-+vjvNOIgVW74+NQMdU2ctAK/ISL37WPiZItpt43izvzNJ85/mREmArzTzMCxbN+kFtYpEwAFe5tRsIJgviOBQQ==
|
integrity sha512-RwlBQNwNsKxvICHjx6MwOwetzGQWLdrwjWOBLMfSqbRqQzNWQrjm/Gp4+TfIT89IUtutxVSxLXlgwgUq6ebKpA==
|
||||||
dependencies:
|
dependencies:
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
jsurl2 "^2.1.0"
|
jsurl2 "^2.1.0"
|
||||||
|
|
Loading…
Reference in New Issue