# 04. 미지수 괄호 학습과 선택지형 풀이 화면 ## 계획 검토 Codex가 작성한 초안은 방향이 맞다. 엔진 설계, store 분리, 화면 구조가 기존 숫자 괄호 흐름과 일관성 있게 정리되어 있다. 조정이 필요한 부분: - `VariableUserAnswer`의 `selectedChoiceIds`는 보기 id 기반 채점이므로 그대로 유지한다. 보기가 매번 섞이므로 계수 값 자체보다 id로 선택을 추적하는 것이 명확하다. - 사용자가 요청한 "보기 하나는 음수, 보기 하나는 양수" 방식이 Codex 초안과 동일하다. 보기는 정답 계수의 절대값을 기준으로 양수/음수 두 개만 표시한다. - 선택지 순서는 매번 셔플한다. - 둘째 항 선택 후 `정답 확인` 버튼으로 명시적으로 제출한다. 자동 제출은 사용자가 선택을 확인할 여지 없이 넘어가므로 사용하지 않는다. - 준비 화면은 `NumberParenthesesClient`와 구조가 동일하므로 처음에는 별도 파일로 구현한다. 중복이 불편해지면 그때 공통 컴포넌트로 분리한다. --- ## 목표 미지수가 포함된 괄호 풀기 문제를 선택지 방식으로 풀 수 있는 학습 흐름을 추가한다. 이번 단계의 최종 상태: - `/`에서 `미지수 괄호 풀기`가 활성 링크로 표시된다. - `/variable-parentheses` 준비 화면에서 문제 수와 괄호 종류를 선택한다. - `/variable-parentheses/play?count=N` 풀이 화면에서 첫째 항, 둘째 항 순서로 보기를 고르고 정답을 확인한다. - 기존 숫자 괄호 풀이 화면은 현재 동작을 유지한다. --- ## 이번 단계에서 할 것 - 첫 화면의 `미지수 괄호 풀기` disabled 해제 (`/variable-parentheses` 링크) - 미지수 괄호 학습 준비 화면 추가 - 미지수 괄호 문제 생성 엔진 추가 - 미지수 괄호 전용 Zustand store 추가 - 미지수 괄호 전용 풀이 화면 추가 (선택지형) - 정답·오답 피드백 및 결과 화면 추가 - 엔진 단위 테스트 추가 ## 이번 단계에서 하지 않을 것 - 분수 괄호, 실수 괄호 문제 생성 - `{}`, `[]` 괄호 문제 생성 - 여러 미지수(`x, y, z`) 지원 - 기존 숫자 괄호 `/play` 화면 동작 변경 - 사용자가 직접 미지수 답을 타이핑하는 입력 방식 - DB, Redis, NextAuth 연동 - 스피드 퀴즈 모드 --- ## 문제 생성 규칙 ### 문제 형태 ```text multiplier(a ± b) ``` 괄호 안 두 항 중 정확히 하나가 `정수 × x` 항이다. 예시: ```text -2(3x - 6) → -6x + 12 -5(4 + 7x) → -20 - 35x -3(8x + 2) → -24x - 6 ``` ### 생성 조건 | 항목 | 범위 | |---|---| | `multiplier` | `-9`~`-1` 정수 | | 숫자 항 계수 | `1`~`9` 정수 | | 미지수 항 계수 | `1`~`9` 정수 | | 미지수 | `x` 고정 | | 연산자 | `+` 또는 `-` | | 미지수 항 위치 | 첫째 항 또는 둘째 항 (한 문제에 하나만) | 두 항 계수가 같아도 허용한다 (숫자 괄호와 달리 항의 종류가 다르므로 혼동이 없다). ### 정답 계산 - 첫째 항 정답: `multiplier × firstTerm.coefficient` (미지수 항이면 `x` 붙임) - 둘째 항 정답: `multiplier × (operator === '-' ? -secondTerm.coefficient : secondTerm.coefficient)` (미지수 항이면 `x` 붙임) ### 보기 생성 정답 계수의 절대값을 기준으로 두 보기를 만든다. ```text 정답: -6x → 보기: -6x, 6x 정답: 12 → 보기: -12, 12 ``` - 보기 두 개는 항 종류(미지수 항 / 숫자 항)가 같다. - 보기 순서는 매번 셔플한다. - 보기 `id`는 안정적으로 생성한다: `${problem.key}:term-0:negative`, `${problem.key}:term-0:positive` ### 중복 방지 key ```text ${multiplier}:${firstKind}:${firstCoeff}:${operator}:${secondKind}:${secondCoeff} ``` 예: `-2:variable:3:-:constant:6` --- ## 타입 설계 신규 파일: `lib/engine/variable-types.ts` ```ts export type VariableTermKind = 'constant' | 'variable' export interface VariableParenthesisTerm { kind: VariableTermKind coefficient: number } export interface DistributedVariableTerm { kind: VariableTermKind coefficient: number latex: string } export interface VariableTermChoice { id: string coefficient: number kind: VariableTermKind latex: string } export interface VariableParenthesisProblem { id: string key: string multiplier: number operator: '+' | '-' firstTerm: VariableParenthesisTerm secondTerm: VariableParenthesisTerm correctTerms: [DistributedVariableTerm, DistributedVariableTerm] choices: [VariableTermChoice[], VariableTermChoice[]] latexBefore: string latexAfter: string } export interface VariableUserAnswer { selectedChoiceIds: [string, string] } export interface VariableGradeResult { correct: boolean firstCorrect: boolean secondCorrect: boolean userChoiceIds: [string, string] correctTerms: [DistributedVariableTerm, DistributedVariableTerm] } ``` --- ## 엔진 설계 신규 파일: `lib/engine/variable-parenthesis.ts` 구현 함수: - `generateVariableParenthesisSession(count?: number): VariableParenthesisProblem[]` - `gradeVariableParenthesisAnswer(problem, answer): VariableGradeResult` 내부 헬퍼: - `formatVariableTermLatex(kind, coefficient): string` - `kind === 'variable'`: `6x`, `-6x` - `kind === 'constant'`: `6`, `-6` - `makeChoices(termIndex, key, distributedTerm): VariableTermChoice[]` - 양수/음수 두 보기를 만들고 셔플 - `createAllVariableSeeds()`: 전체 가능 시드 목록 (최초 1회 생성) - `shuffle()`: 기존 숫자 괄호 엔진과 동일한 방식 세션 생성 방식은 기존 숫자 괄호 엔진과 동일하게 전체 시드 셔플 후 슬라이스로 중복 방지한다. --- ## 상태 관리 설계 신규 파일: `store/variableGameStore.ts` 기존 `gameStore.ts`와 동일한 구조로 분리한다. ```ts interface VariableGameState { session: VariableGameSession | null selectedProblemCount: ProblemCount setProblemCount: (count: ProblemCount) => void initSession: (count?: ProblemCount) => void submitAnswer: (answer: VariableUserAnswer) => void nextProblem: () => void resetSession: () => void } ``` 주의: - selector 안에서 새 객체·배열을 반환하지 않는다. - 현재 선택 중인 보기 상태(`selectedChoiceIds`, `activeTermIndex`)는 `VariablePlayClient` 로컬 state로 관리한다. --- ## 화면 설계 ### 첫 화면 (`app/page.tsx`) - `미지수 괄호 풀기` disabled 해제, `href="/variable-parentheses"` 추가 - 분수 괄호, 실수 괄호는 계속 disabled 유지 - 안내 문구를 `숫자 괄호`, `미지수 괄호` 두 개가 활성화된 상태로 수정 ### 미지수 괄호 학습 준비 화면 신규 파일: ```text app/(game)/variable-parentheses/page.tsx app/(game)/variable-parentheses/VariableParenthesesClient.tsx ``` 화면 구성: - 문제 수: `5`, `10` 버튼 그룹 (기본값 `10`) - 괄호 종류: `()` selected, `{}` disabled, `[]` disabled - `연습 시작` 버튼 → `/variable-parentheses/play?count=${selectedProblemCount}` 이동, 클릭 시 세션 초기화 ### 미지수 괄호 풀이 화면 신규 파일: ```text app/(game)/variable-parentheses/play/page.tsx app/(game)/variable-parentheses/play/VariablePlayClient.tsx components/game/VariableChoiceAnswerForm.tsx components/game/VariableFeedbackPanel.tsx components/game/VariableScoreSummary.tsx ``` 풀이 흐름: ```text 문제 표시 (KaTeX 카드) ↓ 첫째 항 보기 2개 표시 → 선택 ↓ 둘째 항 보기 2개 표시 → 선택 ↓ [정답 확인] 버튼 활성화 → 클릭 ↓ 피드백 표시 → [다음 문제] 또는 [결과 보기] ↓ 최종 결과 화면 ``` `VariableChoiceAnswerForm` props 초안: ```ts interface VariableChoiceAnswerFormProps { choices: [VariableTermChoice[], VariableTermChoice[]] selectedChoiceIds: [string | null, string | null] activeTermIndex: 0 | 1 onSelectChoice: (termIndex: 0 | 1, choiceId: string) => void onSubmit: () => void } ``` 접근성: - 첫째 항 선택 후 둘째 항 첫 번째 보기 버튼에 focus 이동 - 두 항 모두 선택되면 `정답 확인` 버튼 활성화 - 피드백 표시 시 `다음 문제` 버튼에 focus 이동 - 결과 화면 표시 시 `다시 시작` 버튼에 focus 이동 `VariableFeedbackPanel` 표시 형식: ```text (-2) × 3x = -6x ○ (-2) × (-6) = 12 ○ ``` `VariableScoreSummary` 표시 형식: ```text -2(3x - 6) = -6x + 12 ○ ``` 기존 `AnswerResultLine` 컴포넌트를 재사용한다. 항 표현이 복잡해지는 경우 `userAnswerContent` / `correctAnswerContent` prop으로 커스텀 렌더링한다. --- ## 작업 순서 ### 4-A. 미지수 괄호 엔진 추가 완료 기준: `pnpm test` 통과 - `lib/engine/variable-types.ts` 타입 정의 - `lib/engine/variable-parenthesis.ts` 엔진 구현 - `lib/engine/variable-parenthesis.test.ts` 단위 테스트 테스트 항목: - 괄호 안 두 항 중 정확히 하나만 미지수 항이다 - 계수는 모두 정수다 - 정답 분배 계산이 맞다 - 둘째 항이 `-` 뒤에 있을 때 부호가 올바르게 뒤집힌다 - 각 항의 보기는 정답 절대값 기준 양수/음수 두 개다 - choice id 기준으로 채점이 올바르다 - 세션 문제 key가 중복되지 않는다 - 기존 숫자 괄호 엔진 테스트가 회귀 없이 통과한다 ### 4-B. 미지수 괄호 store 추가 완료 기준: 세션 생성 및 진행 가능 - `store/variableGameStore.ts` ### 4-C. 준비 화면 추가 완료 기준: `/variable-parentheses` 접속 시 준비 화면이 표시된다 - `app/(game)/variable-parentheses/page.tsx` - `app/(game)/variable-parentheses/VariableParenthesesClient.tsx` ### 4-D. 첫 화면 수정 완료 기준: `/`에서 `미지수 괄호 풀기`가 활성 링크로 표시된다 - `app/page.tsx` 수정 ### 4-E. 선택지형 풀이 화면 추가 완료 기준: `/variable-parentheses/play?count=5`에서 문제를 풀 수 있다 - `app/(game)/variable-parentheses/play/page.tsx` - `app/(game)/variable-parentheses/play/VariablePlayClient.tsx` - `components/game/VariableChoiceAnswerForm.tsx` ### 4-F. 피드백 및 결과 화면 추가 완료 기준: 선택 결과에 따라 피드백과 최종 결과가 표시된다 - `components/game/VariableFeedbackPanel.tsx` - `components/game/VariableScoreSummary.tsx` ### 4-G. 회귀 확인 완료 기준: 숫자 괄호와 미지수 괄호 흐름이 모두 동작한다 1. `/` → `숫자 괄호 풀기` → 준비 화면 → `/play?count=5` → 풀이 → 결과 2. `/` → `미지수 괄호 풀기` → 준비 화면 → `/variable-parentheses/play?count=5` → 풀이 → 결과 --- ## 생성 또는 수정 파일 ### 신규 생성 ```text lib/engine/variable-types.ts lib/engine/variable-parenthesis.ts lib/engine/variable-parenthesis.test.ts store/variableGameStore.ts app/(game)/variable-parentheses/page.tsx app/(game)/variable-parentheses/VariableParenthesesClient.tsx app/(game)/variable-parentheses/play/page.tsx app/(game)/variable-parentheses/play/VariablePlayClient.tsx components/game/VariableChoiceAnswerForm.tsx components/game/VariableFeedbackPanel.tsx components/game/VariableScoreSummary.tsx ``` ### 수정 ```text app/page.tsx ``` ### 변경 없음 ```text app/(game)/play/page.tsx app/(game)/play/PlayClient.tsx store/gameStore.ts lib/engine/parenthesis-sign.ts lib/engine/parenthesis-sign.test.ts ``` --- ## 완료 기준 - `/`에서 `미지수 괄호 풀기`가 활성화되어 있다 - `/variable-parentheses` 준비 화면에서 문제 수 `5`, `10`을 선택할 수 있다 - `()`가 selected 상태이고 `{}`, `[]`는 disabled 상태다 - `연습 시작`으로 `/variable-parentheses/play?count=N`에 진입한다 - 미지수 괄호 문제는 괄호 안 두 항 중 하나만 `정수x` 항을 포함한다 - 풀이 화면에서 첫째 항, 둘째 항을 순서대로 보기에서 선택할 수 있다 - 각 항의 보기는 정답 절대값 기준 음수/양수 두 개다 - 두 항 선택 후 `정답 확인` 버튼으로 제출할 수 있다 - 정답·오답 피드백이 기존 색상과 기호 규칙(`○`, `×`)으로 표시된다 - 최종 결과 화면이 표시된다 - 기존 숫자 괄호 풀이 화면은 현재 동작을 유지한다 - `pnpm test` 통과 - `pnpm build` 통과