diff --git a/package-lock.json b/package-lock.json index 6069a13..8fc9c26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@tauri-apps/cli": "^2.0.0", + "@types/node": "^25.9.1", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.4.0", @@ -1472,6 +1473,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, "node_modules/@types/react": { "version": "19.2.15", "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.15.tgz", @@ -2360,6 +2371,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/package.json b/package.json index b58ec50..fdbd73c 100644 --- a/package.json +++ b/package.json @@ -11,21 +11,22 @@ "test:watch": "vitest --watch" }, "dependencies": { + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-opener": "^2.0.0", + "i18next": "^24.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "zustand": "^5.0.0", - "i18next": "^24.0.0", "react-i18next": "^15.0.0", - "@tauri-apps/api": "^2.0.0", - "@tauri-apps/plugin-opener": "^2.0.0" + "zustand": "^5.0.0" }, "devDependencies": { + "@tauri-apps/cli": "^2.0.0", + "@types/node": "^25.9.1", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.4.0", "typescript": "~5.7.0", "vite": "^6.0.0", - "vitest": "^3.0.0", - "@tauri-apps/cli": "^2.0.0" + "vitest": "^3.0.0" } } diff --git a/src/App.css b/src/App.css index d3e9b8b..0850123 100644 --- a/src/App.css +++ b/src/App.css @@ -1,7 +1,190 @@ +:root { + --bg-primary: #3C2415; + --bg-secondary: #F5DEB3; + --text-primary: #F5DEB3; + --text-secondary: #3C2415; + --accent: #8B4513; + --accent-hover: #A0522D; + --button-bg: #DEB887; + --button-hover: #D2B48C; + --border: #8B7355; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Microsoft YaHei', sans-serif; + background-color: var(--bg-primary); + color: var(--text-primary); +} + .app { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +.main-menu { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; + gap: 40px; +} + +.menu-title { + font-size: 42px; + color: var(--text-primary); + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); +} + +.menu-buttons { + display: flex; + flex-direction: column; + gap: 16px; +} + +.menu-buttons button { + width: 240px; + padding: 14px 28px; + font-size: 18px; + border: 2px solid var(--border); + border-radius: 8px; + background: var(--button-bg); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; +} + +.menu-buttons button:hover { + background: var(--button-hover); + transform: scale(1.03); +} + +.setup-panel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + gap: 24px; +} + +.setup-panel h2 { + font-size: 28px; +} + +.setup-panel label { + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; +} + +.setup-panel select, .setup-panel input { + padding: 6px 12px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg-secondary); + color: var(--text-secondary); + font-size: 14px; +} + +.setup-actions { + display: flex; + gap: 12px; + margin-top: 16px; +} + +button { + padding: 10px 24px; + font-size: 16px; + border: 2px solid var(--border); + border-radius: 6px; + background: var(--button-bg); + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; +} + +button:hover { + background: var(--button-hover); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.game-view { + display: flex; + flex-direction: column; + align-items: center; + width: 100vw; + height: 100vh; + padding: 12px; + gap: 8px; +} + +.board-container { + flex: 1; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.game-info { + font-size: 20px; + font-weight: bold; + padding: 8px; +} + +.game-controls { + display: flex; + gap: 12px; + padding: 8px; +} + +.timer-display { + font-size: 24px; + font-family: monospace; +} + +.timer-warning { + color: #ff4444; + animation: blink 0.5s infinite alternate; +} + +@keyframes blink { + from { opacity: 1; } + to { opacity: 0.3; } +} + +.replay-view { + display: flex; + flex-direction: column; + align-items: center; + width: 100vw; + height: 100vh; + padding: 12px; + gap: 8px; +} + +.step-slider { + width: 80%; + accent-color: var(--accent); +} + +.replay-controls { + display: flex; + gap: 12px; } diff --git a/src/App.tsx b/src/App.tsx index 5e48a8a..27d7d32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,26 @@ +import { useState } from 'react'; +import MainMenu from './components/menu/MainMenu'; +import GameView from './components/game/GameView'; +import ReplayView from './components/replay/ReplayView'; +import './App.css'; + +type Page = 'menu' | 'game' | 'replay'; + function App() { + const [page, setPage] = useState('menu'); + + const handleGameStart = () => setPage('game'); + const handleReplayStart = () => setPage('replay'); + const handleBackToMenu = () => setPage('menu'); + + if (page === 'game') return ; + if (page === 'replay') return ; + return ( -
-

五子棋 v2.0

-
+ ); } diff --git a/src/components/menu/MainMenu.tsx b/src/components/menu/MainMenu.tsx index 7e2a86c..e279d20 100644 --- a/src/components/menu/MainMenu.tsx +++ b/src/components/menu/MainMenu.tsx @@ -9,16 +9,17 @@ type View = 'main' | 'local' | 'ai' | 'online' | 'replay'; interface Props { onGameStart: () => void; + onReplayStart: () => void; } -export default function MainMenu({ onGameStart }: Props) { +export default function MainMenu({ onGameStart, onReplayStart }: Props) { const { t } = useTranslation(); const [view, setView] = useState('main'); if (view === 'local') return setView('main')} onStart={onGameStart} />; if (view === 'ai') return setView('main')} onStart={onGameStart} />; if (view === 'online') return setView('main')} onStart={onGameStart} />; - if (view === 'replay') return setView('main')} onStart={onGameStart} />; + if (view === 'replay') return setView('main')} onStart={onReplayStart} />; return (
diff --git a/tsconfig.node.json b/tsconfig.node.json index d3c52ea..a796fe5 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -17,7 +17,6 @@ /* Linting */ "noUnusedLocals": true, "noUnusedParameters": true, - "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true }, "include": ["vite.config.ts"]