feat(frontend): 类型定义 + i18n 中英双语 + Zustand store

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 00:20:32 +08:00
parent 36f6b15b8e
commit eeafa92e90
6 changed files with 238 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
export const DEFAULT_BOARD_SIZE = 15;
export const MIN_BOARD_SIZE = 9;
export const MAX_BOARD_SIZE = 19;
export const CELL_COLORS: Record<number, string> = {
0: 'transparent',
1: '#1a1a1a',
2: '#f5f5f5',
};
+34
View File
@@ -0,0 +1,34 @@
export type Color = 'Black' | 'White';
export interface Position {
x: number;
y: number;
}
export type CellState = 0 | 1 | 2; // 0=Empty, 1=Black, 2=White
export type GameStatus = 'waiting' | 'playing' | 'ai_thinking' | 'game_over';
export type GameModeType = 'Local' | 'VsAi' | 'Online' | 'Replay';
export interface GameConfig {
boardSize: number;
useForbiddenRules: boolean;
useTimer: boolean;
timeLimitSecs: number;
aiDifficulty: number;
playerColor: Color;
isServer: boolean;
}
export interface MoveResult {
position: Position;
is_win: boolean;
is_forbidden: boolean;
}
export interface Move {
position: Position;
color: Color;
turn: number;
}
+40
View File
@@ -0,0 +1,40 @@
{
"app": { "title": "Gobang v2.0" },
"menu": {
"local_game": "Local 2-Player",
"ai_game": "VS AI",
"online_game": "Online",
"load_replay": "Load Replay",
"settings": "Settings"
},
"game": {
"black_turn": "Black's Turn",
"white_turn": "White's Turn",
"black_win": "Black Wins!",
"white_win": "White Wins!",
"draw": "Draw",
"ai_thinking": "AI Thinking...",
"undo": "Undo",
"resign": "Resign",
"save": "Save Record",
"new_game": "New Game",
"waiting_opponent": "Waiting for Opponent...",
"your_turn": "Your Turn",
"opponent_turn": "Opponent's Turn"
},
"replay": {
"play": "Play",
"pause": "Pause",
"next": "Next",
"prev": "Prev",
"step": "Step {{current}}/{{total}}"
},
"settings": {
"board_size": "Board Size",
"forbidden_rules": "Forbidden Rules",
"timer": "Timer",
"time_limit": "Time Limit (s)",
"difficulty": "AI Difficulty",
"language": "Language"
}
}
+18
View File
@@ -0,0 +1,18 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import zhCN from './zh-CN.json';
import en from './en.json';
i18n
.use(initReactI18next)
.init({
resources: {
'zh-CN': { translation: zhCN },
en: { translation: en },
},
lng: 'zh-CN',
fallbackLng: 'zh-CN',
interpolation: { escapeValue: false },
});
export default i18n;
+40
View File
@@ -0,0 +1,40 @@
{
"app": { "title": "五子棋 v2.0" },
"menu": {
"local_game": "本地双人",
"ai_game": "人机对战",
"online_game": "网络对战",
"load_replay": "加载棋谱",
"settings": "设置"
},
"game": {
"black_turn": "黑棋回合",
"white_turn": "白棋回合",
"black_win": "黑棋获胜!",
"white_win": "白棋获胜!",
"draw": "平局",
"ai_thinking": "AI 思考中...",
"undo": "悔棋",
"resign": "认输",
"save": "保存棋谱",
"new_game": "新游戏",
"waiting_opponent": "等待对手加入...",
"your_turn": "你的回合",
"opponent_turn": "对手回合"
},
"replay": {
"play": "播放",
"pause": "暂停",
"next": "下一步",
"prev": "上一步",
"step": "第 {{current}}/{{total}} 步"
},
"settings": {
"board_size": "棋盘大小",
"forbidden_rules": "禁手规则",
"timer": "计时器",
"time_limit": "时间限制(秒)",
"difficulty": "AI 难度",
"language": "语言"
}
}
+97
View File
@@ -0,0 +1,97 @@
import { create } from 'zustand';
import { invoke } from '@tauri-apps/api/core';
import type { CellState, Color, GameConfig, GameModeType, GameStatus, Move, MoveResult } from '../core/types';
interface GameState {
mode: GameModeType;
board: CellState[][];
boardSize: number;
currentColor: Color;
status: GameStatus;
winner: Color | null;
moves: Move[];
config: GameConfig;
isSaving: boolean;
startGame: (mode: GameModeType, config: GameConfig) => Promise<void>;
placePiece: (x: number, y: number) => Promise<MoveResult>;
undo: (steps?: number) => Promise<void>;
aiMove: () => Promise<void>;
refreshBoard: () => Promise<void>;
loadReplayBoard: (board: CellState[][], moves: Move[]) => void;
}
export const useGameStore = create<GameState>((set, get) => ({
mode: 'Local',
board: [],
boardSize: 15,
currentColor: 'Black',
status: 'waiting',
winner: null,
moves: [],
config: {
boardSize: 15,
useForbiddenRules: true,
useTimer: false,
timeLimitSecs: 60,
aiDifficulty: 3,
playerColor: 'Black',
isServer: false,
},
isSaving: false,
startGame: async (mode, config) => {
await invoke('new_game', { mode, config });
set({
mode,
config,
boardSize: config.boardSize,
status: mode === 'VsAi' && config.playerColor === 'White' ? 'ai_thinking' : 'playing',
currentColor: 'Black',
winner: null,
moves: [],
});
await get().refreshBoard();
},
placePiece: async (x, y) => {
const result: MoveResult = await invoke('place_piece', { x, y });
await get().refreshBoard();
if (result.is_win) {
set({ status: 'game_over' });
}
return result;
},
undo: async (steps = 1) => {
await invoke('undo', { steps });
await get().refreshBoard();
},
aiMove: async () => {
set({ status: 'ai_thinking' });
const pos: [number, number] | null = await invoke('ai_move');
if (pos) {
const result = await get().placePiece(pos[0], pos[1]);
if (!result.is_win) {
set({ status: 'playing' });
}
} else {
set({ status: 'playing' });
}
},
refreshBoard: async () => {
const state: { board: CellState[][]; current_color: string; game_over: boolean } =
await invoke('get_game_state');
set({
board: state.board,
currentColor: state.current_color as Color,
status: state.game_over ? 'game_over' : get().status === 'ai_thinking' ? 'ai_thinking' : 'playing',
});
},
loadReplayBoard: (board, moves) => {
set({ board, moves, mode: 'Replay', status: 'playing' });
},
}));