Files
math-quiz/docs/vibe/02-simple-play.md
T

9.3 KiB

02. 괄호 앞 음수 분배 — 간단한 풀이 화면

계획 평가

Claude Code가 작성한 기존 계획은 방향이 맞다. 01-project-start.md에서 만든 / -> /play 흐름 위에 문제 생성 엔진, KaTeX 표시, 답 입력, 채점, 10문제 세션을 붙이는 순서가 자연스럽다.

다만 실제 구현 단계로 쓰기에는 몇 가지를 조정하는 편이 좋다.

  • 엔진 단위 테스트만 하는 단계에서는 @vitejs/plugin-react, jsdom, @testing-library/react가 아직 필요하지 않다.
  • generateSession()의 중복 방지는 랜덤 id가 아니라 multiplier/a/operator/b 조합 기준이어야 한다.
  • Zustand는 한 문제 풀이가 동작한 뒤 도입한다. 이 순서를 지키면 버그가 엔진, UI, 상태 관리 중 어디에서 생겼는지 분리하기 쉽다.
  • PlayClient에서 세션을 초기화할 때 렌더링 중 initSession()을 직접 호출하지 않고 useEffect에서 호출해야 한다.
  • 이번 문서는 “간단한 풀이 화면”이 목표이므로 Framer Motion, 정교한 진행 바, 배너 디자인, E2E는 다음 단계로 넘긴다.

목표

/play 화면에서 괄호 앞 음수 분배 문제를 10개 풀고 점수를 확인할 수 있게 만든다.

이번 단계의 최종 상태는 다음과 같다.

  • 자체 알고리즘으로 괄호 앞 음수 분배 문제를 생성한다.
  • 문제와 정답 수식을 KaTeX로 렌더링한다.
  • 사용자가 두 항의 답을 입력하고 채점 결과를 확인한다.
  • 10문제 풀이가 끝나면 점수 요약을 보여준다.

이번 단계에서 할 것

  • lib/engine/에 문제 생성·채점 순수 함수와 Vitest 단위 테스트 추가
  • components/math/에 KaTeX 렌더링 컴포넌트 추가
  • /play에 한 문제 풀이 UI 구현
  • 한 문제 풀이 확인 후 store/gameStore.ts로 10문제 세션 구현
  • 세션 완료 후 간단한 점수 요약 표시

이번 단계에서 하지 않을 것

  • Framer Motion 애니메이션
  • 정교한 ProgressBar, FeedbackBanner, ScoreSummary 디자인 컴포넌트화
  • DB, Prisma, Redis, NextAuth
  • 유형 선택 화면, 모드 선택 화면
  • 스피드 퀴즈 모드
  • 미지수 x, y, z가 포함된 문제
  • Playwright E2E 테스트

의존성

필요한 시점에 나누어 설치한다.

엔진 테스트

pnpm add -D vitest

수식 렌더링과 세션 상태

pnpm add katex react-katex zustand
pnpm add -D @types/katex

React 컴포넌트 테스트를 추가하지 않는 한 @testing-library/react, jsdom, @vitejs/plugin-react는 이번 단계에 넣지 않는다.


생성 또는 수정 파일

신규 생성

lib/engine/types.ts
lib/engine/parenthesis-sign.ts
lib/engine/parenthesis-sign.test.ts
vitest.config.ts
components/math/KatexRenderer.tsx
store/gameStore.ts
app/(game)/play/PlayClient.tsx

수정

package.json              # test script 추가
app/layout.tsx            # KaTeX CSS import 추가
app/(game)/play/page.tsx  # PlayClient 마운트

작업 순서

2-A. 문제 생성 엔진

완료 기준: pnpm test 통과

lib/engine/types.ts

export interface ParenthesisSignProblem {
  id: string
  key: string
  multiplier: number
  a: number
  operator: '+' | '-'
  b: number
  distributedA: number
  distributedB: number
  latexBefore: string
  latexAfter: string
}

export interface UserAnswer {
  a: number
  b: number
}

export interface GradeResult {
  correct: boolean
  correctAnswer: [number, number]
}

lib/engine/parenthesis-sign.ts에 구현할 함수:

  • generateProblem(): ParenthesisSignProblem

    • multiplier: -9부터 -1 사이 정수
    • a, b: 1부터 9 사이 정수
    • a !== b
    • operator: '+' | '-'
    • distributedA = multiplier * a
    • distributedB = operator === '-' ? multiplier * -b : multiplier * b
    • keymultiplier:a:operator:b 형식으로 만든다.
    • latexBefore: 예시 -2(3 - 6)
    • latexAfter: 예시 -6 + 12
  • generateSession(count = 10): ParenthesisSignProblem[]

    • count1 이상인지 확인한다.
    • 가능한 문제 수는 9 * 8 * 2 = 144개이므로 count > 144이면 에러를 던진다.
    • 중복 방지는 id가 아니라 key 기준으로 한다.
  • gradeAnswer(problem, answer): GradeResult

    • 두 항이 모두 일치할 때만 정답이다.
    • 입력 순서를 바꾸어 쓴 답은 오답으로 처리한다.

