nice good refactoring to make the practice and test modes work properly
This commit is contained in:
parent
5d8888634f
commit
07ea0a286a
|
@ -17,6 +17,7 @@
|
|||
"bootstrap": "4.5.3",
|
||||
"classnames": "^2.3.1",
|
||||
"cron": "^1.8.2",
|
||||
"froebel": "^0.21.3",
|
||||
"lokijs": "^1.5.12",
|
||||
"markdown-to-jsx": "^7.1.3",
|
||||
"react": "^17.0.2",
|
||||
|
|
|
@ -23,33 +23,187 @@ import ReactGA from "react-ga";
|
|||
import { isProd } from "../lib/isProd";
|
||||
import autoAnimate from "@formkit/auto-animate";
|
||||
const errorVibration = 200;
|
||||
const strikesToFail = 3;
|
||||
|
||||
const maxStrikes = 2;
|
||||
type GameState<Question> = ({
|
||||
mode: "practice",
|
||||
showAnswer: boolean,
|
||||
} | {
|
||||
mode: "intro" | "test" | "fail" | "timeout" | "complete",
|
||||
showAnswer: false,
|
||||
}) & {
|
||||
numberComplete: number,
|
||||
current: Question,
|
||||
timerKey: number,
|
||||
strikes: number,
|
||||
justStruck: boolean,
|
||||
}
|
||||
|
||||
function GameCore<T>({ inChapter, questions, Display, timeLimit, Instructions, studyLink, id }:{
|
||||
type GameReducerAction = {
|
||||
type: "handle question response",
|
||||
payload: { correct: boolean },
|
||||
} | {
|
||||
type: "start",
|
||||
payload: "practice" | "test",
|
||||
} | {
|
||||
type: "quit",
|
||||
} | {
|
||||
type: "timeout",
|
||||
} | {
|
||||
type: "show answer",
|
||||
} | {
|
||||
type: "skip",
|
||||
}
|
||||
|
||||
function GameCore<Question>({ inChapter, getQuestion, amount, Display, DisplayCorrectAnswer, timeLimit, Instructions, studyLink, id }: {
|
||||
inChapter: boolean,
|
||||
id: string,
|
||||
studyLink: string,
|
||||
Instructions: (props: { opts?: Types.TextOptions }) => JSX.Element,
|
||||
questions: () => QuestionGenerator<T>,
|
||||
Display: (props: QuestionDisplayProps<T>) => JSX.Element,
|
||||
timeLimit: number;
|
||||
getQuestion: () => Question,
|
||||
DisplayCorrectAnswer: (props: { question: Question }) => JSX.Element,
|
||||
Display: (props: QuestionDisplayProps<Question>) => JSX.Element,
|
||||
timeLimit: number,
|
||||
amount: number,
|
||||
}) {
|
||||
const initialState: GameState<Question> = {
|
||||
mode: "intro",
|
||||
numberComplete: 0,
|
||||
current: getQuestion(),
|
||||
timerKey: 0,
|
||||
strikes: 0,
|
||||
justStruck: false,
|
||||
showAnswer: false,
|
||||
};
|
||||
// TODO: report pass with id to user info
|
||||
const rewardRef = useRef<RewardElement | null>(null);
|
||||
const parent = useRef<HTMLDivElement | null>(null);
|
||||
const { user, pullUser, setUser } = useUser();
|
||||
const [mode, setMode] = useState<"practice" | "test">("test");
|
||||
const [finish, setFinish] = useState<undefined | "pass" | { msg: "fail", answer: JSX.Element } | "time out">(undefined);
|
||||
const [strikes, setStrikes] = useState<number>(0);
|
||||
const [justStruck, setJustStruck] = useState<boolean>(false);
|
||||
const [current, setCurrent] = useState<Current<T> | undefined>(undefined);
|
||||
const [questionBox, setQuestionBox] = useState<QuestionGenerator<T>>(questions());
|
||||
const [timerKey, setTimerKey] = useState<number>(1);
|
||||
const [state, setStateDangerous] = useState<GameState<Question>>(initialState);
|
||||
useEffect(() => {
|
||||
parent.current && autoAnimate(parent.current)
|
||||
}, [parent]);
|
||||
|
||||
const gameReducer = (gs: GameState<Question>, action: GameReducerAction): GameState<Question> => {
|
||||
if (action.type === "handle question response") {
|
||||
if (gs.mode === "test") {
|
||||
if (action.payload.correct) {
|
||||
const numberComplete = gs.numberComplete + 1;
|
||||
if (numberComplete === amount) {
|
||||
logGameEvent("passed");
|
||||
rewardRef.current?.rewardMe();
|
||||
handleResult(true);
|
||||
return {
|
||||
...gs,
|
||||
numberComplete,
|
||||
justStruck: false,
|
||||
mode: "complete",
|
||||
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
...gs,
|
||||
numberComplete,
|
||||
current: getQuestion(),
|
||||
justStruck: false,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
punish();
|
||||
const strikes = gs.strikes + 1;
|
||||
if (strikes === strikesToFail) {
|
||||
logGameEvent("fail");
|
||||
handleResult(false);
|
||||
return {
|
||||
...gs,
|
||||
strikes,
|
||||
mode: "fail",
|
||||
justStruck: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...gs,
|
||||
strikes,
|
||||
justStruck: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
else /* (gs.mode === "practice") */ {
|
||||
if (action.payload.correct) {
|
||||
const numberComplete = gs.numberComplete + 1;
|
||||
return {
|
||||
...gs,
|
||||
numberComplete,
|
||||
current: getQuestion(),
|
||||
justStruck: false,
|
||||
showAnswer: false,
|
||||
};
|
||||
} else {
|
||||
punish();
|
||||
const strikes = gs.strikes + 1;
|
||||
return {
|
||||
...gs,
|
||||
strikes,
|
||||
justStruck: true,
|
||||
showAnswer: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.type === "start") {
|
||||
logGameEvent(`started ${action.payload}`);
|
||||
return {
|
||||
...initialState,
|
||||
mode: action.payload,
|
||||
current: getQuestion(),
|
||||
timerKey: gs.timerKey + 1,
|
||||
}
|
||||
}
|
||||
if (action.type === "quit") {
|
||||
return {
|
||||
...initialState,
|
||||
timerKey: gs.timerKey + 1,
|
||||
}
|
||||
}
|
||||
if (action.type === "timeout") {
|
||||
logGameEvent("timeout");
|
||||
handleResult(false);
|
||||
return {
|
||||
...gs,
|
||||
mode: "timeout",
|
||||
justStruck: false,
|
||||
showAnswer: false,
|
||||
};
|
||||
}
|
||||
if (action.type === "show answer") {
|
||||
if (gs.mode === "practice" && gs.justStruck) {
|
||||
return {
|
||||
...gs,
|
||||
justStruck: false,
|
||||
showAnswer: true,
|
||||
};
|
||||
}
|
||||
return gs;
|
||||
}
|
||||
if (action.type === "skip") {
|
||||
if (gs.mode === "practice") {
|
||||
return {
|
||||
...gs,
|
||||
current: getQuestion(),
|
||||
justStruck: false,
|
||||
showAnswer: false,
|
||||
};
|
||||
}
|
||||
return gs;
|
||||
}
|
||||
throw new Error("unknown GameReducerAction");
|
||||
}
|
||||
|
||||
function dispatch(action: GameReducerAction) {
|
||||
setStateDangerous(gs => gameReducer(gs, action));
|
||||
}
|
||||
|
||||
function logGameEvent(action: string) {
|
||||
if (isProd && !(user?.admin)) {
|
||||
ReactGA.event({
|
||||
|
@ -59,35 +213,15 @@ function GameCore<T>({ inChapter, questions, Display, timeLimit, Instructions, s
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCallback(correct: true | JSX.Element) {
|
||||
if (correct === true) {
|
||||
handleAdvance();
|
||||
return;
|
||||
}
|
||||
setStrikes(s => s + 1);
|
||||
function punish() {
|
||||
navigator.vibrate(errorVibration);
|
||||
if (strikes < maxStrikes) {
|
||||
setJustStruck(true);
|
||||
} else {
|
||||
logGameEvent("fail on game");
|
||||
setJustStruck(false);
|
||||
setFinish({ msg: "fail", answer: correct });
|
||||
const result: AT.TestResult = {
|
||||
done: false,
|
||||
time: getTimestamp(),
|
||||
id,
|
||||
};
|
||||
handleResult(result);
|
||||
}
|
||||
}
|
||||
function handleAdvance() {
|
||||
setJustStruck(false);
|
||||
const next = questionBox.next();
|
||||
if (next.done) handleFinish();
|
||||
else setCurrent(next.value);
|
||||
}
|
||||
function handleResult(result: AT.TestResult) {
|
||||
function handleResult(done: boolean) {
|
||||
const result: AT.TestResult = {
|
||||
done,
|
||||
time: getTimestamp(),
|
||||
id,
|
||||
};
|
||||
// add the test to the user object
|
||||
if (!user) return;
|
||||
setUser((u) => {
|
||||
|
@ -105,127 +239,105 @@ function GameCore<T>({ inChapter, questions, Display, timeLimit, Instructions, s
|
|||
if (r === "sent") pullUser();
|
||||
}).catch(console.error);
|
||||
}
|
||||
function handleFinish() {
|
||||
logGameEvent("passed game")
|
||||
setFinish("pass");
|
||||
rewardRef.current?.rewardMe();
|
||||
if (!user) return;
|
||||
const result: AT.TestResult = {
|
||||
done: true,
|
||||
time: getTimestamp(),
|
||||
id,
|
||||
};
|
||||
handleResult(result);
|
||||
}
|
||||
function handleQuit() {
|
||||
setFinish(undefined);
|
||||
setCurrent(undefined);
|
||||
}
|
||||
function handleRestart(mode: "test" | "practice") {
|
||||
logGameEvent(`started game ${mode}`);
|
||||
setMode(mode);
|
||||
const newQuestionBox = questions();
|
||||
const { value } = newQuestionBox.next();
|
||||
// just for type safety -- the generator will have at least one question
|
||||
if (!value) return;
|
||||
setQuestionBox(newQuestionBox);
|
||||
setJustStruck(false);
|
||||
setStrikes(0);
|
||||
setFinish(undefined);
|
||||
setCurrent(value);
|
||||
setTimerKey(prev => prev + 1);
|
||||
}
|
||||
function handleTimeOut() {
|
||||
logGameEvent("timeout on game");
|
||||
setJustStruck(false);
|
||||
setFinish("time out");
|
||||
navigator.vibrate(errorVibration);
|
||||
const result: AT.TestResult = {
|
||||
done: false,
|
||||
time: getTimestamp(),
|
||||
id,
|
||||
};
|
||||
handleResult(result);
|
||||
}
|
||||
function getProgressWidth(): string {
|
||||
const num = !current
|
||||
const num = !state.current
|
||||
? 0
|
||||
: (finish === "pass")
|
||||
: (state.mode === "complete")
|
||||
? 100
|
||||
: getPercentageDone(current.progress);
|
||||
: getPercentageDone(state.numberComplete, amount);
|
||||
return `${num}%`;
|
||||
}
|
||||
const progressColor = finish === "pass"
|
||||
const progressColor = state.mode === "complete"
|
||||
? "success"
|
||||
: typeof finish === "object"
|
||||
: (state.mode === "fail" || state.mode === "timeout")
|
||||
? "danger"
|
||||
: "primary";
|
||||
const gameRunning = current && finish === undefined;
|
||||
const gameRunning = state.mode === "practice" || state.mode === "test";
|
||||
function ActionButtons() {
|
||||
return <div>
|
||||
{!inChapter && <Link to={studyLink}>
|
||||
<button className="btn btn-danger mt-4 mx-3">Study</button>
|
||||
</Link>}
|
||||
<button className="btn btn-warning mt-4 mx-3" onClick={() => handleRestart("practice")}>Practice</button>
|
||||
<button className="btn btn-success mt-4 mx-3" onClick={() => handleRestart("test")}>Test</button>
|
||||
<button className="btn btn-warning mt-4 mx-3" onClick={() => dispatch({ type: "start", payload: "practice" })}>Practice</button>
|
||||
<button className="btn btn-success mt-4 mx-3" onClick={() => dispatch({ type: "start", payload: "test" })}>Test</button>
|
||||
</div>;
|
||||
}
|
||||
return <>
|
||||
<div className="text-center" style={{ minHeight: "200px", zIndex: 10, position: "relative" }}>
|
||||
{mode === "test" && <div className="progress" style={{ height: "5px" }}>
|
||||
{(state.mode === "test" || state.mode === "intro") && <div className="progress" style={{ height: "5px" }}>
|
||||
<div className={`progress-bar bg-${progressColor}`} role="progressbar" style={{ width: getProgressWidth() }} />
|
||||
</div>}
|
||||
{current && <div className="d-flex flex-row justify-content-between mt-2">
|
||||
<StrikesDisplay strikes={strikes} />
|
||||
<div className="d-flex flex-row-reverse">
|
||||
{mode === "test" && <CountdownCircleTimer
|
||||
key={timerKey}
|
||||
isPlaying={!!current && !finish}
|
||||
<div className="d-flex flex-row justify-content-between mt-2">
|
||||
{state.mode === "test" && <StrikesDisplay strikes={state.strikes} />}
|
||||
{state.mode === "practice" && <PracticeStatusDisplay
|
||||
correct={state.numberComplete}
|
||||
incorrect={state.strikes}
|
||||
/>}
|
||||
<div className="d-flex flex-row justify-content-right">
|
||||
{state.mode === "test" && <CountdownCircleTimer
|
||||
key={state.timerKey}
|
||||
isPlaying={gameRunning}
|
||||
size={30}
|
||||
colors={["#555555", "#F7B801", "#A30000"]}
|
||||
colorsTime={[timeLimit, timeLimit*0.33, 0]}
|
||||
strokeWidth={4}
|
||||
strokeLinecap="square"
|
||||
duration={timeLimit}
|
||||
onComplete={handleTimeOut}
|
||||
onComplete={() => dispatch({ type: "timeout" })}
|
||||
/>}
|
||||
<button onClick={handleQuit} className="btn btn-outline-secondary btn-sm mr-2">Quit</button>
|
||||
{state.mode !== "intro" && <button onClick={() => dispatch({ type: "quit" })} className="btn btn-outline-secondary btn-sm ml-2">
|
||||
Quit
|
||||
</button>}
|
||||
</div>
|
||||
</div>}
|
||||
{mode === "test" && <div ref={parent}>
|
||||
{justStruck && <div className="alert alert-warning my-2" role="alert" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
</div>
|
||||
<div ref={parent}>
|
||||
{state.justStruck && <div className="alert alert-warning my-2" role="alert" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
{getStrikeMessage()}
|
||||
</div>}
|
||||
</div>}
|
||||
</div>
|
||||
<Reward ref={rewardRef} config={{ lifetime: 130, spread: 90, elementCount: 150, zIndex: 999999999 }} type="confetti">
|
||||
<div>
|
||||
{finish === undefined &&
|
||||
(current
|
||||
? <div>
|
||||
<Display question={current.question} callback={handleCallback} />
|
||||
</div>
|
||||
: <div>
|
||||
<div className="pt-3">
|
||||
{/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/}
|
||||
<Instructions />
|
||||
</div>
|
||||
<ActionButtons />
|
||||
</div>)
|
||||
}
|
||||
{finish === "pass" && <div>
|
||||
{state.mode === "intro" && <div>
|
||||
<div className="pt-3">
|
||||
{/* TODO: ADD IN TEXT DISPLAY OPTIONS HERE TOO - WHEN WE START USING THEM*/}
|
||||
<Instructions />
|
||||
</div>
|
||||
<ActionButtons />
|
||||
</div>}
|
||||
{gameRunning && <Display
|
||||
question={state.current}
|
||||
callback={(correct) => dispatch({ type: "handle question response", payload: { correct }})}
|
||||
/>}
|
||||
{(state.mode === "practice" && state.justStruck) && <div className="my-3">
|
||||
<button className="btn btn-sm btn-secondary" onClick={() => dispatch({ type: "show answer" })}>
|
||||
Show Answer
|
||||
</button>
|
||||
</div>}
|
||||
{(state.showAnswer && state.mode === "practice") && <div className="my-2">
|
||||
<div>The correct answer was:</div>
|
||||
<div className="my-1">
|
||||
<DisplayCorrectAnswer question={state.current} />
|
||||
</div>
|
||||
<button className="btn btn-sm btn-primary my-2" onClick={() => dispatch({ type: "skip" })}>
|
||||
Next Question
|
||||
</button>
|
||||
</div>}
|
||||
{state.mode === "complete" && <div>
|
||||
<h4 className="mt-4">
|
||||
<span role="img" aria-label="celebration">🎉</span> Finished!
|
||||
</h4>
|
||||
<button className="btn btn-secondary mt-4" onClick={() => handleRestart("test")}>Try Again</button>
|
||||
<button className="btn btn-secondary mt-4" onClick={() => dispatch({ type: "start", payload: "test" })}>Try Again</button>
|
||||
</div>}
|
||||
{(typeof finish === "object" || finish === "time out") && <div>
|
||||
{mode === "test" && <h4 className="mt-4">{failMessage(current?.progress, finish)}</h4>}
|
||||
{typeof finish === "object" && <div>
|
||||
<div>The correct answer was:</div>
|
||||
<div className="my-2">
|
||||
{finish?.answer}
|
||||
</div>
|
||||
</div>}
|
||||
{(state.mode === "timeout" || state.mode === "fail") && <div className="mb-4">
|
||||
<h4 className="mt-4">{failMessage({
|
||||
numberComplete: state.numberComplete,
|
||||
amount,
|
||||
type: state.mode,
|
||||
})}</h4>
|
||||
<div>The correct answer was:</div>
|
||||
<div className="my-2">
|
||||
<DisplayCorrectAnswer question={state.current} />
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<ActionButtons />
|
||||
</div>
|
||||
|
@ -246,6 +358,13 @@ function GameCore<T>({ inChapter, questions, Display, timeLimit, Instructions, s
|
|||
</>;
|
||||
}
|
||||
|
||||
function PracticeStatusDisplay({ correct, incorrect }: { correct: number, incorrect: number }) {
|
||||
return <div className="d-flex flex-row justify-content-between align-items-center small">
|
||||
<div className="mr-3">✅ <samp>Correct: {correct}</samp></div>
|
||||
<div>❌ <samp>Incorrect: {incorrect}</samp></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function StrikesDisplay({ strikes }: { strikes: number }) {
|
||||
return <div>
|
||||
{[...Array(strikes)].map(_ => <span key={Math.random()} className="mr-2">❌</span>)}
|
||||
|
@ -262,8 +381,12 @@ function getStrikeMessage() {
|
|||
]);
|
||||
}
|
||||
|
||||
function failMessage(progress: Progress | undefined, finish: "time out" | { msg: "fail", answer: JSX.Element }): string {
|
||||
const pDone = progress ? getPercentageDone(progress) : 0;
|
||||
function failMessage({ numberComplete, amount, type }: {
|
||||
numberComplete: number,
|
||||
amount: number,
|
||||
type: "timeout" | "fail",
|
||||
}): string {
|
||||
const pDone = getPercentageDone(numberComplete, amount);
|
||||
const { message, face } = pDone < 20
|
||||
? { message: "No, sorry", face: "😑" }
|
||||
: pDone < 30
|
||||
|
@ -273,7 +396,7 @@ function failMessage(progress: Progress | undefined, finish: "time out" | { msg:
|
|||
: pDone < 78
|
||||
? { message: "You almost got it!", face: "😩" }
|
||||
: { message: "Nooo! So close!", face: "😭" };
|
||||
return typeof finish === "object"
|
||||
return type === "fail"
|
||||
? `${message} ${face}`
|
||||
: `⏳ Time's Up ${face}`;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import EquativeGame from "./sub-cores/EquativeGame";
|
|||
import VerbGame from "./sub-cores/VerbGame";
|
||||
import GenderGame from "./sub-cores/GenderGame";
|
||||
import UnisexNounGame from "./sub-cores/UnisexNounGame";
|
||||
import EquativeSituations from "./sub-cores/EquativeSituations";
|
||||
import EquativeIdentify from "./sub-cores/EquativeIdentify";
|
||||
|
||||
// NOUNS
|
||||
export const nounGenderGame1 = makeGameRecord({
|
||||
|
@ -87,21 +89,21 @@ export const equativeGameAllIdentify = makeGameRecord({
|
|||
title: "Identify the equative (all tenses)",
|
||||
id: "equative-past-summary-identify",
|
||||
link: "/equatives/other-equatives",
|
||||
level: "allIdentify",
|
||||
SubCore: EquativeGame,
|
||||
level: "allTenses",
|
||||
SubCore: EquativeIdentify,
|
||||
});
|
||||
export const equativeGameSituations = makeGameRecord({
|
||||
title: "Choose the right equative for the situation",
|
||||
id: "equative-past-situations",
|
||||
link: "/equatives/other-equatives",
|
||||
level: "situations",
|
||||
SubCore: EquativeGame,
|
||||
SubCore: EquativeSituations,
|
||||
});
|
||||
export const equativeGameAllProduce = makeGameRecord({
|
||||
title: "Write the equative (all tenses)",
|
||||
id: "equative-past-summary-produce",
|
||||
link: "/equatives/other-equatives",
|
||||
level: "allProduce",
|
||||
level: "allTenses",
|
||||
SubCore: EquativeGame,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,173 +1,22 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
comparePs,
|
||||
makeProgress,
|
||||
} from "../../lib/game-utils";
|
||||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
typePredicates as tp,
|
||||
makeNounSelection,
|
||||
randFromArray,
|
||||
renderEP,
|
||||
compileEP,
|
||||
flattenLengths,
|
||||
randomPerson,
|
||||
InlinePs,
|
||||
grammarUnits,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { psStringEquals } from "@lingdocs/pashto-inflector/dist/lib/p-text-helpers";
|
||||
import { randomEPSPool } from "./makeRandomEPS";
|
||||
|
||||
const kidsColor = "#017BFE";
|
||||
|
||||
// @ts-ignore
|
||||
const nouns: T.NounEntry[] = [
|
||||
{"ts":1527815251,"i":7790,"p":"سړی","f":"saRéy","g":"saRey","e":"man","c":"n. m.","ec":"man","ep":"men"},
|
||||
{"ts":1527812797,"i":8605,"p":"ښځه","f":"xúdza","g":"xudza","e":"woman, wife","c":"n. f.","ec":"woman","ep":"women"},
|
||||
{"ts":1527812881,"i":11691,"p":"ماشوم","f":"maashoom","g":"maashoom","e":"child, kid","c":"n. m. anim. unisex","ec":"child","ep":"children"},
|
||||
{"ts":1527815197,"i":2503,"p":"پښتون","f":"puxtoon","g":"puxtoon","e":"Pashtun","c":"n. m. anim. unisex / adj.","infap":"پښتانه","infaf":"puxtaanu","infbp":"پښتن","infbf":"puxtan"},
|
||||
{"ts":1527815737,"i":484,"p":"استاذ","f":"Ustaaz","g":"Ustaaz","e":"teacher, professor, expert, master (in a field)","c":"n. m. anim. unisex anim.","ec":"teacher"},
|
||||
{"ts":1527816747,"i":6418,"p":"ډاکټر","f":"DaakTar","g":"DaakTar","e":"doctor","c":"n. m. anim. unisex"},
|
||||
{"ts":1527812661,"i":13938,"p":"هلک","f":"halík, halúk","g":"halik,haluk","e":"boy, young lad","c":"n. m. anim."},
|
||||
].filter(tp.isNounEntry);
|
||||
|
||||
// @ts-ignore
|
||||
const adjectives: T.AdjectiveEntry[] = [
|
||||
{"ts":1527815306,"i":7582,"p":"ستړی","f":"stúRey","g":"stuRey","e":"tired","c":"adj."},
|
||||
{"ts":1527812625,"i":9116,"p":"غټ","f":"ghuT, ghaT","g":"ghuT,ghaT","e":"big, fat","c":"adj."},
|
||||
{"ts":1527812792,"i":5817,"p":"خوشاله","f":"khoshaala","g":"khoshaala","e":"happy, glad","c":"adj."},
|
||||
{"ts":1527812796,"i":8641,"p":"ښه","f":"xu","g":"xu","e":"good","c":"adj."},
|
||||
{"ts":1527812798,"i":5636,"p":"خفه","f":"khúfa","g":"khufa","e":"sad, upset, angry; choked, suffocated","c":"adj."},
|
||||
{"ts":1527822049,"i":3610,"p":"تکړه","f":"takRá","g":"takRa","e":"strong, energetic, skillful, great, competent","c":"adj."},
|
||||
{"ts":1527815201,"i":2240,"p":"پټ","f":"puT","g":"puT","e":"hidden","c":"adj."},
|
||||
{"ts":1527815381,"i":3402,"p":"تږی","f":"túGey","g":"tugey","e":"thirsty","c":"adj."},
|
||||
{"ts":1527812822,"i":10506,"p":"کوچنی","f":"koochnéy","g":"koochney","e":"little, small; child, little one","c":"adj. / n. m. anim. unisex"},
|
||||
{"ts":1527815451,"i":7243,"p":"زوړ","f":"zoR","g":"zoR","e":"old","c":"adj. irreg.","infap":"زاړه","infaf":"zaaRu","infbp":"زړ","infbf":"zaR"},
|
||||
{"ts":1527812927,"i":12955,"p":"موړ","f":"moR","g":"moR","e":"full, satisfied, sated","c":"adj. irreg.","infap":"ماړه","infaf":"maaRu","infbp":"مړ","infbf":"maR"},
|
||||
].filter(tp.isAdjectiveEntry);
|
||||
|
||||
// @ts-ignore
|
||||
const locAdverbs: T.LocativeAdverbEntry[] = [
|
||||
{"ts":1527812558,"i":6241,"p":"دلته","f":"dălta","g":"dalta","e":"here","c":"loc. adv."},
|
||||
{"ts":1527812449,"i":13937,"p":"هلته","f":"hálta, álta","g":"halta,alta","e":"there","c":"loc. adv."},
|
||||
].filter(tp.isLocativeAdverbEntry);
|
||||
const tenses: T.EquativeTense[] = [
|
||||
"present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen"
|
||||
];
|
||||
|
||||
type Situation = {
|
||||
description: string | JSX.Element,
|
||||
tense: T.EquativeTense[],
|
||||
};
|
||||
const situations: Situation[] = [
|
||||
{
|
||||
description: <>A is B, for sure, right now</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A is <em>probably</em> B, right now</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>A will be B in the future</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>We can assume that A is most likely B</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>You <em>know</em> A is B, currently</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A tends to be B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is usually B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is generally B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is B, right now</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A is always B, as a matter of habit</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: "It's a good thing for A to be B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A needs to be B (out of obligation/necessity)",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You hope that A is B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You desire A to be B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "If A is B ...",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "...so that A will be B (a purpose)",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A was definately B",
|
||||
tense: ["past"],
|
||||
},
|
||||
{
|
||||
description: "A was B",
|
||||
tense: ["past"],
|
||||
},
|
||||
{
|
||||
description: "A was probably B in the past",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "A used to be B (habitually, repeatedly)",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "assume that A would have probably been B",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "under different circumstances, A would have been B",
|
||||
tense: ["wouldBe", "pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You wish A were B (but it's not)",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "If A were B (but it's not)",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "Aaagh! If only A were B!",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A should have been B!",
|
||||
tense: ["pastSubjunctive", "wouldBe"],
|
||||
},
|
||||
];
|
||||
|
||||
const amount = 12;
|
||||
const timeLimit = 100;
|
||||
|
||||
|
@ -175,99 +24,28 @@ type Question = {
|
|||
EPS: T.EPSelectionComplete,
|
||||
phrase: { ps: T.PsString[], e?: string[] },
|
||||
equative: T.EquativeRendered,
|
||||
} | {
|
||||
situation: Situation,
|
||||
};
|
||||
|
||||
const pronounTypes = [
|
||||
[T.Person.FirstSingMale, T.Person.FirstSingFemale],
|
||||
[T.Person.SecondSingMale, T.Person.SecondSingFemale],
|
||||
[T.Person.ThirdSingMale],
|
||||
[T.Person.ThirdSingFemale],
|
||||
[T.Person.FirstPlurMale, T.Person.FirstPlurFemale],
|
||||
[T.Person.SecondPlurMale, T.Person.SecondPlurFemale],
|
||||
[T.Person.ThirdPlurMale, T.Person.ThirdPlurFemale],
|
||||
];
|
||||
|
||||
export default function EquativeGame({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allProduce" | "allIdentify" | "situations" }) {
|
||||
function* questions (): Generator<Current<Question>> {
|
||||
let pool = [...pronounTypes];
|
||||
let situationPool = [...situations];
|
||||
function makeRandPronoun(): T.PronounSelection {
|
||||
let person: T.Person;
|
||||
do {
|
||||
person = randomPerson();
|
||||
// eslint-disable-next-line
|
||||
} while (!pool.some(p => p.includes(person)));
|
||||
pool = pool.filter(p => !p.includes(person));
|
||||
if (pool.length === 0) {
|
||||
pool = pronounTypes;
|
||||
}
|
||||
return {
|
||||
type: "pronoun",
|
||||
distance: "far",
|
||||
person,
|
||||
};
|
||||
}
|
||||
function makeRandomNoun(): T.NounSelection {
|
||||
const n = makeNounSelection(randFromArray(nouns), undefined);
|
||||
return {
|
||||
...n,
|
||||
gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender,
|
||||
number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number,
|
||||
};
|
||||
}
|
||||
function makeRandomEPS(l: T.EquativeTense | "allIdentify" | "allProduce"): T.EPSelectionComplete {
|
||||
const subj: T.NPSelection = {
|
||||
type: "NP",
|
||||
selection: randFromArray([
|
||||
makeRandPronoun,
|
||||
makeRandPronoun,
|
||||
makeRandomNoun,
|
||||
makeRandPronoun,
|
||||
])(),
|
||||
};
|
||||
const pred = randFromArray([...adjectives, ...locAdverbs]);
|
||||
const tense = (l === "allIdentify" || l === "allProduce")
|
||||
? randFromArray(tenses)
|
||||
: l;
|
||||
return makeEPS(subj, pred, tense);
|
||||
}
|
||||
for (let i = 0; i < amount; i++) {
|
||||
if (level === "situations") {
|
||||
const picked = randFromArray(situationPool);
|
||||
situationPool = situationPool.filter(x => picked.description !== x.description);
|
||||
if (situationPool.length === 0) situationPool = [...situations];
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question: {
|
||||
situation: picked,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const EPS = makeRandomEPS(level);
|
||||
const EP = renderEP(EPS);
|
||||
const compiled = compileEP(
|
||||
EP,
|
||||
true,
|
||||
level === "allIdentify" ? undefined : { equative: true, kidsSection: true },
|
||||
);
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: level === "allIdentify" ? undefined : compiled.e,
|
||||
};
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question: {
|
||||
EPS,
|
||||
phrase,
|
||||
equative: getEqFromRendered(EP),
|
||||
},
|
||||
};
|
||||
}
|
||||
export default function EquativeGame({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: T.EquativeTense | "allTenses" }) {
|
||||
const epsPool = randomEPSPool(level);
|
||||
function getQuestion(): Question {
|
||||
const EPS = epsPool();
|
||||
const EP = renderEP(EPS);
|
||||
const compiled = compileEP(
|
||||
EP,
|
||||
true,
|
||||
{ equative: true, kidsSection: true },
|
||||
);
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: compiled.e,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
EPS,
|
||||
phrase,
|
||||
equative: getEqFromRendered(EP),
|
||||
};
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
const [answer, setAnswer] = useState<string>("");
|
||||
|
@ -276,96 +54,30 @@ export default function EquativeGame({ inChapter, id, link, level }: { inChapter
|
|||
setAnswer(value);
|
||||
}
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
if ("situation" in question) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
const correct = comparePs(answer, question.equative.ps)
|
||||
&& (withBa === question.equative.hasBa);
|
||||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(!correct ? makeCorrectAnswer(question) : true);
|
||||
}
|
||||
const handleTenseIdentify = (tense: T.EquativeTense) => {
|
||||
if ("situation" in question) {
|
||||
const wasCorrect = question.situation.tense.includes(tense);
|
||||
if (wasCorrect) {
|
||||
callback(true);
|
||||
} else {
|
||||
callback(makeCorrectAnswer(question));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const renderedWAnswer = renderEP({
|
||||
...question.EPS,
|
||||
equative: {
|
||||
...question.EPS.equative,
|
||||
tense,
|
||||
},
|
||||
});
|
||||
const compiledWAnswer = compileEP(renderedWAnswer, true);
|
||||
const wasCorrect = compiledWAnswer.ps.some(a => (
|
||||
question.phrase.ps.some(b => psStringEquals(a, b))
|
||||
));
|
||||
if (wasCorrect) {
|
||||
return callback(wasCorrect);
|
||||
} else {
|
||||
const possibleCorrect = tenses.filter(tn => {
|
||||
const r = renderEP({
|
||||
...question.EPS,
|
||||
equative: {
|
||||
...question.EPS.equative,
|
||||
tense: tn,
|
||||
},
|
||||
});
|
||||
const c = compileEP(r, true);
|
||||
return c.ps.some(a => (
|
||||
question.phrase.ps.some(b => psStringEquals(a, b))
|
||||
));
|
||||
});
|
||||
callback(<div className="lead">
|
||||
{possibleCorrect.map(humanReadableTense).join(" or ")}
|
||||
</div>)
|
||||
}
|
||||
callback(correct);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (level === "allProduce") setWithBa(false);
|
||||
if (level === "allTenses") setWithBa(false);
|
||||
}, [question]);
|
||||
|
||||
return <div>
|
||||
{(level === "allIdentify" || level === "situations") ?
|
||||
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
{"situation" in question ? <p className="lead">
|
||||
{question.situation.description}
|
||||
</p> : <Examples opts={opts}>
|
||||
{randFromArray(question.phrase.ps)}
|
||||
</Examples>}
|
||||
</div>
|
||||
: !("situation" in question) && <div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<Examples lineHeight={1} opts={opts}>
|
||||
{/* @ts-ignore TODO: REMOVE AS P_INFLE */}
|
||||
{modExs(question.phrase.ps, withBa)[0]}
|
||||
</Examples>
|
||||
{question.phrase.e && question.phrase.e.map((e, i) => (
|
||||
<div key={e+i} className="text-muted">{e}</div>
|
||||
))}
|
||||
<div>{humanReadableTense(question.EPS.equative.tense)} equative</div>
|
||||
</div>
|
||||
}
|
||||
{level === "allIdentify" || "situation" in question ? <div className="text-center">
|
||||
<div className="row">
|
||||
{tenses.map(t => <div className="col" key={Math.random()}>
|
||||
<button
|
||||
style={{ width: "8rem" }}
|
||||
className="btn btn-outline-secondary mb-3"
|
||||
onClick={() => handleTenseIdentify(t)}
|
||||
>
|
||||
{humanReadableTense(t)}
|
||||
</button>
|
||||
</div>)}
|
||||
</div>
|
||||
</div> : <form onSubmit={handleSubmit}>
|
||||
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<Examples lineHeight={1} opts={opts}>
|
||||
{/* @ts-ignore TODO: REMOVE AS P_INFLE */}
|
||||
{modExs(question.phrase.ps, withBa)[0]}
|
||||
</Examples>
|
||||
{question.phrase.e && question.phrase.e.map((e, i) => (
|
||||
<div key={e+i} className="text-muted">{e}</div>
|
||||
))}
|
||||
<div>{humanReadableTense(question.EPS.equative.tense)} equative</div>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-check mt-1">
|
||||
<input
|
||||
id="baCheckbox"
|
||||
|
@ -392,44 +104,39 @@ export default function EquativeGame({ inChapter, id, link, level }: { inChapter
|
|||
</div>
|
||||
<div className="text-center my-2">
|
||||
{/* <div> */}
|
||||
<button className="btn btn-primary" type="submit">return ↵</button>
|
||||
<button className="btn btn-primary" type="submit">submit ↵</button>
|
||||
{/* </div> */}
|
||||
{/* <div className="text-muted small text-center mt-2">
|
||||
Type <kbd>Enter</kbd> to check
|
||||
</div> */}
|
||||
</div>
|
||||
</form>}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return <div>
|
||||
{level === "allIdentify"
|
||||
? <p className="lead">Identify a correct tense for each equative phrase you see</p>
|
||||
: level === "situations"
|
||||
? <p className="lead">Choose the right type of equative for each given situation</p>
|
||||
: <p className="lead">Fill in the blank with the correct <strong>{humanReadableTense(level)} equative</strong> <strong>in Pashto script</strong></p>}
|
||||
{level === "allProduce" && <div>⚠ All tenses included...</div>}
|
||||
</div>
|
||||
<p className="lead">
|
||||
Fill in the blank with the correct {level === "allTenses" ? "" : humanReadableTense(level)} equative
|
||||
</p>
|
||||
{level === "allTenses" && <div>⚠ All tenses included...</div>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
timeLimit={level === "allProduce" ? timeLimit * 1.4 : timeLimit}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={level === "allTenses" ? timeLimit * 1.3 : timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
||||
function makeCorrectAnswer(question: Question): JSX.Element {
|
||||
if ("situation" in question) {
|
||||
return <div>
|
||||
{question.situation.tense.map(humanReadableTense).join(" or ")}
|
||||
</div>;
|
||||
}
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
return <div>
|
||||
<div>
|
||||
{flattenLengths(question.equative.ps).reduce(((accum, curr, i): JSX.Element[] => (
|
||||
|
@ -472,38 +179,7 @@ function humanReadableTense(tense: T.EquativeTense | "allProduce"): string {
|
|||
: tense;
|
||||
}
|
||||
|
||||
function makeEPS(subject: T.NPSelection, predicate: T.AdjectiveEntry | T.LocativeAdverbEntry, tense: T.EquativeTense): T.EPSelectionComplete {
|
||||
return {
|
||||
blocks: [
|
||||
{
|
||||
key: Math.random(),
|
||||
block: {
|
||||
type: "subjectSelection",
|
||||
selection: subject,
|
||||
},
|
||||
},
|
||||
],
|
||||
predicate: {
|
||||
type: "predicateSelection",
|
||||
selection: {
|
||||
type: "complement",
|
||||
selection: tp.isAdjectiveEntry(predicate) ? {
|
||||
type: "adjective",
|
||||
entry: predicate,
|
||||
sandwich: undefined,
|
||||
} : {
|
||||
type: "loc. adv.",
|
||||
entry: predicate,
|
||||
},
|
||||
},
|
||||
},
|
||||
equative: {
|
||||
tense,
|
||||
negative: false,
|
||||
},
|
||||
omitSubject: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function getEqFromRendered(e: T.EPRendered): T.EquativeRendered {
|
||||
const eblock = e.blocks[0].find(x => x.block.type === "equative");
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
Examples,
|
||||
defaultTextOptions as opts,
|
||||
randFromArray,
|
||||
renderEP,
|
||||
compileEP,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { psStringEquals } from "@lingdocs/pashto-inflector/dist/lib/p-text-helpers";
|
||||
import { randomEPSPool } from "./makeRandomEPS";
|
||||
import { useEffect, useState } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
const tenses: T.EquativeTense[] = [
|
||||
"present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen",
|
||||
];
|
||||
|
||||
const amount = 12;
|
||||
const timeLimit = 120;
|
||||
|
||||
type Question = {
|
||||
EPS: T.EPSelectionComplete,
|
||||
phrase: T.PsString,
|
||||
possibleEquatives: T.EquativeTense[],
|
||||
};
|
||||
|
||||
export default function EquativeIdentify({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "allTenses" }) {
|
||||
const epsPool = randomEPSPool("allTenses");
|
||||
function getQuestion(): Question {
|
||||
const EPS = epsPool();
|
||||
const EP = renderEP(EPS);
|
||||
const compiled = compileEP(EP, true);
|
||||
const phrase = randFromArray(compiled.ps);
|
||||
return {
|
||||
EPS,
|
||||
phrase,
|
||||
possibleEquatives: getPossibleEquatives(phrase, EPS),
|
||||
};
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
console.log({ question });
|
||||
const [selected, setSelected] = useState<T.EquativeTense[]>([]);
|
||||
useEffect(() => {
|
||||
setSelected([]);
|
||||
}, [question, setSelected]);
|
||||
function handleTenseClick(t: T.EquativeTense) {
|
||||
setSelected(s => selected.includes(t)
|
||||
? s.filter(x => x !== t)
|
||||
: [...s, t],
|
||||
);
|
||||
}
|
||||
function handleSubmitAnswer() {
|
||||
const correct = (
|
||||
(selected.length === question.possibleEquatives.length)
|
||||
&&
|
||||
(question.possibleEquatives.every(e => selected.includes(e)))
|
||||
);
|
||||
callback(correct);
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<Examples opts={opts}>
|
||||
{question.phrase}
|
||||
</Examples>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="small text-muted mb-2">Select all possible tenses</div>
|
||||
<div className="row">
|
||||
{tenses.map(t => <div className="col" key={Math.random()}>
|
||||
<button
|
||||
style={{ width: "8rem" }}
|
||||
className={classNames(
|
||||
"btn btn-outline-secondary mb-3",
|
||||
{ active: selected.includes(t) },
|
||||
)}
|
||||
onClick={() => handleTenseClick(t)}
|
||||
>
|
||||
{humanReadableTense(t)}
|
||||
</button>
|
||||
</div>)}
|
||||
</div>
|
||||
<button className="btn btn-primary mb-2" onClick={handleSubmitAnswer}>Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return <div>
|
||||
<p className="lead">Identify ALL the possible tenses for each equative phrase you see</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
return <div className="lead">
|
||||
{question.possibleEquatives.map(humanReadableTense).join(" or ")}
|
||||
</div>;
|
||||
}
|
||||
function getPossibleEquatives(ps: T.PsString, eps: T.EPSelectionComplete): T.EquativeTense[] {
|
||||
const possible = tenses.filter(tense => {
|
||||
const rendered = renderEP({
|
||||
...eps,
|
||||
equative: {
|
||||
...eps.equative,
|
||||
tense,
|
||||
},
|
||||
});
|
||||
const compiled = compileEP(rendered, true);
|
||||
return compiled.ps.some(x => psStringEquals(x, ps, false));
|
||||
});
|
||||
if (possible.length === 0) throw new Error("no possible tenses found");
|
||||
return possible;
|
||||
}
|
||||
|
||||
function humanReadableTense(tense: T.EquativeTense | "allProduce"): string {
|
||||
return tense === "allProduce"
|
||||
? ""
|
||||
: tense === "pastSubjunctive"
|
||||
? "past subjunctive"
|
||||
: tense === "wouldBe"
|
||||
? `"would be"`
|
||||
: tense === "wouldHaveBeen"
|
||||
? `"would have been"`
|
||||
: tense;
|
||||
}
|
|
@ -0,0 +1,199 @@
|
|||
import GameCore from "../GameCore";
|
||||
import {
|
||||
Types as T,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { makePool } from "../../lib/pool";
|
||||
|
||||
const tenses: T.EquativeTense[] = [
|
||||
"present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen"
|
||||
];
|
||||
|
||||
type Situation = {
|
||||
description: string | JSX.Element,
|
||||
tense: T.EquativeTense[],
|
||||
};
|
||||
|
||||
const amount = 12;
|
||||
const timeLimit = 100;
|
||||
|
||||
const situations: Situation[] = [
|
||||
{
|
||||
description: <>A is B, for sure, right now</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A is <em>probably</em> B, right now</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>A will be B in the future</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>We can assume that A is most likely B</>,
|
||||
tense: ["future"],
|
||||
},
|
||||
{
|
||||
description: <>You <em>know</em> A is B, currently</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A tends to be B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is usually B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is generally B</>,
|
||||
tense: ["habitual"],
|
||||
},
|
||||
{
|
||||
description: <>A is B, right now</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: <>A is always B, as a matter of habit</>,
|
||||
tense: ["present"],
|
||||
},
|
||||
{
|
||||
description: "It's a good thing for A to be B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A needs to be B (out of obligation/necessity)",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You hope that A is B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You desire A to be B",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "If A is B ...",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "...so that A will be B (a purpose)",
|
||||
tense: ["subjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A was definately B",
|
||||
tense: ["past"],
|
||||
},
|
||||
{
|
||||
description: "A was B",
|
||||
tense: ["past"],
|
||||
},
|
||||
{
|
||||
description: "A was probably B in the past",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "A used to be B (habitually, repeatedly)",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "assume that A would have probably been B",
|
||||
tense: ["wouldBe"],
|
||||
},
|
||||
{
|
||||
description: "under different circumstances, A would have been B",
|
||||
tense: ["wouldBe", "pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "You wish A were B (but it's not)",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "If A were B (but it's not)",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "Aaagh! If only A were B!",
|
||||
tense: ["pastSubjunctive"],
|
||||
},
|
||||
{
|
||||
description: "A should have been B!",
|
||||
tense: ["pastSubjunctive", "wouldBe"],
|
||||
},
|
||||
];
|
||||
|
||||
type Question = Situation;
|
||||
|
||||
|
||||
export default function EquativeSituations({ inChapter, id, link, level }: { inChapter: boolean, id: string, link: string, level: "situations" }) {
|
||||
const situationsPool = makePool(situations);
|
||||
function getQuestion(): Question {
|
||||
return situationsPool();
|
||||
};
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
|
||||
function handleTenseClick(t: T.EquativeTense) {
|
||||
callback(question.tense.includes(t));
|
||||
}
|
||||
return <div>
|
||||
<div className="mb-2" style={{ maxWidth: "300px", margin: "0 auto" }}>
|
||||
<p className="lead">
|
||||
{question.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="row">
|
||||
{tenses.map(t => <div className="col" key={Math.random()}>
|
||||
<button
|
||||
style={{ width: "8rem" }}
|
||||
className="btn btn-outline-secondary mb-3"
|
||||
onClick={() => handleTenseClick(t)}
|
||||
>
|
||||
{humanReadableTense(t)}
|
||||
</button>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function Instructions() {
|
||||
return <p className="lead">Choose a type of equative that works for each given situation</p>;
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
|
||||
// callback(<div className="lead">
|
||||
// {possibleCorrect.map(humanReadableTense).join(" or ")}
|
||||
// </div>)
|
||||
return <div>
|
||||
{question.tense.map(humanReadableTense).join(" or ")}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function humanReadableTense(tense: T.EquativeTense | "allProduce"): string {
|
||||
return tense === "allProduce"
|
||||
? ""
|
||||
: tense === "pastSubjunctive"
|
||||
? "past subjunctive"
|
||||
: tense === "wouldBe"
|
||||
? `"would be"`
|
||||
: tense === "wouldHaveBeen"
|
||||
? `"would have been"`
|
||||
: tense;
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
import {
|
||||
makeProgress,
|
||||
} from "../../lib/game-utils";
|
||||
import genderColors from "../../lib/gender-colors";
|
||||
import GameCore from "../GameCore";
|
||||
import {
|
||||
|
@ -84,7 +81,7 @@ const exceptions: Record<string, CategorySet> = {
|
|||
},
|
||||
};
|
||||
|
||||
const amount = 35;
|
||||
const amount = 30;
|
||||
type Question = T.DictionaryEntry;
|
||||
|
||||
export default function GenderGame({level, id, link, inChapter }: {
|
||||
|
@ -92,38 +89,25 @@ export default function GenderGame({level, id, link, inChapter }: {
|
|||
level: 1 | 2, id: string,
|
||||
link: string,
|
||||
}) {
|
||||
function* questions (): Generator<Current<Question>> {
|
||||
const wordPool = {...types};
|
||||
const exceptionsPool = {...exceptions};
|
||||
console.log(exceptionsPool);
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const base = level === 1
|
||||
? wordPool
|
||||
: randFromArray([wordPool, exceptionsPool]);
|
||||
const gender = randFromArray(genders);
|
||||
let typeToUse: string;
|
||||
do {
|
||||
typeToUse = randFromArray(Object.keys(base[gender]));
|
||||
} while (!base[gender][typeToUse].length);
|
||||
const question = randFromArray(base[gender][typeToUse]);
|
||||
base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts);
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question,
|
||||
};
|
||||
}
|
||||
const wordPool = {...types};
|
||||
const exceptionsPool = {...exceptions};
|
||||
function getQuestion(): Question {
|
||||
const base = level === 1
|
||||
? wordPool
|
||||
: randFromArray([wordPool, exceptionsPool]);
|
||||
const gender = randFromArray(genders);
|
||||
let typeToUse: string;
|
||||
do {
|
||||
typeToUse = randFromArray(Object.keys(base[gender]));
|
||||
} while (!base[gender][typeToUse].length);
|
||||
const question = randFromArray(base[gender][typeToUse]);
|
||||
base[gender][typeToUse] = base[gender][typeToUse].filter((entry) => entry.ts !== question.ts);
|
||||
return question;
|
||||
}
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
function check(gender: T.Gender) {
|
||||
const nounGender: T.Gender = nounNotIn(mascNouns)(question) ? "fem" : "masc";
|
||||
const correct = gender === nounGender;
|
||||
callback(!correct
|
||||
? <div className="my-2 text-center">
|
||||
<button style={{ background: genderColors[nounGender === "masc" ? "m" : "f"], color: "black" }} className="btn btn-lg" disabled>
|
||||
{nounGender === "masc" ? "Masculine" : "Feminine"}
|
||||
</button>
|
||||
</div>
|
||||
: true);
|
||||
callback(gender === nounGender);
|
||||
}
|
||||
return <div>
|
||||
<div className="mb-4" style={{ fontSize: "larger" }}>
|
||||
|
@ -149,12 +133,23 @@ export default function GenderGame({level, id, link, inChapter }: {
|
|||
</div>
|
||||
}
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question}) {
|
||||
const nounGender: T.Gender = nounNotIn(mascNouns)(question) ? "fem" : "masc";
|
||||
return <div className="my-2 text-center">
|
||||
<button style={{ background: genderColors[nounGender === "masc" ? "m" : "f"], color: "black" }} className="btn btn-lg" disabled>
|
||||
{nounGender === "masc" ? "Masculine" : "Feminine"}
|
||||
</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
amount={amount}
|
||||
timeLimit={level === 1 ? 70 : 80}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
makeProgress,
|
||||
comparePs,
|
||||
} from "../../lib/game-utils";
|
||||
import genderColors from "../../lib/gender-colors";
|
||||
|
@ -28,29 +27,24 @@ const amount = 20;
|
|||
type Question = { entry: T.DictionaryEntry, gender: T.Gender };
|
||||
|
||||
export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boolean, id: string, link: string }) {
|
||||
function* questions (): Generator<Current<Question>> {
|
||||
let pool = { ...types };
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const keys = Object.keys(types) as NType[];
|
||||
let type: NType
|
||||
do {
|
||||
type = randFromArray(keys);
|
||||
} while (!pool[type].length);
|
||||
const entry = randFromArray<T.UnisexNounEntry>(
|
||||
// @ts-ignore
|
||||
pool[type]
|
||||
);
|
||||
const gender = randFromArray(genders) as T.Gender;
|
||||
let pool = { ...types };
|
||||
function getQuestion(): Question {
|
||||
const keys = Object.keys(types) as NType[];
|
||||
let type: NType
|
||||
do {
|
||||
type = randFromArray(keys);
|
||||
} while (!pool[type].length);
|
||||
const entry = randFromArray<T.UnisexNounEntry>(
|
||||
// @ts-ignore
|
||||
pool[type] = pool[type].filter((x) => x.ts !== entry.ts);
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question: {
|
||||
entry,
|
||||
gender,
|
||||
},
|
||||
};
|
||||
}
|
||||
pool[type]
|
||||
);
|
||||
const gender = randFromArray(genders) as T.Gender;
|
||||
// @ts-ignore
|
||||
pool[type] = pool[type].filter((x) => x.ts !== entry.ts);
|
||||
return {
|
||||
entry,
|
||||
gender,
|
||||
};
|
||||
}
|
||||
|
||||
function Display({ question, callback }: QuestionDisplayProps<Question>) {
|
||||
|
@ -81,14 +75,7 @@ export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boo
|
|||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(!correct
|
||||
? <div>
|
||||
{correctAnswer.length > 1 && <div className="text-muted">One of the following:</div>}
|
||||
{correctAnswer.map((ps) => (
|
||||
<Examples opts={opts}>{ps}</Examples>
|
||||
))}
|
||||
</div>
|
||||
: true);
|
||||
callback(correct);
|
||||
}
|
||||
|
||||
return <div>
|
||||
|
@ -128,12 +115,28 @@ export default function UnisexNounGame({ id, link, inChapter }: { inChapter: boo
|
|||
</div>
|
||||
}
|
||||
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }) {
|
||||
const infOut = inflectWord(question.entry);
|
||||
if (!infOut) return <div>WORD ERROR</div>;
|
||||
const { inflections } = infOut;
|
||||
// @ts-ignore
|
||||
const correctAnswer = inflections[flipGender(question.gender)][0];
|
||||
return <div>
|
||||
{correctAnswer.length > 1 && <div className="text-muted">One of the following:</div>}
|
||||
{correctAnswer.map((ps: any) => (
|
||||
<Examples opts={opts}>{ps}</Examples>
|
||||
))}
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
amount={amount}
|
||||
timeLimit={130}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
comparePs,
|
||||
makeProgress,
|
||||
} from "../../lib/game-utils";
|
||||
import GameCore from "../GameCore";
|
||||
import {
|
||||
|
@ -95,6 +94,26 @@ type VerbGameLevel = {
|
|||
| "futureVerb" | "imperative" | "intransitivePerfectivePast"
|
||||
| "intransitiveImperfectivePast" | "transitivePerfectivePast" | "transitiveImperfectivePast";
|
||||
}
|
||||
type VerbPoolName = "basic" | "transitivePast" | "intransitivePast";
|
||||
|
||||
function selectVerbPool({ type }: VerbGameLevel): VerbPoolName {
|
||||
return type === "presentVerb"
|
||||
? "basic"
|
||||
: type === "futureVerb"
|
||||
? "basic"
|
||||
: type === "subjunctiveVerb"
|
||||
? "basic"
|
||||
: type === "imperative"
|
||||
? "basic"
|
||||
: type === "intransitiveImperfectivePast"
|
||||
? "intransitivePast"
|
||||
: type === "intransitivePerfectivePast"
|
||||
? "intransitivePast"
|
||||
: type === "transitiveImperfectivePast"
|
||||
? "transitivePast"
|
||||
// : type === "transitivePerfectivePast"
|
||||
: "transitivePast";
|
||||
}
|
||||
|
||||
// TODO: Level where you create the formulas (seperate file)
|
||||
// level where you choose the right situation
|
||||
|
@ -105,93 +124,87 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({ id, link, level, inChapter }: {
|
|||
link: string,
|
||||
level: VerbGameLevel,
|
||||
}) => {
|
||||
function* questions (): Generator<Current<Question>> {
|
||||
const personPool = makePool(level.type === "imperative"
|
||||
? secondPersons
|
||||
: persons
|
||||
);
|
||||
const verbsUsed = level.type.startsWith("intransitive")
|
||||
? intransitivePastVerbs
|
||||
: level.type.startsWith("transitive")
|
||||
? transitivePastVerbs
|
||||
: verbs;
|
||||
const oneVerb = randFromArray(verbsUsed);
|
||||
const verbPool = makePool(verbsUsed, 15);
|
||||
const getVerb = level.level === 1
|
||||
? () => oneVerb
|
||||
: () => verbPool();
|
||||
function makeRandomNoun(): T.NounSelection {
|
||||
const n = makeNounSelection(randFromArray(nouns), undefined);
|
||||
return {
|
||||
...n,
|
||||
gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender,
|
||||
number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number,
|
||||
};
|
||||
}
|
||||
function makeRandomVPS(l: T.VerbTense | T.ImperativeTense): T.VPSelectionComplete {
|
||||
function personToNPSelection(p: T.Person): T.NPSelection {
|
||||
if (isThirdPerson(p)) {
|
||||
return {
|
||||
type: "NP",
|
||||
selection: randFromArray([
|
||||
() => makePronounS(p),
|
||||
makeRandomNoun,
|
||||
() => makePronounS(p),
|
||||
])(),
|
||||
};
|
||||
}
|
||||
const personPool = makePool(level.type === "imperative"
|
||||
? secondPersons
|
||||
: persons
|
||||
);
|
||||
const verbPools: Record<VerbPoolName, () => T.VerbEntry> = {
|
||||
basic: makePool(verbs, 15),
|
||||
transitivePast: makePool(transitivePastVerbs, 15),
|
||||
intransitivePast: makePool(intransitivePastVerbs, 15),
|
||||
};
|
||||
const oneVerb: T.VerbEntry = verbPools[selectVerbPool(level)]();
|
||||
const getVerb = level.level === 1
|
||||
? () => oneVerb
|
||||
: () => verbPools[selectVerbPool(level)]();
|
||||
function makeRandomNoun(): T.NounSelection {
|
||||
const n = makeNounSelection(randFromArray(nouns), undefined);
|
||||
return {
|
||||
...n,
|
||||
gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender,
|
||||
number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number,
|
||||
};
|
||||
}
|
||||
function makeRandomVPS(l: T.VerbTense | T.ImperativeTense): T.VPSelectionComplete {
|
||||
function personToNPSelection(p: T.Person): T.NPSelection {
|
||||
if (isThirdPerson(p)) {
|
||||
return {
|
||||
type: "NP",
|
||||
selection: makePronounS(p),
|
||||
selection: randFromArray([
|
||||
() => makePronounS(p),
|
||||
makeRandomNoun,
|
||||
() => makePronounS(p),
|
||||
])(),
|
||||
};
|
||||
}
|
||||
function makePronounS(p: T.Person): T.PronounSelection {
|
||||
return {
|
||||
type: "pronoun",
|
||||
person: p,
|
||||
distance: randFromArray(["far", "near", "far"]),
|
||||
};
|
||||
}
|
||||
const verb = getVerb();
|
||||
const king = personPool();
|
||||
let servant: T.Person;
|
||||
do {
|
||||
servant = randomPerson();
|
||||
} while (isInvalidSubjObjCombo(king, servant));
|
||||
// const tense = (l === "allIdentify" || l === "allProduce")
|
||||
// ? randFromArray(tenses)
|
||||
// : l;
|
||||
const tense = l;
|
||||
return makeVPS({
|
||||
verb,
|
||||
king: personToNPSelection(king),
|
||||
servant: personToNPSelection(servant),
|
||||
tense,
|
||||
defaultTransitivity: level.type.startsWith("transitive")
|
||||
? "transitive"
|
||||
: "grammatically transitive",
|
||||
});
|
||||
return {
|
||||
type: "NP",
|
||||
selection: makePronounS(p),
|
||||
};
|
||||
}
|
||||
for (let i = 0; i < amount; i++) {
|
||||
const VPS = makeRandomVPS(levelToTense(level));
|
||||
const VP = renderVP(VPS);
|
||||
const compiled = compileVP(
|
||||
VP,
|
||||
{ removeKing: false, shrinkServant: false },
|
||||
true,
|
||||
{ ba: true, verb: true },
|
||||
);
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: compiled.e,
|
||||
};
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question: {
|
||||
rendered: VP,
|
||||
phrase,
|
||||
},
|
||||
function makePronounS(p: T.Person): T.PronounSelection {
|
||||
return {
|
||||
type: "pronoun",
|
||||
person: p,
|
||||
distance: randFromArray(["far", "near", "far"]),
|
||||
};
|
||||
}
|
||||
const verb = getVerb();
|
||||
const king = personPool();
|
||||
let servant: T.Person;
|
||||
do {
|
||||
servant = randomPerson();
|
||||
} while (isInvalidSubjObjCombo(king, servant));
|
||||
// const tense = (l === "allIdentify" || l === "allProduce")
|
||||
// ? randFromArray(tenses)
|
||||
// : l;
|
||||
const tense = l;
|
||||
return makeVPS({
|
||||
verb,
|
||||
king: personToNPSelection(king),
|
||||
servant: personToNPSelection(servant),
|
||||
tense,
|
||||
defaultTransitivity: level.type.startsWith("transitive")
|
||||
? "transitive"
|
||||
: "grammatically transitive",
|
||||
});
|
||||
}
|
||||
function getQuestion(): Question {
|
||||
const VPS = makeRandomVPS(levelToTense(level));
|
||||
const VP = renderVP(VPS);
|
||||
const compiled = compileVP(
|
||||
VP,
|
||||
{ removeKing: false, shrinkServant: false },
|
||||
true,
|
||||
{ ba: true, verb: true },
|
||||
);
|
||||
const phrase = {
|
||||
ps: compiled.ps,
|
||||
e: compiled.e,
|
||||
};
|
||||
return {
|
||||
rendered: VP,
|
||||
phrase,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -208,7 +221,7 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({ id, link, level, inChapter }: {
|
|||
if (correct) {
|
||||
setAnswer("");
|
||||
}
|
||||
callback(!correct ? makeCorrectAnswer(question) : true);
|
||||
callback(correct);
|
||||
}
|
||||
// useEffect(() => {
|
||||
// if (level === "allProduce") setWithBa(false);
|
||||
|
@ -264,10 +277,12 @@ const VerbGame: GameSubCore<VerbGameLevel> = ({ id, link, level, inChapter }: {
|
|||
return <GameCore
|
||||
inChapter={inChapter}
|
||||
studyLink={link}
|
||||
questions={questions}
|
||||
getQuestion={getQuestion}
|
||||
id={id}
|
||||
Display={Display}
|
||||
DisplayCorrectAnswer={DisplayCorrectAnswer}
|
||||
timeLimit={timeLimit}
|
||||
amount={amount}
|
||||
Instructions={Instructions}
|
||||
/>
|
||||
};
|
||||
|
@ -307,7 +322,7 @@ function QuestionDisplay({ question, userAnswer }: {
|
|||
</div>;
|
||||
}
|
||||
|
||||
function makeCorrectAnswer(question: Question): JSX.Element {
|
||||
function DisplayCorrectAnswer({ question }: { question: Question }): JSX.Element {
|
||||
return <div>
|
||||
<div>
|
||||
{getVerbPs(question.rendered).reduce(((accum, curr, i): JSX.Element[] => (
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
import {
|
||||
Types as T,
|
||||
typePredicates as tp,
|
||||
makeNounSelection,
|
||||
randFromArray,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
import { makePool } from "../../lib/pool";
|
||||
|
||||
const pronouns: T.Person[] = [
|
||||
0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 9, 10, 11,
|
||||
];
|
||||
|
||||
const tenses: T.EquativeTense[] = [
|
||||
"present", "habitual", "subjunctive", "future", "past", "wouldBe", "pastSubjunctive", "wouldHaveBeen"
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
const nouns: T.NounEntry[] = [
|
||||
{"ts":1527815251,"i":7790,"p":"سړی","f":"saRéy","g":"saRey","e":"man","c":"n. m.","ec":"man","ep":"men"},
|
||||
{"ts":1527812797,"i":8605,"p":"ښځه","f":"xúdza","g":"xudza","e":"woman, wife","c":"n. f.","ec":"woman","ep":"women"},
|
||||
{"ts":1527812881,"i":11691,"p":"ماشوم","f":"maashoom","g":"maashoom","e":"child, kid","c":"n. m. anim. unisex","ec":"child","ep":"children"},
|
||||
{"ts":1527815197,"i":2503,"p":"پښتون","f":"puxtoon","g":"puxtoon","e":"Pashtun","c":"n. m. anim. unisex / adj.","infap":"پښتانه","infaf":"puxtaanu","infbp":"پښتن","infbf":"puxtan"},
|
||||
{"ts":1527815737,"i":484,"p":"استاذ","f":"Ustaaz","g":"Ustaaz","e":"teacher, professor, expert, master (in a field)","c":"n. m. anim. unisex anim.","ec":"teacher"},
|
||||
{"ts":1527816747,"i":6418,"p":"ډاکټر","f":"DaakTar","g":"DaakTar","e":"doctor","c":"n. m. anim. unisex"},
|
||||
{"ts":1527812661,"i":13938,"p":"هلک","f":"halík, halúk","g":"halik,haluk","e":"boy, young lad","c":"n. m. anim."},
|
||||
].filter(tp.isNounEntry);
|
||||
|
||||
// @ts-ignore
|
||||
const adjectives: T.AdjectiveEntry[] = [
|
||||
{"ts":1527815306,"i":7582,"p":"ستړی","f":"stúRey","g":"stuRey","e":"tired","c":"adj."},
|
||||
{"ts":1527812625,"i":9116,"p":"غټ","f":"ghuT, ghaT","g":"ghuT,ghaT","e":"big, fat","c":"adj."},
|
||||
{"ts":1527812792,"i":5817,"p":"خوشاله","f":"khoshaala","g":"khoshaala","e":"happy, glad","c":"adj."},
|
||||
{"ts":1527812796,"i":8641,"p":"ښه","f":"xu","g":"xu","e":"good","c":"adj."},
|
||||
{"ts":1527812798,"i":5636,"p":"خفه","f":"khúfa","g":"khufa","e":"sad, upset, angry; choked, suffocated","c":"adj."},
|
||||
{"ts":1527822049,"i":3610,"p":"تکړه","f":"takRá","g":"takRa","e":"strong, energetic, skillful, great, competent","c":"adj."},
|
||||
{"ts":1527815201,"i":2240,"p":"پټ","f":"puT","g":"puT","e":"hidden","c":"adj."},
|
||||
{"ts":1527815381,"i":3402,"p":"تږی","f":"túGey","g":"tugey","e":"thirsty","c":"adj."},
|
||||
{"ts":1527812822,"i":10506,"p":"کوچنی","f":"koochnéy","g":"koochney","e":"little, small; child, little one","c":"adj. / n. m. anim. unisex"},
|
||||
{"ts":1527815451,"i":7243,"p":"زوړ","f":"zoR","g":"zoR","e":"old","c":"adj. irreg.","infap":"زاړه","infaf":"zaaRu","infbp":"زړ","infbf":"zaR"},
|
||||
{"ts":1527812927,"i":12955,"p":"موړ","f":"moR","g":"moR","e":"full, satisfied, sated","c":"adj. irreg.","infap":"ماړه","infaf":"maaRu","infbp":"مړ","infbf":"maR"},
|
||||
].filter(tp.isAdjectiveEntry);
|
||||
|
||||
// @ts-ignore
|
||||
const locAdverbs: T.LocativeAdverbEntry[] = [
|
||||
{"ts":1527812558,"i":6241,"p":"دلته","f":"dălta","g":"dalta","e":"here","c":"loc. adv."},
|
||||
{"ts":1527812449,"i":13937,"p":"هلته","f":"hálta, álta","g":"halta,alta","e":"there","c":"loc. adv."},
|
||||
].filter(tp.isLocativeAdverbEntry);
|
||||
|
||||
export function randomEPSPool(l: T.EquativeTense | "allTenses") {
|
||||
const pronounPool = makePool(pronouns);
|
||||
const nounPool = makePool(nouns, 20);
|
||||
const predPool = makePool([...adjectives, ...locAdverbs], 20);
|
||||
const tensePool = makePool(tenses, 15);
|
||||
function makeRandPronoun(): T.PronounSelection {
|
||||
return {
|
||||
type: "pronoun",
|
||||
distance: "far",
|
||||
person: pronounPool(),
|
||||
};
|
||||
}
|
||||
function makeRandomNoun(): T.NounSelection {
|
||||
const n = makeNounSelection(nounPool(), undefined);
|
||||
return {
|
||||
...n,
|
||||
gender: n.genderCanChange ? randFromArray(["masc", "fem"]) : n.gender,
|
||||
number: n.numberCanChange ? randFromArray(["singular", "plural"]) : n.number,
|
||||
};
|
||||
}
|
||||
return function makeRandomEPS(): T.EPSelectionComplete {
|
||||
const subj: T.NPSelection = {
|
||||
type: "NP",
|
||||
selection: randFromArray([
|
||||
makeRandPronoun,
|
||||
makeRandPronoun,
|
||||
makeRandomNoun,
|
||||
makeRandPronoun,
|
||||
])(),
|
||||
};
|
||||
const pred = predPool();
|
||||
const tense = (l === "allTenses")
|
||||
? tensePool()
|
||||
: l;
|
||||
return makeEPS(subj, pred, tense);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function makeEPS(subject: T.NPSelection, predicate: T.AdjectiveEntry | T.LocativeAdverbEntry, tense: T.EquativeTense): T.EPSelectionComplete {
|
||||
return {
|
||||
blocks: [
|
||||
{
|
||||
key: Math.random(),
|
||||
block: {
|
||||
type: "subjectSelection",
|
||||
selection: subject,
|
||||
},
|
||||
},
|
||||
],
|
||||
predicate: {
|
||||
type: "predicateSelection",
|
||||
selection: {
|
||||
type: "complement",
|
||||
selection: tp.isAdjectiveEntry(predicate) ? {
|
||||
type: "adjective",
|
||||
entry: predicate,
|
||||
sandwich: undefined,
|
||||
} : {
|
||||
type: "loc. adv.",
|
||||
entry: predicate,
|
||||
},
|
||||
},
|
||||
},
|
||||
equative: {
|
||||
tense,
|
||||
negative: false,
|
||||
},
|
||||
omitSubject: false,
|
||||
};
|
||||
}
|
|
@ -7,33 +7,12 @@ import {
|
|||
flattenLengths,
|
||||
} from "@lingdocs/pashto-inflector";
|
||||
|
||||
export function makeRandomQs<Q>(
|
||||
amount: number,
|
||||
makeQuestion: () => Q
|
||||
): () => QuestionGenerator<Q> {
|
||||
function makeProgress(i: number, total: number): Progress {
|
||||
return { current: i + 1, total };
|
||||
}
|
||||
return function* () {
|
||||
for (let i = 0; i < amount; i++) {
|
||||
yield {
|
||||
progress: makeProgress(i, amount),
|
||||
question: makeQuestion(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getPercentageDone(progress: Progress): number {
|
||||
export function getPercentageDone(current: number, total: number): number {
|
||||
return Math.round(
|
||||
(progress.current / (progress.total + 1)) * 100
|
||||
(current / (total + 1)) * 100
|
||||
);
|
||||
}
|
||||
|
||||
export function makeProgress(i: number, total: number): Progress {
|
||||
return { current: i + 1, total };
|
||||
}
|
||||
|
||||
/**
|
||||
* Says if an input written in phonetics by the user is correct/the same as a given answer
|
||||
*
|
||||
|
|
|
@ -3,11 +3,6 @@ type Progress = {
|
|||
current: number,
|
||||
};
|
||||
|
||||
type Current<T> = {
|
||||
progress: Progress,
|
||||
question: T,
|
||||
};
|
||||
|
||||
type GameSubCore<T> = (props: {
|
||||
inChapter: boolean,
|
||||
id: string,
|
||||
|
@ -15,11 +10,9 @@ type GameSubCore<T> = (props: {
|
|||
link: string;
|
||||
}) => JSX.Element;
|
||||
|
||||
type QuestionGenerator<T> = Generator<Current<T>, void, unknown>;
|
||||
|
||||
type QuestionDisplayProps<T> = {
|
||||
question: T,
|
||||
callback: (correct: true | JSX.Element) => void,
|
||||
callback: (correct: boolean) => void,
|
||||
};
|
||||
|
||||
type GameRecord = {
|
||||
|
|
|
@ -5897,6 +5897,11 @@ fresh@0.5.2:
|
|||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
|
||||
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
|
||||
|
||||
froebel@^0.21.3:
|
||||
version "0.21.3"
|
||||
resolved "https://registry.yarnpkg.com/froebel/-/froebel-0.21.3.tgz#a780c45ccd6599c3840c0b74c6e3538b6e87a566"
|
||||
integrity sha512-MqyU/nwqUZULHcr7dD0vaSrt+LUEuvgF0aN8LfXbwTJysQF/s3Y0P46SKnOoiVpELftdJXdUbOZ8VReRIOG5DQ==
|
||||
|
||||
from2@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
|
||||
|
|
Loading…
Reference in New Issue