smooth first stage

This commit is contained in:
lingdocs 2022-04-13 16:07:35 +05:00
parent 39996bc92c
commit 352d30e80c
4 changed files with 145 additions and 56 deletions

View File

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

View File

@ -0,0 +1,35 @@
import { CSSProperties } from "react";
interface IProps {
name: string;
[key: string]: CSSProperties | string;
}
const Keyframes = (props: IProps) => {
const toCss = (cssObject: CSSProperties | string) =>
typeof cssObject === "string"
? cssObject
: Object.keys(cssObject).reduce((accumulator, key) => {
const cssKey = key.replace(/[A-Z]/g, v => `-${v.toLowerCase()}`);
const cssValue = (cssObject as any)[key].toString().replace("'", "");
return `${accumulator}${cssKey}:${cssValue};`;
}, "");
return (
<style>
{`@keyframes ${props.name} {
${Object.keys(props)
.map(key => {
return ["from", "to"].includes(key)
? `${key} { ${toCss(props[key])} }`
: /^_[0-9]+$/.test(key)
? `${key.replace("_", "")}% { ${toCss(props[key])} }`
: "";
})
.join(" ")}
}`}
</style>
);
};
export default Keyframes;

View File

@ -10,6 +10,8 @@ import { getRandomTense } from "./TensePicker";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
import playAudio from "../../lib/play-audio";
import TensePicker from "./TensePicker";
import Keyframes from "../Keyframes";
import energyDrink from "./energy-drink.jpeg";
const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", "🕺", "💃", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"];
@ -25,8 +27,12 @@ const answerFeedback: CSSProperties = {
}
const checkDuration = 400;
const stageLength = 7;
type QuizState = {
stage: "multiple choice",
qNumber: number,
vps: T.VPSelectionComplete,
answer: {
ps: T.SingleOrLengthOpts<T.PsString[]>;
e?: string[] | undefined;
@ -40,16 +46,10 @@ 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 startingQs = tickQuizState(props.vps);
const [quizState, setQuizState] = useState<QuizState>(startingQs);
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)) {
@ -57,7 +57,7 @@ function VPExplorerQuiz(props: {
if (toPlay) playAudio(`correct-${randFromArray([1,2,3])}`);
setShowCheck(true);
setTimeout(() => {
handleResetQuiz();
setQuizState(tickQuizState);
}, checkDuration / 2);
setTimeout(() => {
setShowCheck(false);
@ -75,11 +75,15 @@ function VPExplorerQuiz(props: {
});
}
}
const rendered = renderVP(vps);
const rendered = renderVP(quizState.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" }}>
function handleRestart() {
setQuizState(tickQuizState(quizState.vps));
}
return <div className="mt-4">
<ProgressBar quizState={quizState} />
<div className="d-flex flex-row justify-content-around flex-wrap">
<div className="my-2">
<div className="h5 text-center">Subject</div>
<QuizNPDisplay>{subject}</QuizNPDisplay>
@ -90,44 +94,86 @@ function VPExplorerQuiz(props: {
</div>}
<div className="my-2">
<TensePicker
vps={vps}
vps={quizState.vps}
onChange={() => null}
mode={"quiz"}
/>
</div>
</div>
{e && <div className="text-center text-muted text-small">
{e && <div className="text-center text-muted">
{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>
{quizState.qNumber === stageLength ?
<div className="mt-4" style={{ animation: "fade-in 0.5s" }}>
<h4>👏 Congratulations</h4>
<p className="lead">You finished the first level!</p>
<p>The <strong>other levels are still in development</strong>... In the meantime have an energy drink.</p>
<div className="mb-4">
<img src={energyDrink} alt="energy-dring" className="img-fluid" />
</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 type="button" className="btn btn-primary" onClick={handleRestart}>
Restart
</button>
</div>
</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} style={{ animation: "fade-in 0.5s" }}>
<button className="btn btn-answer btn-outline-secondary" onClick={() => {
checkQuizAnswer(o);
}}>
<InlinePs opts={props.opts}>{o}</InlinePs>
</button>
</div>)}
</>
: <div style={{ animation: "fade-in 0.5s" }}>
<div className="h4 mt-4"> Wrong 😭</div>
<div className="my-4 lead">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={handleRestart}>
Try Again
</button>
</div>
</div>)
}
<Keyframes name="fade-in" from={{ opacity: 0 }} to={{ opacity: 1 }} />
</div>
</div>;
}
function ProgressBar({ quizState }: { quizState: QuizState }) {
function getProgressWidth(): string {
const num = getPercentageDone({ current: quizState.qNumber, total: stageLength });
return `${num}%`;
}
return <div className="mb-3">
<div className="progress mb-1" style={{ height: "3px" }}>
<div
className={`progress-bar bg-${quizState.result === "fail" ? "danger" : "primary"}`}
role="progressbar"
style={{ width: getProgressWidth() }}
/>
</div>
<div>
Level 1: Multiple Choice
</div>
</div>;
}
function getPercentageDone(progress: { current: number, total: number }): number {
return Math.round(
(progress.current / (progress.total + 1)) * 100
);
}
function QuizNPDisplay({ children }: { children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale }) {
return <div className="mb-3">
{(typeof children === "number")
@ -136,14 +182,21 @@ function QuizNPDisplay({ children }: { children: T.Rendered<T.NPSelection> | T.P
</div>;
}
function makeQuizState(oldVps: T.VPSelection): { VPS: T.VPSelectionComplete, qs: QuizState } {
/**
* creates a fresh QuizState when a VPSelection is passed
* advances a QuizState when a QuizState is passed
*
* @param startingWith
* @returns
*/
function tickQuizState(startingWith: T.VPSelection | 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 vps = getRandomVPSelection("both")({ ...oldVps, verb: { ...oldVps.verb, negative: false }});
const wrongStates: T.VPSelectionComplete[] = [];
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(() => {
@ -152,23 +205,24 @@ function makeQuizState(oldVps: T.VPSelection): { VPS: T.VPSelectionComplete, qs:
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,
SOSwitch ? switchSubjObj(newVps) : newVps,
);
// eslint-disable-next-line
} while (wrongStates.find(x => x.verb.tense === v.verb.tense));
wrongStates.push(v);
} while (wrongVpsS.find(x => x.verb.tense === v.verb.tense));
wrongVpsS.push(v);
});
const answer = makeRes(vps);
const wrongAnswers = wrongStates.map(makeRes);
const answer = makeRes(newVps);
const wrongAnswers = wrongVpsS.map(makeRes);
const allAnswers = shuffleArray([...wrongAnswers, answer]);
const options = allAnswers.map(getOptionFromResult);
const qNumber = ("stage" in startingWith) ? (startingWith.qNumber + 1) : 0;
return {
VPS: vps,
qs: {
answer,
options,
result: "waiting",
},
stage: "multiple choice",
qNumber,
vps: newVps,
answer,
options,
result: "waiting",
};
}
@ -224,8 +278,14 @@ function getRandomVPSelection(mix: MixType = "both") {
distance: "far",
person: obj,
};
const s = randSubj;
// ensure that the verb selection is complete
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
// @ts-ignore
verb: randomizeTense(verb, true),
}
}
const v: T.VerbSelectionComplete = {
...verb,
object: (
@ -236,14 +296,8 @@ function getRandomVPSelection(mix: MixType = "both") {
? randObj
: verb.object,
};
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(v, true),
}
}
return {
subject: s,
subject: randSubj,
verb: randomizeTense(v, true),
};
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB