add perfect verb games

This commit is contained in:
adueck 2023-01-11 15:35:57 +05:00
parent 65339221ea
commit 9957e5b024
5 changed files with 548 additions and 9 deletions

View File

@ -18,7 +18,11 @@ import BasicVerbShowCase from "../../components/BasicVerbShowCase";
import perfectDogMeme from "./perfect-dog-meme.jpg";
import chemistryPerfect from "./chemistry-perfect.jpg";
import BasicBlocks from "../../components/BasicBlocks";
import GameDisplay from "../../games/GameDisplay";
import {
perfectGameOne,
perfectGameMix,
} from "../../games/games";
In the previous chapter we explained how [perfect](https://en.wikipedia.org/wiki/Perfect_(grammar)) forms are made by combining the <Link to="/verbs/roots-and-stems/#the-past-participle">past participle</Link> of a verb with an equative.
@ -316,4 +320,8 @@ Just like the <Link to="/equatives/other-equatives/#would-have-been-equative">"w
f: "no tu ba raaghúle waay ku nu!",
e: "Ah, you (f.) should have come!",
},
])}</Examples>
])}</Examples>
<GameDisplay record={perfectGameOne} />
<GameDisplay record={perfectGameMix} />

View File

@ -20,6 +20,12 @@ import presentPerfect from "./present-perfect.svg";
import chemistryPerfect from "./chemistry-perfect.jpg";
import BasicBlocks from "../../components/BasicBlocks";
import VideoPlayer from "../../components/VideoPlayer";
import GameDisplay from "../../games/GameDisplay";
import {
intransitivePresentPerfectGameOne,
intransitivePresentPerfectGameMix,
presentPerfectGame,
} from "../../games/games";
## Introduction
@ -236,6 +242,12 @@ With transitive verbs everything works the same, but we follow the <Link to="/ph
{"blocks":[{"key":0.48107357509198234,"block":{"type":"subjectSelection","selection":{"type":"NP","selection":{"type":"noun","entry":{"ts":1527812881,"i":11710,"p":"ماشوم","f":"maashoom","g":"maashoom","e":"child, kid","c":"n. m. anim. unisex","ec":"child","ep":"children"},"gender":"masc","genderCanChange":true,"number":"plural","numberCanChange":true,"adjectives":[],"possesor":{"np":{"type":"NP","selection":{"type":"pronoun","person":2,"distance":"far"}},"shrunken":false}}}}},{"key":0.8253773159904139,"block":{"type":"objectSelection","selection":{"type":"NP","selection":{"type":"pronoun","person":0,"distance":"far"}}}}],"verb":{"type":"verb","verb":{"entry":{"ts":1527815399,"i":14484,"p":"وهل","f":"wahul","g":"wahul","e":"to hit","c":"v. trans.","tppp":"واهه","tppf":"waahu","ec":"hit,hits,hitting,hit,hit"}},"verbTense":"perfectivePast","perfectTense":"presentPerfect","imperativeTense":"imperfectiveImperative","tenseCategory":"perfect","transitivity":"transitive","isCompound":false,"voice":"active","negative":false,"canChangeTransitivity":false,"canChangeVoice":true,"canChangeStatDyn":false},"form":{"removeKing":false,"shrinkServant":false}}
}</EditableVPEx>
<GameDisplay record={intransitivePresentPerfectGameOne} />
<GameDisplay record={intransitivePresentPerfectGameMix} />
<GameDisplay record={presentPerfectGame} />
## Negatives with the perfect
To make perfect forms negative you add a <InlinePs opts={opts} ps={{ p: "نه", f: "nú", e: "" }} /> block, just as you would with any other verb. But interestingly, **the past participle and equative blocks switch places**.

View File

