added verb formula game

This commit is contained in:
lingdocs 2022-09-10 15:18:23 +04:00
parent cbe51eed48
commit 6eab368245
5 changed files with 298 additions and 7 deletions

View File

@ -17,6 +17,7 @@
"bootstrap": "4.5.3", "bootstrap": "4.5.3",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"cron": "^1.8.2", "cron": "^1.8.2",
"fast-deep-equal": "^3.1.3",
"froebel": "^0.21.3", "froebel": "^0.21.3",
"lokijs": "^1.5.12", "lokijs": "^1.5.12",
"markdown-to-jsx": "^7.1.3", "markdown-to-jsx": "^7.1.3",

View File

@ -5,6 +5,7 @@ import UnisexNounGame from "./sub-cores/UnisexNounGame";
import EquativeSituations from "./sub-cores/EquativeSituations"; import EquativeSituations from "./sub-cores/EquativeSituations";
import VerbSituations from "./sub-cores/VerbSituations"; import VerbSituations from "./sub-cores/VerbSituations";
import EquativeIdentify from "./sub-cores/EquativeIdentify"; import EquativeIdentify from "./sub-cores/EquativeIdentify";
import VerbFormulas from "./sub-cores/VerbFormulas";
// NOUNS // NOUNS
export const nounGenderGame1 = makeGameRecord({ export const nounGenderGame1 = makeGameRecord({
@ -270,6 +271,13 @@ export const verbSituationsGame = makeGameRecord({
level: "situations", level: "situations",
SubCore: VerbSituations, SubCore: VerbSituations,
}); });
export const verbFormulasGame = makeGameRecord({
title: "Choose the verb tense formula",
id: "verb-tense-formulas",
link: "/verbs/master-chart/",
level: "all",
SubCore: VerbFormulas,
});
const games: { chapter: string, items: GameRecord[] }[] = [ const games: { chapter: string, items: GameRecord[] }[] = [
{ {
@ -322,6 +330,7 @@ const games: { chapter: string, items: GameRecord[] }[] = [
allVerbGame1, allVerbGame1,
allVerbGame2, allVerbGame2,
verbSituationsGame, verbSituationsGame,
verbFormulasGame,
], ],
}, },
]; ];

View File

@ -0,0 +1,285 @@
import GameCore from "../GameCore";
import {
humanReadableVerbForm,
Types as T,
InlinePs,
grammarUnits,
defaultTextOptions as opts,
} from "@lingdocs/pashto-inflector";
import { makePool } from "../../lib/pool";
import { CSSProperties, useEffect, useState } from "react";
import classNames from "classnames";
const amount = 10;
const timeLimit = 60;
type StemRoot = "imperfective stem" | "perfective stem" | "imperfective root" | "perfective root";
type Ending = "present" | "past" | "imperative";
type Formula = {
ba: boolean,
stemRoot: StemRoot,
ending: Ending,
};
type Question = {
tense: T.VerbTense,
formula: Formula,
}
const questions: Question[] = [
{
tense: "presentVerb",
formula: {
ba: false,
stemRoot: "imperfective stem",
ending: "present",
},
},
{
tense: "subjunctiveVerb",
formula: {
ba: false,
stemRoot: "perfective stem",
ending: "present",
},
},
{
tense: "imperfectiveFuture",
formula: {
ba: true,
stemRoot: "imperfective stem",
ending: "present",
},
},
{
tense: "perfectiveFuture",
formula: {
ba: true,
stemRoot: "perfective stem",
ending: "present",
},
},
{
tense: "imperfectivePast",
formula: {
ba: false,
stemRoot: "imperfective root",
ending: "past",
},
},
{
tense: "perfectivePast",
formula: {
ba: false,
stemRoot: "perfective root",
ending: "past",
},
},
{
tense: "habitualImperfectivePast",
formula: {
ba: true,
stemRoot: "imperfective root",
ending: "past",
},
},
{
tense: "habitualPerfectivePast",
formula: {
ba: true,
stemRoot: "perfective root",
ending: "past",
},
},
];
export default function VerbFormulas({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "all" }) {
const questionsPool = makePool(questions);
function getQuestion() {
return questionsPool();
}
function Instructions() {
return <p className="lead">Pick the formula for each verb tense</p>;
}
return <GameCore
inChapter={inChapter}
studyLink={link}
getQuestion={getQuestion}
id={id}
Display={Display}
DisplayCorrectAnswer={DisplayCorrectAnswer}
timeLimit={timeLimit}
amount={amount}
Instructions={Instructions}
/>
};
function Display({ question, callback }: QuestionDisplayProps<Question>) {
const [ba, setBa] = useState<boolean>(false);
const [stemRoot, setStemRoot] = useState<StemRoot | "">("");
const [ending, setEnding] = useState<Ending | "">("");
useEffect(() => {
setBa(false);
setStemRoot("");
setEnding("");
}, [question]);
const canSubmit = !!(stemRoot && ending);
function handleSubmit() {
const { formula } = question;
callback(
(ba === formula.ba)
&&
(stemRoot === formula.stemRoot)
&&
(ending === formula.ending)
);
}
return <div>
<div className="my-4" style={{ maxWidth: "300px", margin: "0 auto" }}>
<p className="lead">
{humanReadableVerbForm(question.tense)}
</p>
</div>
<div className="text-center mb-2">
<BaPicker hasBa={ba} onChange={setBa} />
<RootsAndStemsPicker stemRoot={stemRoot} onChange={setStemRoot} />
<EndingPicker ending={ending} onChange={setEnding} />
<button
className="btn btn-primary my-2"
disabled={!canSubmit}
onClick={canSubmit ? handleSubmit : undefined}
>
Submit
</button>
</div>
<samp>{canSubmit ? printFormula({ ba, stemRoot, ending }) : " "}</samp>
</div>
}
function printFormula(f: Formula): string {
return `${f.ba ? "ba + " : ""}${f.stemRoot} + ${f.ending} ending`;
}
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
return <div>
<samp>{printFormula(question.formula)}</samp>
</div>;
}
function EndingPicker({ onChange, ending }: { ending: Ending | "", onChange: (e: Ending | "") => void }) {
const options: { label: string, value: Ending }[] = [
{ label: "Present", value: "present" },
{ label: "Past", value: "past" },
{ label: "Imperative", value: "imperative" },
];
function handleClick(e: Ending) {
// onChange(ending === e ? "" : e);
onChange(e);
}
return <div className="my-3">
<span className="mr-2">Ending:</span>
<div className="btn-group">
{options.map((option) => (
<button
key={option.value}
type="button"
className={classNames(
"btn",
"btn-outline-secondary",
{ active: ending === option.value },
)}
onClick={() => handleClick(option.value)}
>
{option.label}
</button>
))}
</div>
</div>;
}
function BaPicker({ onChange, hasBa }: { hasBa: boolean, onChange: (b: boolean) => void }) {
return <div className="form-check my-3" style={{ fontSize: "larger" }}>
<input
id="baCheckbox"
className="form-check-input"
type="checkbox"
checked={hasBa}
onChange={e => onChange(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids' section
</label>
</div>
}
function RootsAndStemsPicker({ onChange, stemRoot }: { stemRoot: StemRoot | "", onChange: (s: StemRoot | "" ) => void }) {
const colClass = "col col-md-5 px-0 mb-1";
const rowClass = "row justify-content-between";
const title: CSSProperties = {
fontWeight: "bolder",
paddingBottom: "1.75rem",
paddingTop: "1.75rem",
};
const highlight = {
background: "rgba(255, 227, 10, 0.6)",
};
function handleStemRootClick(s: StemRoot) {
onChange(stemRoot === s ? "" : s);
}
return <div className="verb-info" style={{
textAlign: "center",
maxWidth: "400px",
margin: "0 auto",
// backgroundImage: `url(${fadedTree})`,
backgroundRepeat: "no-repeat",
backgroundPosition: "50% 45%",
backgroundSize: "50%",
}}>
<div style={{
border: "2px solid black",
padding: "1rem",
margin: "0.25rem",
}} className="container">
<div className={rowClass + " align-items-center"}>
<div className={colClass}>
<i className="fas fa-video fa-lg" />
</div>
<div className={colClass}>
<div className="d-flex flex-row justify-content-center align-items-center">
<div>
<i className="fas fa-camera fa-lg mx-3" />
</div>
</div>
</div>
</div>
<div className={rowClass}>
<div className={colClass} style={stemRoot === "imperfective stem" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("imperfective stem")}>
<div>Imperfective Stem</div>
</div>
</div>
<div className={colClass} style={stemRoot === "perfective stem" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("perfective stem")}>
<div>Perfective Stem</div>
</div>
</div>
</div>
<div className={rowClass}>
<div className={colClass} style={stemRoot === "imperfective root" ? highlight : {}}>
<div className="clickable" style={title} onClick={() => handleStemRootClick("imperfective root")}>
<div>Imperfective Root</div>
</div>
</div>
<div className={colClass} style={stemRoot === "perfective root" ? highlight : {}}>
<div>
<div className="clickable" style={title} onClick={() => handleStemRootClick("perfective root")}>
<div>Perfective Root</div>
</div>
</div>
</div>
</div>
</div>
</div>;
}

View File

@ -1,4 +1,5 @@
import { randFromArray } from "@lingdocs/pashto-inflector"; import { randFromArray } from "@lingdocs/pashto-inflector";
import equal from "fast-deep-equal";
/** /**
* *
@ -22,7 +23,7 @@ export function makePool<P>(poolBase: P[], removalLaxity = 0): () => P {
if (shouldStillKeepIt()) { if (shouldStillKeepIt()) {
return pick; return pick;
} }
const index = pool.findIndex(v => matches(v, pick)) const index = pool.findIndex(v => equal(v, pick))
if (index === -1) throw new Error("could not find pick from pool"); if (index === -1) throw new Error("could not find pick from pool");
pool.splice(index, 1); pool.splice(index, 1);
// If the pool is empty, reset it // If the pool is empty, reset it
@ -34,8 +35,3 @@ export function makePool<P>(poolBase: P[], removalLaxity = 0): () => P {
return pickRandomFromPool; return pickRandomFromPool;
} }
function matches(a: unknown, b: unknown): boolean {
return JSON.stringify(a) === JSON.stringify(b);
}

View File

@ -5648,7 +5648,7 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
fast-deep-equal@^3.1.1: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==