refactor and improve quiz

This commit is contained in:
lingdocs 2022-04-13 14:37:04 +05:00
parent 68633089f9
commit 39996bc92c
6 changed files with 309 additions and 259 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/pashto-inflector", "name": "@lingdocs/pashto-inflector",
"version": "1.9.8", "version": "1.9.9",
"author": "lingdocs.com", "author": "lingdocs.com",
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations", "description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
"homepage": "https://verbs.lingdocs.com", "homepage": "https://verbs.lingdocs.com",

View File

@ -68,11 +68,10 @@ export function getRandomTense(type: "basic" | "modal" | "perfect", o?: T.Perfec
return tns; return tns;
} }
function TensePicker({ onChange, vps, mode, locked }: { function TensePicker({ onChange, vps, mode }: {
vps: T.VPSelection, vps: T.VPSelection,
onChange: (p: T.VPSelection) => void, onChange: (p: T.VPSelection) => void,
mode: "charts" | "phrases" | "quiz", mode: "charts" | "phrases" | "quiz",
locked: boolean,
}) { }) {
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense } | null) { function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense } | null) {
const value = o?.value ? o.value : undefined; const value = o?.value ? o.value : undefined;
@ -167,7 +166,7 @@ function TensePicker({ onChange, vps, mode, locked }: {
label: "Modal", label: "Modal",
value: "modal", value: "modal",
}]} }]}
handleChange={onTenseCategorySelect} handleChange={mode === "quiz" ? onTenseCategorySelect : () => null}
/> />
</div>} </div>}
</div> </div>
@ -180,11 +179,11 @@ function TensePicker({ onChange, vps, mode, locked }: {
options={tOptions} options={tOptions}
{...zIndexProps} {...zIndexProps}
/> />
{vps.verb && !locked && <div className="d-flex flex-row justify-content-between align-items-center mt-3 mb-1" style={{ width: "100%" }}> {vps.verb && (mode !== "quiz") && <div className="d-flex flex-row justify-content-between align-items-center mt-3 mb-1" style={{ width: "100%" }}>
<div className="btn btn-light clickable" onClick={moveTense("back")}> <div className="btn btn-light clickable" onClick={moveTense("back")}>
<i className="fas fa-chevron-left" /> <i className="fas fa-chevron-left" />
</div> </div>
{mode !== "charts" && <ButtonSelect {mode === "phrases" && <ButtonSelect
small small
value={vps.verb.negative.toString()} value={vps.verb.negative.toString()}
options={[{ options={[{

View File

@ -4,10 +4,20 @@ import InlinePs from "../InlinePs";
import AbbreviationFormSelector from "./AbbreviationFormSelector"; import AbbreviationFormSelector from "./AbbreviationFormSelector";
import { isPastTense } from "../../lib/phrase-building/vp-tools"; import { isPastTense } from "../../lib/phrase-building/vp-tools";
import { useStickyState } from "../../library"; import { useStickyState } from "../../library";
import { isVPSelectionComplete } from "../../lib/type-predicates";
function VPDisplay({ VP, opts }: { VP: T.VPSelectionComplete, opts: T.TextOptions }) {
function VPDisplay({ VP, opts }: { VP: T.VPSelection | T.VPSelectionComplete, opts: T.TextOptions }) {
const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationForm"); const [form, setForm] = useStickyState<T.FormVersion>({ removeKing: false, shrinkServant: false }, "abbreviationForm");
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV"); const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
if (!isVPSelectionComplete(VP)) {
return <div className="lead text-muted text-center mt-4">
{(() => {
const twoNPs = (VP.subject === undefined) && (VP.verb.object === undefined);
return `Choose NP${twoNPs ? "s " : ""} to make a phrase`;
})()}
</div>;
}
const result = compileVP(renderVP(VP), { ...form, OSV }); const result = compileVP(renderVP(VP), { ...form, OSV });
return <div className="text-center mt-2"> return <div className="text-center mt-2">
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2"> {VP.verb.transitivity === "transitive" && <div className="form-check mb-2">

View File

@ -1,9 +1,9 @@
import NPPicker from "../np-picker/NPPicker"; import NPPicker from "../np-picker/NPPicker";
import VerbPicker from "./VerbPicker"; import VerbPicker from "./VerbPicker";
import TensePicker, { getRandomTense } from "./TensePicker"; import TensePicker from "./TensePicker";
import VPDisplay from "./VPDisplay"; import VPDisplay from "./VPDisplay";
import ButtonSelect from "../ButtonSelect"; import ButtonSelect from "../ButtonSelect";
import { renderVP, compileVP } from "../../lib/phrase-building/index";
import { import {
isInvalidSubjObjCombo, isInvalidSubjObjCombo,
} from "../../lib/phrase-building/vp-tools"; } from "../../lib/phrase-building/vp-tools";
@ -11,32 +11,15 @@ import * as T from "../../types";
import ChartDisplay from "./ChartDisplay"; import ChartDisplay from "./ChartDisplay";
import useStickyState from "../../lib/useStickyState"; import useStickyState from "../../lib/useStickyState";
import { makeVPSelectionState } from "./verb-selection"; import { makeVPSelectionState } from "./verb-selection";
import { CSSProperties, useEffect, useState } from "react"; import { useEffect } from "react";
import { randomSubjObj } from "../../library";
import shuffleArray from "../../lib/shuffle-array";
import InlinePs from "../InlinePs";
import { psStringEquals } from "../../lib/p-text-helpers";
import { randFromArray } from "../../lib/misc-helpers";
import playAudio from "../../lib/play-audio";
import { isVPSelectionComplete } from "../../lib/type-predicates";
import { getKingAndServant } from "../../lib/phrase-building/render-vp"; import { getKingAndServant } from "../../lib/phrase-building/render-vp";
import { isPastTense } from "../../lib/phrase-building/vp-tools"; import { isPastTense } from "../../lib/phrase-building/vp-tools";
// import { useReward } from 'react-rewards'; import VPExplorerQuiz from "./VPExplorerQuiz";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools"
const kingEmoji = "👑"; const kingEmoji = "👑";
const servantEmoji = "🙇‍♂️"; const servantEmoji = "🙇‍♂️";
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%)",
}
// TODO: make answerFeedback emojis appear at random translate angles a little bit // TODO: make answerFeedback emojis appear at random translate angles a little bit
// add energy drinks? // add energy drinks?
@ -54,18 +37,6 @@ const answerFeedback: CSSProperties = {
// TODO: error handling on error with rendering etc // TODO: error handling on error with rendering etc
const checkDuration = 400;
type QuizState = {
answer: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
e?: string[] | undefined;
},
options: T.PsString[],
result: "waiting" | "fail",
}
type MixType = "NPs" | "tenses" | "both";
export function VPExplorer(props: { export function VPExplorer(props: {
verb: T.VerbEntry, verb: T.VerbEntry,
opts: T.TextOptions, opts: T.TextOptions,
@ -90,33 +61,16 @@ export function VPExplorer(props: {
}, },
"verbExplorerMode", "verbExplorerMode",
); );
const [quizState, setQuizState] = useState<QuizState | undefined>(undefined);
const [showCheck, setShowCheck] = useState<boolean>(false);
const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(randFromArray(correctEmoji));
useEffect(() => { useEffect(() => {
setVps(o => { setVps(oldVps => {
if (mode === "quiz") { if (mode === "quiz") {
setMode("phrases"); setMode("phrases");
} }
return makeVPSelectionState(props.verb, o); return makeVPSelectionState(props.verb, oldVps);
}); });
// eslint-disable-next-line // eslint-disable-next-line
}, [props.verb]); }, [props.verb]);
function handleChangeMode(m: "charts" | "phrases" | "quiz") {
if (m === "quiz") {
handleResetQuiz();
}
setMode(m);
}
function handleResetQuiz() {
if (!vps.verb) {
alert("Choose a verb to quiz");
return;
}
const { VPS, qs } = makeQuizState(vps);
setVps(VPS);
setQuizState(qs);
}
function handleSubjectChange(subject: T.NPSelection | undefined, skipPronounConflictCheck?: boolean) { function handleSubjectChange(subject: T.NPSelection | undefined, skipPronounConflictCheck?: boolean) {
if (!skipPronounConflictCheck && hasPronounConflict(subject, vps.verb?.object)) { if (!skipPronounConflictCheck && hasPronounConflict(subject, vps.verb?.object)) {
alert("That combination of pronouns is not allowed"); alert("That combination of pronouns is not allowed");
@ -153,31 +107,6 @@ export function VPExplorer(props: {
} }
return f; return f;
} }
function checkQuizAnswer(a: T.PsString) {
if (!quizState) return;
if (isInAnswer(a, quizState.answer)) {
const toPlay = randFromArray([true, false, false]);
if (toPlay) playAudio(`correct-${randFromArray([1,2,3])}`);
setShowCheck(true);
setTimeout(() => {
handleResetQuiz();
}, 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(250);
setQuizState({
...quizState,
result: "fail",
});
}
}
return <div className="mt-3" style={{ maxWidth: "950px"}}> return <div className="mt-3" style={{ maxWidth: "950px"}}>
<VerbPicker <VerbPicker
{..."getNounByTs" in props ? { {..."getNounByTs" in props ? {
@ -198,7 +127,7 @@ export function VPExplorer(props: {
{ label: "Phrases", value: "phrases" }, { label: "Phrases", value: "phrases" },
{ label: "Quiz", value: "quiz" }, { label: "Quiz", value: "quiz" },
]} ]}
handleChange={handleChangeMode} handleChange={setMode}
/> />
</div> </div>
{(vps.verb && (typeof vps.verb.object === "object") && (vps.verb.isCompound !== "dynamic") && (mode === "phrases")) && {(vps.verb && (typeof vps.verb.object === "object") && (vps.verb.isCompound !== "dynamic") && (mode === "phrases")) &&
@ -207,8 +136,8 @@ export function VPExplorer(props: {
<i className="fas fa-exchange-alt mr-2" /> subj/obj <i className="fas fa-exchange-alt mr-2" /> subj/obj
</button> </button>
</div>} </div>}
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}> {mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
{mode !== "charts" && <> {mode === "phrases" && <>
<div className="my-2"> <div className="my-2">
<div className="h5 text-center">Subject {showRole(vps, "subject")}</div> <div className="h5 text-center">Subject {showRole(vps, "subject")}</div>
<NPPicker <NPPicker
@ -223,9 +152,8 @@ export function VPExplorer(props: {
}} }}
np={vps.subject} np={vps.subject}
counterPart={vps.verb ? vps.verb.object : undefined} counterPart={vps.verb ? vps.verb.object : undefined}
onChange={quizLock(handleSubjectChange)} onChange={handleSubjectChange}
opts={props.opts} opts={props.opts}
cantClear={mode === "quiz"}
/> />
</div> </div>
{vps.verb && (vps.verb.object !== "none") && <div className="my-2"> {vps.verb && (vps.verb.object !== "none") && <div className="my-2">
@ -245,9 +173,8 @@ export function VPExplorer(props: {
asObject asObject
np={vps.verb.object} np={vps.verb.object}
counterPart={vps.subject} counterPart={vps.subject}
onChange={quizLock(handleObjectChange)} onChange={handleObjectChange}
opts={props.opts} opts={props.opts}
cantClear={mode === "quiz"}
/>} />}
</div>} </div>}
</>} </>}
@ -256,40 +183,12 @@ export function VPExplorer(props: {
vps={vps} vps={vps}
onChange={quizLock(setVps)} onChange={quizLock(setVps)}
mode={mode} mode={mode}
locked={!!(mode === "quiz" && quizState)}
/> />
</div> </div>
</div>
{(isVPSelectionComplete(vps) && (mode === "phrases")) &&
<VPDisplay VP={vps} opts={props.opts} />
}
{(vps.verb && (mode === "charts")) && <ChartDisplay VS={vps.verb} opts={props.opts} />}
{(mode === "quiz" && quizState) && <div className="text-center">
<div style={showCheck ? answerFeedback : { ...answerFeedback, opacity: 0 }}>
{currentCorrectEmoji}
</div>
{quizState.result === "waiting" ? <>
<div className="text-muted my-3">Choose a correct answer:</div>
{quizState.options.map(o => <div className="pb-3" key={o.f}>
<div className="btn btn-answer btn-outline-secondary" onClick={() => {
checkQuizAnswer(o);
}}>
<InlinePs opts={props.opts}>{o}</InlinePs>
</div>
</div>)}
</> : <div>
<div className="h5 mt-4"> Wrong 😭</div>
<div className="my-4">The correct answer was:</div>
<InlinePs opts={props.opts}>
{quizState.options.find(x => isInAnswer(x, quizState.answer)) as T.PsString}
</InlinePs>
<div className="my-4">
<button type="button" className="btn btn-primary" onClick={handleResetQuiz}>
Try Again
</button>
</div>
</div>}
</div>} </div>}
{mode === "phrases" && <VPDisplay VP={vps} opts={props.opts} />}
{mode === "charts" && <ChartDisplay VS={vps.verb} opts={props.opts} />}
{mode === "quiz" && <VPExplorerQuiz opts={props.opts} vps={vps} />}
</div> </div>
} }
@ -313,138 +212,3 @@ function showRole(VP: T.VPSelection, member: "subject" | "object") {
</span> </span>
: ""; : "";
} }
function switchSubjObj({ subject, verb }: T.VPSelection): T.VPSelection {
if (!subject|| !verb || !verb.object || !(typeof verb.object === "object")) {
return { subject, verb };
}
return {
subject: verb.object,
verb: {
...verb,
object: subject,
}
};
}
function makeQuizState(oldVps: T.VPSelection): { VPS: T.VPSelectionComplete, qs: QuizState } {
function makeRes(x: T.VPSelectionComplete) {
return compileVP(renderVP(x), { removeKing: false, shrinkServant: false });
}
const vps = getRandomVPSelection("both")(oldVps);
const wrongStates: 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(vps) : vps,
);
// eslint-disable-next-line
} while (wrongStates.find(x => x.verb.tense === v.verb.tense));
wrongStates.push(v);
});
const answer = makeRes(vps);
const wrongAnswers = wrongStates.map(makeRes);
const allAnswers = shuffleArray([...wrongAnswers, answer]);
const options = allAnswers.map(getOptionFromResult);
return {
VPS: vps,
qs: {
answer,
options,
result: "waiting",
},
};
}
function isInAnswer(a: T.PsString, answer: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
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));
}
function getOptionFromResult(r: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
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];
}
function getRandomVPSelection(mix: MixType = "both") {
// TODO: Type safety to make sure it's safe?
return ({ subject, verb }: T.VPSelection): T.VPSelectionComplete => {
const oldSubj = (subject?.type === "pronoun")
? subject.person
: undefined;
const oldObj = (typeof verb?.object === "object" && verb.object.type === "pronoun")
? verb.object.person
: undefined;
const { subj, obj } = randomSubjObj(
oldSubj !== undefined ? { subj: oldSubj, obj: oldObj } : undefined
);
const randSubj: T.PronounSelection = subject?.type === "pronoun" ? {
...subject,
person: subj,
} : {
type: "pronoun",
distance: "far",
person: subj,
};
const randObj: T.PronounSelection = typeof verb?.object === "object" && verb.object.type === "pronoun" ? {
...verb.object,
person: obj,
} : {
type: "pronoun",
distance: "far",
person: obj,
};
const s = randSubj;
// ensure that the verb selection is complete
const v: T.VerbSelectionComplete = {
...verb,
object: (
(typeof verb.object === "object" && !(verb.object.type === "noun" && verb.object.dynamicComplement))
||
verb.object === undefined
)
? randObj
: verb.object,
};
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(v, true),
}
}
return {
subject: s,
verb: randomizeTense(v, true),
};
};
};
function randomizeTense(verb: T.VerbSelectionComplete, dontRepeatTense: boolean): T.VerbSelectionComplete {
return {
...verb,
tense: getRandomTense(
// TODO: WHY ISN'T THE OVERLOADING ON THIS
// @ts-ignore
verb.tenseCategory,
dontRepeatTense ? verb.tense : undefined,
),
};
}

View File

@ -0,0 +1,264 @@
import { CSSProperties, useState } from "react";
import * as T from "../../types";
import { randFromArray } from "../../lib/misc-helpers";
import { randomSubjObj } from "../../library";
import shuffleArray from "../../lib/shuffle-array";
import InlinePs from "../InlinePs";
import { psStringEquals } from "../../lib/p-text-helpers";
import { renderVP, compileVP } from "../../lib/phrase-building/index";
import { getRandomTense } from "./TensePicker";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
import playAudio from "../../lib/play-audio";
import TensePicker from "./TensePicker";
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%)",
}
const checkDuration = 400;
type QuizState = {
answer: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
e?: string[] | undefined;
},
options: T.PsString[],
result: "waiting" | "fail",
}
type MixType = "NPs" | "tenses" | "both";
function VPExplorerQuiz(props: {
opts: T.TextOptions,
vps: T.VPSelection,
}) {
const startingQs = makeQuizState(props.vps);
const [vps, setVps] = useState<T.VPSelectionComplete>(startingQs.VPS)
const [quizState, setQuizState] = useState<QuizState>(startingQs.qs);
const [showCheck, setShowCheck] = useState<boolean>(false);
const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(randFromArray(correctEmoji));
function handleResetQuiz() {
const { VPS, qs } = makeQuizState(vps);
setVps(VPS);
setQuizState(qs);
}
function checkQuizAnswer(a: T.PsString) {
if (!quizState) return;
if (isInAnswer(a, quizState.answer)) {
const toPlay = randFromArray([true, false, false]);
if (toPlay) playAudio(`correct-${randFromArray([1,2,3])}`);
setShowCheck(true);
setTimeout(() => {
handleResetQuiz();
}, 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(250);
setQuizState({
...quizState,
result: "fail",
});
}
}
const rendered = renderVP(vps);
const { subject, object } = rendered;
const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false });
return <div>
<div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
<div className="my-2">
<div className="h5 text-center">Subject</div>
<QuizNPDisplay>{subject}</QuizNPDisplay>
</div>
{(object !== "none") && <div className="my-2">
<div className="h5 text-center">Object</div>
<QuizNPDisplay>{object}</QuizNPDisplay>
</div>}
<div className="my-2">
<TensePicker
vps={vps}
onChange={() => null}
mode={"quiz"}
/>
</div>
</div>
{e && <div className="text-center text-muted text-small">
{e.map(eLine => <div key={eLine}>{eLine}</div>)}
</div>}
<div className="text-center">
<div style={showCheck ? answerFeedback : { ...answerFeedback, opacity: 0 }}>
{currentCorrectEmoji}
</div>
{quizState.result === "waiting" ? <>
<div className="text-muted my-3">Choose a correct answer:</div>
{quizState.options.map(o => <div className="pb-3" key={o.f}>
<div className="btn btn-answer btn-outline-secondary" onClick={() => {
checkQuizAnswer(o);
}}>
<InlinePs opts={props.opts}>{o}</InlinePs>
</div>
</div>)}
</> : <div>
<div className="h5 mt-4"> Wrong 😭</div>
<div className="my-4">The correct answer was:</div>
<InlinePs opts={props.opts}>
{quizState.options.find(x => isInAnswer(x, quizState.answer)) as T.PsString}
</InlinePs>
<div className="my-4">
<button type="button" className="btn btn-primary" onClick={handleResetQuiz}>
Try Again
</button>
</div>
</div>}
</div>
</div>;
}
function QuizNPDisplay({ children }: { children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale }) {
return <div className="mb-3">
{(typeof children === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <div style={{ fontSize: "larger" }}>{children.e}</div>}
</div>;
}
function makeQuizState(oldVps: T.VPSelection): { VPS: T.VPSelectionComplete, qs: QuizState } {
function makeRes(x: T.VPSelectionComplete) {
return compileVP(renderVP(x), { removeKing: false, shrinkServant: false });
}
// for now, always inforce positive
const vps = getRandomVPSelection("both")({ ...oldVps, verb: { ...oldVps.verb, negative: false }});
const wrongStates: 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(vps) : vps,
);
// eslint-disable-next-line
} while (wrongStates.find(x => x.verb.tense === v.verb.tense));
wrongStates.push(v);
});
const answer = makeRes(vps);
const wrongAnswers = wrongStates.map(makeRes);
const allAnswers = shuffleArray([...wrongAnswers, answer]);
const options = allAnswers.map(getOptionFromResult);
return {
VPS: vps,
qs: {
answer,
options,
result: "waiting",
},
};
}
function isInAnswer(a: T.PsString, answer: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
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));
}
function getOptionFromResult(r: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
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];
}
function getRandomVPSelection(mix: MixType = "both") {
// TODO: Type safety to make sure it's safe?
return ({ subject, verb }: T.VPSelection): T.VPSelectionComplete => {
const oldSubj = (subject?.type === "pronoun")
? subject.person
: undefined;
const oldObj = (typeof verb?.object === "object" && verb.object.type === "pronoun")
? verb.object.person
: undefined;
const { subj, obj } = randomSubjObj(
oldSubj !== undefined ? { subj: oldSubj, obj: oldObj } : undefined
);
const randSubj: T.PronounSelection = subject?.type === "pronoun" ? {
...subject,
person: subj,
} : {
type: "pronoun",
distance: "far",
person: subj,
};
const randObj: T.PronounSelection = typeof verb?.object === "object" && verb.object.type === "pronoun" ? {
...verb.object,
person: obj,
} : {
type: "pronoun",
distance: "far",
person: obj,
};
const s = randSubj;
// ensure that the verb selection is complete
const v: T.VerbSelectionComplete = {
...verb,
object: (
(typeof verb.object === "object" && !(verb.object.type === "noun" && verb.object.dynamicComplement))
||
verb.object === undefined
)
? randObj
: verb.object,
};
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(v, true),
}
}
return {
subject: s,
verb: randomizeTense(v, true),
};
};
};
function randomizeTense(verb: T.VerbSelectionComplete, dontRepeatTense: boolean): T.VerbSelectionComplete {
return {
...verb,
tense: getRandomTense(
// TODO: WHY ISN'T THE OVERLOADING ON THIS
// @ts-ignore
verb.tenseCategory,
dontRepeatTense ? verb.tense : undefined,
),
};
}
export default VPExplorerQuiz;

View File

@ -154,3 +154,16 @@ export function removeDuplicates(psv: T.PsString[]): T.PsString[] {
)) ))
)); ));
} }
export function switchSubjObj({ subject, verb }: T.VPSelection): T.VPSelection {
if (!subject|| !verb || !verb.object || !(typeof verb.object === "object")) {
return { subject, verb };
}
return {
subject: verb.object,
verb: {
...verb,
object: subject,
}
};
}