added more inflection games
This commit is contained in:
parent
199c93915c
commit
f4bb65cd9c
|
@ -7,7 +7,7 @@
|
||||||
"@formkit/auto-animate": "^1.0.0-beta.1",
|
"@formkit/auto-animate": "^1.0.0-beta.1",
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||||
"@lingdocs/lingdocs-main": "^0.3.3",
|
"@lingdocs/lingdocs-main": "^0.3.3",
|
||||||
"@lingdocs/pashto-inflector": "^3.9.4",
|
"@lingdocs/pashto-inflector": "^3.9.6",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
"@testing-library/react": "^11.1.0",
|
"@testing-library/react": "^11.1.0",
|
||||||
"@testing-library/user-event": "^12.1.10",
|
"@testing-library/user-event": "^12.1.10",
|
||||||
|
|
|
@ -50,7 +50,7 @@ type GameReducerAction = {
|
||||||
} | {
|
} | {
|
||||||
type: "timeout",
|
type: "timeout",
|
||||||
} | {
|
} | {
|
||||||
type: "show answer",
|
type: "toggle show answer",
|
||||||
} | {
|
} | {
|
||||||
type: "skip",
|
type: "skip",
|
||||||
}
|
}
|
||||||
|
@ -176,12 +176,12 @@ function GameCore<Question>({ inChapter, getQuestion, amount, Display, DisplayCo
|
||||||
showAnswer: false,
|
showAnswer: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (action.type === "show answer") {
|
if (action.type === "toggle show answer") {
|
||||||
if (gs.mode === "practice" && gs.justStruck) {
|
if (gs.mode === "practice") {
|
||||||
return {
|
return {
|
||||||
...gs,
|
...gs,
|
||||||
justStruck: false,
|
justStruck: false,
|
||||||
showAnswer: true,
|
showAnswer: !gs.showAnswer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return gs;
|
return gs;
|
||||||
|
@ -310,13 +310,12 @@ function GameCore<Question>({ inChapter, getQuestion, amount, Display, DisplayCo
|
||||||
question={state.current}
|
question={state.current}
|
||||||
callback={(correct) => dispatch({ type: "handle question response", payload: { correct }})}
|
callback={(correct) => dispatch({ type: "handle question response", payload: { correct }})}
|
||||||
/>}
|
/>}
|
||||||
{(state.mode === "practice" && state.justStruck) && <div className="my-3">
|
{(state.mode === "practice" && (state.justStruck || state.showAnswer)) && <div className="my-3">
|
||||||
<button className="btn btn-sm btn-secondary" onClick={() => dispatch({ type: "show answer" })}>
|
<button className="btn btn-sm btn-secondary" onClick={() => dispatch({ type: "toggle show answer" })}>
|
||||||
Show Answer
|
{state.showAnswer ? "Hide" : "Show"} Answer
|
||||||
</button>
|
</button>
|
||||||
</div>}
|
</div>}
|
||||||
{(state.showAnswer && state.mode === "practice") && <div className="my-2">
|
{(state.showAnswer && state.mode === "practice") && <div className="my-2">
|
||||||
<div>The correct answer was:</div>
|
|
||||||
<div className="my-1">
|
<div className="my-1">
|
||||||
<DisplayCorrectAnswer question={state.current} />
|
<DisplayCorrectAnswer question={state.current} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,6 +7,8 @@ import VerbSituations from "./sub-cores/VerbSituations";
|
||||||
import EquativeIdentify from "./sub-cores/EquativeIdentify";
|
import EquativeIdentify from "./sub-cores/EquativeIdentify";
|
||||||
import VerbFormulas from "./sub-cores/VerbFormulas";
|
import VerbFormulas from "./sub-cores/VerbFormulas";
|
||||||
import InflectionPatterns from "./sub-cores/InflectionPatterns";
|
import InflectionPatterns from "./sub-cores/InflectionPatterns";
|
||||||
|
import InflectionsWriting from "./sub-cores/InflectionsWriting";
|
||||||
|
|
||||||
|
|
||||||
// NOUNS
|
// NOUNS
|
||||||
export const nounGenderGame1 = makeGameRecord({
|
export const nounGenderGame1 = makeGameRecord({
|
||||||
|
@ -32,6 +34,48 @@ export const unisexNounGame = makeGameRecord({
|
||||||
});
|
});
|
||||||
|
|
||||||
// INFLECTIONS
|
// INFLECTIONS
|
||||||
|
export const inflectionTableGame1 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #1`,
|
||||||
|
id: "write-inflections-1",
|
||||||
|
link: "/inflection/inflection-patterns/#1-basic",
|
||||||
|
level: 1,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
|
export const inflectionTableGame2 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #2`,
|
||||||
|
id: "write-inflections-2",
|
||||||
|
link: "/inflection/inflection-patterns/#2-words-ending-in-an-unstressed-ی---ey",
|
||||||
|
level: 2,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
|
export const inflectionTableGame3 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #3`,
|
||||||
|
id: "write-inflections-3",
|
||||||
|
link: "/inflection/inflection-patterns/#3-words-ending-in-a-stressed-ی---éy",
|
||||||
|
level: 3,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
|
export const inflectionTableGame4 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #4`,
|
||||||
|
id: "write-inflections-4",
|
||||||
|
link: "/inflection/inflection-patterns/#4-words-with-the-pashtoon-pattern",
|
||||||
|
level: 4,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
|
export const inflectionTableGame5 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #5`,
|
||||||
|
id: "write-inflections-5",
|
||||||
|
link: "/inflection/inflection-patterns/#5-shorter-words-that-squish",
|
||||||
|
level: 5,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
|
export const inflectionTableGame6 = makeGameRecord({
|
||||||
|
title: `Write the inflections - Pattern #6`,
|
||||||
|
id: "write-inflections-6",
|
||||||
|
link: "/inflection/inflection-patterns/#6-inanimate-feminine-nouns-ending-in-ي---ee",
|
||||||
|
level: 6,
|
||||||
|
SubCore: InflectionsWriting,
|
||||||
|
});
|
||||||
export const inflectionPatternsGame1 = makeGameRecord({
|
export const inflectionPatternsGame1 = makeGameRecord({
|
||||||
title: "Identify the inflection pattern (Level 1)",
|
title: "Identify the inflection pattern (Level 1)",
|
||||||
id: "inflection-patterns-1",
|
id: "inflection-patterns-1",
|
||||||
|
@ -308,6 +352,12 @@ const games: { chapter: string, items: GameRecord[] }[] = [
|
||||||
{
|
{
|
||||||
chapter: "Inflection",
|
chapter: "Inflection",
|
||||||
items: [
|
items: [
|
||||||
|
inflectionTableGame1,
|
||||||
|
inflectionTableGame2,
|
||||||
|
inflectionTableGame3,
|
||||||
|
inflectionTableGame4,
|
||||||
|
inflectionTableGame5,
|
||||||
|
inflectionTableGame6,
|
||||||
inflectionPatternsGame1,
|
inflectionPatternsGame1,
|
||||||
inflectionPatternsGame2,
|
inflectionPatternsGame2,
|
||||||
],
|
],
|
||||||
|
@ -363,8 +413,8 @@ const games: { chapter: string, items: GameRecord[] }[] = [
|
||||||
games.forEach(({ items }) => {
|
games.forEach(({ items }) => {
|
||||||
const allAreUnique = (arr: unknown[]) => arr.length === new Set(arr).size;
|
const allAreUnique = (arr: unknown[]) => arr.length === new Set(arr).size;
|
||||||
const ids = items.map(x => x.id);
|
const ids = items.map(x => x.id);
|
||||||
const title = items.map(x => x.title);
|
const titles = items.map(x => x.title);
|
||||||
if (!allAreUnique(title)) throw new Error("duplicate game title");
|
if (!allAreUnique(titles)) throw new Error("duplicate game title");
|
||||||
if (!allAreUnique(ids)) throw new Error("duplicate game key");
|
if (!allAreUnique(ids)) throw new Error("duplicate game key");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
import GameCore from "../GameCore";
|
||||||
|
import {
|
||||||
|
Types as T,
|
||||||
|
getInflectionPattern,
|
||||||
|
Examples,
|
||||||
|
defaultTextOptions as opts,
|
||||||
|
firstVariation,
|
||||||
|
inflectWord,
|
||||||
|
humanReadableInflectionPattern,
|
||||||
|
isUnisexSet,
|
||||||
|
InflectionsTable,
|
||||||
|
} from "@lingdocs/pashto-inflector";
|
||||||
|
import { makePool } from "../../lib/pool";
|
||||||
|
import { nouns, adjectives } from "../../words/words";
|
||||||
|
import { isAdverbEntry } from "@lingdocs/pashto-inflector/dist/lib/type-predicates";
|
||||||
|
import { ChangeEvent, FormEvent, useEffect, useRef, useState } from "react";
|
||||||
|
import { comparePs } from "../../lib/game-utils";
|
||||||
|
|
||||||
|
const amount = 8;
|
||||||
|
const timeLimit = 300;
|
||||||
|
|
||||||
|
type Question = {
|
||||||
|
entry: T.NounEntry | T.AdjectiveEntry,
|
||||||
|
inflections: T.Inflections,
|
||||||
|
};
|
||||||
|
|
||||||
|
type InfFormContent = {
|
||||||
|
masc: [string, string, string],
|
||||||
|
fem: [string, string, string],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function InflectionsWriting({ inChapter, id, link, level }: {
|
||||||
|
inChapter: boolean,
|
||||||
|
id: string,
|
||||||
|
link: string,
|
||||||
|
level: T.InflectionPattern,
|
||||||
|
}) {
|
||||||
|
const wordPool = makePool(
|
||||||
|
[...nouns, ...adjectives]
|
||||||
|
.filter(x => {
|
||||||
|
if (isAdverbEntry(x)) return false;
|
||||||
|
const infs = inflectWord(x);
|
||||||
|
if (!infs || !infs.inflections) return false;
|
||||||
|
return (getInflectionPattern(x) === level);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
function getQuestion(): Question {
|
||||||
|
const word = wordPool();
|
||||||
|
const r = inflectWord(word);
|
||||||
|
if (!r || !r.inflections) {
|
||||||
|
throw new Error(`error getting inflections for ${word.f}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
entry: word,
|
||||||
|
inflections: r.inflections,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||||
|
function handleAnswer(inf: InfFormContent) {
|
||||||
|
callback(infAnswerCorrect(inf, question.inflections));
|
||||||
|
}
|
||||||
|
return <div>
|
||||||
|
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||||
|
<Examples opts={opts}>{[
|
||||||
|
{
|
||||||
|
p: firstVariation(question.entry.p),
|
||||||
|
f: firstVariation(question.entry.f),
|
||||||
|
e: `${firstVariation(question.entry.e)} - ${question.entry.c}`,
|
||||||
|
}
|
||||||
|
]}</Examples>
|
||||||
|
</div>
|
||||||
|
<InflectionTableForm
|
||||||
|
onSubmit={handleAnswer}
|
||||||
|
question={question}
|
||||||
|
genders={isUnisexSet(question.inflections)
|
||||||
|
? "unisex"
|
||||||
|
: "masc" in question.inflections
|
||||||
|
? "masc"
|
||||||
|
: "fem"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function Instructions() {
|
||||||
|
return <div>
|
||||||
|
<p className="lead">Complete the inflections for the <strong>{humanReadableInflectionPattern(level, opts)}</strong> pattern word</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <GameCore
|
||||||
|
inChapter={inChapter}
|
||||||
|
studyLink={link}
|
||||||
|
getQuestion={getQuestion}
|
||||||
|
id={id}
|
||||||
|
Display={Display}
|
||||||
|
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||||
|
timeLimit={timeLimit}
|
||||||
|
amount={amount}
|
||||||
|
Instructions={Instructions}
|
||||||
|
/>
|
||||||
|
};
|
||||||
|
|
||||||
|
function InflectionTableForm({ onSubmit, genders, question }: {
|
||||||
|
onSubmit: (i: InfFormContent) => void,
|
||||||
|
genders: T.Gender | "unisex",
|
||||||
|
question: Question,
|
||||||
|
}) {
|
||||||
|
const [inf, setInf] = useState<InfFormContent>({ fem: ["", "", ""], masc: ["", "", ""] });
|
||||||
|
const mascInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const femInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
setInf({ fem: ["", "", ""], masc: ["", "", ""] });
|
||||||
|
femInputRef.current && femInputRef.current.focus();
|
||||||
|
mascInputRef.current && mascInputRef.current.focus();
|
||||||
|
}, [question, setInf]);
|
||||||
|
|
||||||
|
function handleClearInf() {
|
||||||
|
setInf({ fem: ["", "", ""], masc: ["", "", ""] });
|
||||||
|
}
|
||||||
|
function handleInfInput(gender: T.Gender, inflection: number) {
|
||||||
|
return ({ target: { value }}: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
inf[gender][inflection] = value;
|
||||||
|
setInf({...inf});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function handleSubmit(e: FormEvent<HTMLFormElement>) {
|
||||||
|
e.preventDefault();
|
||||||
|
onSubmit(inf);
|
||||||
|
}
|
||||||
|
return <form onSubmit={handleSubmit}>
|
||||||
|
<table className="table" style={{ tableLayout: "fixed" }}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style={{ width: "3.5rem" }}></th>
|
||||||
|
{genders !== "fem" && <th scope="col" style={{ maxWidth: "10rem", textAlign: "left" }}>Masculine</th>}
|
||||||
|
{genders !== "masc" && <th scope="col" style={{ maxWidth: "10rem", textAlign: "left" }}>Feminine</th>}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{["Plain", "1st", "2nd"].map((title, i) => (
|
||||||
|
<tr key={title}>
|
||||||
|
<th scope="row">{title}</th>
|
||||||
|
{genders !== "fem" && <td>
|
||||||
|
<input
|
||||||
|
ref={i === 0 ? mascInputRef : undefined}
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
dir="auto"
|
||||||
|
value={inf.masc[i]}
|
||||||
|
onChange={handleInfInput("masc", i)}
|
||||||
|
style={{ maxWidth: "12rem" }}
|
||||||
|
/>
|
||||||
|
</td>}
|
||||||
|
{genders !== "masc" && <td>
|
||||||
|
<input
|
||||||
|
ref={i === 0 ? femInputRef : undefined}
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
dir="auto"
|
||||||
|
value={inf.fem[i]}
|
||||||
|
onChange={handleInfInput("fem", i)}
|
||||||
|
style={{ maxWidth: "12rem" }}
|
||||||
|
/>
|
||||||
|
</td>}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="text-center">
|
||||||
|
<button type="button" className="btn btn-secondary mx-3" onClick={handleClearInf}>Clear</button>
|
||||||
|
<button type="submit" className="btn btn-primary mx-3">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||||
|
return <div>
|
||||||
|
<InflectionsTable
|
||||||
|
inf={question.inflections}
|
||||||
|
textOptions={opts}
|
||||||
|
hideTitle
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function infAnswerCorrect(answer: InfFormContent, inf: T.Inflections): boolean {
|
||||||
|
function genInfCorrect(gender: T.Gender): boolean {
|
||||||
|
// @ts-ignore
|
||||||
|
const genInf = inf[gender] as T.InflectionSet;
|
||||||
|
return genInf.every((x, i) => (
|
||||||
|
x.some(ps => comparePs(answer[gender][i], ps))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (isUnisexSet(inf)) {
|
||||||
|
return genInfCorrect("masc") && genInfCorrect("fem");
|
||||||
|
}
|
||||||
|
return genInfCorrect("masc" in inf ? "masc": "fem");
|
||||||
|
}
|
||||||
|
|
|
@ -284,7 +284,6 @@ module.exports = [
|
||||||
1527823047, // اچانک - achaanak
|
1527823047, // اچانک - achaanak
|
||||||
1527813881, // په نامه - pu naama
|
1527813881, // په نامه - pu naama
|
||||||
1527817130, // ترخوا - turkhwaa
|
1527817130, // ترخوا - turkhwaa
|
||||||
1595515640766, // تېر و بېر - ter-U-ber
|
|
||||||
1527822042, // راپورته - raaporta
|
1527822042, // راپورته - raaporta
|
||||||
1527818051, // تر ابده - tur abada
|
1527818051, // تر ابده - tur abada
|
||||||
1585821444042, // اتفاقاً - itifaaqan
|
1585821444042, // اتفاقاً - itifaaqan
|
||||||
|
|
|
@ -1804,10 +1804,10 @@
|
||||||
rambda "^6.7.0"
|
rambda "^6.7.0"
|
||||||
react-select "^5.2.2"
|
react-select "^5.2.2"
|
||||||
|
|
||||||
"@lingdocs/pashto-inflector@^3.9.4":
|
"@lingdocs/pashto-inflector@^3.9.6":
|
||||||
version "3.9.4"
|
version "3.9.6"
|
||||||
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-3.9.4.tgz#b85bd8b9c235da1b77a04bd4178be91371a94726"
|
resolved "https://npm.lingdocs.com/@lingdocs%2fpashto-inflector/-/pashto-inflector-3.9.6.tgz#308d50c81108f47fccedadecdf32c2eaaa7a797c"
|
||||||
integrity sha512-bthgBbzkFHIYUOTFNUNcwWFuhCkFlX7RCPQIbZdXFPOdUoEGtlngqJkECKm+pIShQioEGuMxhW0plhXDIccA8w==
|
integrity sha512-UGei0f8Yf19CFvrtBBMABxe1ICAFcepk9ytnsbguX6F1hCNRD13RmNcT8GnmIm+wPIXRS1Ae0Z7zgmx7fKcT7w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@formkit/auto-animate" "^1.0.0-beta.1"
|
"@formkit/auto-animate" "^1.0.0-beta.1"
|
||||||
classnames "^2.2.6"
|
classnames "^2.2.6"
|
||||||
|
|
Loading…
Reference in New Issue