adjective game
This commit is contained in:
parent
cd774fc304
commit
1550409440
|
@ -7,7 +7,7 @@
|
|||
"@formkit/auto-animate": "^1.0.0-beta.1",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@lingdocs/lingdocs-main": "^0.3.3",
|
||||
"@lingdocs/ps-react": "^5.3.0",
|
||||
"@lingdocs/ps-react": "^5.3.1",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
|
|
|
@ -94,6 +94,8 @@ Now we have two words, but it's still **one NP**, one building block. We can add
|
|||
}
|
||||
}</EditableBlock>
|
||||
|
||||
It's important to note that the adjective will <Link to="/inflection/inflection-patterns/">inflect</Link> according to the noun it's attached to.
|
||||
|
||||
#### Adding a possesor
|
||||
|
||||
We can also add a **possesor** by adding another NP <Link to="/sandwiches/sandwiches/">sandwiched</Link> in with a <InlinePs opts={opts} ps={{ p: "د", f: "du", e: "of"}} />. (Notice that the word sandwiched in there will <Link to="/inflection/inflection-intro/">inflect</Link> if possible.) Now we have a NP inside of an NP, but it's still all **one NP** or **one building block**.
|
||||
|
|
|
@ -9,6 +9,7 @@ import VerbFormulas from "./sub-cores/VerbFormulas";
|
|||
import InflectionPatterns from "./sub-cores/InflectionPatterns";
|
||||
import InflectionsWriting from "./sub-cores/InflectionsWriting";
|
||||
import PerfectVerbsIntransitive from "./sub-cores/PerfectGame";
|
||||
import NPAdjWriting from "./sub-cores/NPAdjGame";
|
||||
|
||||
|
||||
// NOUNS
|
||||
|
@ -396,6 +397,22 @@ export const perfectGameMix = makeGameRecord({
|
|||
SubCore: PerfectVerbsIntransitive,
|
||||
});
|
||||
|
||||
// ADJECTIVES
|
||||
export const npWithAdjectivesHints = makeGameRecord({
|
||||
title: "Write the adjective and noun togehter (with inflection hints)",
|
||||
id: "adjective-nps-hints",
|
||||
link: "/phrase-structure/np/",
|
||||
level: "hints",
|
||||
SubCore: NPAdjWriting,
|
||||
});
|
||||
export const npWithAdjectivesNoHints = makeGameRecord({
|
||||
title: "Write the adjective and noun togehter",
|
||||
id: "adjective-nps-no-hints",
|
||||
link: "/phrase-structure/np/",
|
||||
level: "no-hints",
|
||||
SubCore: NPAdjWriting,
|
||||
});
|
||||
|
||||
const games: { chapter: string, items: GameRecord[] }[] = [
|
||||
{
|
||||
chapter: "Nouns",
|
||||
|
@ -473,6 +490,13 @@ const games: { chapter: string, items: GameRecord[] }[] = [
|
|||
perfectGameMix,
|
||||
],
|
||||
},
|
||||
{
|
||||
chapter: "Adjectives",
|
||||
items: [
|
||||
npWithAdjectivesHints,
|
||||
npWithAdjectivesNoHints,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// check to make sure we have no duplicate game keys
|
||||
|
|
|
@ -143,7 +143,7 @@ function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element
|
|||
[
|
||||
...accum,
|
||||
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||
<span>{curr.p}</span>,
|
||||
<span key={i}>{curr.p}</span>,
|
||||
]
|
||||
)), [] as JSX.Element[])}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
firstVariation,
|
||||
renderNPSelection,
|
||||
getEnglishFromRendered,
|
||||
removeFVarients,
|
||||
concatPsString,
|
||||
getInflectionPattern,
|
||||
HumanReadableInflectionPattern,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { makeNPAdjGenerator } from "../../lib/np-adj-generator";
|
||||
import { useState } from "react";
|
||||
import { comparePs } from "../../lib/game-utils";
|
||||
|
||||
const amount = 15;
|
||||
const timeLimit = 230;
|
||||
|
||||
type Question = {
|
||||
selection: T.NPSelection,
|
||||
answer: T.PsString[],
|
||||
english: string,
|
||||
};
|
||||
|
||||
type Level = "hints" | "no-hints";
|
||||
|
||||
// LEVELS
|
||||
// - without plurals
|
||||
// - with inflection category hinting
|
||||
|
||||
const NPAdjWriting: GameSubCore<Level> = ({ inChapter, id, link, level }: {
|
||||
inChapter: boolean,
|
||||
id: string,
|
||||
link: string,
|
||||
level: Level,
|
||||
}) => {
|
||||
const npPool = makeNPAdjGenerator();
|
||||
|
||||
// todo زړې ډاکټرې should work!
|
||||
// feminineplural space issue
|
||||
|
||||
function getQuestion(): Question {
|
||||
const np = npPool();
|
||||
const rendered = renderNPSelection(np, false, false, "subject", "none", false);
|
||||
const renderedAdj: T.Rendered<T.AdjectiveSelection> | undefined = rendered.selection.adjectives && rendered.selection.adjectives[0];
|
||||
if (!renderedAdj) {
|
||||
throw new Error("error getting rendered adjective");
|
||||
}
|
||||
const answer = renderedAdj.ps.flatMap((adjPs) => (
|
||||
rendered.selection.ps.map((nounPs) => (
|
||||
concatPsString(adjPs, " ", nounPs)
|
||||
))
|
||||
));
|
||||
return {
|
||||
selection: np,
|
||||
answer,
|
||||
english: getEnglishFromRendered(rendered) || "ERROR",
|
||||
};
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const correct = comparePs(answer, question.answer);
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(correct);
|
||||
}
|
||||
if (question.selection.type !== "NP" || question.selection.selection.type !== "noun") {
|
||||
throw new Error("QUESTION ERROR");
|
||||
}
|
||||
const nounEntry = question.selection.selection.entry;
|
||||
const adjEntry: T.AdjectiveEntry | undefined = question.selection.selection.adjectives[0]?.entry;
|
||||
if (!adjEntry) {
|
||||
throw new Error("QUESTION ERROR - MISSING ADJECTIVE");
|
||||
}
|
||||
const handleInput = ({ target: { value }}: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(value);
|
||||
}
|
||||
return <div>
|
||||
<div className="my-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<div className="d-flex flex-row justify-content-center">
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={adjEntry}
|
||||
selection={undefined}
|
||||
/>
|
||||
<WordCard
|
||||
showHint={level === "hints"}
|
||||
entry={nounEntry}
|
||||
selection={question.selection.selection}
|
||||
/>
|
||||
</div>
|
||||
<div className="my-3 h5">
|
||||
{question.english}
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mt-2 mb-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-3">
|
||||
<button className="btn btn-primary" type="submit">submit ↵</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return <div>
|
||||
<p className="lead">Write the adjective and noun together with the proper inflections.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
// TODO: extract this reduces for the or, or different variations, used in VerbGame (etc.?)
|
||||
return <div>
|
||||
<div>
|
||||
{question.answer.reduce(((accum, curr, i): JSX.Element[] => (
|
||||
[
|
||||
...accum,
|
||||
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||
<span key={i}>{curr.p} - {curr.f}</span>,
|
||||
]
|
||||
)), [] as JSX.Element[])}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function WordCard({ showHint, entry, selection }: {
|
||||
showHint: boolean
|
||||
} & ({
|
||||
entry: T.AdjectiveEntry,
|
||||
selection: undefined,
|
||||
} | {
|
||||
entry: T.NounEntry,
|
||||
selection: T.NounSelection,
|
||||
})) {
|
||||
const e = removeFVarients(entry);
|
||||
return <div className="card ml-2" style={{ minWidth: "10rem"}}>
|
||||
<div className="card-body" style={{ padding: "0.75rem" }}>
|
||||
<Examples opts={opts}>{[
|
||||
{
|
||||
p: e.p,
|
||||
f: e.f,
|
||||
e: `${firstVariation(e.e)} - ${e.c}`,
|
||||
}
|
||||
]}</Examples>
|
||||
{selection && <div style={{ marginTop: "-0.75rem"}}>
|
||||
{selection.genderCanChange && selection.gender === "fem"
|
||||
&& <span style={{ margin: "0 0.2rem" }}><strong>feminine</strong></span>}
|
||||
{selection.numberCanChange && selection.number === "plural"
|
||||
&& <span style={{ marginLeft: "0 0.2rem" }}><strong>plural</strong></span>}
|
||||
</div>}
|
||||
{showHint && <EntryCategoryTip entry={entry} />}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
function EntryCategoryTip({ entry }: { entry: T.AdjectiveEntry | T.NounEntry }) {
|
||||
const pattern = getInflectionPattern(entry);
|
||||
return <div className="badge bg-light small">
|
||||
{HumanReadableInflectionPattern(pattern, opts)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default NPAdjWriting;
|
|
@ -342,7 +342,7 @@ function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element
|
|||
[
|
||||
...accum,
|
||||
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||
<span>{curr.p} - {curr.f}</span>,
|
||||
<span key={i}>{curr.p} - {curr.f}</span>,
|
||||
]
|
||||
)), [] as JSX.Element[])}
|
||||
</div>
|
||||
|
|
|
@ -343,7 +343,7 @@ function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element
|
|||
[
|
||||
...accum,
|
||||
...i > 0 ? [<span className="text-muted"> or </span>] : [],
|
||||
<span>{curr.p} - {curr.f}</span>,
|
||||
<span key={i}>{curr.p} - {curr.f}</span>,
|
||||
]
|
||||
)), [] as JSX.Element[])}
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,8 @@ export function compareF(input: string, answer: string): boolean {
|
|||
return inp === (hasAccents(inp) ? ans : removeAccents(ans));
|
||||
}
|
||||
|
||||
export function comparePs(input: string, answer: T.SingleOrLengthOpts<T.PsString | T.PsString[]>): boolean {
|
||||
export function comparePs(inputRaw: string, answer: T.SingleOrLengthOpts<T.PsString | T.PsString[]>): boolean {
|
||||
const input = inputRaw.replace(/\s+/g, " ");
|
||||
if ("long" in answer) {
|
||||
return comparePs(input, flattenLengths(answer));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import {
|
||||
Types as T,
|
||||
makeNounSelection,
|
||||
makeAdjectiveSelection,
|
||||
randFromArray,
|
||||
} from "@lingdocs/ps-react";
|
||||
import { makePool } from "./pool";
|
||||
import { wordQuery } from "../words/words";
|
||||
|
||||
const nouns = wordQuery("nouns", [
|
||||
"saRey",
|
||||
"xudza",
|
||||
"maashoom",
|
||||
"Ustaaz",
|
||||
"puxtoon",
|
||||
"DaakTar",
|
||||
"halik",
|
||||
"tajriba",
|
||||
"melma",
|
||||
"khabura",
|
||||
"kitaab",
|
||||
"oobu",
|
||||
"korba",
|
||||
"shpoon",
|
||||
"gaawanDey",
|
||||
"lmasey",
|
||||
"lobghaaRey",
|
||||
"sandurghaaRey",
|
||||
"malgurey",
|
||||
"shpoonkey",
|
||||
"khalk",
|
||||
"ghul",
|
||||
"khur",
|
||||
]);
|
||||
|
||||
const adjectives = wordQuery("adjectives", [
|
||||
"muR",
|
||||
"sheen",
|
||||
"soor",
|
||||
"rixtooney",
|
||||
"pakhwaaney",
|
||||
"stuRey",
|
||||
"ooGd",
|
||||
"ghuT",
|
||||
"xu",
|
||||
"khufa",
|
||||
"takRa",
|
||||
"puT",
|
||||
"tuGey",
|
||||
"koochney",
|
||||
"wroostey",
|
||||
"pradey",
|
||||
"zoR",
|
||||
"moR",
|
||||
"treekh",
|
||||
"oom",
|
||||
"khoG",
|
||||
"droond",
|
||||
"loomRey",
|
||||
"Roond",
|
||||
"droond",
|
||||
"prot",
|
||||
"soR",
|
||||
"post",
|
||||
"pokh",
|
||||
"rooN",
|
||||
"woR",
|
||||
"tod",
|
||||
"khpor",
|
||||
"kooN",
|
||||
"koG",
|
||||
"zeeG",
|
||||
]);
|
||||
|
||||
export function makeNPAdjGenerator(avoidPlurals?: boolean): () => T.NPSelection {
|
||||
const nounPool = makePool(nouns);
|
||||
const adjPool = makePool(adjectives);
|
||||
|
||||
return () => {
|
||||
const ns = makeNounSelection(nounPool(), undefined);
|
||||
const selection: T.NounSelection = {
|
||||
...ns,
|
||||
adjectives: [makeAdjectiveSelection(adjPool())],
|
||||
...(ns.numberCanChange && !avoidPlurals) ? {
|
||||
number: randFromArray(["singular", "plural", "plural", "plural", "singular"]),
|
||||
} : {},
|
||||
...ns.genderCanChange ? {
|
||||
gender: randFromArray(["masc", "fem", "fem", "fem", "masc"]),
|
||||
} : {},
|
||||
};
|
||||
return {
|
||||
type: "NP",
|
||||
selection,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ module.exports = [
|
|||
1527822788, // بېګاته - begáata
|
||||
1527812183, // ورپام - wărpaam
|
||||
1527814711, // لنډ پر لنډه - lanD par lanDa
|
||||
1527821930, // بالبدیهه - bilbadeehá
|
||||
1527814164, // دېخوا - dekhwaa
|
||||
1527815160, // پرون - paroon
|
||||
1623688539364, // ګانګوړ - gaangóR, gaangwÚR
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module.exports = [
|
||||
1527812797, // xudza
|
||||
1527815329, // tajriba
|
||||
1527812797, // xudza
|
||||
1527812766, // khabura
|
||||
1527816466, // صلح - sUlha
|
||||
1527816589, // طرح - tarha
|
||||
1589023873660, // فتح - fathá
|
||||
|
|
|
@ -15,4 +15,5 @@ module.exports = [
|
|||
1527822725, // لوېشت - lwesht
|
||||
1527811609, // منګل - mangul
|
||||
1527821684, // ورېځ - wurédz
|
||||
1527815129, // oobu
|
||||
];
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = [
|
||||
1527812342, // khalk
|
||||
1527814694, // mawaad
|
||||
1527815177, // پلار
|
||||
1527812828, // کور
|
||||
1527812432, // آسمان - aasmaan
|
||||
|
|
|
@ -12,7 +12,6 @@ module.exports = [
|
|||
1527818402, // اتلولي - atalwalée
|
||||
1527814060, // اساني - asaanee
|
||||
1527821293, // امادګي - amaadagee
|
||||
1527819502, // باچهي - baachahee
|
||||
1527820035, // باداري - baadaaree
|
||||
1527817732, // بدبختي - badbakhtee
|
||||
1588786872582, // بدنامي - badnaamee
|
||||
|
|
|
@ -16,4 +16,5 @@ module.exports = [
|
|||
1527815087, // مړ - muR
|
||||
1527814151, // مل - mal
|
||||
1527813580, // یو - yo
|
||||
1527819902, // zeeG
|
||||
];
|
Loading…
Reference in New Issue