- {finish === null &&
+ {finish === undefined &&
(current
?
@@ -151,8 +154,12 @@ function GameCore({ questions, Display, timeLimit, Instructions, studyLink, i
Try Again
}
- {(finish === "fail" || finish === "time out") &&
+ {(typeof finish === "object" || finish === "time out") &&
{failMessage(current?.progress, finish)}
+ {typeof finish === "object" &&
+
The correct answer was:
+ {finish?.answer}
+
}
Try Again
@@ -168,7 +175,7 @@ function GameCore
({ questions, Display, timeLimit, Instructions, studyLink, i
;
}
-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}`;
}
diff --git a/src/games/GameDisplay.tsx b/src/games/GameDisplay.tsx
index 6713a28..898c717 100644
--- a/src/games/GameDisplay.tsx
+++ b/src/games/GameDisplay.tsx
@@ -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
(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
-
-
-
🎮 {title}
-
-
-
{completed ? "✅" : ""}
+ function onStartStop(a: "start" | "stop") {
+ if (a === "start" && !running) {
+ setRunning(true);
+ }
+ if (a === "stop") {
+ setRunning(false);
+ }
+ }
+ return <>
+ {running &&
}
+
+
+
+
🎮 {title}
+
+
+
{completed ? "✅" : ""}
+
+ {Game(onStartStop)}
-
-
+ >
}
export default GameDisplay;
\ No newline at end of file
diff --git a/src/games/GamesBrowser.tsx b/src/games/GamesBrowser.tsx
index d5a05de..33ddf09 100644
--- a/src/games/GamesBrowser.tsx
+++ b/src/games/GamesBrowser.tsx
@@ -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() {
-
+ {Game(() => null)}
})}
diff --git a/src/games/games.tsx b/src/games/games.tsx
index 6b00a25..441e89a 100644
--- a/src/games/games.tsx
+++ b/src/games/games.tsx
@@ -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) => () =>
,
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
);
export const nounGenderGame2 = makeGameRecord(
"Identify Noun Genders - Level 2",
"gender-nouns-2",
"/nouns/nouns-gender#exceptions",
- (id, link) => () =>
,
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
);
export const unisexNounGame = makeGameRecord(
"Changing genders on unisex nouns",
"unisex-nouns-1",
"/nouns/nouns-unisex/",
- (id, link) => () =>
,
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+export const equativeGamePresent = makeGameRecord(
+ "Write the present equative",
+ "equative-present",
+ "/equatives/present-equative/",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+export const equativeGameHabitual = makeGameRecord(
+ "Write the habitual equative",
+ "equative-habitual",
+ "/equatives/habitual-equative/",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+export const equativeGameSubjunctive = makeGameRecord(
+ "Write the subjunctive equative",
+ "equative-subjunctive",
+ "/equatives/other-equatives/#subjunctive-equative",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+export const equativeGameFuture = makeGameRecord(
+ "Write the future equative",
+ "equative-future",
+ "/equatives/other-equatives/#future-equative",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+export const equativeGamePast = makeGameRecord(
+ "Write the past equative",
+ "equative-past",
+ "/equatives/other-equatives/#past-equative",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
+);
+
+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) =>
,
+);
+
+export const equativeGamePastSubjunctive = makeGameRecord(
+ 'Write the past subjunctive equative',
+ "equative-past-subjunctive",
+ "/equatives/other-equatives/#past-subjunctive",
+ (id, link) => (s: (a: "start" | "stop") => void) =>
,
);
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;
diff --git a/src/games/sub-cores/EquativeGame.tsx b/src/games/sub-cores/EquativeGame.tsx
new file mode 100644
index 0000000..8c33c5f
--- /dev/null
+++ b/src/games/sub-cores/EquativeGame.tsx
@@ -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
> {
+ 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) {
+ const [answer, setAnswer] = useState("");
+ const [withBa, setWithBa] = useState(false);
+ const handleInput = ({ target: { value }}: React.ChangeEvent) => {
+ setAnswer(value);
+ }
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const given = standardizePashto(answer.trim());
+ const correct = checkAnswer(given, question.equative.ps)
+ && withBa === question.equative.hasBa;
+ if (correct) {
+ setAnswer("");
+ }
+ callback(!correct
+ ?
+
+ {flattenLengths(question.equative.ps).reduce(((accum, curr, i): JSX.Element[] => (
+ [
+ ...accum,
+ ...i > 0 ? [ or ] : [],
+ {curr.p} ,
+ ]
+ )), [] as JSX.Element[])}
+
+
{question.equative.hasBa ? "with" : "without"} a {grammarUnits.baParticle} in the kids' section.
+
+ : true);
+ }
+
+ return
+
+ {/*
+ @ts-ignore */}
+
+ {question.phrase.e && question.phrase.e.map(e => (
+
{e}
+ ))}
+
+
+
+
+ }
+
+ function Instructions() {
+ return
+
Fill in the blank with the correct {humanReadableTense(tense)} equative in Pashto script
+
+ }
+
+ return
+};
+
+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]} ___ {splitP[1]}>,
+ f: <>{splitF[0]} ___ {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): 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,
+ };
+}
\ No newline at end of file
diff --git a/src/games/sub-cores/GenderGame.tsx b/src/games/sub-cores/GenderGame.tsx
index 422f0f1..e5e3d7b 100644
--- a/src/games/sub-cores/GenderGame.tsx
+++ b/src/games/sub-cores/GenderGame.tsx
@@ -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 = {
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) {
function check(gender: "m" | "f") {
- callback(!nounNotIn(gender === "m" ? mascNouns : femNouns)(question));
+ const correct = !nounNotIn(gender === "m" ? mascNouns : femNouns)(question);
+ callback(!correct
+ ? ANSWER HERE
+ : true);
}
return
@@ -132,12 +135,13 @@ export default function GenderGame({level, id, link}: { level: 1 | 2, id: string
function Instructions() {
return
-
Choose the right gender for each word
+
Choose the right gender for each word
{level === 2 &&
⚠ Exceptions included...
}
}
return
void }) {
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 = getRandomFromList(keys);
+ type = randFromArray(keys);
} while (!pool[type].length);
- const entry = getRandomFromList(
+ const entry = randFromArray(
// @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
+ ? CORRECT ANSWER HERE
+ : true);
}
return
@@ -120,11 +122,12 @@ export default function UnisexNounGame({ id, link }: { id: string, link: string
function Instructions() {
return
-
Change the gender of a given noun
+ Change the gender of a given noun
}
return
(list: T[]): T {
- return list[Math.floor((Math.random()*list.length))];
-}
-
export function makeProgress(i: number, total: number): Progress {
return { current: i + 1, total };
}
diff --git a/src/types/game-types.d.ts b/src/types/game-types.d.ts
index c797070..619052d 100644
--- a/src/types/game-types.d.ts
+++ b/src/types/game-types.d.ts
@@ -12,12 +12,12 @@ type QuestionGenerator = Generator, void, unknown>;
type QuestionDisplayProps = {
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,
};
diff --git a/yarn.lock b/yarn.lock
index 8eb8ab7..d5dc09b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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"