beta new verb engine
This commit is contained in:
parent
3b6d013402
commit
5edf0d1e02
|
@ -10,11 +10,14 @@ import { psStringEquals } from "../../../lib/src/p-text-helpers";
|
||||||
import { renderVP } from "../../../lib/src/phrase-building/render-vp";
|
import { renderVP } from "../../../lib/src/phrase-building/render-vp";
|
||||||
import { compileVP } from "../../../lib/src/phrase-building/compile";
|
import { compileVP } from "../../../lib/src/phrase-building/compile";
|
||||||
import { getRandomTense } from "./TensePicker";
|
import { getRandomTense } from "./TensePicker";
|
||||||
import { getTenseFromVerbSelection, removeBa, switchSubjObj } from "../../../lib/src/phrase-building/vp-tools";
|
import {
|
||||||
|
getTenseFromVerbSelection,
|
||||||
|
removeBa,
|
||||||
|
switchSubjObj,
|
||||||
|
} from "../../../lib/src/phrase-building/vp-tools";
|
||||||
import playAudio from "../play-audio";
|
import playAudio from "../play-audio";
|
||||||
import TensePicker from "./TensePicker";
|
import TensePicker from "./TensePicker";
|
||||||
import Keyframes from "../Keyframes";
|
import Keyframes from "../Keyframes";
|
||||||
import energyDrink from "./energy-drink.jpg";
|
|
||||||
import { isImperativeTense } from "../../../lib/src/type-predicates";
|
import { isImperativeTense } from "../../../lib/src/type-predicates";
|
||||||
import {
|
import {
|
||||||
adjustObjectSelection,
|
adjustObjectSelection,
|
||||||
|
@ -25,51 +28,71 @@ import {
|
||||||
getSubjectSelection,
|
getSubjectSelection,
|
||||||
} from "../../../lib/src/phrase-building/blocks-utils";
|
} from "../../../lib/src/phrase-building/blocks-utils";
|
||||||
|
|
||||||
const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"];
|
const correctEmoji = [
|
||||||
|
"✅",
|
||||||
|
"🤓",
|
||||||
|
"✅",
|
||||||
|
"😊",
|
||||||
|
"🌹",
|
||||||
|
"✅",
|
||||||
|
"✅",
|
||||||
|
"🥳",
|
||||||
|
"👏",
|
||||||
|
"✅",
|
||||||
|
"💯",
|
||||||
|
"😎",
|
||||||
|
"✅",
|
||||||
|
"👍",
|
||||||
|
];
|
||||||
|
|
||||||
const answerFeedback: CSSProperties = {
|
const answerFeedback: CSSProperties = {
|
||||||
"fontSize": "4rem",
|
fontSize: "4rem",
|
||||||
"transition": "opacity 0.3s ease-in",
|
transition: "opacity 0.3s ease-in",
|
||||||
"opacity": 0.9,
|
opacity: 0.9,
|
||||||
"position": "fixed",
|
position: "fixed",
|
||||||
"top": "60%",
|
top: "60%",
|
||||||
"left": "50%",
|
left: "50%",
|
||||||
"zIndex": 99999999,
|
zIndex: 99999999,
|
||||||
"transform": "translate(-50%, -50%)",
|
transform: "translate(-50%, -50%)",
|
||||||
}
|
};
|
||||||
|
|
||||||
const checkDuration = 400;
|
const checkDuration = 400;
|
||||||
const stageLength = 5;
|
const stageLength = 5;
|
||||||
|
|
||||||
type QuizState = ({
|
type QuizState = (
|
||||||
stage: "multiple choice",
|
| {
|
||||||
|
stage: "multiple choice";
|
||||||
answer: {
|
answer: {
|
||||||
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
||||||
e?: string[] | undefined;
|
e?: string[] | undefined;
|
||||||
},
|
};
|
||||||
options: T.PsString[],
|
options: T.PsString[];
|
||||||
} | {
|
|
||||||
stage: "blanks",
|
|
||||||
answer: {
|
|
||||||
ps: T.PsString[],
|
|
||||||
withBa: boolean,
|
|
||||||
},
|
|
||||||
}) & {
|
|
||||||
qNumber: number,
|
|
||||||
vps: T.VPSelectionComplete,
|
|
||||||
result: "waiting" | "fail",
|
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
stage: "blanks";
|
||||||
|
answer: {
|
||||||
|
ps: T.PsString[];
|
||||||
|
withBa: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
) & {
|
||||||
|
qNumber: number;
|
||||||
|
vps: T.VPSelectionComplete;
|
||||||
|
result: "waiting" | "fail";
|
||||||
|
};
|
||||||
type MixType = "NPs" | "tenses" | "both";
|
type MixType = "NPs" | "tenses" | "both";
|
||||||
|
|
||||||
function VPExplorerQuiz(props: {
|
function VPExplorerQuiz(props: {
|
||||||
opts: T.TextOptions,
|
opts: T.TextOptions;
|
||||||
vps: T.VPSelectionState,
|
vps: T.VPSelectionState;
|
||||||
}) {
|
}) {
|
||||||
const [quizState, setQuizState] = useState<QuizState | "loading">("loading");
|
const [quizState, setQuizState] = useState<QuizState | "loading">("loading");
|
||||||
const [showCheck, setShowCheck] = useState<boolean>(false);
|
const [showCheck, setShowCheck] = useState<boolean>(false);
|
||||||
const [answerBlank, setAnswerBlank] = useState<string>("");
|
const [answerBlank, setAnswerBlank] = useState<string>("");
|
||||||
const [withBa, setWithBa] = useState<boolean>(false);
|
const [withBa, setWithBa] = useState<boolean>(false);
|
||||||
const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(randFromArray(correctEmoji));
|
const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(
|
||||||
|
randFromArray(correctEmoji)
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setQuizState(tickQuizState(completeVPs(props.vps)));
|
setQuizState(tickQuizState(completeVPs(props.vps)));
|
||||||
}, [props.vps]);
|
}, [props.vps]);
|
||||||
|
@ -82,13 +105,14 @@ function VPExplorerQuiz(props: {
|
||||||
setAnswerBlank("");
|
setAnswerBlank("");
|
||||||
setQuizState(tickQuizState(quizState.vps));
|
setQuizState(tickQuizState(quizState.vps));
|
||||||
}
|
}
|
||||||
function checkAnswer(a: T.PsString | { text: string, withBa: boolean }) {
|
function checkAnswer(a: T.PsString | { text: string; withBa: boolean }) {
|
||||||
if (quizState === "loading") return;
|
if (quizState === "loading") return;
|
||||||
if (!quizState) return;
|
if (!quizState) return;
|
||||||
const correct = "p" in a
|
const correct =
|
||||||
|
"p" in a
|
||||||
? isInAnswer(a, quizState.answer)
|
? isInAnswer(a, quizState.answer)
|
||||||
// @ts-ignore // TODO: CLEANUP
|
: // @ts-ignore // TODO: CLEANUP
|
||||||
: blanksAnswerCorrect(a, quizState.answer);
|
blanksAnswerCorrect(a, quizState.answer);
|
||||||
setAnswerBlank("");
|
setAnswerBlank("");
|
||||||
setWithBa(false);
|
setWithBa(false);
|
||||||
if (correct) {
|
if (correct) {
|
||||||
|
@ -96,7 +120,7 @@ function VPExplorerQuiz(props: {
|
||||||
if (toPlay) playAudio(`correct-${randFromArray([1, 2, 3])}`);
|
if (toPlay) playAudio(`correct-${randFromArray([1, 2, 3])}`);
|
||||||
setShowCheck(true);
|
setShowCheck(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setQuizState(o => {
|
setQuizState((o) => {
|
||||||
if (o === "loading") return o;
|
if (o === "loading") return o;
|
||||||
return tickQuizState(o);
|
return tickQuizState(o);
|
||||||
});
|
});
|
||||||
|
@ -118,20 +142,32 @@ function VPExplorerQuiz(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const rendered = renderVP(quizState.vps);
|
const rendered = renderVP(quizState.vps);
|
||||||
const subject: T.Rendered<T.NPSelection> = getSubjectSelectionFromBlocks(rendered.blocks).selection;
|
const subject: T.Rendered<T.NPSelection> = getSubjectSelectionFromBlocks(
|
||||||
|
rendered.blocks
|
||||||
|
).selection;
|
||||||
const object = getObjectSelectionFromBlocks(rendered.blocks).selection;
|
const object = getObjectSelectionFromBlocks(rendered.blocks).selection;
|
||||||
const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false });
|
const { e } = compileVP(rendered, {
|
||||||
return <div className="mt-4">
|
removeKing: false,
|
||||||
|
shrinkServant: false,
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div className="mt-4">
|
||||||
<ProgressBar quizState={quizState} />
|
<ProgressBar quizState={quizState} />
|
||||||
<div className="d-flex flex-row justify-content-around flex-wrap">
|
<div className="d-flex flex-row justify-content-around flex-wrap">
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<div className="h5 text-center">Subject</div>
|
<div className="h5 text-center">Subject</div>
|
||||||
<QuizNPDisplay opts={props.opts} stage={quizState.stage}>{subject}</QuizNPDisplay>
|
<QuizNPDisplay opts={props.opts} stage={quizState.stage}>
|
||||||
|
{subject}
|
||||||
|
</QuizNPDisplay>
|
||||||
</div>
|
</div>
|
||||||
{(object !== "none") && <div className="my-2">
|
{object !== "none" && (
|
||||||
|
<div className="my-2">
|
||||||
<div className="h5 text-center">Object</div>
|
<div className="h5 text-center">Object</div>
|
||||||
<QuizNPDisplay opts={props.opts} stage={quizState.stage}>{object}</QuizNPDisplay>
|
<QuizNPDisplay opts={props.opts} stage={quizState.stage}>
|
||||||
</div>}
|
{object}
|
||||||
|
</QuizNPDisplay>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="my-2">
|
<div className="my-2">
|
||||||
<TensePicker
|
<TensePicker
|
||||||
vpsComplete={quizState.vps}
|
vpsComplete={quizState.vps}
|
||||||
|
@ -140,52 +176,81 @@ function VPExplorerQuiz(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{e && <div className="text-center text-muted">
|
{e && (
|
||||||
{e.map(eLine => <div key={eLine}>{eLine}</div>)}
|
<div className="text-center text-muted">
|
||||||
</div>}
|
{e.map((eLine) => (
|
||||||
|
<div key={eLine}>{eLine}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div style={showCheck ? answerFeedback : { ...answerFeedback, opacity: 0 } as any}>
|
<div
|
||||||
|
style={
|
||||||
|
showCheck
|
||||||
|
? answerFeedback
|
||||||
|
: ({ ...answerFeedback, opacity: 0 } as any)
|
||||||
|
}
|
||||||
|
>
|
||||||
{currentCorrectEmoji}
|
{currentCorrectEmoji}
|
||||||
</div>
|
</div>
|
||||||
{quizState.qNumber === stageLength ?
|
{quizState.qNumber === stageLength ? (
|
||||||
<div className="mt-4" style={{ animation: "fade-in 0.5s" }}>
|
<div className="mt-4" style={{ animation: "fade-in 0.5s" }}>
|
||||||
<h4>👏 Congratulations</h4>
|
<h4>👏 Congratulations</h4>
|
||||||
<p className="lead">You finished the first two levels!</p>
|
<p className="lead">You finished the first two levels!</p>
|
||||||
<p>The <strong>other levels are still in development</strong>... In the meantime have an energy drink.</p>
|
<p>There may be other levels in the future...</p>
|
||||||
<div className="mb-4">
|
<button
|
||||||
<img src={energyDrink} alt="energy-dring" className="img-fluid" />
|
type="button"
|
||||||
</div>
|
className="btn btn-primary"
|
||||||
<button type="button" className="btn btn-primary" onClick={handleRestart}>
|
onClick={handleRestart}
|
||||||
|
>
|
||||||
Restart
|
Restart
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
: (quizState.result === "waiting"
|
) : quizState.result === "waiting" ? (
|
||||||
? (quizState.stage === "multiple choice" ? <>
|
quizState.stage === "multiple choice" ? (
|
||||||
|
<>
|
||||||
<div className="text-muted my-3">Choose a correct answer:</div>
|
<div className="text-muted my-3">Choose a correct answer:</div>
|
||||||
{quizState.options.map(o => <div className="pb-3" key={o.f} style={{ animation: "fade-in 0.5s" }}>
|
{quizState.options.map((o) => (
|
||||||
|
<div
|
||||||
|
className="pb-3"
|
||||||
|
key={o.f}
|
||||||
|
style={{ animation: "fade-in 0.5s" }}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="btn btn-answer btn-outline-secondary"
|
className="btn btn-answer btn-outline-secondary"
|
||||||
onClick={() => checkAnswer(o)}>
|
onClick={() => checkAnswer(o)}
|
||||||
|
>
|
||||||
<InlinePs opts={props.opts}>{o}</InlinePs>
|
<InlinePs opts={props.opts}>{o}</InlinePs>
|
||||||
</button>
|
</button>
|
||||||
</div>)}
|
</div>
|
||||||
</> : <div>
|
))}
|
||||||
<div className="text-muted my-3">Type the <strong>verb in Pashto script</strong> to finish the phrase:</div>
|
</>
|
||||||
<form onSubmit={e => {
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="text-muted my-3">
|
||||||
|
Type the <strong>verb in Pashto script</strong> to finish the
|
||||||
|
phrase:
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
if (!answerBlank) {
|
if (!answerBlank) {
|
||||||
alert("Enter the verb in Pashto script");
|
alert("Enter the verb in Pashto script");
|
||||||
};
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
checkAnswer({ text: answerBlank, withBa });
|
checkAnswer({ text: answerBlank, withBa });
|
||||||
}}>
|
}}
|
||||||
<div className="mb-3" style={{ maxWidth: "250px", margin: "0 auto"}}>
|
>
|
||||||
|
<div
|
||||||
|
className="mb-3"
|
||||||
|
style={{ maxWidth: "250px", margin: "0 auto" }}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder="type verb here"
|
placeholder="type verb here"
|
||||||
value={answerBlank}
|
value={answerBlank}
|
||||||
onChange={e => setAnswerBlank(e.target.value)}
|
onChange={(e) => setAnswerBlank(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-check mb-4" style={{ fontSize: "large" }}>
|
<div className="form-check mb-4" style={{ fontSize: "large" }}>
|
||||||
|
@ -193,102 +258,142 @@ function VPExplorerQuiz(props: {
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={withBa}
|
checked={withBa}
|
||||||
onChange={e => setWithBa(e.target.checked)}
|
onChange={(e) => setWithBa(e.target.checked)}
|
||||||
id="addBa"
|
id="addBa"
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label text-muted" htmlFor="addBa">
|
<label
|
||||||
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in kids' section
|
className="form-check-label text-muted"
|
||||||
|
htmlFor="addBa"
|
||||||
|
>
|
||||||
|
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in
|
||||||
|
kids' section
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" className="btn btn-primary">
|
<button type="submit" className="btn btn-primary">
|
||||||
Check
|
Check
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>)
|
</div>
|
||||||
: <div style={{ animation: "fade-in 0.5s" }}>
|
)
|
||||||
|
) : (
|
||||||
|
<div style={{ animation: "fade-in 0.5s" }}>
|
||||||
<div className="h4 mt-4">❌ Wrong 😭</div>
|
<div className="h4 mt-4">❌ Wrong 😭</div>
|
||||||
{quizState.stage === "multiple choice" ?
|
{quizState.stage === "multiple choice" ? (
|
||||||
<div>
|
<div>
|
||||||
<div className="my-4 lead">The correct answer was:</div>
|
<div className="my-4 lead">The correct answer was:</div>
|
||||||
<InlinePs opts={props.opts}>
|
<InlinePs opts={props.opts}>
|
||||||
{quizState.options.find(x => isInAnswer(x, quizState.answer)) as T.PsString}
|
{
|
||||||
|
quizState.options.find((x) =>
|
||||||
|
isInAnswer(x, quizState.answer)
|
||||||
|
) as T.PsString
|
||||||
|
}
|
||||||
</InlinePs>
|
</InlinePs>
|
||||||
</div>
|
</div>
|
||||||
:
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="my-4 lead">Possible correct answers were:</div>
|
<div className="my-4 lead">Possible correct answers were:</div>
|
||||||
{quizState.answer.ps.map((p, i) => <div key={i}>
|
{quizState.answer.ps.map((p, i) => (
|
||||||
|
<div key={i}>
|
||||||
<InlinePs opts={props.opts}>{p}</InlinePs>
|
<InlinePs opts={props.opts}>{p}</InlinePs>
|
||||||
</div>)}
|
</div>
|
||||||
|
))}
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<strong>{("withBa" in quizState.answer && quizState.answer.withBa) ? "With" : "without"}</strong>
|
<strong>
|
||||||
{` `}
|
{"withBa" in quizState.answer && quizState.answer.withBa
|
||||||
a <InlinePs opts={props.opts}>{baParticle}</InlinePs> in the phrase
|
? "With"
|
||||||
|
: "without"}
|
||||||
|
</strong>
|
||||||
|
{` `}a <InlinePs opts={props.opts}>{baParticle}</InlinePs> in
|
||||||
|
the phrase
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
<button type="button" className="btn btn-primary mt-4" onClick={handleRestart}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary mt-4"
|
||||||
|
onClick={handleRestart}
|
||||||
|
>
|
||||||
Try Again
|
Try Again
|
||||||
</button>
|
</button>
|
||||||
</div>)
|
</div>
|
||||||
}
|
)}
|
||||||
<Keyframes name="fade-in" from={{ opacity: 0 }} to={{ opacity: 1 }} />
|
<Keyframes name="fade-in" from={{ opacity: 0 }} to={{ opacity: 1 }} />
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function blanksAnswerCorrect(a: { text: string, withBa: boolean }, answer: { ps: T.PsString[], withBa?: boolean }): boolean {
|
function blanksAnswerCorrect(
|
||||||
|
a: { text: string; withBa: boolean },
|
||||||
|
answer: { ps: T.PsString[]; withBa?: boolean }
|
||||||
|
): boolean {
|
||||||
const p = standardizePashto(a.text).trim();
|
const p = standardizePashto(a.text).trim();
|
||||||
const given = removeBa({ p, f: "" }).p;
|
const given = removeBa({ p, f: "" }).p;
|
||||||
return (
|
return a.withBa === answer.withBa && answer.ps.some((x) => x.p === given);
|
||||||
a.withBa === answer.withBa
|
|
||||||
&&
|
|
||||||
answer.ps.some(x => x.p === given)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProgressBar({ quizState }: { quizState: QuizState }) {
|
function ProgressBar({ quizState }: { quizState: QuizState }) {
|
||||||
function getPercentageDone({ current, total }: { current: number, total: number }): number {
|
function getPercentageDone({
|
||||||
return Math.round(
|
current,
|
||||||
(current / total) * 100
|
total,
|
||||||
);
|
}: {
|
||||||
|
current: number;
|
||||||
|
total: number;
|
||||||
|
}): number {
|
||||||
|
return Math.round((current / total) * 100);
|
||||||
}
|
}
|
||||||
function getProgressWidth(): string {
|
function getProgressWidth(): string {
|
||||||
const num = getPercentageDone({ current: quizState.qNumber, total: stageLength });
|
const num = getPercentageDone({
|
||||||
|
current: quizState.qNumber,
|
||||||
|
total: stageLength,
|
||||||
|
});
|
||||||
return `${num}%`;
|
return `${num}%`;
|
||||||
}
|
}
|
||||||
return <div className="mb-3">
|
return (
|
||||||
|
<div className="mb-3">
|
||||||
<div className="progress mb-1" style={{ height: "3px" }}>
|
<div className="progress mb-1" style={{ height: "3px" }}>
|
||||||
<div
|
<div
|
||||||
className={`progress-bar bg-${quizState.result === "fail" ? "danger" : "primary"}`}
|
className={`progress-bar bg-${
|
||||||
|
quizState.result === "fail" ? "danger" : "primary"
|
||||||
|
}`}
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
style={{ width: getProgressWidth() }}
|
style={{ width: getProgressWidth() }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{quizState.stage === "multiple choice"
|
{quizState.stage === "multiple choice"
|
||||||
? "Level 1: Multiple Choice"
|
? "Level 1: Multiple Choice"
|
||||||
: "Level 2: Type the Verb"}
|
: "Level 2: Type the Verb"}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function QuizNPDisplay({ children, stage, opts }: {
|
function QuizNPDisplay({
|
||||||
stage: "blanks" | "multiple choice",
|
children,
|
||||||
children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale,
|
stage,
|
||||||
opts: T.TextOptions,
|
opts,
|
||||||
|
}: {
|
||||||
|
stage: "blanks" | "multiple choice";
|
||||||
|
children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale;
|
||||||
|
opts: T.TextOptions;
|
||||||
}) {
|
}) {
|
||||||
return <div className="mb-3">
|
return (
|
||||||
{(typeof children === "number")
|
<div className="mb-3">
|
||||||
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
{typeof children === "number" ? (
|
||||||
: <div className="text-centered" style={{ fontSize: "large" }}>
|
<div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
||||||
{stage === "blanks" && <div>
|
) : (
|
||||||
|
<div className="text-centered" style={{ fontSize: "large" }}>
|
||||||
|
{stage === "blanks" && (
|
||||||
|
<div>
|
||||||
<InlinePs opts={opts}>{children.selection.ps[0]}</InlinePs>
|
<InlinePs opts={opts}>{children.selection.ps[0]}</InlinePs>
|
||||||
</div>}
|
</div>
|
||||||
|
)}
|
||||||
<div>{children.selection.e}</div>
|
<div>{children.selection.e}</div>
|
||||||
</div>}
|
</div>
|
||||||
</div>;
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -298,13 +403,18 @@ function QuizNPDisplay({ children, stage, opts }: {
|
||||||
* @param startingWith
|
* @param startingWith
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizState {
|
function tickQuizState(
|
||||||
|
startingWith: T.VPSelectionComplete | QuizState
|
||||||
|
): QuizState {
|
||||||
function makeRes(x: T.VPSelectionComplete) {
|
function makeRes(x: T.VPSelectionComplete) {
|
||||||
return compileVP(renderVP(x), { removeKing: false, shrinkServant: false });
|
return compileVP(renderVP(x), { removeKing: false, shrinkServant: false });
|
||||||
}
|
}
|
||||||
const oldVps = "stage" in startingWith ? startingWith.vps : startingWith;
|
const oldVps = "stage" in startingWith ? startingWith.vps : startingWith;
|
||||||
// for now, always inforce positive
|
// for now, always inforce positive
|
||||||
const newVps = getRandomVPSelection("both")({ ...oldVps, verb: { ...oldVps.verb, negative: false }});
|
const newVps = getRandomVPSelection("both")({
|
||||||
|
...oldVps,
|
||||||
|
verb: { ...oldVps.verb, negative: false },
|
||||||
|
});
|
||||||
const wrongVpsS: T.VPSelectionComplete[] = [];
|
const wrongVpsS: T.VPSelectionComplete[] = [];
|
||||||
// don't do the SO switches every time
|
// don't do the SO switches every time
|
||||||
const wholeTimeSOSwitch = randFromArray([true, false]);
|
const wholeTimeSOSwitch = randFromArray([true, false]);
|
||||||
|
@ -314,17 +424,22 @@ function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizSta
|
||||||
const SOSwitch = wholeTimeSOSwitch && randFromArray([true, false]);
|
const SOSwitch = wholeTimeSOSwitch && randFromArray([true, false]);
|
||||||
// TODO: if switich subj and obj, include the tense being correct maybe
|
// TODO: if switich subj and obj, include the tense being correct maybe
|
||||||
v = getRandomVPSelection("tenses")(
|
v = getRandomVPSelection("tenses")(
|
||||||
SOSwitch ? switchSubjObj(newVps) : newVps,
|
SOSwitch ? switchSubjObj(newVps) : newVps
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
} while (wrongVpsS.find(x => x.verb.tense === v.verb.tense));
|
} while (wrongVpsS.find((x) => x.verb.tense === v.verb.tense));
|
||||||
wrongVpsS.push(v);
|
wrongVpsS.push(v);
|
||||||
});
|
});
|
||||||
const qNumber = "stage" in startingWith ? (startingWith.qNumber + 1) : 0;
|
const qNumber = "stage" in startingWith ? startingWith.qNumber + 1 : 0;
|
||||||
const beatFirstStage = "stage" in startingWith && (qNumber === stageLength) && startingWith.stage === "multiple choice";
|
const beatFirstStage =
|
||||||
|
"stage" in startingWith &&
|
||||||
|
qNumber === stageLength &&
|
||||||
|
startingWith.stage === "multiple choice";
|
||||||
const stage = beatFirstStage
|
const stage = beatFirstStage
|
||||||
? "blanks"
|
? "blanks"
|
||||||
: ("stage" in startingWith ? startingWith.stage : "multiple choice");
|
: "stage" in startingWith
|
||||||
|
? startingWith.stage
|
||||||
|
: "multiple choice";
|
||||||
const blanksAnswer = getBlanksAnswer(newVps);
|
const blanksAnswer = getBlanksAnswer(newVps);
|
||||||
if (stage === "blanks") {
|
if (stage === "blanks") {
|
||||||
return {
|
return {
|
||||||
|
@ -350,7 +465,10 @@ function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizSta
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa: boolean } {
|
function getBlanksAnswer(vps: T.VPSelectionComplete): {
|
||||||
|
ps: T.PsString[];
|
||||||
|
withBa: boolean;
|
||||||
|
} {
|
||||||
// TODO: !!!
|
// TODO: !!!
|
||||||
// const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks);
|
// const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks);
|
||||||
// const ps = flattenLengths(verb.block.ps).map(x => {
|
// const ps = flattenLengths(verb.block.ps).map(x => {
|
||||||
|
@ -363,27 +481,32 @@ function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa
|
||||||
return {
|
return {
|
||||||
ps: [{ p: "TOOD", f: "TODO" }],
|
ps: [{ p: "TOOD", f: "TODO" }],
|
||||||
withBa: false, // verb.block.hasBa,
|
withBa: false, // verb.block.hasBa,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInAnswer(a: T.PsString, answer: {
|
function isInAnswer(
|
||||||
|
a: T.PsString,
|
||||||
|
answer: {
|
||||||
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
||||||
e?: string[] | undefined;
|
e?: string[] | undefined;
|
||||||
}): boolean {
|
}
|
||||||
|
): boolean {
|
||||||
if ("long" in answer.ps) {
|
if ("long" in answer.ps) {
|
||||||
return isInAnswer(a, { ...answer, ps: answer.ps.long }) ||
|
return (
|
||||||
|
isInAnswer(a, { ...answer, ps: answer.ps.long }) ||
|
||||||
isInAnswer(a, { ...answer, ps: answer.ps.short }) ||
|
isInAnswer(a, { ...answer, ps: answer.ps.short }) ||
|
||||||
!!(answer.ps.mini && isInAnswer(a, { ...answer, ps: answer.ps.mini }));
|
!!(answer.ps.mini && isInAnswer(a, { ...answer, ps: answer.ps.mini }))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return answer.ps.some((x) => psStringEquals(x, a));
|
return answer.ps.some((x) => psStringEquals(x, a));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getOptionFromResult(r: {
|
function getOptionFromResult(r: {
|
||||||
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
ps: T.SingleOrLengthOpts<T.PsString[]>;
|
||||||
e?: string[] | undefined;
|
e?: string[] | undefined;
|
||||||
}): T.PsString {
|
}): T.PsString {
|
||||||
const ps = "long" in r.ps
|
const ps =
|
||||||
|
"long" in r.ps
|
||||||
? r.ps[randFromArray(["short", "long"] as ("short" | "long")[])]
|
? r.ps[randFromArray(["short", "long"] as ("short" | "long")[])]
|
||||||
: r.ps;
|
: r.ps;
|
||||||
// not randomizing version pick (for now)
|
// not randomizing version pick (for now)
|
||||||
|
@ -393,10 +516,12 @@ function getOptionFromResult(r: {
|
||||||
function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
|
function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
|
||||||
const vpsSubj = getSubjectSelection(vps.blocks).selection;
|
const vpsSubj = getSubjectSelection(vps.blocks).selection;
|
||||||
const vpsObj = getObjectSelection(vps.blocks).selection;
|
const vpsObj = getObjectSelection(vps.blocks).selection;
|
||||||
const oldSubj = vpsSubj?.selection.type === "pronoun"
|
const oldSubj =
|
||||||
|
vpsSubj?.selection.type === "pronoun"
|
||||||
? vpsSubj.selection.person
|
? vpsSubj.selection.person
|
||||||
: undefined;
|
: undefined;
|
||||||
const oldObj = (typeof vpsObj === "object" && vpsObj.selection.type === "pronoun")
|
const oldObj =
|
||||||
|
typeof vpsObj === "object" && vpsObj.selection.type === "pronoun"
|
||||||
? vpsObj.selection.person
|
? vpsObj.selection.person
|
||||||
: undefined;
|
: undefined;
|
||||||
const { subj, obj } = randomSubjObj(
|
const { subj, obj } = randomSubjObj(
|
||||||
|
@ -423,20 +548,22 @@ function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
|
||||||
person: subj,
|
person: subj,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
(
|
(typeof vpsObj === "object" &&
|
||||||
(typeof vpsObj === "object" && !(vpsObj.selection.type === "noun" && vpsObj.selection.dynamicComplement))
|
!(
|
||||||
||
|
vpsObj.selection.type === "noun" &&
|
||||||
|
(vpsObj.selection.dynamicComplement ||
|
||||||
|
vpsObj.selection.genStativeComplement)
|
||||||
|
)) ||
|
||||||
vpsObj === undefined
|
vpsObj === undefined
|
||||||
)
|
|
||||||
? {
|
? {
|
||||||
type: "NP",
|
type: "NP",
|
||||||
selection: {
|
selection: {
|
||||||
type: "pronoun",
|
type: "pronoun",
|
||||||
distance: "far",
|
distance: "far",
|
||||||
person: obj,
|
person: obj,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
: vpsObj
|
||||||
: vpsObj,
|
|
||||||
),
|
),
|
||||||
verb,
|
verb,
|
||||||
};
|
};
|
||||||
|
@ -448,10 +575,12 @@ function getRandomVPSelection(mix: MixType = "both") {
|
||||||
const subject = getSubjectSelection(VPS.blocks).selection;
|
const subject = getSubjectSelection(VPS.blocks).selection;
|
||||||
const object = getObjectSelection(VPS.blocks).selection;
|
const object = getObjectSelection(VPS.blocks).selection;
|
||||||
const verb = VPS.verb;
|
const verb = VPS.verb;
|
||||||
const oldSubj = (subject.selection.type === "pronoun")
|
const oldSubj =
|
||||||
|
subject.selection.type === "pronoun"
|
||||||
? subject.selection.person
|
? subject.selection.person
|
||||||
: undefined;
|
: undefined;
|
||||||
const oldObj = (typeof object === "object" && object.selection.type === "pronoun")
|
const oldObj =
|
||||||
|
typeof object === "object" && object.selection.type === "pronoun"
|
||||||
? object.selection.person
|
? object.selection.person
|
||||||
: undefined;
|
: undefined;
|
||||||
const { subj, obj } = randomSubjObj(
|
const { subj, obj } = randomSubjObj(
|
||||||
|
@ -459,59 +588,70 @@ function getRandomVPSelection(mix: MixType = "both") {
|
||||||
);
|
);
|
||||||
const randSubj: T.NPSelection = {
|
const randSubj: T.NPSelection = {
|
||||||
type: "NP",
|
type: "NP",
|
||||||
selection: (
|
selection:
|
||||||
subject?.selection.type === "pronoun" ? {
|
subject?.selection.type === "pronoun"
|
||||||
|
? {
|
||||||
...subject.selection,
|
...subject.selection,
|
||||||
person: subj,
|
person: subj,
|
||||||
} : {
|
}
|
||||||
|
: {
|
||||||
type: "pronoun",
|
type: "pronoun",
|
||||||
distance: "far",
|
distance: "far",
|
||||||
person: subj,
|
person: subj,
|
||||||
}
|
},
|
||||||
),
|
|
||||||
};
|
};
|
||||||
const randObj: T.NPSelection = {
|
const randObj: T.NPSelection = {
|
||||||
type: "NP",
|
type: "NP",
|
||||||
selection: (
|
selection:
|
||||||
typeof object === "object" && object.selection.type === "pronoun" ? {
|
typeof object === "object" && object.selection.type === "pronoun"
|
||||||
|
? {
|
||||||
...object.selection,
|
...object.selection,
|
||||||
person: obj,
|
person: obj,
|
||||||
} : {
|
}
|
||||||
|
: {
|
||||||
type: "pronoun",
|
type: "pronoun",
|
||||||
distance: "far",
|
distance: "far",
|
||||||
person: obj,
|
person: obj,
|
||||||
}
|
},
|
||||||
),
|
|
||||||
};
|
};
|
||||||
// ensure that the verb selection is complete
|
// ensure that the verb selection is complete
|
||||||
if (mix === "tenses") {
|
if (mix === "tenses") {
|
||||||
return {
|
return {
|
||||||
blocks: possibleShuffleArray(adjustObjectSelection(
|
blocks: possibleShuffleArray(
|
||||||
adjustSubjectSelection(VPS.blocks, subject !== undefined ? subject : randSubj),
|
adjustObjectSelection(
|
||||||
object !== undefined ? object : randObj,
|
adjustSubjectSelection(
|
||||||
)),
|
VPS.blocks,
|
||||||
|
subject !== undefined ? subject : randSubj
|
||||||
|
),
|
||||||
|
object !== undefined ? object : randObj
|
||||||
|
)
|
||||||
|
),
|
||||||
verb: randomizeTense(verb, true),
|
verb: randomizeTense(verb, true),
|
||||||
form: { removeKing: false, shrinkServant: false },
|
form: { removeKing: false, shrinkServant: false },
|
||||||
externalComplement: undefined,
|
externalComplement: undefined,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
blocks: possibleShuffleArray(adjustObjectSelection(
|
blocks: possibleShuffleArray(
|
||||||
|
adjustObjectSelection(
|
||||||
adjustSubjectSelection(VPS.blocks, randSubj),
|
adjustSubjectSelection(VPS.blocks, randSubj),
|
||||||
(
|
(typeof object === "object" &&
|
||||||
(typeof object === "object" && !(object.selection.type === "noun" && object.selection.dynamicComplement))
|
!(
|
||||||
||
|
object.selection.type === "noun" &&
|
||||||
|
(object.selection.dynamicComplement ||
|
||||||
|
object.selection.genStativeComplement)
|
||||||
|
)) ||
|
||||||
object === undefined
|
object === undefined
|
||||||
)
|
|
||||||
? randObj
|
? randObj
|
||||||
: object,
|
: object
|
||||||
)),
|
)
|
||||||
|
),
|
||||||
verb: randomizeTense(verb, true),
|
verb: randomizeTense(verb, true),
|
||||||
form: { removeKing: false, shrinkServant: false },
|
form: { removeKing: false, shrinkServant: false },
|
||||||
externalComplement: undefined,
|
externalComplement: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
function possibleShuffleArray<X>(arr: X[]): X[] {
|
function possibleShuffleArray<X>(arr: X[]): X[] {
|
||||||
const willShuffle = randFromArray([true, false, false]);
|
const willShuffle = randFromArray([true, false, false]);
|
||||||
|
@ -521,12 +661,13 @@ function possibleShuffleArray<X>(arr: X[]): X[] {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomizeTense(verb: T.VerbSelectionComplete, dontRepeatTense: boolean): T.VerbSelectionComplete {
|
function randomizeTense(
|
||||||
|
verb: T.VerbSelectionComplete,
|
||||||
|
dontRepeatTense: boolean
|
||||||
|
): T.VerbSelectionComplete {
|
||||||
return {
|
return {
|
||||||
...verb,
|
...verb,
|
||||||
tense: getRandomTense(
|
tense: getRandomTense(dontRepeatTense ? verb.tense : undefined),
|
||||||
dontRepeatTense ? verb.tense : undefined,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB |
|
@ -81,7 +81,11 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
|
||||||
kids: getVPKids(hasBa, VP.blocks, VP.form, king),
|
kids: getVPKids(hasBa, VP.blocks, VP.form, king),
|
||||||
englishBase: renderEnglishVPBase({
|
englishBase: renderEnglishVPBase({
|
||||||
subjectPerson,
|
subjectPerson,
|
||||||
object: VP.verb.isCompound === "dynamic" ? "none" : object,
|
object:
|
||||||
|
VP.verb.isCompound === "dynamic" ||
|
||||||
|
VP.verb.isCompound === "generative stative"
|
||||||
|
? "none"
|
||||||
|
: object,
|
||||||
vs: VP.verb,
|
vs: VP.verb,
|
||||||
}),
|
}),
|
||||||
form: VP.form,
|
form: VP.form,
|
||||||
|
|
Loading…
Reference in New Issue