414 lines
12 KiB
Markdown
414 lines
12 KiB
Markdown
# 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` 통과
|