212 lines
6.7 KiB
TypeScript
212 lines
6.7 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import { flushSync } from "react-dom";
|
|
|
|
import StageIndicator from "@/components/game/StageIndicator";
|
|
import VariableChoiceAnswerForm from "@/components/game/VariableChoiceAnswerForm";
|
|
import VariableFeedbackPanel from "@/components/game/VariableFeedbackPanel";
|
|
import VariableScoreSummary from "@/components/game/VariableScoreSummary";
|
|
import KatexRenderer from "@/components/math/KatexRenderer";
|
|
import { formatVariableTermLatex } from "@/lib/engine/variable-parenthesis";
|
|
import {
|
|
useVariableGameStore,
|
|
type VariableProblemCount,
|
|
} from "@/store/variableGameStore";
|
|
|
|
interface VariablePlayClientProps {
|
|
problemCount: VariableProblemCount;
|
|
}
|
|
|
|
export default function VariablePlayClient({
|
|
problemCount,
|
|
}: VariablePlayClientProps) {
|
|
const session = useVariableGameStore((state) => state.session);
|
|
const selectedProblemCount = useVariableGameStore(
|
|
(state) => state.selectedProblemCount,
|
|
);
|
|
const setProblemCount = useVariableGameStore(
|
|
(state) => state.setProblemCount,
|
|
);
|
|
const initSession = useVariableGameStore((state) => state.initSession);
|
|
const submitAnswer = useVariableGameStore((state) => state.submitAnswer);
|
|
const nextProblem = useVariableGameStore((state) => state.nextProblem);
|
|
const resetSession = useVariableGameStore((state) => state.resetSession);
|
|
|
|
const [selectedChoiceIds, setSelectedChoiceIds] = useState<
|
|
[string | null, string | null]
|
|
>([null, null]);
|
|
const [activeTermIndex, setActiveTermIndex] = useState<0 | 1>(0);
|
|
|
|
const sessionProblemCount = session?.problems.length ?? null;
|
|
const currentProblem = session?.problems[session.currentIndex] ?? null;
|
|
const currentResult = session?.results[session.currentIndex] ?? null;
|
|
const isComplete = Boolean(
|
|
session && session.currentIndex >= session.problems.length,
|
|
);
|
|
const sessionNeedsInit = sessionProblemCount !== problemCount;
|
|
const correctCount =
|
|
session?.results.filter((result) => result?.correct).length ?? 0;
|
|
const totalCount = session?.problems.length ?? 0;
|
|
|
|
useEffect(() => {
|
|
if (selectedProblemCount !== problemCount) {
|
|
setProblemCount(problemCount);
|
|
}
|
|
|
|
if (sessionNeedsInit) {
|
|
initSession(problemCount);
|
|
}
|
|
}, [
|
|
initSession,
|
|
problemCount,
|
|
selectedProblemCount,
|
|
sessionProblemCount,
|
|
sessionNeedsInit,
|
|
setProblemCount,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
setSelectedChoiceIds([null, null]);
|
|
setActiveTermIndex(0);
|
|
}, [currentProblem?.key]);
|
|
|
|
function handleSelectChoice(termIndex: 0 | 1, choiceId: string) {
|
|
const nextSelectedChoiceIds: [string | null, string | null] = [
|
|
...selectedChoiceIds,
|
|
];
|
|
nextSelectedChoiceIds[termIndex] = choiceId;
|
|
|
|
setSelectedChoiceIds(nextSelectedChoiceIds);
|
|
|
|
if (termIndex === 0) {
|
|
setActiveTermIndex(1);
|
|
return;
|
|
}
|
|
|
|
if (currentResult || !currentProblem) return;
|
|
if (
|
|
nextSelectedChoiceIds[0] === null ||
|
|
nextSelectedChoiceIds[1] === null
|
|
) {
|
|
return;
|
|
}
|
|
|
|
submitAnswer({ selectedChoiceIds: nextSelectedChoiceIds });
|
|
}
|
|
|
|
function handleNextProblem() {
|
|
flushSync(() => {
|
|
nextProblem();
|
|
});
|
|
}
|
|
|
|
if (!session || sessionNeedsInit) {
|
|
return (
|
|
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
|
<section className="mx-auto w-full max-w-3xl">
|
|
<p className="text-lg font-semibold text-slate-700">
|
|
문제를 준비하고 있습니다.
|
|
</p>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
if (isComplete) {
|
|
return (
|
|
<VariableScoreSummary
|
|
correctCount={correctCount}
|
|
totalCount={totalCount}
|
|
problems={session.problems}
|
|
results={session.results}
|
|
onRestart={resetSession}
|
|
/>
|
|
);
|
|
}
|
|
|
|
if (!currentProblem) {
|
|
return (
|
|
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
|
<section className="mx-auto w-full max-w-3xl">
|
|
<p className="text-lg font-semibold text-slate-700">
|
|
현재 문제를 불러올 수 없습니다.
|
|
</p>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
const submitted = currentResult !== null;
|
|
const isLastProblem = session.currentIndex === session.problems.length - 1;
|
|
const pendingChoiceLabels: [string, string] = [
|
|
formatVariableTermLatex(currentProblem.firstTerm),
|
|
formatVariableTermLatex({
|
|
...currentProblem.secondTerm,
|
|
coefficient:
|
|
currentProblem.operator === "-"
|
|
? -currentProblem.secondTerm.coefficient
|
|
: currentProblem.secondTerm.coefficient,
|
|
}),
|
|
];
|
|
|
|
return (
|
|
<main className="min-h-screen bg-slate-50 px-6 py-10 text-slate-950">
|
|
<section className="mx-auto flex w-full max-w-3xl flex-col gap-8">
|
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
<div className="space-y-2">
|
|
<p className="text-sm font-semibold text-emerald-700">
|
|
미지수 괄호 풀기
|
|
</p>
|
|
<h1 className="text-3xl font-bold tracking-normal sm:text-4xl">
|
|
문제 풀이
|
|
</h1>
|
|
</div>
|
|
<Link
|
|
href="/variable-parentheses"
|
|
onClick={resetSession}
|
|
className="inline-flex h-10 items-center justify-center rounded-md bg-red-700 px-4 text-sm font-semibold text-white transition hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
|
|
>
|
|
포기
|
|
</Link>
|
|
</div>
|
|
|
|
<div className="rounded-md bg-white px-6 pt-6 pb-4 shadow-sm ring-1 ring-slate-200 sm:p-8">
|
|
<p className="mb-4 text-sm font-semibold text-slate-500">
|
|
괄호를 풀어 첫째 항과 둘째 항의 답을 보기에서 고르세요.
|
|
</p>
|
|
<div className="text-3xl font-semibold text-slate-950 sm:text-4xl">
|
|
<KatexRenderer latex={currentProblem.latexBefore} displayMode />
|
|
</div>
|
|
<div className="mt-6">
|
|
<StageIndicator
|
|
currentIndex={session.currentIndex}
|
|
results={session.results}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{!submitted ? (
|
|
<VariableChoiceAnswerForm
|
|
choices={currentProblem.choices}
|
|
selectedChoiceIds={selectedChoiceIds}
|
|
activeTermIndex={activeTermIndex}
|
|
pendingChoiceLabels={pendingChoiceLabels}
|
|
onSelectChoice={handleSelectChoice}
|
|
/>
|
|
) : null}
|
|
|
|
{currentResult ? (
|
|
<VariableFeedbackPanel
|
|
result={currentResult}
|
|
problem={currentProblem}
|
|
isLastProblem={isLastProblem}
|
|
onNext={handleNextProblem}
|
|
/>
|
|
) : null}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|