From 07ea0a286aeee198f34d73830dce93a5f837615e Mon Sep 17 00:00:00 2001 From: lingdocs <71590811+lingdocs@users.noreply.github.com> Date: Mon, 5 Sep 2022 18:05:40 +0400 Subject: [PATCH] nice good refactoring to make the practice and test modes work properly --- package.json | 1 + src/games/GameCore.tsx | 379 ++++++++++++------- src/games/games.tsx | 10 +- src/games/sub-cores/EquativeGame.tsx | 416 +++------------------ src/games/sub-cores/EquativeIdentify.tsx | 140 +++++++ src/games/sub-cores/EquativeSituations.tsx | 199 ++++++++++ src/games/sub-cores/GenderGame.tsx | 61 ++- src/games/sub-cores/UnisexNounGame.tsx | 67 ++-- src/games/sub-cores/VerbGame.tsx | 185 ++++----- src/games/sub-cores/makeRandomEPS.ts | 120 ++++++ src/lib/game-utils.ts | 25 +- src/types/game-types.d.ts | 9 +- yarn.lock | 5 + 13 files changed, 934 insertions(+), 683 deletions(-) create mode 100644 src/games/sub-cores/EquativeIdentify.tsx create mode 100644 src/games/sub-cores/EquativeSituations.tsx create mode 100644 src/games/sub-cores/makeRandomEPS.ts diff --git a/package.json b/package.json index 7c3eb78..7d8b7fc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "bootstrap": "4.5.3", "classnames": "^2.3.1", "cron": "^1.8.2", + "froebel": "^0.21.3", "lokijs": "^1.5.12", "markdown-to-jsx": "^7.1.3", "react": "^17.0.2", diff --git a/src/games/GameCore.tsx b/src/games/GameCore.tsx index 303a9e1..59cd870 100644 --- a/src/games/GameCore.tsx +++ b/src/games/GameCore.tsx @@ -23,33 +23,187 @@ import ReactGA from "react-ga"; import { isProd } from "../lib/isProd"; import autoAnimate from "@formkit/auto-animate"; const errorVibration = 200; +const strikesToFail = 3; -const maxStrikes = 2; +type GameState = ({ + mode: "practice", + showAnswer: boolean, +} | { + mode: "intro" | "test" | "fail" | "timeout" | "complete", + showAnswer: false, +}) & { + numberComplete: number, + current: Question, + timerKey: number, + strikes: number, + justStruck: boolean, +} -function GameCore({ inChapter, questions, Display, timeLimit, Instructions, studyLink, id }:{ +type GameReducerAction = { + type: "handle question response", + payload: { correct: boolean }, +} | { + type: "start", + payload: "practice" | "test", +} | { + type: "quit", +} | { + type: "timeout", +} | { + type: "show answer", +} | { + type: "skip", +} + +function GameCore({ inChapter, getQuestion, amount, Display, DisplayCorrectAnswer, timeLimit, Instructions, studyLink, id }: { inChapter: boolean, id: string, studyLink: string, Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element, - questions: () => QuestionGenerator, - Display: (props: QuestionDisplayProps) => JSX.Element, - timeLimit: number; + getQuestion: () => Question, + DisplayCorrectAnswer: (props: { question: Question }) => JSX.Element, + Display: (props: QuestionDisplayProps) => JSX.Element, + timeLimit: number, + amount: number, }) { + const initialState: GameState = { + mode: "intro", + numberComplete: 0, + current: getQuestion(), + timerKey: 0, + strikes: 0, + justStruck: false, + showAnswer: false, + }; // TODO: report pass with id to user info const rewardRef = useRef(null); const parent = useRef(null); const { user, pullUser, setUser } = useUser(); - const [mode, setMode] = useState<"practice" | "test">("test"); - const [finish, setFinish] = useState(undefined); - const [strikes, setStrikes] = useState(0); - const [justStruck, setJustStruck] = useState(false); - const [current, setCurrent] = useState | undefined>(undefined); - const [questionBox, setQuestionBox] = useState>(questions()); - const [timerKey, setTimerKey] = useState(1); + const [state, setStateDangerous] = useState>(initialState); useEffect(() => { parent.current && autoAnimate(parent.current) }, [parent]); + const gameReducer = (gs: GameState, action: GameReducerAction): GameState => { + if (action.type === "handle question response") { + if (gs.mode === "test") { + if (action.payload.correct) { + const numberComplete = gs.numberComplete + 1; + if (numberComplete === amount) { + logGameEvent("passed"); + rewardRef.current?.rewardMe(); + handleResult(true); + return { + ...gs, + numberComplete, + justStruck: false, + mode: "complete", + + } + } else { + return { + ...gs, + numberComplete, + current: getQuestion(), + justStruck: false, + }; + } + } else { + punish(); + const strikes = gs.strikes + 1; + if (strikes === strikesToFail) { + logGameEvent("fail"); + handleResult(false); + return { + ...gs, + strikes, + mode: "fail", + justStruck: false, + }; + } else { + return { + ...gs, + strikes, + justStruck: true, + }; + } + } + } + else /* (gs.mode === "practice") */ { + if (action.payload.correct) { + const numberComplete = gs.numberComplete + 1; + return { + ...gs, + numberComplete, + current: getQuestion(), + justStruck: false, + showAnswer: false, + }; + } else { + punish(); + const strikes = gs.strikes + 1; + return { + ...gs, + strikes, + justStruck: true, + showAnswer: false, + }; + } + } + } + if (action.type === "start") { + logGameEvent(`started ${action.payload}`); + return { + ...initialState, + mode: action.payload, + current: getQuestion(), + timerKey: gs.timerKey + 1, + } + } + if (action.type === "quit") { + return { + ...initialState, + timerKey: gs.timerKey + 1, + } + } + if (action.type === "timeout") { + logGameEvent("timeout"); + handleResult(false); + return { + ...gs, + mode: "timeout", + justStruck: false, + showAnswer: false, + }; + } + if (action.type === "show answer") { + if (gs.mode === "practice" && gs.justStruck) { + return { + ...gs, + justStruck: false, + showAnswer: true, + }; + } + return gs; + } + if (action.type === "skip") { + if (gs.mode === "practice") { + return { + ...gs, + current: getQuestion(), + justStruck: false, + showAnswer: false, + }; + } + return gs; + } + throw new Error("unknown GameReducerAction"); + } + + function dispatch(action: GameReducerAction) { + setStateDangerous(gs => gameReducer(gs, action)); + } + function logGameEvent(action: string) { if (isProd && !(user?.admin)) { ReactGA.event({ @@ -59,35 +213,15 @@ function GameCore({ inChapter, questions, Display, timeLimit, Instructions, s }); } } - - function handleCallback(correct: true | JSX.Element) { - if (correct === true) { - handleAdvance(); - return; - } - setStrikes(s => s + 1); + function punish() { navigator.vibrate(errorVibration); - if (strikes < maxStrikes) { - setJustStruck(true); - } else { - logGameEvent("fail on game"); - setJustStruck(false); - setFinish({ msg: "fail", answer: correct }); - const result: AT.TestResult = { - done: false, - time: getTimestamp(), - id, - }; - handleResult(result); - } } - function handleAdvance() { - setJustStruck(false); - const next = questionBox.next(); - if (next.done) handleFinish(); - else setCurrent(next.value); - } - function handleResult(result: AT.TestResult) { + function handleResult(done: boolean) { + const result: AT.TestResult = { + done, + time: getTimestamp(), + id, + }; // add the test to the user object if (!user) return; setUser((u) => { @@ -105,127 +239,105 @@ function GameCore({ inChapter, questions, Display, timeLimit, Instructions, s if (r === "sent") pullUser(); }).catch(console.error); } - function handleFinish() { - logGameEvent("passed game") - setFinish("pass"); - rewardRef.current?.rewardMe(); - if (!user) return; - const result: AT.TestResult = { - done: true, - time: getTimestamp(), - id, - }; - handleResult(result); - } - function handleQuit() { - setFinish(undefined); - setCurrent(undefined); - } - function handleRestart(mode: "test" | "practice") { - logGameEvent(`started game ${mode}`); - setMode(mode); - const newQuestionBox = questions(); - const { value } = newQuestionBox.next(); - // just for type safety -- the generator will have at least one question - if (!value) return; - setQuestionBox(newQuestionBox); - setJustStruck(false); - setStrikes(0); - setFinish(undefined); - setCurrent(value); - setTimerKey(prev => prev + 1); - } - function handleTimeOut() { - logGameEvent("timeout on game"); - setJustStruck(false); - setFinish("time out"); - navigator.vibrate(errorVibration); - const result: AT.TestResult = { - done: false, - time: getTimestamp(), - id, - }; - handleResult(result); - } function getProgressWidth(): string { - const num = !current + const num = !state.current ? 0 - : (finish === "pass") + : (state.mode === "complete") ? 100 - : getPercentageDone(current.progress); + : getPercentageDone(state.numberComplete, amount); return `${num}%`; } - const progressColor = finish === "pass" + const progressColor = state.mode === "complete" ? "success" - : typeof finish === "object" + : (state.mode === "fail" || state.mode === "timeout") ? "danger" : "primary"; - const gameRunning = current && finish === undefined; + const gameRunning = state.mode === "practice" || state.mode === "test"; function ActionButtons() { return
{!inChapter && } - - + +
; } return <>
- {mode === "test" &&
+ {(state.mode === "test" || state.mode === "intro") &&
} - {current &&
- -
- {mode === "test" && + {state.mode === "test" && } + {state.mode === "practice" && } +
+ {state.mode === "test" && dispatch({ type: "timeout" })} />} - + {state.mode !== "intro" && }
-
} - {mode === "test" &&
- {justStruck &&
+
+
+ {state.justStruck &&
{getStrikeMessage()}
} -
} +
- {finish === undefined && - (current - ?
- -
- :
-
- {/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/} - -
- -
) - } - {finish === "pass" &&
+ {state.mode === "intro" &&
+
+ {/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/} + +
+ +
} + {gameRunning && dispatch({ type: "handle question response", payload: { correct }})} + />} + {(state.mode === "practice" && state.justStruck) &&
+ +
} + {(state.showAnswer && state.mode === "practice") &&
+
The correct answer was:
+
+ +
+ +
} + {state.mode === "complete" &&

🎉 Finished!

- +
} - {(typeof finish === "object" || finish === "time out") &&
- {mode === "test" &&

{failMessage(current?.progress, finish)}

} - {typeof finish === "object" &&
-
The correct answer was:
-
- {finish?.answer} -
-
} + {(state.mode === "timeout" || state.mode === "fail") &&
+

{failMessage({ + numberComplete: state.numberComplete, + amount, + type: state.mode, + })}

+
The correct answer was:
+
+ +
@@ -246,6 +358,13 @@ function GameCore({ inChapter, questions, Display, timeLimit, Instructions, s ; } +function PracticeStatusDisplay({ correct, incorrect }: { correct: number, incorrect: number }) { + return
+
Correct: {correct}
+
Incorrect: {incorrect}
+
+} + function StrikesDisplay({ strikes }: { strikes: number }) { return
{[...Array(strikes)].map(_ => )} @@ -262,8 +381,12 @@ function getStrikeMessage() { ]); } -function failMessage(progress: Progress | undefined, finish: "time out" | { msg: "fail", answer: JSX.Element }): string { - const pDone = progress ? getPercentageDone(progress) : 0; +function failMessage({ numberComplete, amount, type }: { + numberComplete: number, + amount: number, + type: "timeout" | "fail", +}): string { + const pDone = getPercentageDone(numberComplete, amount); const { message, face } = pDone < 20 ? { message: "No, sorry", face: "😑" } : pDone < 30 @@ -273,7 +396,7 @@ function failMessage(progress: Progress | undefined, finish: "time out" | { msg: : pDone < 78 ? { message: "You almost got it!", face: "😩" } : { message: "Nooo! So close!", face: "😭" }; - return typeof finish === "object" + return type === "fail" ? `${message} ${face}` : `⏳ Time's Up ${face}`; } diff --git a/src/games/games.tsx b/src/games/games.tsx index f337388..1209074 100644 --- a/src/games/games.tsx +++ b/src/games/games.tsx @@ -2,6 +2,8 @@ import EquativeGame from "./sub-cores/EquativeGame"; import VerbGame from "./sub-cores/VerbGame"; import GenderGame from "./sub-cores/GenderGame"; import UnisexNounGame from "./sub-cores/UnisexNounGame"; +import EquativeSituations from "./sub-cores/EquativeSituations"; +import EquativeIdentify from "./sub-cores/EquativeIdentify"; // NOUNS export const nounGenderGame1 = makeGameRecord({ @@ -87,21 +89,21 @@ export const equativeGameAllIdentify = makeGameRecord({ title: "Identify the equative (all tenses)", id: "equative-past-summary-identify", link: "/equatives/other-equatives", - level: "allIdentify", - SubCore: EquativeGame, + level: "allTenses", + SubCore: EquativeIdentify, }); export const equativeGameSituations = makeGameRecord({ title: "Choose the right equative for the situation", id: "equative-past-situations", link: "/equatives/other-equatives", level: "situations", - SubCore: EquativeGame, + SubCore: EquativeSituations, }); export const equativeGameAllProduce = makeGameRecord({ title: "Write the equative (all tenses)", id: "equative-past-summary-produce", link: "/equatives/other-equatives", - level: "allProduce", + level: "allTenses", SubCore: EquativeGame, }); diff --git a/src/games/sub-cores/EquativeGame.tsx b/src/games/sub-cores/EquativeGame.tsx index 91d9327..c79ed43 100644 --- a/src/games/sub-cores/EquativeGame.tsx +++ b/src/games/sub-cores/EquativeGame.tsx @@ -1,173 +1,22 @@ import { useEffect, useState } from "react"; import { comparePs, - makeProgress, } from "../../lib/game-utils"; import GameCore from "../GameCore"; import { Types as T, Examples, defaultTextOptions as opts, - typePredicates as tp, - makeNounSelection, - randFromArray, renderEP, compileEP, flattenLengths, - randomPerson, InlinePs, grammarUnits, } from "@lingdocs/pashto-inflector"; -import { psStringEquals } from "@lingdocs/pashto-inflector/dist/lib/p-text-helpers"; +import { randomEPSPool } from "./makeRandomEPS"; 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"}, - {"ts":1527816747,"i":6418,"p":"ډاکټر","f":"DaakTar","g":"DaakTar","e":"doctor","c":"n. m. anim. unisex"}, - {"ts":1527812661,"i":13938,"p":"هلک","f":"halík, halúk","g":"halik,haluk","e":"boy, young lad","c":"n. m. anim."}, -].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."}, - {"ts":1527822049,"i":3610,"p":"تکړه","f":"takRá","g":"takRa","e":"strong, energetic, skillful, great, competent","c":"adj."}, - {"ts":1527815201,"i":2240,"p":"پټ","f":"puT","g":"puT","e":"hidden","c":"adj."}, - {"ts":1527815381,"i":3402,"p":"تږی","f":"túGey","g":"tugey","e":"thirsty","c":"adj."}, - {"ts":1527812822,"i":10506,"p":"کوچنی","f":"koochnéy","g":"koochney","e":"little, small; child, little one","c":"adj. / n. m. anim. unisex"}, - {"ts":1527815451,"i":7243,"p":"زوړ","f":"zoR","g":"zoR","e":"old","c":"adj. irreg.","infap":"زاړه","infaf":"zaaRu","infbp":"زړ","infbf":"zaR"}, - {"ts":1527812927,"i":12955,"p":"موړ","f":"moR","g":"moR","e":"full, satisfied, sated","c":"adj. irreg.","infap":"ماړه","infaf":"maaRu","infbp":"مړ","infbf":"maR"}, -].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 tenses: T.EquativeTense[] = [ - "present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen" -]; - -type Situation = { - description: string | JSX.Element, - tense: T.EquativeTense[], -}; -const situations: Situation[] = [ - { - description: <>A is B, for sure, right now, - tense: ["present"], - }, - { - description: <>A is probably B, right now, - tense: ["future"], - }, - { - description: <>A will be B in the future, - tense: ["future"], - }, - { - description: <>We can assume that A is most likely B, - tense: ["future"], - }, - { - description: <>You know A is B, currently, - tense: ["present"], - }, - { - description: <>A tends to be B, - tense: ["habitual"], - }, - { - description: <>A is usually B, - tense: ["habitual"], - }, - { - description: <>A is generally B, - tense: ["habitual"], - }, - { - description: <>A is B, right now, - tense: ["present"], - }, - { - description: <>A is always B, as a matter of habit, - tense: ["present"], - }, - { - description: "It's a good thing for A to be B", - tense: ["subjunctive"], - }, - { - description: "A needs to be B (out of obligation/necessity)", - tense: ["subjunctive"], - }, - { - description: "You hope that A is B", - tense: ["subjunctive"], - }, - { - description: "You desire A to be B", - tense: ["subjunctive"], - }, - { - description: "If A is B ...", - tense: ["subjunctive"], - }, - { - description: "...so that A will be B (a purpose)", - tense: ["subjunctive"], - }, - { - description: "A was definately B", - tense: ["past"], - }, - { - description: "A was B", - tense: ["past"], - }, - { - description: "A was probably B in the past", - tense: ["wouldBe"], - }, - { - description: "A used to be B (habitually, repeatedly)", - tense: ["wouldBe"], - }, - { - description: "assume that A would have probably been B", - tense: ["wouldBe"], - }, - { - description: "under different circumstances, A would have been B", - tense: ["wouldBe", "pastSubjunctive"], - }, - { - description: "You wish A were B (but it's not)", - tense: ["pastSubjunctive"], - }, - { - description: "If A were B (but it's not)", - tense: ["pastSubjunctive"], - }, - { - description: "Aaagh! If only A were B!", - tense: ["pastSubjunctive"], - }, - { - description: "A should have been B!", - tense: ["pastSubjunctive", "wouldBe"], - }, -]; - const amount = 12; const timeLimit = 100; @@ -175,99 +24,28 @@ type Question = { EPS: T.EPSelectionComplete, phrase: { ps: T.PsString[], e?: string[] }, equative: T.EquativeRendered, -} | { - situation: Situation, }; -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({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allProduce" | "allIdentify" | "situations" }) { - function* questions (): Generator> { - let pool = [...pronounTypes]; - let situationPool = [...situations]; - function makeRandPronoun(): T.PronounSelection { - let person: T.Person; - 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, - }; - } - function makeRandomEPS(l: T.EquativeTense | "allIdentify" | "allProduce"): T.EPSelectionComplete { - const subj: T.NPSelection = { - type: "NP", - selection: randFromArray([ - makeRandPronoun, - makeRandPronoun, - makeRandomNoun, - makeRandPronoun, - ])(), - }; - const pred = randFromArray([...adjectives, ...locAdverbs]); - const tense = (l === "allIdentify" || l === "allProduce") - ? randFromArray(tenses) - : l; - return makeEPS(subj, pred, tense); - } - for (let i = 0; i < amount; i++) { - if (level === "situations") { - const picked = randFromArray(situationPool); - situationPool = situationPool.filter(x => picked.description !== x.description); - if (situationPool.length === 0) situationPool = [...situations]; - yield { - progress: makeProgress(i, amount), - question: { - situation: picked, - }, - }; - } else { - const EPS = makeRandomEPS(level); - const EP = renderEP(EPS); - const compiled = compileEP( - EP, - true, - level === "allIdentify" ? undefined : { equative: true, kidsSection: true }, - ); - const phrase = { - ps: compiled.ps, - e: level === "allIdentify" ? undefined : compiled.e, - }; - yield { - progress: makeProgress(i, amount), - question: { - EPS, - phrase, - equative: getEqFromRendered(EP), - }, - }; - } +export default function EquativeGame({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allTenses" }) { + const epsPool = randomEPSPool(level); + function getQuestion(): Question { + const EPS = epsPool(); + const EP = renderEP(EPS); + const compiled = compileEP( + EP, + true, + { equative: true, kidsSection: true }, + ); + const phrase = { + ps: compiled.ps, + e: compiled.e, }; - } - + return { + EPS, + phrase, + equative: getEqFromRendered(EP), + }; + }; function Display({ question, callback }: QuestionDisplayProps) { const [answer, setAnswer] = useState(""); @@ -276,96 +54,30 @@ export default function EquativeGame({ inChapter, id, link, level }: { inChapter setAnswer(value); } const handleSubmit = (e: React.FormEvent) => { - if ("situation" in question) { - return; - } e.preventDefault(); const correct = comparePs(answer, question.equative.ps) && (withBa === question.equative.hasBa); if (correct) { setAnswer(""); } - callback(!correct ? makeCorrectAnswer(question) : true); - } - const handleTenseIdentify = (tense: T.EquativeTense) => { - if ("situation" in question) { - const wasCorrect = question.situation.tense.includes(tense); - if (wasCorrect) { - callback(true); - } else { - callback(makeCorrectAnswer(question)); - } - return; - } - const renderedWAnswer = renderEP({ - ...question.EPS, - equative: { - ...question.EPS.equative, - tense, - }, - }); - const compiledWAnswer = compileEP(renderedWAnswer, true); - const wasCorrect = compiledWAnswer.ps.some(a => ( - question.phrase.ps.some(b => psStringEquals(a, b)) - )); - if (wasCorrect) { - return callback(wasCorrect); - } else { - const possibleCorrect = tenses.filter(tn => { - const r = renderEP({ - ...question.EPS, - equative: { - ...question.EPS.equative, - tense: tn, - }, - }); - const c = compileEP(r, true); - return c.ps.some(a => ( - question.phrase.ps.some(b => psStringEquals(a, b)) - )); - }); - callback(
- {possibleCorrect.map(humanReadableTense).join(" or ")} -
) - } + callback(correct); } useEffect(() => { - if (level === "allProduce") setWithBa(false); + if (level === "allTenses") setWithBa(false); }, [question]); return
- {(level === "allIdentify" || level === "situations") ? -
- {"situation" in question ?

- {question.situation.description} -

: - {randFromArray(question.phrase.ps)} - } -
- : !("situation" in question) &&
- - {/* @ts-ignore TODO: REMOVE AS P_INFLE */} - {modExs(question.phrase.ps, withBa)[0]} - - {question.phrase.e && question.phrase.e.map((e, i) => ( -
{e}
- ))} -
{humanReadableTense(question.EPS.equative.tense)} equative
-
- } - {level === "allIdentify" || "situation" in question ?
-
- {tenses.map(t =>
- -
)} -
-
:
+
+ + {/* @ts-ignore TODO: REMOVE AS P_INFLE */} + {modExs(question.phrase.ps, withBa)[0]} + + {question.phrase.e && question.phrase.e.map((e, i) => ( +
{e}
+ ))} +
{humanReadableTense(question.EPS.equative.tense)} equative
+
+
{/*
*/} - + {/*
*/} {/*
Type Enter to check
*/}
- } +
} function Instructions() { return
- {level === "allIdentify" - ?

Identify a correct tense for each equative phrase you see

- : level === "situations" - ?

Choose the right type of equative for each given situation

- :

Fill in the blank with the correct {humanReadableTense(level)} equative in Pashto script

} - {level === "allProduce" &&
⚠ All tenses included...
} -
+

+ Fill in the blank with the correct {level === "allTenses" ? "" : humanReadableTense(level)} equative +

+ {level === "allTenses" &&
⚠ All tenses included...
} +
; } return }; -function makeCorrectAnswer(question: Question): JSX.Element { - if ("situation" in question) { - return
- {question.situation.tense.map(humanReadableTense).join(" or ")} -
; - } +function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element { return
{flattenLengths(question.equative.ps).reduce(((accum, curr, i): JSX.Element[] => ( @@ -472,38 +179,7 @@ function humanReadableTense(tense: T.EquativeTense | "allProduce"): string { : tense; } -function makeEPS(subject: T.NPSelection, predicate: T.AdjectiveEntry | T.LocativeAdverbEntry, tense: T.EquativeTense): T.EPSelectionComplete { - return { - blocks: [ - { - key: Math.random(), - block: { - type: "subjectSelection", - selection: subject, - }, - }, - ], - predicate: { - type: "predicateSelection", - selection: { - type: "complement", - selection: tp.isAdjectiveEntry(predicate) ? { - type: "adjective", - entry: predicate, - sandwich: undefined, - } : { - type: "loc. adv.", - entry: predicate, - }, - }, - }, - equative: { - tense, - negative: false, - }, - omitSubject: false, - }; -} + function getEqFromRendered(e: T.EPRendered): T.EquativeRendered { const eblock = e.blocks[0].find(x => x.block.type === "equative"); diff --git a/src/games/sub-cores/EquativeIdentify.tsx b/src/games/sub-cores/EquativeIdentify.tsx new file mode 100644 index 0000000..de5341f --- /dev/null +++ b/src/games/sub-cores/EquativeIdentify.tsx @@ -0,0 +1,140 @@ +import GameCore from "../GameCore"; +import { + Types as T, + Examples, + defaultTextOptions as opts, + randFromArray, + renderEP, + compileEP, +} from "@lingdocs/pashto-inflector"; +import { psStringEquals } from "@lingdocs/pashto-inflector/dist/lib/p-text-helpers"; +import { randomEPSPool } from "./makeRandomEPS"; +import { useEffect, useState } from "react"; +import classNames from "classnames"; + +const tenses: T.EquativeTense[] = [ + "present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen", +]; + +const amount = 12; +const timeLimit = 120; + +type Question = { + EPS: T.EPSelectionComplete, + phrase: T.PsString, + possibleEquatives: T.EquativeTense[], +}; + +export default function EquativeIdentify({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "allTenses" }) { + const epsPool = randomEPSPool("allTenses"); + function getQuestion(): Question { + const EPS = epsPool(); + const EP = renderEP(EPS); + const compiled = compileEP(EP, true); + const phrase = randFromArray(compiled.ps); + return { + EPS, + phrase, + possibleEquatives: getPossibleEquatives(phrase, EPS), + }; + }; + + function Display({ question, callback }: QuestionDisplayProps) { + console.log({ question }); + const [selected, setSelected] = useState([]); + useEffect(() => { + setSelected([]); + }, [question, setSelected]); + function handleTenseClick(t: T.EquativeTense) { + setSelected(s => selected.includes(t) + ? s.filter(x => x !== t) + : [...s, t], + ); + } + function handleSubmitAnswer() { + const correct = ( + (selected.length === question.possibleEquatives.length) + && + (question.possibleEquatives.every(e => selected.includes(e))) + ); + callback(correct); + } + + return
+
+ + {question.phrase} + +
+
+
Select all possible tenses
+
+ {tenses.map(t =>
+ +
)} +
+ +
+
+ } + + function Instructions() { + return
+

Identify ALL the possible tenses for each equative phrase you see

+
; + } + + return +}; + +function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element { + return
+ {question.possibleEquatives.map(humanReadableTense).join(" or ")} +
; +} +function getPossibleEquatives(ps: T.PsString, eps: T.EPSelectionComplete): T.EquativeTense[] { + const possible = tenses.filter(tense => { + const rendered = renderEP({ + ...eps, + equative: { + ...eps.equative, + tense, + }, + }); + const compiled = compileEP(rendered, true); + return compiled.ps.some(x => psStringEquals(x, ps, false)); + }); + if (possible.length === 0) throw new Error("no possible tenses found"); + return possible; +} + +function humanReadableTense(tense: T.EquativeTense | "allProduce"): string { + return tense === "allProduce" + ? "" + : tense === "pastSubjunctive" + ? "past subjunctive" + : tense === "wouldBe" + ? `"would be"` + : tense === "wouldHaveBeen" + ? `"would have been"` + : tense; +} diff --git a/src/games/sub-cores/EquativeSituations.tsx b/src/games/sub-cores/EquativeSituations.tsx new file mode 100644 index 0000000..4e23d1b --- /dev/null +++ b/src/games/sub-cores/EquativeSituations.tsx @@ -0,0 +1,199 @@ +import GameCore from "../GameCore"; +import { + Types as T, +} from "@lingdocs/pashto-inflector"; +import { makePool } from "../../lib/pool"; + +const tenses: T.EquativeTense[] = [ + "present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen" +]; + +type Situation = { + description: string | JSX.Element, + tense: T.EquativeTense[], +}; + +const amount = 12; +const timeLimit = 100; + +const situations: Situation[] = [ + { + description: <>A is B, for sure, right now, + tense: ["present"], + }, + { + description: <>A is probably B, right now, + tense: ["future"], + }, + { + description: <>A will be B in the future, + tense: ["future"], + }, + { + description: <>We can assume that A is most likely B, + tense: ["future"], + }, + { + description: <>You know A is B, currently, + tense: ["present"], + }, + { + description: <>A tends to be B, + tense: ["habitual"], + }, + { + description: <>A is usually B, + tense: ["habitual"], + }, + { + description: <>A is generally B, + tense: ["habitual"], + }, + { + description: <>A is B, right now, + tense: ["present"], + }, + { + description: <>A is always B, as a matter of habit, + tense: ["present"], + }, + { + description: "It's a good thing for A to be B", + tense: ["subjunctive"], + }, + { + description: "A needs to be B (out of obligation/necessity)", + tense: ["subjunctive"], + }, + { + description: "You hope that A is B", + tense: ["subjunctive"], + }, + { + description: "You desire A to be B", + tense: ["subjunctive"], + }, + { + description: "If A is B ...", + tense: ["subjunctive"], + }, + { + description: "...so that A will be B (a purpose)", + tense: ["subjunctive"], + }, + { + description: "A was definately B", + tense: ["past"], + }, + { + description: "A was B", + tense: ["past"], + }, + { + description: "A was probably B in the past", + tense: ["wouldBe"], + }, + { + description: "A used to be B (habitually, repeatedly)", + tense: ["wouldBe"], + }, + { + description: "assume that A would have probably been B", + tense: ["wouldBe"], + }, + { + description: "under different circumstances, A would have been B", + tense: ["wouldBe", "pastSubjunctive"], + }, + { + description: "You wish A were B (but it's not)", + tense: ["pastSubjunctive"], + }, + { + description: "If A were B (but it's not)", + tense: ["pastSubjunctive"], + }, + { + description: "Aaagh! If only A were B!", + tense: ["pastSubjunctive"], + }, + { + description: "A should have been B!", + tense: ["pastSubjunctive", "wouldBe"], + }, +]; + +type Question = Situation; + + +export default function EquativeSituations({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "situations" }) { + const situationsPool = makePool(situations); + function getQuestion(): Question { + return situationsPool(); + }; + + function Display({ question, callback }: QuestionDisplayProps) { + + function handleTenseClick(t: T.EquativeTense) { + callback(question.tense.includes(t)); + } + return
+
+

+ {question.description} +

+
+
+
+ {tenses.map(t =>
+ +
)} +
+
+
+ } + + function Instructions() { + return

Choose a type of equative that works for each given situation

; + } + + return +}; + +function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element { + + // callback(
+ // {possibleCorrect.map(humanReadableTense).join(" or ")} + //
) + return
+ {question.tense.map(humanReadableTense).join(" or ")} +
; +} + +function humanReadableTense(tense: T.EquativeTense | "allProduce"): string { + return tense === "allProduce" + ? "" + : tense === "pastSubjunctive" + ? "past subjunctive" + : tense === "wouldBe" + ? `"would be"` + : tense === "wouldHaveBeen" + ? `"would have been"` + : tense; +} diff --git a/src/games/sub-cores/GenderGame.tsx b/src/games/sub-cores/GenderGame.tsx index b5c2c42..4637205 100644 --- a/src/games/sub-cores/GenderGame.tsx +++ b/src/games/sub-cores/GenderGame.tsx @@ -1,6 +1,3 @@ -import { - makeProgress, -} from "../../lib/game-utils"; import genderColors from "../../lib/gender-colors"; import GameCore from "../GameCore"; import { @@ -84,7 +81,7 @@ const exceptions: Record = { }, }; -const amount = 35; +const amount = 30; type Question = T.DictionaryEntry; export default function GenderGame({level, id, link, inChapter }: { @@ -92,38 +89,25 @@ export default function GenderGame({level, id, link, inChapter }: { level: 1 | 2, id: string, link: string, }) { - function* questions (): Generator> { - const wordPool = {...types}; - const exceptionsPool = {...exceptions}; - console.log(exceptionsPool); - for (let i = 0; i < amount; i++) { - const base = level === 1 - ? wordPool - : randFromArray([wordPool, exceptionsPool]); - const gender = randFromArray(genders); - let typeToUse: string; - do { - typeToUse = randFromArray(Object.keys(base[gender])); - } while (!base[gender][typeToUse].length); - const question = randFromArray(base[gender][typeToUse]); - base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts); - yield { - progress: makeProgress(i, amount), - question, - }; - } + const wordPool = {...types}; + const exceptionsPool = {...exceptions}; + function getQuestion(): Question { + const base = level === 1 + ? wordPool + : randFromArray([wordPool, exceptionsPool]); + const gender = randFromArray(genders); + let typeToUse: string; + do { + typeToUse = randFromArray(Object.keys(base[gender])); + } while (!base[gender][typeToUse].length); + const question = randFromArray(base[gender][typeToUse]); + base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts); + return question; } function Display({ question, callback }: QuestionDisplayProps) { function check(gender: T.Gender) { const nounGender: T.Gender = nounNotIn(mascNouns)(question) ? "fem" : "masc"; - const correct = gender === nounGender; - callback(!correct - ?
- -
- : true); + callback(gender === nounGender); } return
@@ -149,12 +133,23 @@ export default function GenderGame({level, id, link, inChapter }: {
} + function DisplayCorrectAnswer({ question }: { question: Question}) { + const nounGender: T.Gender = nounNotIn(mascNouns)(question) ? "fem" : "masc"; + return
+ +
; + } + return diff --git a/src/games/sub-cores/UnisexNounGame.tsx b/src/games/sub-cores/UnisexNounGame.tsx index 501e6ad..332e9e6 100644 --- a/src/games/sub-cores/UnisexNounGame.tsx +++ b/src/games/sub-cores/UnisexNounGame.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { - makeProgress, comparePs, } from "../../lib/game-utils"; import genderColors from "../../lib/gender-colors"; @@ -28,29 +27,24 @@ const amount = 20; type Question = { entry: T.DictionaryEntry, gender: T.Gender }; export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boolean, id: string, link: string }) { - function* questions (): Generator> { - let pool = { ...types }; - for (let i = 0; i < amount; i++) { - const keys = Object.keys(types) as NType[]; - let type: NType - do { - type = randFromArray(keys); - } while (!pool[type].length); - const entry = randFromArray( - // @ts-ignore - pool[type] - ); - const gender = randFromArray(genders) as T.Gender; + let pool = { ...types }; + function getQuestion(): Question { + const keys = Object.keys(types) as NType[]; + let type: NType + do { + type = randFromArray(keys); + } while (!pool[type].length); + const entry = randFromArray( // @ts-ignore - pool[type] = pool[type].filter((x) => x.ts !== entry.ts); - yield { - progress: makeProgress(i, amount), - question: { - entry, - gender, - }, - }; - } + pool[type] + ); + const gender = randFromArray(genders) as T.Gender; + // @ts-ignore + pool[type] = pool[type].filter((x) => x.ts !== entry.ts); + return { + entry, + gender, + }; } function Display({ question, callback }: QuestionDisplayProps) { @@ -81,14 +75,7 @@ export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boo if (correct) { setAnswer(""); } - callback(!correct - ?
- {correctAnswer.length > 1 &&
One of the following:
} - {correctAnswer.map((ps) => ( - {ps} - ))} -
- : true); + callback(correct); } return
@@ -128,12 +115,28 @@ export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boo
} + function DisplayCorrectAnswer({ question }: { question: Question }) { + const infOut = inflectWord(question.entry); + if (!infOut) return
WORD ERROR
; + const { inflections } = infOut; + // @ts-ignore + const correctAnswer = inflections[flipGender(question.gender)][0]; + return
+ {correctAnswer.length > 1 &&
One of the following:
} + {correctAnswer.map((ps: any) => ( + {ps} + ))} +
; + } + return diff --git a/src/games/sub-cores/VerbGame.tsx b/src/games/sub-cores/VerbGame.tsx index 21b2e47..0f2c0bb 100644 --- a/src/games/sub-cores/VerbGame.tsx +++ b/src/games/sub-cores/VerbGame.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { comparePs, - makeProgress, } from "../../lib/game-utils"; import GameCore from "../GameCore"; import { @@ -95,6 +94,26 @@ type VerbGameLevel = { | "futureVerb" | "imperative" | "intransitivePerfectivePast" | "intransitiveImperfectivePast" | "transitivePerfectivePast" | "transitiveImperfectivePast"; } +type VerbPoolName = "basic" | "transitivePast" | "intransitivePast"; + +function selectVerbPool({ type }: VerbGameLevel): VerbPoolName { + return type === "presentVerb" + ? "basic" + : type === "futureVerb" + ? "basic" + : type === "subjunctiveVerb" + ? "basic" + : type === "imperative" + ? "basic" + : type === "intransitiveImperfectivePast" + ? "intransitivePast" + : type === "intransitivePerfectivePast" + ? "intransitivePast" + : type === "transitiveImperfectivePast" + ? "transitivePast" + // : type === "transitivePerfectivePast" + : "transitivePast"; +} // TODO: Level where you create the formulas (seperate file) // level where you choose the right situation @@ -105,93 +124,87 @@ const VerbGame: GameSubCore = ({ id, link, level, inChapter }: { link: string, level: VerbGameLevel, }) => { - function* questions (): Generator> { - const personPool = makePool(level.type === "imperative" - ? secondPersons - : persons - ); - const verbsUsed = level.type.startsWith("intransitive") - ? intransitivePastVerbs - : level.type.startsWith("transitive") - ? transitivePastVerbs - : verbs; - const oneVerb = randFromArray(verbsUsed); - const verbPool = makePool(verbsUsed, 15); - const getVerb = level.level === 1 - ? () => oneVerb - : () => verbPool(); - 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, - }; - } - function makeRandomVPS(l: T.VerbTense | T.ImperativeTense): T.VPSelectionComplete { - function personToNPSelection(p: T.Person): T.NPSelection { - if (isThirdPerson(p)) { - return { - type: "NP", - selection: randFromArray([ - () => makePronounS(p), - makeRandomNoun, - () => makePronounS(p), - ])(), - }; - } + const personPool = makePool(level.type === "imperative" + ? secondPersons + : persons + ); + const verbPools: Record T.VerbEntry> = { + basic: makePool(verbs, 15), + transitivePast: makePool(transitivePastVerbs, 15), + intransitivePast: makePool(intransitivePastVerbs, 15), + }; + const oneVerb: T.VerbEntry = verbPools[selectVerbPool(level)](); + const getVerb = level.level === 1 + ? () => oneVerb + : () => verbPools[selectVerbPool(level)](); + 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, + }; + } + function makeRandomVPS(l: T.VerbTense | T.ImperativeTense): T.VPSelectionComplete { + function personToNPSelection(p: T.Person): T.NPSelection { + if (isThirdPerson(p)) { return { type: "NP", - selection: makePronounS(p), + selection: randFromArray([ + () => makePronounS(p), + makeRandomNoun, + () => makePronounS(p), + ])(), }; } - function makePronounS(p: T.Person): T.PronounSelection { - return { - type: "pronoun", - person: p, - distance: randFromArray(["far", "near", "far"]), - }; - } - const verb = getVerb(); - const king = personPool(); - let servant: T.Person; - do { - servant = randomPerson(); - } while (isInvalidSubjObjCombo(king, servant)); - // const tense = (l === "allIdentify" || l === "allProduce") - // ? randFromArray(tenses) - // : l; - const tense = l; - return makeVPS({ - verb, - king: personToNPSelection(king), - servant: personToNPSelection(servant), - tense, - defaultTransitivity: level.type.startsWith("transitive") - ? "transitive" - : "grammatically transitive", - }); + return { + type: "NP", + selection: makePronounS(p), + }; } - for (let i = 0; i < amount; i++) { - const VPS = makeRandomVPS(levelToTense(level)); - const VP = renderVP(VPS); - const compiled = compileVP( - VP, - { removeKing: false, shrinkServant: false }, - true, - { ba: true, verb: true }, - ); - const phrase = { - ps: compiled.ps, - e: compiled.e, - }; - yield { - progress: makeProgress(i, amount), - question: { - rendered: VP, - phrase, - }, + function makePronounS(p: T.Person): T.PronounSelection { + return { + type: "pronoun", + person: p, + distance: randFromArray(["far", "near", "far"]), }; + } + const verb = getVerb(); + const king = personPool(); + let servant: T.Person; + do { + servant = randomPerson(); + } while (isInvalidSubjObjCombo(king, servant)); + // const tense = (l === "allIdentify" || l === "allProduce") + // ? randFromArray(tenses) + // : l; + const tense = l; + return makeVPS({ + verb, + king: personToNPSelection(king), + servant: personToNPSelection(servant), + tense, + defaultTransitivity: level.type.startsWith("transitive") + ? "transitive" + : "grammatically transitive", + }); + } + function getQuestion(): Question { + const VPS = makeRandomVPS(levelToTense(level)); + const VP = renderVP(VPS); + const compiled = compileVP( + VP, + { removeKing: false, shrinkServant: false }, + true, + { ba: true, verb: true }, + ); + const phrase = { + ps: compiled.ps, + e: compiled.e, + }; + return { + rendered: VP, + phrase, }; } @@ -208,7 +221,7 @@ const VerbGame: GameSubCore = ({ id, link, level, inChapter }: { if (correct) { setAnswer(""); } - callback(!correct ? makeCorrectAnswer(question) : true); + callback(correct); } // useEffect(() => { // if (level === "allProduce") setWithBa(false); @@ -264,10 +277,12 @@ const VerbGame: GameSubCore = ({ id, link, level, inChapter }: { return }; @@ -307,7 +322,7 @@ function QuestionDisplay({ question, userAnswer }: {
; } -function makeCorrectAnswer(question: Question): JSX.Element { +function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element { return
{getVerbPs(question.rendered).reduce(((accum, curr, i): JSX.Element[] => ( diff --git a/src/games/sub-cores/makeRandomEPS.ts b/src/games/sub-cores/makeRandomEPS.ts new file mode 100644 index 0000000..b4b7622 --- /dev/null +++ b/src/games/sub-cores/makeRandomEPS.ts @@ -0,0 +1,120 @@ +import { + Types as T, + typePredicates as tp, + makeNounSelection, + randFromArray, +} from "@lingdocs/pashto-inflector"; +import { makePool } from "../../lib/pool"; + +const pronouns: T.Person[] = [ + 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 11, +]; + +const tenses: T.EquativeTense[] = [ + "present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen" +]; + +// @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"}, + {"ts":1527816747,"i":6418,"p":"ډاکټر","f":"DaakTar","g":"DaakTar","e":"doctor","c":"n. m. anim. unisex"}, + {"ts":1527812661,"i":13938,"p":"هلک","f":"halík, halúk","g":"halik,haluk","e":"boy, young lad","c":"n. m. anim."}, +].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."}, + {"ts":1527822049,"i":3610,"p":"تکړه","f":"takRá","g":"takRa","e":"strong, energetic, skillful, great, competent","c":"adj."}, + {"ts":1527815201,"i":2240,"p":"پټ","f":"puT","g":"puT","e":"hidden","c":"adj."}, + {"ts":1527815381,"i":3402,"p":"تږی","f":"túGey","g":"tugey","e":"thirsty","c":"adj."}, + {"ts":1527812822,"i":10506,"p":"کوچنی","f":"koochnéy","g":"koochney","e":"little, small; child, little one","c":"adj. / n. m. anim. unisex"}, + {"ts":1527815451,"i":7243,"p":"زوړ","f":"zoR","g":"zoR","e":"old","c":"adj. irreg.","infap":"زاړه","infaf":"zaaRu","infbp":"زړ","infbf":"zaR"}, + {"ts":1527812927,"i":12955,"p":"موړ","f":"moR","g":"moR","e":"full, satisfied, sated","c":"adj. irreg.","infap":"ماړه","infaf":"maaRu","infbp":"مړ","infbf":"maR"}, +].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); + +export function randomEPSPool(l: T.EquativeTense | "allTenses") { + const pronounPool = makePool(pronouns); + const nounPool = makePool(nouns, 20); + const predPool = makePool([...adjectives, ...locAdverbs], 20); + const tensePool = makePool(tenses, 15); + function makeRandPronoun(): T.PronounSelection { + return { + type: "pronoun", + distance: "far", + person: pronounPool(), + }; + } + function makeRandomNoun(): T.NounSelection { + const n = makeNounSelection(nounPool(), undefined); + return { + ...n, + gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender, + number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number, + }; + } + return function makeRandomEPS(): T.EPSelectionComplete { + const subj: T.NPSelection = { + type: "NP", + selection: randFromArray([ + makeRandPronoun, + makeRandPronoun, + makeRandomNoun, + makeRandPronoun, + ])(), + }; + const pred = predPool(); + const tense = (l === "allTenses") + ? tensePool() + : l; + return makeEPS(subj, pred, tense); + } + +} + + +function makeEPS(subject: T.NPSelection, predicate: T.AdjectiveEntry | T.LocativeAdverbEntry, tense: T.EquativeTense): T.EPSelectionComplete { + return { + blocks: [ + { + key: Math.random(), + block: { + type: "subjectSelection", + selection: subject, + }, + }, + ], + predicate: { + type: "predicateSelection", + selection: { + type: "complement", + selection: tp.isAdjectiveEntry(predicate) ? { + type: "adjective", + entry: predicate, + sandwich: undefined, + } : { + type: "loc. adv.", + entry: predicate, + }, + }, + }, + equative: { + tense, + negative: false, + }, + omitSubject: false, + }; +} \ No newline at end of file diff --git a/src/lib/game-utils.ts b/src/lib/game-utils.ts index 5dae75d..f563a90 100644 --- a/src/lib/game-utils.ts +++ b/src/lib/game-utils.ts @@ -7,33 +7,12 @@ import { flattenLengths, } from "@lingdocs/pashto-inflector"; -export function makeRandomQs( - amount: number, - makeQuestion: () => Q -): () => QuestionGenerator { - function makeProgress(i: number, total: number): Progress { - return { current: i + 1, total }; - } - return function* () { - for (let i = 0; i < amount; i++) { - yield { - progress: makeProgress(i, amount), - question: makeQuestion(), - }; - } - } -} - -export function getPercentageDone(progress: Progress): number { +export function getPercentageDone(current: number, total: number): number { return Math.round( - (progress.current / (progress.total + 1)) * 100 + (current / (total + 1)) * 100 ); } -export function makeProgress(i: number, total: number): Progress { - return { current: i + 1, total }; -} - /** * Says if an input written in phonetics by the user is correct/the same as a given answer * diff --git a/src/types/game-types.d.ts b/src/types/game-types.d.ts index 9bdf607..b5f5648 100644 --- a/src/types/game-types.d.ts +++ b/src/types/game-types.d.ts @@ -3,11 +3,6 @@ type Progress = { current: number, }; -type Current = { - progress: Progress, - question: T, -}; - type GameSubCore = (props: { inChapter: boolean, id: string, @@ -15,11 +10,9 @@ type GameSubCore = (props: { link: string; }) => JSX.Element; -type QuestionGenerator = Generator, void, unknown>; - type QuestionDisplayProps = { question: T, - callback: (correct: true | JSX.Element) => void, + callback: (correct: boolean) => void, }; type GameRecord = { diff --git a/yarn.lock b/yarn.lock index d60419f..23f0e48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5897,6 +5897,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +froebel@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/froebel/-/froebel-0.21.3.tgz#a780c45ccd6599c3840c0b74c6e3538b6e87a566" + integrity sha512-MqyU/nwqUZULHcr7dD0vaSrt+LUEuvgF0aN8LfXbwTJysQF/s3Y0P46SKnOoiVpELftdJXdUbOZ8VReRIOG5DQ== + from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"