beta new verb engine

This commit is contained in:
adueck 2023-07-22 20:37:34 +04:00
parent 3b6d013402
commit 5edf0d1e02
3 changed files with 608 additions and 463 deletions

View File

@ -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", | {
stage: "blanks";
answer: { answer: {
ps: T.PsString[], ps: T.PsString[];
withBa: boolean, withBa: boolean;
}, };
}) & { }
qNumber: number, ) & {
vps: T.VPSelectionComplete, qNumber: number;
result: "waiting" | "fail", 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,21 +105,22 @@ 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) {
const toPlay = randFromArray([true, false, false]); const toPlay = randFromArray([true, false, false]);
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);
}); });
@ -109,7 +133,7 @@ function VPExplorerQuiz(props: {
setCurrentCorrectEmoji(randFromArray(correctEmoji)); setCurrentCorrectEmoji(randFromArray(correctEmoji));
}, checkDuration * 2); }, checkDuration * 2);
} else { } else {
playAudio(`wrong-${randFromArray([1,2])}`); playAudio(`wrong-${randFromArray([1, 2])}`);
navigator.vibrate(300); navigator.vibrate(300);
setQuizState({ setQuizState({
...quizState, ...quizState,
@ -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

View File

@ -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,