added verb formula game
This commit is contained in:
parent
cbe51eed48
commit
6eab368245
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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>;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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==
|
||||||
|
|
Loading…
Reference in New Issue