Files
math-quiz/app/(game)/play/PlayClient.tsx
T

154 lines
4.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import type { FormEvent } from "react";
import AnswerForm from "@/components/game/AnswerForm";
import FeedbackPanel from "@/components/game/FeedbackPanel";
import ScoreSummary from "@/components/game/ScoreSummary";
import KatexRenderer from "@/components/math/KatexRenderer";
import { useGameStore } from "@/store/gameStore";
function parseAnswer(value: string) {
const trimmed = value.trim();
if (!/^-?\d+$/.test(trimmed)) return null;
return Number(trimmed);
}
export default function PlayClient() {
const session = useGameStore((state) => state.session);
const initSession = useGameStore((state) => state.initSession);
const submitAnswer = useGameStore((state) => state.submitAnswer);
const nextProblem = useGameStore((state) => state.nextProblem);
const resetSession = useGameStore((state) => state.resetSession);
const [answerA, setAnswerA] = useState("");
const [answerB, setAnswerB] = useState("");
const [inputError, setInputError] = useState("");
const currentProblem = session?.problems[session.currentIndex] ?? null;
const currentResult = session?.results[session.currentIndex] ?? null;
const isComplete = Boolean(
session && session.currentIndex >= session.problems.length,
);
const correctCount =
session?.results.filter((result) => result?.correct).length ?? 0;
const totalCount = session?.problems.length ?? 0;
useEffect(() => {
if (session === null) {
initSession();
}
}, [initSession, session]);
useEffect(() => {
setAnswerA("");
setAnswerB("");
setInputError("");
}, [currentProblem?.key]);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (currentResult || !currentProblem) return;
const parsedA = parseAnswer(answerA);
const parsedB = parseAnswer(answerB);
if (parsedA === null || parsedB === null) {
setInputError("두 칸 모두 정수로 입력하세요.");
return;
}
setInputError("");
submitAnswer({ a: parsedA, b: parsedB });
}
if (!session) {
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 (
<ScoreSummary
correctCount={correctCount}
totalCount={totalCount}
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;
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>
<p className="rounded-md bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm ring-1 ring-slate-200">
{session.currentIndex + 1} / {session.problems.length}
</p>
</div>
<div className="rounded-md bg-white p-6 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>
<AnswerForm
answerA={answerA}
answerB={answerB}
disabled={submitted}
error={inputError}
onAnswerAChange={setAnswerA}
onAnswerBChange={setAnswerB}
onSubmit={handleSubmit}
/>
{currentResult ? (
<FeedbackPanel
result={currentResult}
answerLatex={currentProblem.latexAfter}
isLastProblem={isLastProblem}
onNext={nextProblem}
/>
) : null}
</section>
</main>
);
}