phonetics update
This commit is contained in:
parent
ddf967ae63
commit
c82fc04821
|
@ -1,14 +1,14 @@
|
||||||
import GameCore from "../GameCore";
|
import GameCore from "../GameCore";
|
||||||
import {
|
import {
|
||||||
Types as T,
|
Types as T,
|
||||||
getInflectionPattern,
|
getInflectionPattern,
|
||||||
Examples,
|
Examples,
|
||||||
defaultTextOptions as opts,
|
defaultTextOptions as opts,
|
||||||
firstVariation,
|
firstVariation,
|
||||||
inflectWord,
|
inflectWord,
|
||||||
HumanReadableInflectionPattern,
|
HumanReadableInflectionPattern,
|
||||||
isUnisexSet,
|
isUnisexSet,
|
||||||
InflectionsTable,
|
InflectionsTable,
|
||||||
} from "@lingdocs/ps-react";
|
} from "@lingdocs/ps-react";
|
||||||
import { makePool } from "../../lib/pool";
|
import { makePool } from "../../lib/pool";
|
||||||
import { nouns, adjectives } from "../../words/words";
|
import { nouns, adjectives } from "../../words/words";
|
||||||
|
@ -20,188 +20,240 @@ const amount = 8;
|
||||||
const timeLimit = 300;
|
const timeLimit = 300;
|
||||||
|
|
||||||
type Question = {
|
type Question = {
|
||||||
entry: T.NounEntry | T.AdjectiveEntry,
|
entry: T.NounEntry | T.AdjectiveEntry;
|
||||||
inflections: T.Inflections,
|
inflections: T.Inflections;
|
||||||
};
|
};
|
||||||
|
|
||||||
type InfFormContent = {
|
type InfFormContent = {
|
||||||
masc: [string, string, string],
|
masc: [string, string, string];
|
||||||
fem: [string, string, string],
|
fem: [string, string, string];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InflectionsWriting({ inChapter, id, link, level }: {
|
export default function InflectionsWriting({
|
||||||
inChapter: boolean,
|
inChapter,
|
||||||
id: string,
|
id,
|
||||||
link: string,
|
link,
|
||||||
level: T.InflectionPattern,
|
level,
|
||||||
|
}: {
|
||||||
|
inChapter: boolean;
|
||||||
|
id: string;
|
||||||
|
link: string;
|
||||||
|
level: T.InflectionPattern;
|
||||||
}) {
|
}) {
|
||||||
const wordPool = makePool(
|
const wordPool = makePool(
|
||||||
[...nouns, ...adjectives]
|
[...nouns, ...adjectives].filter((x) => {
|
||||||
.filter(x => {
|
if (isAdverbEntry(x)) return false;
|
||||||
if (isAdverbEntry(x)) return false;
|
const infs = inflectWord(x);
|
||||||
const infs = inflectWord(x);
|
if (!infs || !infs.inflections) return false;
|
||||||
if (!infs || !infs.inflections) return false;
|
return getInflectionPattern(x) === level;
|
||||||
return (getInflectionPattern(x) === level);
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
|
|
||||||
function getQuestion(): Question {
|
function getQuestion(): Question {
|
||||||
const word = wordPool();
|
const word = wordPool();
|
||||||
const r = inflectWord(word);
|
const r = inflectWord(word);
|
||||||
if (!r || !r.inflections) {
|
if (!r || !r.inflections) {
|
||||||
throw new Error(`error getting inflections for ${word.f}`);
|
throw new Error(`error getting inflections for ${word.f}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
entry: word,
|
entry: word,
|
||||||
inflections: r.inflections,
|
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
|
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||||
inChapter={inChapter}
|
function handleAnswer(inf: InfFormContent) {
|
||||||
studyLink={link}
|
callback(infAnswerCorrect(inf, question.inflections));
|
||||||
getQuestion={getQuestion}
|
}
|
||||||
id={id}
|
return (
|
||||||
Display={Display}
|
<div>
|
||||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||||
timeLimit={timeLimit}
|
<Examples opts={opts}>
|
||||||
amount={amount}
|
{[
|
||||||
Instructions={Instructions}
|
{
|
||||||
|
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 {
|
function InflectionTableForm({
|
||||||
return <div>
|
onSubmit,
|
||||||
<InflectionsTable
|
genders,
|
||||||
inf={question.inflections}
|
question,
|
||||||
textOptions={opts}
|
}: {
|
||||||
hideTitle
|
onSubmit: (i: InfFormContent) => void;
|
||||||
/>
|
genders: T.Gender | "unisex";
|
||||||
</div>;
|
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 infAnswerCorrect(answer: InfFormContent, inf: T.Inflections): boolean {
|
||||||
function genInfCorrect(gender: T.Gender): boolean {
|
function genInfCorrect(gender: T.Gender): boolean {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const genInf = inf[gender] as T.InflectionSet;
|
const genInf = inf[gender] as T.InflectionSet;
|
||||||
return genInf.every((x, i) => (
|
return genInf.every((x, i) =>
|
||||||
x.some(ps => comparePs(answer[gender][i], ps))
|
x.some((ps) => comparePs(answer[gender][i], [ps]))
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
if (isUnisexSet(inf)) {
|
if (isUnisexSet(inf)) {
|
||||||
return genInfCorrect("masc") && genInfCorrect("fem");
|
return genInfCorrect("masc") && genInfCorrect("fem");
|
||||||
}
|
}
|
||||||
return genInfCorrect("masc" in inf ? "masc": "fem");
|
return genInfCorrect("masc" in inf ? "masc" : "fem");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -414,8 +414,8 @@ function addUserAnswer(
|
||||||
function addBa(x: T.PsString) {
|
function addBa(x: T.PsString) {
|
||||||
if (!a.withBa) return x;
|
if (!a.withBa) return x;
|
||||||
return {
|
return {
|
||||||
p: x.p.replace(kidsBlank.p, baParticle.p),
|
p: x.p.replace(kidsBlank.p, baParticle.p + " "),
|
||||||
f: x.f.replace(kidsBlank.f, baParticle.f),
|
f: x.f.replace(kidsBlank.f, baParticle.f + " "),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function addAnswer(x: T.PsString): T.PsString {
|
function addAnswer(x: T.PsString): T.PsString {
|
||||||
|
|
|
@ -1,46 +1,44 @@
|
||||||
import {
|
import {
|
||||||
removeAccents,
|
removeAccents,
|
||||||
hasAccents,
|
hasAccents,
|
||||||
Types as T,
|
Types as T,
|
||||||
standardizePashto,
|
standardizePashto,
|
||||||
standardizePhonetics,
|
standardizePhonetics,
|
||||||
flattenLengths,
|
flattenLengths,
|
||||||
} from "@lingdocs/ps-react";
|
} from "@lingdocs/ps-react";
|
||||||
import { removeAShort } from "./misc-helpers";
|
import { removeAShort } from "./misc-helpers";
|
||||||
|
|
||||||
export function getPercentageDone(current: number, total: number): number {
|
export function getPercentageDone(current: number, total: number): number {
|
||||||
return Math.round(
|
return Math.round((current / (total + 1)) * 100);
|
||||||
(current / (total + 1)) * 100
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Says if an input written in phonetics by the user is correct/the same as a given answer
|
* Says if an input written in phonetics by the user is correct/the same as a given answer
|
||||||
*
|
*
|
||||||
* The user is allowed to leave out the accents, but if they include them they must be the same as the answer
|
* The user is allowed to leave out the accents, but if they include them they must be the same as the answer
|
||||||
*
|
*
|
||||||
* @param input - the answer given by the user in phonetics
|
* @param input - the answer given by the user in phonetics
|
||||||
* @param answer - the correct answer in phonetics
|
* @param answer - the correct answer in phonetics
|
||||||
*/
|
*/
|
||||||
export function compareF(input: string, answer: string): boolean {
|
export function compareF(input: string, answer: string): boolean {
|
||||||
const inp = removeAShort(input);
|
const inp = removeAShort(input);
|
||||||
const ans = removeAShort(answer);
|
const ans = removeAShort(answer);
|
||||||
return inp === (hasAccents(inp) ? ans : removeAccents(ans));
|
return inp === (hasAccents(inp) ? ans : removeAccents(ans));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function comparePs(inputRaw: string, answer: T.SingleOrLengthOpts<T.PsString | T.PsString[]>): boolean {
|
export function comparePs(
|
||||||
function cleanSpaces(s: string): string {
|
inputRaw: string,
|
||||||
return s.replace(/\s+/g, " ");
|
answer: T.SingleOrLengthOpts<T.PsString[]>
|
||||||
}
|
): boolean {
|
||||||
const input = cleanSpaces(inputRaw);
|
function cleanSpaces(s: string): string {
|
||||||
if ("long" in answer) {
|
return s.replace(/\s+/g, " ");
|
||||||
return comparePs(input, flattenLengths(answer));
|
}
|
||||||
}
|
const input = cleanSpaces(inputRaw);
|
||||||
if (Array.isArray(answer)) {
|
if ("long" in answer) {
|
||||||
return answer.some(a => comparePs(input, a));
|
return comparePs(input, flattenLengths(answer));
|
||||||
}
|
}
|
||||||
const stand = standardizePhonetics(
|
return answer.some((a) => {
|
||||||
standardizePashto(input)
|
const stand = standardizePhonetics(standardizePashto(input)).trim();
|
||||||
).trim();
|
return stand === cleanSpaces(a.p) || compareF(stand, cleanSpaces(a.f));
|
||||||
return stand === cleanSpaces(answer.p) || compareF(stand, cleanSpaces(answer.f));
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue