wohoo also blanks working I THINK

This commit is contained in:
lingdocs 2022-04-13 17:50:27 +05:00
parent 94e0b8fd54
commit da7c90d5cc
3 changed files with 155 additions and 40 deletions

View File

@ -6,7 +6,6 @@ import { isPastTense } from "../../lib/phrase-building/vp-tools";
import { useStickyState } from "../../library"; import { useStickyState } from "../../library";
import { isVPSelectionComplete } from "../../lib/type-predicates"; import { isVPSelectionComplete } from "../../lib/type-predicates";
function VPDisplay({ VP, opts }: { VP: T.VPSelection | 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");

View File

@ -1,19 +1,23 @@
import { CSSProperties, useState } from "react"; import { CSSProperties, useState } from "react";
import * as T from "../../types"; import * as T from "../../types";
import { randFromArray } from "../../lib/misc-helpers"; import { randFromArray } from "../../lib/misc-helpers";
import { randomSubjObj } from "../../library"; import { baParticle } from "../../lib/grammar-units";
import { randomSubjObj } from "../../lib/np-tools";
import { standardizePashto } from "../../lib/standardize-pashto";
import shuffleArray from "../../lib/shuffle-array"; import shuffleArray from "../../lib/shuffle-array";
import InlinePs from "../InlinePs"; import InlinePs from "../InlinePs";
import { psStringEquals } from "../../lib/p-text-helpers"; import { psStringEquals } from "../../lib/p-text-helpers";
import { renderVP, compileVP } from "../../lib/phrase-building/index"; import { renderVP, compileVP } from "../../lib/phrase-building/index";
import { getRandomTense } from "./TensePicker"; import { getRandomTense } from "./TensePicker";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools"; import { removeBa, switchSubjObj } from "../../lib/phrase-building/vp-tools";
import playAudio from "../../lib/play-audio"; import playAudio from "../../lib/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 energyDrink from "./energy-drink.jpg";
import { flattenLengths } from "../../lib/phrase-building/compile-vp";
import { concatPsString } from "../../lib/p-text-helpers";
const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", "🕺", "💃", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"]; const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"];
const answerFeedback: CSSProperties = { const answerFeedback: CSSProperties = {
"fontSize": "4rem", "fontSize": "4rem",
@ -27,17 +31,24 @@ const answerFeedback: CSSProperties = {
} }
const checkDuration = 400; const checkDuration = 400;
const stageLength = 7; const stageLength = 6;
type QuizState = { type QuizState = ({
stage: "multiple choice", stage: "multiple choice",
qNumber: number,
vps: T.VPSelectionComplete,
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", result: "waiting" | "fail",
} }
type MixType = "NPs" | "tenses" | "both"; type MixType = "NPs" | "tenses" | "both";
@ -49,10 +60,18 @@ function VPExplorerQuiz(props: {
const startingQs = tickQuizState(props.vps); const startingQs = tickQuizState(props.vps);
const [quizState, setQuizState] = useState<QuizState>(startingQs); const [quizState, setQuizState] = useState<QuizState>(startingQs);
const [showCheck, setShowCheck] = useState<boolean>(false); const [showCheck, setShowCheck] = useState<boolean>(false);
const [answerBlank, setAnswerBlank] = useState<string>("");
const [withBa, setWithBa] = useState<boolean>(false);
const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(randFromArray(correctEmoji)); const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState<string>(randFromArray(correctEmoji));
function checkQuizAnswer(a: T.PsString) { function checkAnswer(a: T.PsString | { text: string, withBa: boolean }) {
if (!quizState) return; if (!quizState) return;
if (isInAnswer(a, quizState.answer)) { 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]); 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);
@ -79,6 +98,8 @@ function VPExplorerQuiz(props: {
const { subject, object } = rendered; const { subject, object } = rendered;
const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false }); const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false });
function handleRestart() { function handleRestart() {
setWithBa(false);
setAnswerBlank("");
setQuizState(tickQuizState(quizState.vps)); setQuizState(tickQuizState(quizState.vps));
} }
return <div className="mt-4"> return <div className="mt-4">
@ -86,11 +107,11 @@ function VPExplorerQuiz(props: {
<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>{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>{object}</QuizNPDisplay> <QuizNPDisplay opts={props.opts} stage={quizState.stage}>{object}</QuizNPDisplay>
</div>} </div>}
<div className="my-2"> <div className="my-2">
<TensePicker <TensePicker
@ -110,7 +131,7 @@ function VPExplorerQuiz(props: {
{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 level!</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>The <strong>other levels are still in development</strong>... In the meantime have an energy drink.</p>
<div className="mb-4"> <div className="mb-4">
<img src={energyDrink} alt="energy-dring" className="img-fluid" /> <img src={energyDrink} alt="energy-dring" className="img-fluid" />
@ -120,27 +141,72 @@ function VPExplorerQuiz(props: {
</button> </button>
</div> </div>
: (quizState.result === "waiting" : (quizState.result === "waiting"
? <> ? (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 className="btn btn-answer btn-outline-secondary" onClick={() => { <button
checkQuizAnswer(o); className="btn btn-answer btn-outline-secondary"
}}> 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 => {
e.preventDefault();
checkAnswer({ text: answerBlank, withBa: false });
}}>
<div className="mb-3" style={{ maxWidth: "250px", margin: "0 auto"}}>
<input
type="text"
dir="auto"
className="form-control"
placeholder="type verb here"
value={answerBlank}
onChange={e => setAnswerBlank(e.target.value)}
/>
</div>
<div className="form-check mb-4" style={{ fontSize: "large" }}>
<input
className="form-check-input"
type="checkbox"
checked={withBa}
onChange={e => setWithBa(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="OSVCheckbox">
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in phrase
</label>
</div>
<button type="submit" className="btn btn-primary">
Check
</button>
</form>
</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" ?
<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 className="my-4"> </div>
<button type="button" className="btn btn-primary" onClick={handleRestart}> :
<div>
<div className="my-4 lead">Possible correct answers were:</div>
{quizState.answer.ps.map((p, i) => <div key={i}>
<InlinePs opts={props.opts}>{p}</InlinePs>
</div>)}
<div className="mt-2">
<strong>{("withBa" in quizState.answer && quizState.answer.withBa) ? "With" : "without"}</strong>
{` `}
a <InlinePs opts={props.opts}>{baParticle}</InlinePs> in the phrase
</div>
</div>
}
<button type="button" className="btn btn-primary mt-4" onClick={handleRestart}>
Try Again Try Again
</button> </button>
</div>
</div>) </div>)
} }
<Keyframes name="fade-in" from={{ opacity: 0 }} to={{ opacity: 1 }} /> <Keyframes name="fade-in" from={{ opacity: 0 }} to={{ opacity: 1 }} />
@ -148,7 +214,22 @@ function VPExplorerQuiz(props: {
</div>; </div>;
} }
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 ProgressBar({ quizState }: { quizState: QuizState }) {
function getPercentageDone({ current, 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}%`;
@ -163,22 +244,27 @@ function ProgressBar({ quizState }: { quizState: QuizState }) {
</div> </div>
<div> <div>
Level 1: Multiple Choice {quizState.stage === "multiple choice"
? "Level 1: Multiple Choice"
: "Level 2: Type the Verb"}
</div> </div>
</div>; </div>;
} }
function getPercentageDone(progress: { current: number, total: number }): number { function QuizNPDisplay({ children, stage, opts }: {
return Math.round( stage: "blanks" | "multiple choice",
(progress.current / (progress.total + 1)) * 100 children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale,
); opts: T.TextOptions,
} }) {
function QuizNPDisplay({ children }: { children: T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale }) {
return <div className="mb-3"> return <div className="mb-3">
{(typeof children === "number") {(typeof children === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div> ? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <div style={{ fontSize: "larger" }}>{children.e}</div>} : <div className="text-centered" style={{ fontSize: "large" }}>
{stage === "blanks" && <div>
<InlinePs opts={opts}>{children.ps[0]}</InlinePs>
</div>}
<div>{children.e}</div>
</div>}
</div>; </div>;
} }
@ -211,14 +297,28 @@ function tickQuizState(startingWith: T.VPSelection | QuizState): QuizState {
} 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 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 answer = makeRes(newVps);
const wrongAnswers = wrongVpsS.map(makeRes); const wrongAnswers = wrongVpsS.map(makeRes);
const allAnswers = shuffleArray([...wrongAnswers, answer]); const allAnswers = shuffleArray([...wrongAnswers, answer]);
const options = allAnswers.map(getOptionFromResult); const options = allAnswers.map(getOptionFromResult);
const qNumber = ("stage" in startingWith) ? (startingWith.qNumber + 1) : 0;
return { return {
stage: "multiple choice", stage,
qNumber, qNumber: beatFirstStage ? 0 : qNumber,
vps: newVps, vps: newVps,
answer, answer,
options, options,
@ -226,6 +326,22 @@ function tickQuizState(startingWith: T.VPSelection | QuizState): QuizState {
}; };
} }
function getBlanksAnswer(vps: T.VPSelectionComplete): { ps: T.PsString[], withBa: boolean } {
const { verb } = renderVP(vps);
const { head, rest } = verb.ps;
const ps = flattenLengths(rest).map(x => {
const y = removeBa(x);
if (head) {
return concatPsString(head, y);
}
return y;
});
return {
ps,
withBa: verb.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;

View File

@ -362,7 +362,7 @@ function combineSegments(loe: (Segment | " " | "" | T.PsString)[]): T.PsString[]
); );
} }
function flattenLengths(r: T.SingleOrLengthOpts<T.PsString[]>): T.PsString[] { export function flattenLengths(r: T.SingleOrLengthOpts<T.PsString[]>): T.PsString[] {
if ("long" in r) { if ("long" in r) {
return Object.values(r).flat(); return Object.values(r).flat();
} }