문제 수 선택 기능 추가 - 5문제 / 10문제
This commit is contained in:
@@ -9,6 +9,7 @@ import ScoreSummary from "@/components/game/ScoreSummary";
|
|||||||
import StageIndicator from "@/components/game/StageIndicator";
|
import StageIndicator from "@/components/game/StageIndicator";
|
||||||
import KatexRenderer from "@/components/math/KatexRenderer";
|
import KatexRenderer from "@/components/math/KatexRenderer";
|
||||||
import { useGameStore } from "@/store/gameStore";
|
import { useGameStore } from "@/store/gameStore";
|
||||||
|
import type { ProblemCount } from "@/store/gameStore";
|
||||||
|
|
||||||
function parseAnswer(value: string) {
|
function parseAnswer(value: string) {
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
@@ -18,8 +19,16 @@ function parseAnswer(value: string) {
|
|||||||
return Number(trimmed);
|
return Number(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PlayClient() {
|
interface PlayClientProps {
|
||||||
|
problemCount: ProblemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PlayClient({ problemCount }: PlayClientProps) {
|
||||||
const session = useGameStore((state) => state.session);
|
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 initSession = useGameStore((state) => state.initSession);
|
||||||
const submitAnswer = useGameStore((state) => state.submitAnswer);
|
const submitAnswer = useGameStore((state) => state.submitAnswer);
|
||||||
const nextProblem = useGameStore((state) => state.nextProblem);
|
const nextProblem = useGameStore((state) => state.nextProblem);
|
||||||
@@ -34,15 +43,28 @@ export default function PlayClient() {
|
|||||||
const isComplete = Boolean(
|
const isComplete = Boolean(
|
||||||
session && session.currentIndex >= session.problems.length,
|
session && session.currentIndex >= session.problems.length,
|
||||||
);
|
);
|
||||||
|
const sessionNeedsInit =
|
||||||
|
session === null || session.problems.length !== problemCount;
|
||||||
const correctCount =
|
const correctCount =
|
||||||
session?.results.filter((result) => result?.correct).length ?? 0;
|
session?.results.filter((result) => result?.correct).length ?? 0;
|
||||||
const totalCount = session?.problems.length ?? 0;
|
const totalCount = session?.problems.length ?? 0;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session === null) {
|
if (selectedProblemCount !== problemCount) {
|
||||||
initSession();
|
setProblemCount(problemCount);
|
||||||
}
|
}
|
||||||
}, [initSession, session]);
|
|
||||||
|
if (sessionNeedsInit) {
|
||||||
|
initSession(problemCount);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
initSession,
|
||||||
|
problemCount,
|
||||||
|
selectedProblemCount,
|
||||||
|
session,
|
||||||
|
sessionNeedsInit,
|
||||||
|
setProblemCount,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAnswerA("");
|
setAnswerA("");
|
||||||
@@ -67,7 +89,7 @@ export default function PlayClient() {
|
|||||||
submitAnswer({ a: parsedA, b: parsedB });
|
submitAnswer({ a: parsedA, b: parsedB });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session) {
|
if (sessionNeedsInit) {
|
||||||
return (
|
return (
|
||||||
<main className="flex min-h-screen items-center bg-slate-50 px-6 py-12 text-slate-950">
|
<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">
|
<section className="mx-auto w-full max-w-3xl">
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
import PlayClient from "./PlayClient";
|
import PlayClient from "./PlayClient";
|
||||||
|
import type { ProblemCount } from "@/store/gameStore";
|
||||||
|
|
||||||
export default function PlayPage() {
|
interface PlayPageProps {
|
||||||
return <PlayClient />;
|
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)} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -1,4 +1,4 @@
|
|||||||
import Link from "next/link";
|
import StartControls from "./StartControls";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@@ -17,14 +17,7 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<StartControls />
|
||||||
<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>
|
|
||||||
|
|
||||||
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
|
<div className="border-l-4 border-emerald-700 pl-4 text-sm leading-6 text-slate-600">
|
||||||
<p>이번 단계에서는 화면 이동만 확인합니다.</p>
|
<p>이번 단계에서는 화면 이동만 확인합니다.</p>
|
||||||
|
|||||||
+16
-6
@@ -16,17 +16,21 @@ export interface GameSession {
|
|||||||
results: (GradeResult | null)[];
|
results: (GradeResult | null)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProblemCount = 5 | 10;
|
||||||
|
|
||||||
interface GameState {
|
interface GameState {
|
||||||
session: GameSession | null;
|
session: GameSession | null;
|
||||||
initSession: (count?: number) => void;
|
selectedProblemCount: ProblemCount;
|
||||||
|
setProblemCount: (count: ProblemCount) => void;
|
||||||
|
initSession: (count?: ProblemCount) => void;
|
||||||
submitAnswer: (answer: UserAnswer) => void;
|
submitAnswer: (answer: UserAnswer) => void;
|
||||||
nextProblem: () => void;
|
nextProblem: () => void;
|
||||||
resetSession: () => 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);
|
const problems = generateSession(count);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -38,8 +42,14 @@ function createSession(count = DEFAULT_SESSION_COUNT): GameSession {
|
|||||||
|
|
||||||
export const useGameStore = create<GameState>((set, get) => ({
|
export const useGameStore = create<GameState>((set, get) => ({
|
||||||
session: null,
|
session: null,
|
||||||
initSession: (count = DEFAULT_SESSION_COUNT) => {
|
selectedProblemCount: DEFAULT_SESSION_COUNT,
|
||||||
set({ session: createSession(count) });
|
setProblemCount: (count) => {
|
||||||
|
set({ selectedProblemCount: count });
|
||||||
|
},
|
||||||
|
initSession: (count) => {
|
||||||
|
const selectedProblemCount = count ?? get().selectedProblemCount;
|
||||||
|
|
||||||
|
set({ session: createSession(selectedProblemCount) });
|
||||||
},
|
},
|
||||||
submitAnswer: (answer) => {
|
submitAnswer: (answer) => {
|
||||||
const { session } = get();
|
const { session } = get();
|
||||||
@@ -73,6 +83,6 @@ export const useGameStore = create<GameState>((set, get) => ({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetSession: () => {
|
resetSession: () => {
|
||||||
set({ session: createSession() });
|
set({ session: createSession(get().selectedProblemCount) });
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user