diff --git a/src/components/game/GameControls.tsx b/src/components/game/GameControls.tsx
new file mode 100644
index 0000000..8e9e50f
--- /dev/null
+++ b/src/components/game/GameControls.tsx
@@ -0,0 +1,26 @@
+import { useTranslation } from 'react-i18next';
+import { useGameStore } from '../../store/gameStore';
+
+interface Props {
+ onBackToMenu: () => void;
+}
+
+export default function GameControls({ onBackToMenu }: Props) {
+ const { t } = useTranslation();
+ const undo = useGameStore((s) => s.undo);
+ const mode = useGameStore((s) => s.mode);
+ const status = useGameStore((s) => s.status);
+
+ const handleUndo = () => {
+ undo(1);
+ };
+
+ return (
+
+
+
+
+ );
+}
diff --git a/src/components/game/GameInfo.tsx b/src/components/game/GameInfo.tsx
new file mode 100644
index 0000000..1a7bf14
--- /dev/null
+++ b/src/components/game/GameInfo.tsx
@@ -0,0 +1,20 @@
+import { useTranslation } from 'react-i18next';
+import { useGameStore } from '../../store/gameStore';
+
+export default function GameInfo() {
+ const { t } = useTranslation();
+ const currentColor = useGameStore((s) => s.currentColor);
+ const status = useGameStore((s) => s.status);
+ const winner = useGameStore((s) => s.winner);
+
+ let text = '';
+ if (status === 'game_over' && winner) {
+ text = winner === 'Black' ? t('game.black_win') : t('game.white_win');
+ } else if (status === 'ai_thinking') {
+ text = t('game.ai_thinking');
+ } else if (status === 'playing') {
+ text = currentColor === 'Black' ? t('game.black_turn') : t('game.white_turn');
+ }
+
+ return {text}
;
+}
diff --git a/src/components/game/GameView.tsx b/src/components/game/GameView.tsx
new file mode 100644
index 0000000..c698138
--- /dev/null
+++ b/src/components/game/GameView.tsx
@@ -0,0 +1,21 @@
+import BoardCanvas from '../board/BoardCanvas';
+import GameInfo from './GameInfo';
+import GameControls from './GameControls';
+import TimerDisplay from './TimerDisplay';
+
+interface Props {
+ onBackToMenu: () => void;
+}
+
+export default function GameView({ onBackToMenu }: Props) {
+ return (
+
+ );
+}
diff --git a/src/components/game/TimerDisplay.tsx b/src/components/game/TimerDisplay.tsx
new file mode 100644
index 0000000..c384d09
--- /dev/null
+++ b/src/components/game/TimerDisplay.tsx
@@ -0,0 +1,29 @@
+import { useState, useEffect } from 'react';
+import { useGameStore } from '../../store/gameStore';
+
+export default function TimerDisplay() {
+ const config = useGameStore((s) => s.config);
+ const currentColor = useGameStore((s) => s.currentColor);
+ const status = useGameStore((s) => s.status);
+ const [time, setTime] = useState(config.timeLimitSecs);
+
+ useEffect(() => {
+ if (!config.useTimer || status !== 'playing') return;
+ setTime(config.timeLimitSecs);
+ const timer = setInterval(() => {
+ setTime((t) => {
+ if (t <= 1) { clearInterval(timer); return 0; }
+ return t - 1;
+ });
+ }, 1000);
+ return () => clearInterval(timer);
+ }, [currentColor, config.useTimer, config.timeLimitSecs, status]);
+
+ if (!config.useTimer) return null;
+
+ return (
+
+ {Math.floor(time / 60)}:{(time % 60).toString().padStart(2, '0')}
+
+ );
+}
diff --git a/src/components/replay/ReplayControls.tsx b/src/components/replay/ReplayControls.tsx
new file mode 100644
index 0000000..f4aaf3b
--- /dev/null
+++ b/src/components/replay/ReplayControls.tsx
@@ -0,0 +1,19 @@
+import { useTranslation } from 'react-i18next';
+
+interface Props {
+ isPlaying: boolean;
+ onTogglePlay: () => void;
+ onPrev: () => void;
+ onNext: () => void;
+}
+
+export default function ReplayControls({ isPlaying, onTogglePlay, onPrev, onNext }: Props) {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/components/replay/ReplayView.tsx b/src/components/replay/ReplayView.tsx
new file mode 100644
index 0000000..4ab121d
--- /dev/null
+++ b/src/components/replay/ReplayView.tsx
@@ -0,0 +1,44 @@
+import { useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useGameStore } from '../../store/gameStore';
+import BoardCanvas from '../board/BoardCanvas';
+import StepSlider from './StepSlider';
+import ReplayControls from './ReplayControls';
+
+interface Props {
+ onBackToMenu: () => void;
+}
+
+export default function ReplayView({ onBackToMenu }: Props) {
+ const { t } = useTranslation();
+ const moves = useGameStore((s) => s.moves);
+ const [step, setStep] = useState(moves.length);
+ const [isPlaying, setIsPlaying] = useState(false);
+
+ useEffect(() => {
+ if (!isPlaying) return;
+ if (step >= moves.length) {
+ setIsPlaying(false);
+ return;
+ }
+ const timer = setInterval(() => setStep((s) => s + 1), 500);
+ return () => clearInterval(timer);
+ }, [isPlaying, step, moves.length]);
+
+ return (
+
+
+
+
+
+
{t('replay.step', { current: step, total: moves.length })}
+
setIsPlaying(!isPlaying)}
+ onPrev={() => setStep(Math.max(0, step - 1))}
+ onNext={() => setStep(Math.min(moves.length, step + 1))}
+ />
+
+
+ );
+}
diff --git a/src/components/replay/StepSlider.tsx b/src/components/replay/StepSlider.tsx
new file mode 100644
index 0000000..abeedb7
--- /dev/null
+++ b/src/components/replay/StepSlider.tsx
@@ -0,0 +1,18 @@
+interface Props {
+ current: number;
+ total: number;
+ onChange: (step: number) => void;
+}
+
+export default function StepSlider({ current, total, onChange }: Props) {
+ return (
+ onChange(Number(e.target.value))}
+ className="step-slider"
+ />
+ );
+}
diff --git a/src/hooks/useGame.ts b/src/hooks/useGame.ts
new file mode 100644
index 0000000..58548e6
--- /dev/null
+++ b/src/hooks/useGame.ts
@@ -0,0 +1,13 @@
+import { useCallback } from 'react';
+import { useGameStore } from '../store/gameStore';
+import type { GameConfig, GameModeType } from '../core/types';
+
+export function useGame() {
+ const store = useGameStore();
+
+ const startGame = useCallback(async (mode: GameModeType, config: GameConfig) => {
+ await store.startGame(mode, config);
+ }, [store]);
+
+ return { ...store, startGame };
+}
diff --git a/src/hooks/useTimer.ts b/src/hooks/useTimer.ts
new file mode 100644
index 0000000..221d38e
--- /dev/null
+++ b/src/hooks/useTimer.ts
@@ -0,0 +1,19 @@
+import { useState, useEffect } from 'react';
+
+export function useTimer(seconds: number, active: boolean, onTimeout: () => void) {
+ const [time, setTime] = useState(seconds);
+
+ useEffect(() => {
+ if (!active) return;
+ setTime(seconds);
+ const timer = setInterval(() => {
+ setTime((t) => {
+ if (t <= 1) { clearInterval(timer); onTimeout(); return 0; }
+ return t - 1;
+ });
+ }, 1000);
+ return () => clearInterval(timer);
+ }, [active, seconds, onTimeout]);
+
+ return time;
+}