@ -8,6 +8,7 @@ import EquativeIdentify from "./sub-cores/EquativeIdentify";
import VerbFormulas from "./sub-cores/VerbFormulas";
import InflectionPatterns from "./sub-cores/InflectionPatterns";
import InflectionsWriting from "./sub-cores/InflectionsWriting";
import PerfectVerbsIntransitive from "./sub-cores/PerfectGame";
// NOUNS
@ -35,42 +36,42 @@ export const unisexNounGame = makeGameRecord({
// INFLECTIONS
export const inflectionTableGame1 = makeGameRecord({
title: `Write the inflections - Pattern #1`,
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`,
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`,
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`,
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`,
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`,
title: "Write the inflections - Pattern #6",
id: "write-inflections-6",
link: "/inflection/inflection-patterns/#6-inanimate-feminine-nouns-ending-in-ي---ee",
level: 6,
@ -340,6 +341,61 @@ export const verbFormulasGame = makeGameRecord({
SubCore: VerbFormulas,
});
export const intransitivePresentPerfectGameOne = makeGameRecord({
title: "Write the present perfect verb - intransitive (one)",
id: "present-perfect-intransitive-one",
link: "/verbs/perfect-verbs-intro/",
level: {
level: 1,
type: "intransitive",
},
SubCore: PerfectVerbsIntransitive,
});
export const intransitivePresentPerfectGameMix = makeGameRecord({
title: "Write the present perfect verb - intransitive (mix)",
id: "present-perfect-intransitive-mix",
link: "/verbs/perfect-verbs-intro/",
level: {
level: 2,
type: "intransitive",
},
SubCore: PerfectVerbsIntransitive,
});
export const presentPerfectGame = makeGameRecord({
title: "Write the present perfect verb - transitive/intransitive mix",
id: "present-perfect",
link: "/verbs/perfect-verbs-intro/",
level: {
level: 2,
type: "transitive-intransitive",
},
SubCore: PerfectVerbsIntransitive,
});
export const perfectGameOne = makeGameRecord({
title: "Write the perfect verb - all perfect tenses (one verb)",
id: "all-perfect-one",
link: "/verbs/all-perfect-verbs/",
level: {
level: 1,
type: "all-tenses",
},
SubCore: PerfectVerbsIntransitive,
});
export const perfectGameMix = makeGameRecord({
title: "Write the perfect verb - all perfect tenses (mix)",
id: "all-perfect-mix",
link: "/verbs/all-perfect-verbs/",
level: {
level: 2,
type: "all-tenses",
},
SubCore: PerfectVerbsIntransitive,
});
const games: { chapter: string, items: GameRecord[] }[] = [
{
chapter: "Nouns",
@ -407,6 +463,16 @@ const games: { chapter: string, items: GameRecord[] }[] = [
verbFormulasGame,
],
},
{
chapter: "Perfect Verbs",
items: [
intransitivePresentPerfectGameOne,
intransitivePresentPerfectGameMix,
presentPerfectGame,
perfectGameOne,
perfectGameMix,
],
},
];
// check to make sure we have no duplicate game keys

View File

@ -0,0 +1,453 @@
import { useState } from "react";
import {
comparePs,
} from "../../lib/game-utils";
import GameCore from "../GameCore";
import {
Types as T,
defaultTextOptions as opts,
makeNounSelection,
randFromArray,
flattenLengths,
randomPerson,
InlinePs,
grammarUnits,
renderVP,
makeVPSelectionState,
compileVP,
blockUtils,
concatPsString,
isInvalidSubjObjCombo,
removeFVarients,
getEnglishVerb,
RootsAndStems,
getVerbInfo,
defaultTextOptions,
humanReadableVerbForm,
blank,
kidsBlank,
isPashtoScript,
} from "@lingdocs/ps-react";
import { isPastTense, isThirdPerson } from "@lingdocs/ps-react";
import { maybeShuffleArray } from "../../lib/shuffle-array";
import { getVerbFromBlocks } from "@lingdocs/ps-react/dist/lib/src/phrase-building/blocks-utils";
import { baParticle } from "@lingdocs/ps-react/dist/lib/src/grammar-units";
import { intransitivePastVerbs } from "../../content/verbs/basic-present-verbs";
import { makePool } from "../../lib/pool";
import { wordQuery } from "../../words/words";
import { isImperativeTense } from "@lingdocs/ps-react/dist/lib/src/type-predicates";
const kidsColor = "#017BFE";
const amount = 12;
const timeLimit = 140;
type Question = {
rendered: T.VPRendered,
phrase: { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] },
};
const transitivePastVerbs = wordQuery("verbs", [
"leedul",
"wahul",
"khoRul",
"shărmawul",
"pejzandul",
"taRul",
]);
const verbs = wordQuery("verbs", [
"leekul",
"wahul",
"leedul",
"awredul",
"khoRul",
"akhistul",
"katul",
"lwedul",
]);
const nouns = wordQuery("nouns", [
"saRey",
"xudza",
"maashoom",
"puxtoon",
"Ustaaz",
"DaakTar",
"halik",
]);
const persons: T.Person[] = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
];
type PerfectGameLevel = {
/* 1 is just a single verb, 2 picks a random verb for every question */
level: 1 | 2,
type: "intransitive" | "transitive-intransitive" | "all-tenses",
}
type VerbPoolName = "basic" | "transitivePast" | "intransitivePast" | "mixedPast" | "mixedAll";
type LevelInfo = {
description: string | JSX.Element,
tense: T.PerfectTense | "allTenses",
pool: VerbPoolName,
}
const levelInfos: Record<PerfectGameLevel["type"], LevelInfo> = {
"intransitive": {
description: "present perfect form of the verb",
tense: "presentPerfect",
pool: "intransitivePast",
},
"transitive-intransitive": {
description: "present perfect form of the verb",
tense: "presentPerfect",
pool: "mixedPast",
},
"all-tenses": {
description: "correct perfect form of the verb",
tense: "allTenses",
pool: "mixedPast",
},
}
// TODO: Level where you create the formulas (seperate file)
// level where you choose the right situation
const VerbGame: GameSubCore<PerfectGameLevel> = ({ id, link, level, inChapter }: {
inChapter: boolean,
id: string,
link: string,
level: PerfectGameLevel,
}) => {
const levelInfo = levelInfos[level.type];
const personPool = makePool(persons);
const verbPools: Record<VerbPoolName, () => T.VerbEntry> = {
basic: makePool(verbs, 15),
transitivePast: makePool(transitivePastVerbs, 15),
intransitivePast: makePool(intransitivePastVerbs, 15),
mixedPast: makePool([...transitivePastVerbs, ...intransitivePastVerbs], 15),
mixedAll: makePool([...verbs, ...transitivePastVerbs, ...intransitivePastVerbs], 15),
};
const tensePool = makePool<T.PerfectTense>([
"presentPerfect", "pastPerfect", "subjunctivePerfect", "habitualPerfect",
"pastPerfect", "futurePerfect", "wouldBePerfect", "pastSubjunctivePerfect",
"wouldHaveBeenPerfect",
]);
const oneVerb: T.VerbEntry = verbPools[levelInfo.pool]();
const getVerb = level.level === 1
? () => oneVerb
: () => verbPools[levelInfo.pool]();
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(tense: T.PerfectTense): T.VPSelectionComplete {
function personToNPSelection(p: T.Person): T.NPSelection {
if (isThirdPerson(p)) {
return {
type: "NP",
selection: randFromArray([
() => makePronounS(p),
makeRandomNoun,
() => makePronounS(p),
])(),
};
}
return {
type: "NP",
selection: 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));
return makeVPS({
verb,
king: personToNPSelection(king),
servant: personToNPSelection(servant),
tense,
defaultTransitivity: level.type.startsWith("transitive")
? "transitive"
: "grammatically transitive",
});
}
function getQuestion(): Question {
const VPS = makeRandomVPS(levelInfo.tense === "allTenses"
? tensePool()
: levelInfo.tense
);
const VP = renderVP(VPS);
const compiled = compileVP(
VP,
{ removeKing: false, shrinkServant: false },
true,
{ ba: levelInfo.tense === "allTenses", verb: true, negative: true },
);
const phrase = {
ps: compiled.ps,
e: compiled.e,
};
return {
rendered: VP,
phrase,
};
}
function Display({ question, callback }: QuestionDisplayProps<Question>) {
const [answer, setAnswer] = useState<string>("");
const [withBa, setWithBa] = useState<boolean>(false);
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
if (value === "به " || value === "به ") {
setWithBa(true);
setAnswer("");
return;
}
setAnswer(value);
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const correct = comparePs(answer, getVerbPs(question.rendered))
&& (withBa === verbHasBa(question.rendered));
if (correct) {
setAnswer("");
}
callback(correct);
}
// useEffect(() => {
// if (level === "allProduce") setWithBa(false);
// }, [question]);
return <div>
<QuestionDisplay question={question} userAnswer={{
withBa,
answer,
}} />
<form onSubmit={handleSubmit}>
{level.type === "all-tenses" && <div className="form-check mt-1">
<input
id="baCheckbox"
className="form-check-input"
type="checkbox"
checked={withBa}
onChange={e => setWithBa(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="baCheckbox">
with <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the <span style={{ color: kidsColor }}>kids' section</span>
</label>
</div>}
<div className="my-1" style={{ maxWidth: "200px", margin: "0 auto" }}>
<input
type="text"
className="form-control"
autoComplete="off"
autoCapitalize="off"
spellCheck="false"
dir="auto"
value={answer}
onChange={handleInput}
/>
</div>
<div className="text-center my-2">
{/* <div> */}
<button className="btn btn-primary" type="submit">submit </button>
{/* </div> */}
{/* <div className="text-muted small text-center mt-2">
Type <kbd>Enter</kbd> to check
</div> */}
</div>
</form>
</div>
}
function Instructions() {
const desc = levelInfo.description;
return <div>
<p className="lead">
Write the {desc} verb to complete the phrase
{desc ? "" : " (all tenses)"}
</p>
</div>
}
return <GameCore
inChapter={inChapter}
studyLink={link}
getQuestion={getQuestion}
id={id}
Display={Display}
DisplayCorrectAnswer={DisplayCorrectAnswer}
timeLimit={timeLimit}
amount={amount}
Instructions={Instructions}
/>
};
export default VerbGame;
function QuestionDisplay({ question, userAnswer }: {
question: Question,
userAnswer: { withBa: boolean, answer: string },
}) {
const ps = addUserAnswer(
userAnswer,
flattenLengths(question.phrase.ps)[0]
);
const v = getVerbFromBlocks(question.rendered.blocks);
const vEntry = v.block.verb.entry;
const infoV = getVerbInfo(vEntry)
const info = "grammaticallyTransitive" in infoV
? infoV.grammaticallyTransitive
: "stative" in infoV
? infoV.stative
: infoV;
return <div className="mb-3 mt-2">
<div className="mb-2">
<div>{vEntry.p} - {removeFVarients(vEntry.f)} <span className="text-muted">{vEntry.c}</span></div>
<div>"{getEnglishVerb(vEntry)}"</div>
</div>
<details style={{ marginBottom: 0 }}>
<summary>🌳 Show roots and stems</summary>
<RootsAndStems info={info} textOptions={defaultTextOptions} />
</details>
<div dir="rtl">{ps.p}</div>
<div dir="ltr">{ps.f}</div>
{question.phrase.e && <div className="text-muted mt-2">
{question.phrase.e.map(x => <div key={Math.random()}>
{x}
</div>)}
</div>}
<div>{humanReadableVerbForm(v.block.tense)}</div>
</div>;
}
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
return <div>
<div>
{getVerbPs(question.rendered).reduce(((accum, curr, i): JSX.Element[] => (
[
...accum,
...i > 0 ? [<span className="text-muted"> or </span>] : [],
<span>{curr.p} - {curr.f}</span>,
]
)), [] as JSX.Element[])}
</div>
<div><strong>{verbHasBa(question.rendered) ? "with" : "without"}</strong> a <InlinePs opts={opts}>{grammarUnits.baParticle}</InlinePs> in the kids' section.</div>
</div>;
}
// function modExs(exs: T.PsString[], withBa: boolean): { 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]} <span style={{ color: kidsColor }}>{withBa ? "به" : "__"}</span> {splitP[1]}</>,
// f: <>{splitF[0]} <span style={{ color: kidsColor }}>{withBa ? "ba" : "__"}</span> {splitF[1]}</>,
// };
// });
// }
function addUserAnswer(a: { withBa: boolean, answer: string }, ps: T.PsString): T.PsString {
function addBa(x: T.PsString) {
if (!a.withBa) return x;
return {
p: x.p.replace(kidsBlank.p, baParticle.p),
f: x.f.replace(kidsBlank.f, baParticle.f),
}
}
function addAnswer(x: T.PsString): T.PsString {
if (!a.answer) return x;
const field = isPashtoScript(a.answer) ? "p" : "f";
return {
...x,
[field]: x[field].replace(blank[field], a.answer),
};
}
return addAnswer(addBa(ps));
}
function makeVPS({ verb, king, servant, tense, defaultTransitivity }: {
verb: T.VerbEntry,
king: T.NPSelection,
servant: T.NPSelection,
tense: T.PerfectTense,
defaultTransitivity: "transitive" | "grammatically transitive"
}): T.VPSelectionComplete {
const vps = makeVPSelectionState(verb);
const transitivity = (vps.verb.transitivity === "transitive" && vps.verb.canChangeTransitivity)
? defaultTransitivity
: vps.verb.transitivity;
const ergative = vps.verb.transitivity !== "intransitive" && isPastTense(tense);
const subject = ergative ? servant : king;
const object = ergative ? king : servant;
return {
...vps,
verb: {
...vps.verb,
negative: isImperativeTense(tense)
? randFromArray([false, false, true])
: false,
transitivity,
tense,
},
blocks: maybeShuffleArray([
{
key: Math.random(),
block: {
type: "subjectSelection",
selection: subject,
},
},
{
key: Math.random(),
block: {
type: "objectSelection",
selection: transitivity === "intransitive"
? "none"
: transitivity === "grammatically transitive"
? T.Person.ThirdPlurMale
: object,
},
},
]),
};
}
function getVerbPs({ blocks }: T.VPRendered): T.PsString[] {
const { perfectiveHead, verb } = blockUtils.getVerbAndHeadFromBlocks(blocks);
const mU = blocks[0].find(b => b.block.type === "negative" && b.block.imperative);
function vBase() {
if (!perfectiveHead) {
return flattenLengths(verb.block.ps);
}
return flattenLengths(verb.block.ps).map(r => concatPsString(perfectiveHead.ps, r));
}
if (mU) {
return vBase().map(b => concatPsString({ p: "مه", f: "mú" }, " ", b));
}
return vBase();
}
function verbHasBa({ blocks }: T.VPRendered): boolean {
const verb = blockUtils.getVerbFromBlocks(blocks);
return verb.block.hasBa;
}

View File

@ -266,7 +266,7 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({ id, link, level, inChapter }: {
</div>
<div className="text-center my-2">
{/* <div> */}
<button className="btn btn-primary" type="submit">return </button>
<button className="btn btn-primary" type="submit">submit </button>
{/* </div> */}
{/* <div className="text-muted small text-center mt-2">
Type <kbd>Enter</kbd> to check