some rough equative games

This commit is contained in:
lingdocs 2022-05-09 12:57:56 -05:00
parent 77b500693b
commit 2bd4a09f43
12 changed files with 424 additions and 59 deletions

View File

@ -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",

View File

@ -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} />

View File

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

View File

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

View File

@ -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>
})}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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