some rough equative games
This commit is contained in:
parent
77b500693b
commit
2bd4a09f43
|
@ -5,7 +5,7 @@
|
|||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@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/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
|
|
|
@ -24,6 +24,10 @@ import {
|
|||
addToForm,
|
||||
InlinePs,
|
||||
} 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.
|
||||
|
||||
|
@ -212,3 +216,5 @@ When you're asking a question, all you have to do is change the intonation. You
|
|||
<Examples opts={defaultTextOptions}>{[
|
||||
{ p: "خو ته یې افعان", f: "kho tu ye afgháan", e: "But you are Afghan!" }
|
||||
]}</Examples>
|
||||
|
||||
<GameDisplay record={equativeGamePresent} />
|
||||
|
|
|
@ -20,30 +20,29 @@ import {
|
|||
} from "@lingdocs/pashto-inflector";
|
||||
const errorVibration = 200;
|
||||
|
||||
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id }:{
|
||||
function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, id, onStartStop }:{
|
||||
id: string,
|
||||
studyLink: string,
|
||||
Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element,
|
||||
questions: () => QuestionGenerator<T>,
|
||||
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
||||
timeLimit: number;
|
||||
onStartStop: (a: "start" | "stop") => void,
|
||||
}) {
|
||||
// TODO: report pass with id to user info
|
||||
const rewardRef = useRef<RewardElement | null>(null);
|
||||
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 [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 handleCallback(correct: true | JSX.Element) {
|
||||
if (correct === true) handleAdvance();
|
||||
else {
|
||||
setFinish({ msg: "fail", answer: correct });
|
||||
navigator.vibrate(errorVibration);
|
||||
}
|
||||
}
|
||||
function handleAdvance() {
|
||||
const next = questionBox.next();
|
||||
|
@ -69,6 +68,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
}).catch(console.error);
|
||||
}
|
||||
function handleFinish() {
|
||||
onStartStop("stop");
|
||||
setFinish("pass");
|
||||
rewardRef.current?.rewardMe();
|
||||
if (!user) return;
|
||||
|
@ -80,20 +80,23 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
handleResult(result);
|
||||
}
|
||||
function handleQuit() {
|
||||
setFinish(null);
|
||||
onStartStop("stop");
|
||||
setFinish(undefined);
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleRestart() {
|
||||
onStartStop("start");
|
||||
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);
|
||||
setFinish(undefined);
|
||||
setCurrent(value);
|
||||
setTimerKey(prev => prev + 1);
|
||||
}
|
||||
function handleTimeOut() {
|
||||
onStartStop("stop");
|
||||
setFinish("time out");
|
||||
navigator.vibrate(errorVibration);
|
||||
}
|
||||
|
@ -107,7 +110,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
}
|
||||
const progressColor = finish === "pass"
|
||||
? "success"
|
||||
: finish === "fail"
|
||||
: typeof finish === "object"
|
||||
? "danger"
|
||||
: "primary";
|
||||
return <div>
|
||||
|
@ -130,7 +133,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
</div>}
|
||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 125 }} type="confetti">
|
||||
<div className="py-3">
|
||||
{finish === null &&
|
||||
{finish === undefined &&
|
||||
(current
|
||||
? <div>
|
||||
<Display question={current.question} callback={handleCallback} />
|
||||
|
@ -151,8 +154,12 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
</h4>
|
||||
<button className="btn btn-secondary mt-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>}
|
||||
{(finish === "fail" || finish === "time out") && <div>
|
||||
{(typeof finish === "object" || finish === "time out") && <div>
|
||||
<h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>
|
||||
{typeof finish === "object" && <div>
|
||||
<div>The correct answer was:</div>
|
||||
{finish?.answer}
|
||||
</div>}
|
||||
<div>
|
||||
<button className="btn btn-secondary my-4" onClick={handleRestart}>Try Again</button>
|
||||
</div>
|
||||
|
@ -168,7 +175,7 @@ function GameCore<T>({ questions, Display, timeLimit, Instructions, studyLink, i
|
|||
</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 { message, face } = pDone < 20
|
||||
? { message: "No, sorry", face: "😑" }
|
||||
|
@ -179,7 +186,7 @@ function failMessage(progress: Progress | undefined, finish: "time out" | "fail"
|
|||
: pDone < 78
|
||||
? { message: "You almost got it!", face: "😩" }
|
||||
: { message: "Nooo! So close!", face: "😭" };
|
||||
return finish === "fail"
|
||||
return typeof finish === "object"
|
||||
? `${message} ${face}`
|
||||
: `⏳ Time's Up ${face}`;
|
||||
}
|
||||
|
|
|
@ -1,22 +1,44 @@
|
|||
import { useUser } from "../user-context";
|
||||
import { useState } from "react";
|
||||
|
||||
function GameDisplay({ record: { title, Game, id } }: { record: GameRecord }) {
|
||||
const { user } = useUser();
|
||||
const [running, setRunning] = useState<boolean>(false);
|
||||
const completed = user?.tests.some((t) => (
|
||||
// TODO: Or if it's in the locally stored (unposted test results)
|
||||
(t.done === true) && (t.id === id)
|
||||
));
|
||||
return <div>
|
||||
<div className="d-flex flex-row justify-content-between align-items-center">
|
||||
<div>
|
||||
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{completed ? "✅" : ""}</h4>
|
||||
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>
|
||||
<h4 className="my-4"><span role="img" aria-label="">🎮</span> {title}</h4>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{completed ? "✅" : ""}</h4>
|
||||
</div>
|
||||
</div>
|
||||
{Game(onStartStop)}
|
||||
</div>
|
||||
<Game />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export default GameDisplay;
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import games from "./games";
|
||||
import { useUser } from "../user-context";
|
||||
import Link from "../components/Link";
|
||||
|
@ -37,7 +37,7 @@ function GamesBrowser() {
|
|||
</div>
|
||||
</div>
|
||||
<SmoothCollapse expanded={open}>
|
||||
<Game />
|
||||
{Game(() => null)}
|
||||
</SmoothCollapse>
|
||||
</div>
|
||||
})}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import EquativeGame from "./sub-cores/EquativeGame";
|
||||
import GenderGame from "./sub-cores/GenderGame";
|
||||
import UnisexNounGame from "./sub-cores/UnisexNounGame";
|
||||
|
||||
|
@ -5,7 +6,7 @@ function makeGameRecord(
|
|||
title: string,
|
||||
id: string,
|
||||
studyLink: string,
|
||||
game: (id: string, link: string) => (() => JSX.Element),
|
||||
game: (id: string, link: string) => ((s: (a: "start" | "stop") => void) => JSX.Element),
|
||||
): GameRecord {
|
||||
return {
|
||||
title,
|
||||
|
@ -19,19 +20,68 @@ export const nounGenderGame1 = makeGameRecord(
|
|||
"Identify Noun Genders - Level 1",
|
||||
"gender-nouns-1",
|
||||
"/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(
|
||||
"Identify Noun Genders - Level 2",
|
||||
"gender-nouns-2",
|
||||
"/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(
|
||||
"Changing genders on unisex nouns",
|
||||
"unisex-nouns-1",
|
||||
"/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[] }[] = [
|
||||
|
@ -43,6 +93,18 @@ const games: { chapter: string, items: GameRecord[] }[] = [
|
|||
unisexNounGame,
|
||||
],
|
||||
},
|
||||
{
|
||||
chapter: "Equatives",
|
||||
items: [
|
||||
equativeGamePresent,
|
||||
equativeGameHabitual,
|
||||
equativeGameSubjunctive,
|
||||
equativeGameFuture,
|
||||
equativeGamePast,
|
||||
equativeGameWouldBe,
|
||||
equativeGamePastSubjunctive,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
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 {
|
||||
getRandomFromList,
|
||||
makeProgress,
|
||||
} from "../../lib/game-utils";
|
||||
import genderColors from "../../lib/gender-colors";
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
isUnisexSet,
|
||||
typePredicates as tp,
|
||||
firstVariation,
|
||||
randFromArray,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { nouns } from "../../words/words";
|
||||
import { categorize } from "../../lib/categorize";
|
||||
|
@ -86,7 +86,7 @@ const exceptions: Record<string, CategorySet> = {
|
|||
|
||||
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 () {
|
||||
const wordPool = {...types};
|
||||
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++) {
|
||||
const base = level === 1
|
||||
? wordPool
|
||||
: getRandomFromList([wordPool, exceptionsPool]);
|
||||
const gender = getRandomFromList(genders);
|
||||
: randFromArray([wordPool, exceptionsPool]);
|
||||
const gender = randFromArray(genders);
|
||||
let typeToUse: string;
|
||||
do {
|
||||
typeToUse = getRandomFromList(Object.keys(base[gender]));
|
||||
typeToUse = randFromArray(Object.keys(base[gender]));
|
||||
} 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);
|
||||
yield {
|
||||
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 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>
|
||||
<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() {
|
||||
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>}
|
||||
</div>
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
onStartStop={onStartStop}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
id={id}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
getRandomFromList,
|
||||
makeProgress,
|
||||
compareF,
|
||||
} from "../../lib/game-utils";
|
||||
|
@ -14,6 +13,7 @@ import {
|
|||
standardizePashto,
|
||||
firstVariation,
|
||||
typePredicates as tp,
|
||||
randFromArray,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { nouns } from "../../words/words";
|
||||
import { intoPatterns } from "../../lib/categorize";
|
||||
|
@ -28,20 +28,20 @@ const amount = 20;
|
|||
|
||||
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>> {
|
||||
let pool = { ...types };
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const keys = Object.keys(types) as NType[];
|
||||
let type: NType
|
||||
do {
|
||||
type = getRandomFromList(keys);
|
||||
type = randFromArray(keys);
|
||||
} while (!pool[type].length);
|
||||
const entry = getRandomFromList<T.UnisexNounEntry>(
|
||||
const entry = randFromArray<T.UnisexNounEntry>(
|
||||
// @ts-ignore
|
||||
pool[type]
|
||||
);
|
||||
const gender = getRandomFromList(genders) as T.Gender;
|
||||
const gender = randFromArray(genders) as T.Gender;
|
||||
// @ts-ignore
|
||||
pool[type] = pool[type].filter((x) => x.ts !== entry.ts);
|
||||
yield {
|
||||
|
@ -84,7 +84,9 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
|||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
callback(!correct
|
||||
? <div>CORRECT ANSWER HERE</div>
|
||||
: true);
|
||||
}
|
||||
|
||||
return <div>
|
||||
|
@ -120,11 +122,12 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
|
|||
|
||||
function Instructions() {
|
||||
return <div>
|
||||
<h4>Change the gender of a given noun</h4>
|
||||
<h5>Change the gender of a given noun</h5>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
onStartStop={onStartStop}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
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 {
|
||||
return { current: i + 1, total };
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ type QuestionGenerator<T> = Generator<Current<T>, void, unknown>;
|
|||
|
||||
type QuestionDisplayProps<T> = {
|
||||
question: T,
|
||||
callback: (correct: boolean) => void,
|
||||
callback: (correct: true | JSX.Element) => void,
|
||||
};
|
||||
|
||||
type GameRecord = {
|
||||
title: string,
|
||||
id: string,
|
||||
studyLink: string,
|
||||
Game: () => JSX.Element,
|
||||
Game: (onStartStop: (a: "start" | "stop") => void) => JSX.Element,
|
||||
};
|
||||
|
|
|
@ -1684,10 +1684,10 @@
|
|||
pbf "^3.2.1"
|
||||
rambda "^6.7.0"
|
||||
|
||||
"@lingdocs/pashto-inflector@^2.4.2":
|
||||
version "2.4.2"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-2.4.2.tgz#584be92d04051a4b3f4e557fa4473671570ed24d"
|
||||
integrity sha512-+vjvNOIgVW74+NQMdU2ctAK/ISL37WPiZItpt43izvzNJ85/mREmArzTzMCxbN+kFtYpEwAFe5tRsIJgviOBQQ==
|
||||
"@lingdocs/pashto-inflector@^2.4.9":
|
||||
version "2.5.0"
|
||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-2.5.0.tgz#4f90a1d2db15e8389cd84f0a576c37b814f303fd"
|
||||
integrity sha512-RwlBQNwNsKxvICHjx6MwOwetzGQWLdrwjWOBLMfSqbRqQzNWQrjm/Gp4+TfIT89IUtutxVSxLXlgwgUq6ebKpA==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
jsurl2 "^2.1.0"
|
||||
|
|
Loading…
Reference in New Issue