문제 수 선택 기능 추가 - 5문제 / 10문제

This commit is contained in:
2026-05-26 15:23:21 +09:00
parent 44e4d46c6c
commit 2779bf1b0e
5 changed files with 124 additions and 22 deletions
+27 -5
View File
@@ -9,6 +9,7 @@ import ScoreSummary from "@/components/game/ScoreSummary";
import StageIndicator from "@/components/game/StageIndicator";
import KatexRenderer from "@/components/math/KatexRenderer";
import { useGameStore } from "@/store/gameStore";
import type { ProblemCount } from "@/store/gameStore";
function parseAnswer(value: string) {
const trimmed = value.trim();
@@ -18,8 +19,16 @@ function parseAnswer(value: string) {
return Number(trimmed);
}
export default function PlayClient() {
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);
@@ -34,15 +43,28 @@ export default function PlayClient() {
const isComplete = Boolean(
session && session.currentIndex >= session.problems.length,
);
const sessionNeedsInit =
session === null || session.problems.length !== problemCount;
const correctCount =
session?.results.filter((result) => result?.correct).length ?? 0;
const totalCount = session?.problems.length ?? 0;
useEffect(() => {
if (session === null) {
initSession();
if (selectedProblemCount !== problemCount) {
setProblemCount(problemCount);
}
}, [initSession, session]);
if (sessionNeedsInit) {
initSession(problemCount);
}
}, [
initSession,
problemCount,
selectedProblemCount,
session,
sessionNeedsInit,
setProblemCount,
]);
useEffect(() => {
setAnswerA("");
@@ -67,7 +89,7 @@ export default function PlayClient() {
submitAnswer({ a: parsedA, b: parsedB });
}
if (!session) {
if (sessionNeedsInit) {
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">
+13 -2
View File
@@ -1,5 +1,16 @@
import PlayClient from "./PlayClient";
import type { ProblemCount } from "@/store/gameStore";
export default function PlayPage() {
return <PlayClient />;
interface PlayPageProps {
searchParams?: {
count?: string;
};
}
function parseProblemCount(count?: string): ProblemCount {
return count === "5" ? 5 : 10;
}
export default function PlayPage({ searchParams }: PlayPageProps) {
return <PlayClient problemCount={parseProblemCount(searchParams?.count)} />;
}
+66
View File
@@ -0,0 +1,66 @@
"use client";
import Link from "next/link";
import { useEffect, useRef } from "react";
import { useGameStore } from "@/store/gameStore";
import type { ProblemCount } from "@/store/gameStore";
const problemCounts: ProblemCount[] = [5, 10];
export default function StartControls() {
const startLinkRef = useRef<HTMLAnchorElement>(null);
const selectedProblemCount = useGameStore(
(state) => state.selectedProblemCount,
);
const setProblemCount = useGameStore((state) => state.setProblemCount);
useEffect(() => {
startLinkRef.current?.focus();
}, []);
return (
<div className="flex flex-col gap-5">
<div>
<Link
ref={startLinkRef}
href={`/play?count=${selectedProblemCount}`}
className="inline-flex h-12 items-center justify-center rounded-md bg-emerald-700 px-6 text-base font-semibold text-white transition hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
>
</Link>
</div>
<fieldset className="space-y-3">
<legend className="text-sm font-semibold text-slate-700">
</legend>
<div
className="inline-flex gap-1 rounded-md border border-slate-300 bg-white p-1 shadow-sm"
role="group"
aria-label="문제 수 선택"
>
{problemCounts.map((count) => {
const selected = selectedProblemCount === count;
return (
<button
key={count}
type="button"
onClick={() => setProblemCount(count)}
aria-pressed={selected}
className={`h-10 min-w-16 rounded px-4 text-base font-semibold transition focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 ${
selected
? "bg-emerald-700 text-white"
: "text-slate-700 hover:bg-slate-100"
}`}
>
{count}
</button>
);
})}
</div>
</fieldset>
</div>
);
}
+2 -9
View File
@@ -1,4 +1,4 @@
import Link from "next/link";
import StartControls from "./StartControls";
export default function Home() {
return (
@@ -17,14 +17,7 @@ export default function Home() {
</p>
</div>
<div>
<Link
href="/play"
className="inline-flex h-12 items-center justify-center rounded-md bg-emerald-700 px-6 text-base font-semibold text-white transition hover:bg-emerald-800 focus:outline-none focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2"
>
</Link>
</div>
<StartControls />
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
<p> .</p>
+16 -6
View File
@@ -16,17 +16,21 @@ export interface GameSession {
results: (GradeResult | null)[];
}
export type ProblemCount = 5 | 10;
interface GameState {
session: GameSession | null;
initSession: (count?: number) => void;
selectedProblemCount: ProblemCount;
setProblemCount: (count: ProblemCount) => void;
initSession: (count?: ProblemCount) => void;
submitAnswer: (answer: UserAnswer) => void;
nextProblem: () => void;
resetSession: () => void;
}
const DEFAULT_SESSION_COUNT = 10;
const DEFAULT_SESSION_COUNT: ProblemCount = 10;
function createSession(count = DEFAULT_SESSION_COUNT): GameSession {
function createSession(count: ProblemCount = DEFAULT_SESSION_COUNT): GameSession {
const problems = generateSession(count);
return {
@@ -38,8 +42,14 @@ function createSession(count = DEFAULT_SESSION_COUNT): GameSession {
export const useGameStore = create<GameState>((set, get) => ({
session: null,
initSession: (count = DEFAULT_SESSION_COUNT) => {
set({ session: createSession(count) });
selectedProblemCount: DEFAULT_SESSION_COUNT,
setProblemCount: (count) => {
set({ selectedProblemCount: count });
},
initSession: (count) => {
const selectedProblemCount = count ?? get().selectedProblemCount;
set({ session: createSession(selectedProblemCount) });
},
submitAnswer: (answer) => {
const { session } = get();
@@ -73,6 +83,6 @@ export const useGameStore = create<GameState>((set, get) => ({
});
},
resetSession: () => {
set({ session: createSession() });
set({ session: createSession(get().selectedProblemCount) });
},
}));