"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import type { FormEvent } from "react"; import { flushSync } from "react-dom"; import AnswerForm from "@/components/game/AnswerForm"; import type { ActiveAnswerInput, AnswerFormHandle, } from "@/components/game/AnswerForm"; import FeedbackPanel from "@/components/game/FeedbackPanel"; import ScoreSummary from "@/components/game/ScoreSummary"; import StageIndicator from "@/components/game/StageIndicator"; import { DESKTOP_INPUT_DEVICE_QUERY } from "@/lib/input-device"; import KatexRenderer from "@/components/math/KatexRenderer"; import { useGameStore } from "@/store/gameStore"; import type { ProblemCount } from "@/store/gameStore"; const REAL_NUMBER_PATTERN = /^-?\d+(?:\.\d+)?$/; function parseAnswer(value: string) { const trimmed = value.trim(); if (!REAL_NUMBER_PATTERN.test(trimmed)) return null; return Number(trimmed); } interface PlayClientProps { problemCount: ProblemCount; } export default function PlayClient({ problemCount }: PlayClientProps) { const session = useGameStore((state) => state.session); const selectedProblemCount = useGameStore( (state) => state.selectedProblemCount, ); const setProblemCount = useGameStore((state) => state.setProblemCount); 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 problemCardRef = useRef(null); const answerFormRef = useRef(null); const [answerA, setAnswerA] = useState(""); const [answerB, setAnswerB] = useState(""); const [activeInput, setActiveInput] = useState(0); const [inputError, setInputError] = useState(""); 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(() => { setAnswerA(""); setAnswerB(""); setActiveInput(0); setInputError(""); }, [currentProblem?.key]); function handleSubmit(event: FormEvent) { 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 }); } function handleNextProblem() { flushSync(() => { nextProblem(); }); answerFormRef.current?.focusInput(); } const scrollKatexToViewportTop = useCallback(() => { if (window.matchMedia(DESKTOP_INPUT_DEVICE_QUERY).matches) return; function alignKatex() { const katexElement = problemCardRef.current?.querySelector("span.katex"); if (!katexElement) return; const rect = katexElement.getBoundingClientRect(); const scrollElement = document.scrollingElement ?? document.documentElement; const elementDocumentTop = window.scrollY + rect.top; const maxScrollTop = Math.max( 0, scrollElement.scrollHeight - window.innerHeight, ); const scrollTop = Math.min( maxScrollTop, Math.max(0, elementDocumentTop), ); if (Math.abs(window.scrollY - scrollTop) < 4) return; scrollElement.scrollTop = scrollTop; document.body.scrollTop = scrollTop; window.scrollTo(0, scrollTop); } window.requestAnimationFrame(() => { window.requestAnimationFrame(alignKatex); }); }, []); if (!session || sessionNeedsInit) { return (

문제를 준비하고 있습니다.

); } if (isComplete) { return ( ); } if (!currentProblem) { return (

현재 문제를 불러올 수 없습니다.

); } const submitted = currentResult !== null; const isLastProblem = session.currentIndex === session.problems.length - 1; return (

괄호 음수 분배

문제 풀이

괄호를 풀어 두 항을 순서대로 입력하세요.

{!submitted ? ( ) : null} {currentResult ? ( ) : null}
); }