diff --git a/src/components/vp-explorer/VPDisplay.tsx b/src/components/vp-explorer/VPDisplay.tsx index ba6b425..fa57878 100644 --- a/src/components/vp-explorer/VPDisplay.tsx +++ b/src/components/vp-explorer/VPDisplay.tsx @@ -6,7 +6,6 @@ import { isPastTense } from "../../lib/phrase-building/vp-tools"; import { useStickyState } from "../../library"; import { isVPSelectionComplete } from "../../lib/type-predicates"; - function VPDisplay({ VP, opts }: { VP: T.VPSelection | T.VPSelectionComplete, opts: T.TextOptions }) { const [form, setForm] = useStickyState({ removeKing: false, shrinkServant: false }, "abbreviationForm"); const [OSV, setOSV] = useStickyState(false, "includeOSV"); diff --git a/src/components/vp-explorer/VPExplorerQuiz.tsx b/src/components/vp-explorer/VPExplorerQuiz.tsx index e145f45..7631862 100644 --- a/src/components/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/vp-explorer/VPExplorerQuiz.tsx @@ -1,19 +1,23 @@ import { CSSProperties, useState } from "react"; import * as T from "../../types"; 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 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 { removeBa, 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.jpg"; +import { flattenLengths } from "../../lib/phrase-building/compile-vp"; +import { concatPsString } from "../../lib/p-text-helpers"; -const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", "🕺", "💃", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"]; +const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"]; const answerFeedback: CSSProperties = { "fontSize": "4rem", @@ -27,17 +31,24 @@ const answerFeedback: CSSProperties = { } const checkDuration = 400; -const stageLength = 7; +const stageLength = 6; -type QuizState = { +type QuizState = ({ stage: "multiple choice", - qNumber: number, - vps: T.VPSelectionComplete, 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", } type MixType = "NPs" | "tenses" | "both"; @@ -49,10 +60,18 @@ function VPExplorerQuiz(props: { const startingQs = tickQuizState(props.vps); const [quizState, setQuizState] = useState(startingQs); const [showCheck, setShowCheck] = useState(false); + const [answerBlank, setAnswerBlank] = useState(""); + const [withBa, setWithBa] = useState(false); const [currentCorrectEmoji, setCurrentCorrectEmoji] = useState(randFromArray(correctEmoji)); - function checkQuizAnswer(a: T.PsString) { + function checkAnswer(a: T.PsString | { text: string, withBa: boolean }) { 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]); if (toPlay) playAudio(`correct-${randFromArray([1,2,3])}`); setShowCheck(true); @@ -79,6 +98,8 @@ function VPExplorerQuiz(props: { const { subject, object } = rendered; const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false }); function handleRestart() { + setWithBa(false); + setAnswerBlank(""); setQuizState(tickQuizState(quizState.vps)); } return
@@ -86,11 +107,11 @@ function VPExplorerQuiz(props: {
Subject
- {subject} + {subject}
{(object !== "none") &&
Object
- {object} + {object}
}

👏 Congratulations

-

You finished the first level!

+

You finished the first two levels!

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

energy-dring @@ -120,27 +141,72 @@ function VPExplorerQuiz(props: {
: (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:
+
{ + e.preventDefault(); + checkAnswer({ text: answerBlank, withBa: false }); + }}> +
+ setAnswerBlank(e.target.value)} + /> +
+
+ setWithBa(e.target.checked)} + /> + +
+ +
+
) :
❌ Wrong 😭
-
The correct answer was:
- - {quizState.options.find(x => isInAnswer(x, quizState.answer)) as T.PsString} - -
- -
+ {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 +
+
+ } +
) } @@ -148,7 +214,22 @@ function VPExplorerQuiz(props: {
; } +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}%`; @@ -163,22 +244,27 @@ function ProgressBar({ quizState }: { quizState: QuizState }) {
- Level 1: Multiple Choice + {quizState.stage === "multiple choice" + ? "Level 1: Multiple Choice" + : "Level 2: Type the Verb"}
; } -function getPercentageDone(progress: { current: number, total: number }): number { - return Math.round( - (progress.current / (progress.total + 1)) * 100 - ); -} - -function QuizNPDisplay({ children }: { children: T.Rendered | T.Person.ThirdPlurMale }) { +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.
- :
{children.e}
} + :
+ {stage === "blanks" &&
+ {children.ps[0]} +
} +
{children.e}
+
}
; } @@ -211,14 +297,28 @@ function tickQuizState(startingWith: T.VPSelection | QuizState): QuizState { } 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 qNumber = ("stage" in startingWith) ? (startingWith.qNumber + 1) : 0; return { - stage: "multiple choice", - qNumber, + stage, + qNumber: beatFirstStage ? 0 : qNumber, vps: newVps, answer, 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: { ps: T.SingleOrLengthOpts; e?: string[] | undefined; diff --git a/src/lib/phrase-building/compile-vp.ts b/src/lib/phrase-building/compile-vp.ts index 7b34e9c..94f90b3 100644 --- a/src/lib/phrase-building/compile-vp.ts +++ b/src/lib/phrase-building/compile-vp.ts @@ -362,7 +362,7 @@ function combineSegments(loe: (Segment | " " | "" | T.PsString)[]): T.PsString[] ); } -function flattenLengths(r: T.SingleOrLengthOpts): T.PsString[] { +export function flattenLengths(r: T.SingleOrLengthOpts): T.PsString[] { if ("long" in r) { return Object.values(r).flat(); }