From 5edf0d1e028dc2a89c70cd625793113217d6578c Mon Sep 17 00:00:00 2001 From: adueck Date: Sat, 22 Jul 2023 20:37:34 +0400 Subject: [PATCH] beta new verb engine --- .../src/vp-explorer/VPExplorerQuiz.tsx | 1065 ++++++++++------- .../src/vp-explorer/energy-drink.jpg | Bin 7003 -> 0 bytes src/lib/src/phrase-building/render-vp.ts | 6 +- 3 files changed, 608 insertions(+), 463 deletions(-) delete mode 100644 src/components/src/vp-explorer/energy-drink.jpg diff --git a/src/components/src/vp-explorer/VPExplorerQuiz.tsx b/src/components/src/vp-explorer/VPExplorerQuiz.tsx index 1a017e0..b51518e 100644 --- a/src/components/src/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/src/vp-explorer/VPExplorerQuiz.tsx @@ -10,524 +10,665 @@ import { psStringEquals } from "../../../lib/src/p-text-helpers"; import { renderVP } from "../../../lib/src/phrase-building/render-vp"; import { compileVP } from "../../../lib/src/phrase-building/compile"; 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 TensePicker from "./TensePicker"; import Keyframes from "../Keyframes"; -import energyDrink from "./energy-drink.jpg"; import { isImperativeTense } from "../../../lib/src/type-predicates"; import { - adjustObjectSelection, - adjustSubjectSelection, - getObjectSelection, - getObjectSelectionFromBlocks, - getSubjectSelectionFromBlocks, - getSubjectSelection, + adjustObjectSelection, + adjustSubjectSelection, + getObjectSelection, + getObjectSelectionFromBlocks, + getSubjectSelectionFromBlocks, + getSubjectSelection, } from "../../../lib/src/phrase-building/blocks-utils"; -const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"]; +const correctEmoji = [ + "✅", + "🤓", + "✅", + "😊", + "🌹", + "✅", + "✅", + "🥳", + "👏", + "✅", + "💯", + "😎", + "✅", + "👍", +]; const answerFeedback: CSSProperties = { - "fontSize": "4rem", - "transition": "opacity 0.3s ease-in", - "opacity": 0.9, - "position": "fixed", - "top": "60%", - "left": "50%", - "zIndex": 99999999, - "transform": "translate(-50%, -50%)", -} + fontSize: "4rem", + transition: "opacity 0.3s ease-in", + opacity: 0.9, + position: "fixed", + top: "60%", + left: "50%", + zIndex: 99999999, + transform: "translate(-50%, -50%)", +}; const checkDuration = 400; const stageLength = 5; -type QuizState = ({ - stage: "multiple choice", - answer: { +type QuizState = ( + | { + stage: "multiple choice"; + answer: { ps: T.SingleOrLengthOpts; e?: string[] | undefined; - }, - options: T.PsString[], -} | { - stage: "blanks", - answer: { - ps: T.PsString[], - withBa: boolean, - }, -}) & { - qNumber: number, - vps: T.VPSelectionComplete, - result: "waiting" | "fail", -} + }; + options: T.PsString[]; + } + | { + stage: "blanks"; + answer: { + ps: T.PsString[]; + withBa: boolean; + }; + } +) & { + qNumber: number; + vps: T.VPSelectionComplete; + result: "waiting" | "fail"; +}; type MixType = "NPs" | "tenses" | "both"; function VPExplorerQuiz(props: { - opts: T.TextOptions, - vps: T.VPSelectionState, + opts: T.TextOptions; + vps: T.VPSelectionState; }) { - const [quizState, setQuizState] = useState("loading"); - const [showCheck, setShowCheck] = useState(false); - const [answerBlank, setAnswerBlank] = useState(""); - const [withBa, setWithBa] = useState(false); - const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState(randFromArray(correctEmoji)); - useEffect(() => { - setQuizState(tickQuizState(completeVPs(props.vps))); - }, [props.vps]); - if (quizState === "loading") { - return
; + const [quizState, setQuizState] = useState("loading"); + const [showCheck, setShowCheck] = useState(false); + const [answerBlank, setAnswerBlank] = useState(""); + const [withBa, setWithBa] = useState(false); + const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState( + randFromArray(correctEmoji) + ); + useEffect(() => { + setQuizState(tickQuizState(completeVPs(props.vps))); + }, [props.vps]); + if (quizState === "loading") { + return
; + } + function handleRestart() { + if (quizState === "loading") return; + setWithBa(false); + setAnswerBlank(""); + setQuizState(tickQuizState(quizState.vps)); + } + function checkAnswer(a: T.PsString | { text: string; withBa: boolean }) { + if (quizState === "loading") return; + if (!quizState) return; + const correct = + "p" in a + ? isInAnswer(a, quizState.answer) + : // @ts-ignore // TODO: CLEANUP + blanksAnswerCorrect(a, quizState.answer); + setAnswerBlank(""); + setWithBa(false); + if (correct) { + const toPlay = randFromArray([true, false, false]); + if (toPlay) playAudio(`correct-${randFromArray([1, 2, 3])}`); + setShowCheck(true); + setTimeout(() => { + setQuizState((o) => { + if (o === "loading") return o; + return tickQuizState(o); + }); + }, checkDuration / 2); + setTimeout(() => { + setShowCheck(false); + }, checkDuration); + // this sucks, have to do this so the emoji doesn't change in the middle of animation + setTimeout(() => { + setCurrentCorrectEmoji(randFromArray(correctEmoji)); + }, checkDuration * 2); + } else { + playAudio(`wrong-${randFromArray([1, 2])}`); + navigator.vibrate(300); + setQuizState({ + ...quizState, + result: "fail", + }); } - function handleRestart() { - if (quizState === "loading") return; - setWithBa(false); - setAnswerBlank(""); - setQuizState(tickQuizState(quizState.vps)); - } - function checkAnswer(a: T.PsString | { text: string, withBa: boolean }) { - if (quizState === "loading") return; - if (!quizState) return; - const correct = "p" in a - ? isInAnswer(a, quizState.answer) - // @ts-ignore // TODO: CLEANUP - : blanksAnswerCorrect(a, quizState.answer); - setAnswerBlank(""); - setWithBa(false); - if (correct) { - const toPlay = randFromArray([true, false, false]); - if (toPlay) playAudio(`correct-${randFromArray([1,2,3])}`); - setShowCheck(true); - setTimeout(() => { - setQuizState(o => { - if (o === "loading") return o; - return tickQuizState(o); - }); - }, checkDuration / 2); - setTimeout(() => { - setShowCheck(false); - }, checkDuration); - // this sucks, have to do this so the emoji doesn't change in the middle of animation - setTimeout(() => { - setCurrentCorrectEmoji(randFromArray(correctEmoji)); - }, checkDuration * 2); - } else { - playAudio(`wrong-${randFromArray([1,2])}`); - navigator.vibrate(300); - setQuizState({ - ...quizState, - result: "fail", - }); - } - } - const rendered = renderVP(quizState.vps); - const subject: T.Rendered = getSubjectSelectionFromBlocks(rendered.blocks).selection; - const object = getObjectSelectionFromBlocks(rendered.blocks).selection; - const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false }); - return
- -
-
-
Subject
- {subject} -
- {(object !== "none") &&
-
Object
- {object} -
} -
- null} - mode={"quiz"} - /> -
+ } + const rendered = renderVP(quizState.vps); + const subject: T.Rendered = getSubjectSelectionFromBlocks( + rendered.blocks + ).selection; + const object = getObjectSelectionFromBlocks(rendered.blocks).selection; + const { e } = compileVP(rendered, { + removeKing: false, + shrinkServant: false, + }); + return ( +
+ +
+
+
Subject
+ + {subject} +
- {e &&
- {e.map(eLine =>
{eLine}
)} -
} -
-
- {currentCorrectEmoji} -
- {quizState.qNumber === stageLength ? -
-

👏 Congratulations

-

You finished the first two levels!

-

The other levels are still in development... In the meantime have an energy drink.

-
- energy-dring -
- + {object !== "none" && ( +
+
Object
+ + {object} + +
+ )} +
+ null} + mode={"quiz"} + /> +
+
+ {e && ( +
+ {e.map((eLine) => ( +
{eLine}
+ ))} +
+ )} +
+
+ {currentCorrectEmoji} +
+ {quizState.qNumber === stageLength ? ( +
+

👏 Congratulations

+

You finished the first two levels!

+

There may be other levels in the future...

+ +
+ ) : quizState.result === "waiting" ? ( + quizState.stage === "multiple choice" ? ( + <> +
Choose a correct answer:
+ {quizState.options.map((o) => ( +
+
- : (quizState.result === "waiting" - ? (quizState.stage === "multiple choice" ? <> -
Choose a correct answer:
- {quizState.options.map(o =>
- -
)} - :
-
Type the verb in Pashto script to finish the phrase:
-
{ - if (!answerBlank) { - alert("Enter the verb in Pashto script"); - }; - e.preventDefault(); - checkAnswer({ text: answerBlank, withBa }); - }}> -
- setAnswerBlank(e.target.value)} - /> -
-
- setWithBa(e.target.checked)} - id="addBa" - /> - -
- -
-
) - :
-
❌ Wrong 😭
- {quizState.stage === "multiple choice" ? -
-
The correct answer was:
- - {quizState.options.find(x => isInAnswer(x, quizState.answer)) as T.PsString} - -
- : -
-
Possible correct answers were:
- {quizState.answer.ps.map((p, i) =>
- {p} -
)} -
- {("withBa" in quizState.answer && quizState.answer.withBa) ? "With" : "without"} - {` `} - a {baParticle} in the phrase -
-
- } - -
) - } - -
-
; + ))} + + ) : ( +
+
+ Type the verb in Pashto script to finish the + phrase: +
+
{ + if (!answerBlank) { + alert("Enter the verb in Pashto script"); + } + e.preventDefault(); + checkAnswer({ text: answerBlank, withBa }); + }} + > +
+ setAnswerBlank(e.target.value)} + /> +
+
+ setWithBa(e.target.checked)} + id="addBa" + /> + +
+ +
+
+ ) + ) : ( +
+
❌ Wrong 😭
+ {quizState.stage === "multiple choice" ? ( +
+
The correct answer was:
+ + { + quizState.options.find((x) => + isInAnswer(x, quizState.answer) + ) as T.PsString + } + +
+ ) : ( +
+
Possible correct answers were:
+ {quizState.answer.ps.map((p, i) => ( +
+ {p} +
+ ))} +
+ + {"withBa" in quizState.answer && quizState.answer.withBa + ? "With" + : "without"} + + {` `}a {baParticle} in + the phrase +
+
+ )} + +
+ )} + +
+
+ ); } -function blanksAnswerCorrect(a: { text: string, withBa: boolean }, answer: { ps: T.PsString[], withBa?: boolean }): boolean { - const p = standardizePashto(a.text).trim(); - const given = removeBa({ p, f: "" }).p; - return ( - a.withBa === answer.withBa - && - answer.ps.some(x => x.p === given) - ); +function blanksAnswerCorrect( + a: { text: string; withBa: boolean }, + answer: { ps: T.PsString[]; withBa?: boolean } +): boolean { + const p = standardizePashto(a.text).trim(); + const given = removeBa({ p, f: "" }).p; + return a.withBa === answer.withBa && answer.ps.some((x) => x.p === given); } function ProgressBar({ quizState }: { quizState: QuizState }) { - function getPercentageDone({ current, total }: { current: number, total: number }): number { - return Math.round( - (current / total) * 100 - ); - } - function getProgressWidth(): string { - const num = getPercentageDone({ current: quizState.qNumber, total: stageLength }); - return `${num}%`; - } - return
-
-
- -
-
- {quizState.stage === "multiple choice" - ? "Level 1: Multiple Choice" - : "Level 2: Type the Verb"} -
-
; + function getPercentageDone({ + current, + total, + }: { + current: number; + total: number; + }): number { + return Math.round((current / total) * 100); + } + function getProgressWidth(): string { + const num = getPercentageDone({ + current: quizState.qNumber, + total: stageLength, + }); + return `${num}%`; + } + return ( +
+
+
+
+
+ {quizState.stage === "multiple choice" + ? "Level 1: Multiple Choice" + : "Level 2: Type the Verb"} +
+
+ ); } -function QuizNPDisplay({ children, stage, opts }: { - stage: "blanks" | "multiple choice", - children: T.Rendered | T.Person.ThirdPlurMale, - opts: T.TextOptions, +function QuizNPDisplay({ + children, + stage, + opts, +}: { + stage: "blanks" | "multiple choice"; + children: T.Rendered | T.Person.ThirdPlurMale; + opts: T.TextOptions; }) { - return
- {(typeof children === "number") - ?
Unspoken 3rd Pers. Masc. Plur.
- :
- {stage === "blanks" &&
- {children.selection.ps[0]} -
} -
{children.selection.e}
-
} -
; + return ( +
+ {typeof children === "number" ? ( +
Unspoken 3rd Pers. Masc. Plur.
+ ) : ( +
+ {stage === "blanks" && ( +
+ {children.selection.ps[0]} +
+ )} +
{children.selection.e}
+
+ )} +
+ ); } /** * creates a fresh QuizState when a VPSelection is passed * advances a QuizState when a QuizState is passed - * - * @param startingWith - * @returns + * + * @param startingWith + * @returns */ -function tickQuizState(startingWith: T.VPSelectionComplete | QuizState): QuizState { - function makeRes(x: T.VPSelectionComplete) { - return compileVP(renderVP(x), { removeKing: false, shrinkServant: false }); - } - const oldVps = "stage" in startingWith ? startingWith.vps : startingWith; - // for now, always inforce positive - const newVps = getRandomVPSelection("both")({ ...oldVps, verb: { ...oldVps.verb, negative: false }}); - const wrongVpsS: T.VPSelectionComplete[] = []; - // don't do the SO switches every time - const wholeTimeSOSwitch = randFromArray([true, false]); - [1, 2, 3].forEach(() => { - let v: T.VPSelectionComplete; - do { - const SOSwitch = wholeTimeSOSwitch && randFromArray([true, false]); - // TODO: if switich subj and obj, include the tense being correct maybe - v = getRandomVPSelection("tenses")( - SOSwitch ? switchSubjObj(newVps) : newVps, - ); - // eslint-disable-next-line - } while (wrongVpsS.find(x => x.verb.tense === v.verb.tense)); - wrongVpsS.push(v); - }); - const qNumber = "stage" in startingWith ? (startingWith.qNumber + 1) : 0; - const beatFirstStage = "stage" in startingWith && (qNumber === stageLength) && startingWith.stage === "multiple choice"; - const stage = beatFirstStage - ? "blanks" - : ("stage" in startingWith ? startingWith.stage : "multiple choice"); - const blanksAnswer = getBlanksAnswer(newVps); - if (stage === "blanks") { - return { - stage, - qNumber: beatFirstStage ? 0 : qNumber, - vps: newVps, - answer: blanksAnswer, - result: "waiting", - }; - } - const answer = makeRes(newVps); - const wrongAnswers = wrongVpsS.map(makeRes); - const allAnswers = shuffleArray([...wrongAnswers, answer]); - const options = allAnswers.map(getOptionFromResult); - const out: QuizState = { - stage, - qNumber: beatFirstStage ? 0 : qNumber, - vps: newVps, - answer, - options, - result: "waiting", - }; - return out; -} - -function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa: boolean } { - // TODO: !!! - // const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks); - // const ps = flattenLengths(verb.block.ps).map(x => { - // const y = removeBa(x); - // if (perfectiveHead) { - // return concatPsString(perfectiveHead.ps, y); - // } - // return y; - // }); +function tickQuizState( + startingWith: T.VPSelectionComplete | QuizState +): QuizState { + function makeRes(x: T.VPSelectionComplete) { + return compileVP(renderVP(x), { removeKing: false, shrinkServant: false }); + } + const oldVps = "stage" in startingWith ? startingWith.vps : startingWith; + // for now, always inforce positive + const newVps = getRandomVPSelection("both")({ + ...oldVps, + verb: { ...oldVps.verb, negative: false }, + }); + const wrongVpsS: T.VPSelectionComplete[] = []; + // don't do the SO switches every time + const wholeTimeSOSwitch = randFromArray([true, false]); + [1, 2, 3].forEach(() => { + let v: T.VPSelectionComplete; + do { + const SOSwitch = wholeTimeSOSwitch && randFromArray([true, false]); + // TODO: if switich subj and obj, include the tense being correct maybe + v = getRandomVPSelection("tenses")( + SOSwitch ? switchSubjObj(newVps) : newVps + ); + // eslint-disable-next-line + } while (wrongVpsS.find((x) => x.verb.tense === v.verb.tense)); + wrongVpsS.push(v); + }); + const qNumber = "stage" in startingWith ? startingWith.qNumber + 1 : 0; + const beatFirstStage = + "stage" in startingWith && + qNumber === stageLength && + startingWith.stage === "multiple choice"; + const stage = beatFirstStage + ? "blanks" + : "stage" in startingWith + ? startingWith.stage + : "multiple choice"; + const blanksAnswer = getBlanksAnswer(newVps); + if (stage === "blanks") { return { - ps: [{ p: "TOOD", f: "TODO" }], - withBa: false, // verb.block.hasBa, - } + stage, + qNumber: beatFirstStage ? 0 : qNumber, + vps: newVps, + answer: blanksAnswer, + result: "waiting", + }; + } + const answer = makeRes(newVps); + const wrongAnswers = wrongVpsS.map(makeRes); + const allAnswers = shuffleArray([...wrongAnswers, answer]); + const options = allAnswers.map(getOptionFromResult); + const out: QuizState = { + stage, + qNumber: beatFirstStage ? 0 : qNumber, + vps: newVps, + answer, + options, + result: "waiting", + }; + return out; } -function isInAnswer(a: T.PsString, answer: { +function getBlanksAnswer(vps: T.VPSelectionComplete): { + ps: T.PsString[]; + withBa: boolean; +} { + // TODO: !!! + // const { verb, perfectiveHead } = getVerbAndHeadFromBlocks(renderVP(vps).blocks); + // const ps = flattenLengths(verb.block.ps).map(x => { + // const y = removeBa(x); + // if (perfectiveHead) { + // return concatPsString(perfectiveHead.ps, y); + // } + // return y; + // }); + return { + ps: [{ p: "TOOD", f: "TODO" }], + withBa: false, // verb.block.hasBa, + }; +} + +function isInAnswer( + a: T.PsString, + answer: { ps: T.SingleOrLengthOpts; e?: string[] | undefined; -}): boolean { - if ("long" in answer.ps) { - return isInAnswer(a, { ...answer, ps: answer.ps.long }) || - isInAnswer(a, { ...answer, ps: answer.ps.short }) || - !!(answer.ps.mini && isInAnswer(a, { ...answer, ps: answer.ps.mini })); - } - return answer.ps.some((x) => psStringEquals(x, a)); + } +): boolean { + if ("long" in answer.ps) { + return ( + isInAnswer(a, { ...answer, ps: answer.ps.long }) || + isInAnswer(a, { ...answer, ps: answer.ps.short }) || + !!(answer.ps.mini && isInAnswer(a, { ...answer, ps: answer.ps.mini })) + ); + } + return answer.ps.some((x) => psStringEquals(x, a)); } - function getOptionFromResult(r: { - ps: T.SingleOrLengthOpts; - e?: string[] | undefined; + ps: T.SingleOrLengthOpts; + e?: string[] | undefined; }): T.PsString { - const ps = "long" in r.ps - ? r.ps[randFromArray(["short", "long"] as ("short" | "long")[])] - : r.ps; - // not randomizing version pick (for now) - return ps[0]; + const ps = + "long" in r.ps + ? r.ps[randFromArray(["short", "long"] as ("short" | "long")[])] + : r.ps; + // not randomizing version pick (for now) + return ps[0]; } function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete { - const vpsSubj = getSubjectSelection(vps.blocks).selection; - const vpsObj = getObjectSelection(vps.blocks).selection; - const oldSubj = vpsSubj?.selection.type === "pronoun" - ? vpsSubj.selection.person - : undefined; - const oldObj = (typeof vpsObj === "object" && vpsObj.selection.type === "pronoun") - ? vpsObj.selection.person - : undefined; - const { subj, obj } = randomSubjObj( - oldSubj === undefined - ? undefined - : { - subj: oldSubj, - obj: oldObj, - } - ); - const t = getTenseFromVerbSelection(vps.verb); - const verb: T.VerbSelectionComplete = { - ...vps.verb, - tense: isImperativeTense(t) ? "presentVerb" : t, - }; - return { - ...vps, - blocks: adjustObjectSelection( - adjustSubjectSelection(vps.blocks, { - type: "NP", - selection: { - type: "pronoun", - distance: "far", - person: subj, - }, - }), - ( - (typeof vpsObj === "object" && !(vpsObj.selection.type === "noun" && vpsObj.selection.dynamicComplement)) - || - vpsObj === undefined - ) - ? { - type: "NP", - selection: { - type: "pronoun", - distance: "far", - person: obj, - } - } - : vpsObj, - ), - verb, - }; + const vpsSubj = getSubjectSelection(vps.blocks).selection; + const vpsObj = getObjectSelection(vps.blocks).selection; + const oldSubj = + vpsSubj?.selection.type === "pronoun" + ? vpsSubj.selection.person + : undefined; + const oldObj = + typeof vpsObj === "object" && vpsObj.selection.type === "pronoun" + ? vpsObj.selection.person + : undefined; + const { subj, obj } = randomSubjObj( + oldSubj === undefined + ? undefined + : { + subj: oldSubj, + obj: oldObj, + } + ); + const t = getTenseFromVerbSelection(vps.verb); + const verb: T.VerbSelectionComplete = { + ...vps.verb, + tense: isImperativeTense(t) ? "presentVerb" : t, + }; + return { + ...vps, + blocks: adjustObjectSelection( + adjustSubjectSelection(vps.blocks, { + type: "NP", + selection: { + type: "pronoun", + distance: "far", + person: subj, + }, + }), + (typeof vpsObj === "object" && + !( + vpsObj.selection.type === "noun" && + (vpsObj.selection.dynamicComplement || + vpsObj.selection.genStativeComplement) + )) || + vpsObj === undefined + ? { + type: "NP", + selection: { + type: "pronoun", + distance: "far", + person: obj, + }, + } + : vpsObj + ), + verb, + }; } function getRandomVPSelection(mix: MixType = "both") { - // TODO: Type safety to make sure it's safe? - return (VPS: T.VPSelectionComplete): T.VPSelectionComplete => { - const subject = getSubjectSelection(VPS.blocks).selection; - const object = getObjectSelection(VPS.blocks).selection; - const verb = VPS.verb; - const oldSubj = (subject.selection.type === "pronoun") - ? subject.selection.person - : undefined; - const oldObj = (typeof object === "object" && object.selection.type === "pronoun") - ? object.selection.person - : undefined; - const { subj, obj } = randomSubjObj( - oldSubj !== undefined ? { subj: oldSubj, obj: oldObj } : undefined - ); - const randSubj: T.NPSelection = { - type: "NP", - selection: ( - subject?.selection.type === "pronoun" ? { - ...subject.selection, - person: subj, - } : { - type: "pronoun", - distance: "far", - person: subj, - } - ), - }; - const randObj: T.NPSelection = { - type: "NP", - selection: ( - typeof object === "object" && object.selection.type === "pronoun" ? { - ...object.selection, - person: obj, - } : { - type: "pronoun", - distance: "far", - person: obj, - } - ), - }; - // ensure that the verb selection is complete - if (mix === "tenses") { - return { - blocks: possibleShuffleArray(adjustObjectSelection( - adjustSubjectSelection(VPS.blocks, subject !== undefined ? subject : randSubj), - object !== undefined ? object : randObj, - )), - verb: randomizeTense(verb, true), - form: { removeKing: false, shrinkServant: false }, - externalComplement: undefined, + // TODO: Type safety to make sure it's safe? + return (VPS: T.VPSelectionComplete): T.VPSelectionComplete => { + const subject = getSubjectSelection(VPS.blocks).selection; + const object = getObjectSelection(VPS.blocks).selection; + const verb = VPS.verb; + const oldSubj = + subject.selection.type === "pronoun" + ? subject.selection.person + : undefined; + const oldObj = + typeof object === "object" && object.selection.type === "pronoun" + ? object.selection.person + : undefined; + const { subj, obj } = randomSubjObj( + oldSubj !== undefined ? { subj: oldSubj, obj: oldObj } : undefined + ); + const randSubj: T.NPSelection = { + type: "NP", + selection: + subject?.selection.type === "pronoun" + ? { + ...subject.selection, + person: subj, } - } - return { - blocks: possibleShuffleArray(adjustObjectSelection( - adjustSubjectSelection(VPS.blocks, randSubj), - ( - (typeof object === "object" && !(object.selection.type === "noun" && object.selection.dynamicComplement)) - || - object === undefined - ) - ? randObj - : object, - )), - verb: randomizeTense(verb, true), - form: { removeKing: false, shrinkServant: false }, - externalComplement: undefined, - }; + : { + type: "pronoun", + distance: "far", + person: subj, + }, }; -}; + const randObj: T.NPSelection = { + type: "NP", + selection: + typeof object === "object" && object.selection.type === "pronoun" + ? { + ...object.selection, + person: obj, + } + : { + type: "pronoun", + distance: "far", + person: obj, + }, + }; + // ensure that the verb selection is complete + if (mix === "tenses") { + return { + blocks: possibleShuffleArray( + adjustObjectSelection( + adjustSubjectSelection( + VPS.blocks, + subject !== undefined ? subject : randSubj + ), + object !== undefined ? object : randObj + ) + ), + verb: randomizeTense(verb, true), + form: { removeKing: false, shrinkServant: false }, + externalComplement: undefined, + }; + } + return { + blocks: possibleShuffleArray( + adjustObjectSelection( + adjustSubjectSelection(VPS.blocks, randSubj), + (typeof object === "object" && + !( + object.selection.type === "noun" && + (object.selection.dynamicComplement || + object.selection.genStativeComplement) + )) || + object === undefined + ? randObj + : object + ) + ), + verb: randomizeTense(verb, true), + form: { removeKing: false, shrinkServant: false }, + externalComplement: undefined, + }; + }; +} function possibleShuffleArray(arr: X[]): X[] { - const willShuffle = randFromArray([true, false, false]); - if (willShuffle) { - return shuffleArray(arr); - } - return arr; + const willShuffle = randFromArray([true, false, false]); + if (willShuffle) { + return shuffleArray(arr); + } + return arr; } -function randomizeTense(verb: T.VerbSelectionComplete, dontRepeatTense: boolean): T.VerbSelectionComplete { - return { - ...verb, - tense: getRandomTense( - dontRepeatTense ? verb.tense : undefined, - ), - }; +function randomizeTense( + verb: T.VerbSelectionComplete, + dontRepeatTense: boolean +): T.VerbSelectionComplete { + return { + ...verb, + tense: getRandomTense(dontRepeatTense ? verb.tense : undefined), + }; } -export default VPExplorerQuiz; \ No newline at end of file +export default VPExplorerQuiz; diff --git a/src/components/src/vp-explorer/energy-drink.jpg b/src/components/src/vp-explorer/energy-drink.jpg deleted file mode 100644 index 36df01e1cac0fcfdab9a76200548597eb525149b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7003 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 diff --git a/src/lib/src/phrase-building/render-vp.ts b/src/lib/src/phrase-building/render-vp.ts index e478ca6..75cbf7f 100644 --- a/src/lib/src/phrase-building/render-vp.ts +++ b/src/lib/src/phrase-building/render-vp.ts @@ -81,7 +81,11 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { kids: getVPKids(hasBa, VP.blocks, VP.form, king), englishBase: renderEnglishVPBase({ subjectPerson, - object: VP.verb.isCompound === "dynamic" ? "none" : object, + object: + VP.verb.isCompound === "dynamic" || + VP.verb.isCompound === "generative stative" + ? "none" + : object, vs: VP.verb, }), form: VP.form,