lib/engine/parenthesis-sign.test.ts 검증 항목:

  • multiplier는 항상 음수 정수다.
  • a, b는 범위 안에 있고 서로 다르다.
  • distributedA, distributedB 계산이 operator별로 맞다.
  • gradeAnswer()는 정답과 오답을 정확히 구분한다.
  • generateSession(10)은 10개 문제를 만들고 key 중복이 없다.
  • generateSession(145)는 에러를 던진다.

vitest.config.ts

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'node',
  },
})

package.json

"test": "vitest run"

2-B. KaTeX 렌더링

완료 기준: 브라우저에서 문제 수식이 KaTeX로 표시됨

app/layout.tsx에 전역 CSS를 추가한다.

import 'katex/dist/katex.min.css'

components/math/KatexRenderer.tsx

'use client'

import { BlockMath, InlineMath } from 'react-katex'

interface KatexRendererProps {
  latex: string
  displayMode?: boolean
}

export default function KatexRenderer({
  latex,
  displayMode = false,
}: KatexRendererProps) {
  return displayMode ? <BlockMath math={latex} /> : <InlineMath math={latex} />
}

이 단계에서는 /play에 임시 문제 하나를 표시해서 KaTeX 렌더링만 확인한다.


2-C. 한 문제 풀이 UI

완료 기준: 한 문제를 풀고 정답·오답 피드백이 표시됨

app/(game)/play/PlayClient.tsx를 클라이언트 컴포넌트로 만든다.

'use client'

구성:

  • 문제 수식: KatexRendererproblem.latexBefore 표시
  • 답 입력: 숫자 입력 두 칸
  • 제출 버튼
  • Enter 제출
  • 정답이면 간단한 성공 문구
  • 오답이면 정답 수식과 정답 두 항 표시
  • 새 문제 버튼

로컬 상태:

const [problem, setProblem] = useState<ParenthesisSignProblem>(() => generateProblem())
const [answerA, setAnswerA] = useState('')
const [answerB, setAnswerB] = useState('')
const [result, setResult] = useState<GradeResult | null>(null)

입력 처리:

  • 빈 문자열은 제출하지 않는다.
  • Number.parseInt(value, 10) 결과가 NaN이면 제출하지 않는다.
  • 채점 후에는 같은 문제에서 중복 제출하지 않도록 제출 버튼을 비활성화한다.

이 단계에서는 Zustand를 사용하지 않는다.


2-D. 10문제 세션

완료 기준: 10문제를 모두 풀면 점수 요약 화면이 표시됨

store/gameStore.ts

interface GameSession {
  problems: ParenthesisSignProblem[]
  currentIndex: number
  results: (GradeResult | null)[]
}

interface GameState {
  session: GameSession | null
  initSession: (count?: number) => void
  submitAnswer: (answer: UserAnswer) => void
  nextProblem: () => void
  resetSession: () => void
  currentProblem: () => ParenthesisSignProblem | null
  currentResult: () => GradeResult | null
  isComplete: () => boolean
  score: () => { correct: number; total: number }
}

상태 규칙:

  • initSession(10)은 10개 문제와 null 결과 배열을 만든다.
  • submitAnswer()는 현재 문제의 결과가 null일 때만 채점한다.
  • nextProblem()은 채점 후에만 다음 문제로 이동한다.
  • 마지막 문제에서 nextProblem()을 호출하면 완료 상태가 된다.
  • resetSession()은 기존 세션을 버리고 새 세션을 시작한다.

PlayClient.tsx 전환:

  • useEffect에서 session === null이면 initSession() 호출
  • currentProblem() === null이면 로딩 또는 완료 화면 표시
  • 풀이 중에는 현재 문제 번호를 텍스트로 표시한다. 예: 3 / 10
  • 세션 완료 후 맞은 개수와 전체 개수를 표시한다.
  • 다시 시작 버튼은 resetSession()을 호출한다.

app/(game)/play/page.tsx

import PlayClient from './PlayClient'

export default function PlayPage() {
  return <PlayClient />
}

화면 흐름

/ 시작 화면
  - "연습 시작" 클릭

        ↓

/play 풀이 화면
  ├─ 현재 문제 번호
  ├─ 문제 수식 (KaTeX)
  ├─ 답 입력 두 칸
  ├─ 제출 버튼
  ├─ 정답/오답 피드백
  ├─ 다음 문제 버튼
  └─ 10문제 완료 후 점수 요약
       - n / 10 정답
       - 다시 시작

완료 기준

  • pnpm test 통과
  • pnpm lint 통과
  • 브라우저에서 /play 수식이 KaTeX로 렌더링된다.
  • 정답 입력 시 정답 피드백이 표시된다.
  • 오답 입력 시 오답 피드백과 정답이 표시된다.
  • 10문제를 모두 풀면 점수 요약 화면이 표시된다.
  • 다시 시작을 누르면 새 10문제 세션이 시작된다.

확인 명령

pnpm test
pnpm lint
pnpm dev

브라우저 확인은 pnpm dev 실행 후 http://localhost:3000/play에서 한다.


다음 단계

03-ux-polish.md에서 UI 품질을 높인다.

  • 진행률 바
  • 정답·오답 배너 컴포넌트
  • 점수 요약 컴포넌트
  • Framer Motion 기반 전환 애니메이션
  • 키보드 탐색과 포커스 처리 개선