mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
feat(frontend): 类型定义 + i18n 中英双语 + Zustand store
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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',
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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": "语言"
|
||||
}
|
||||
}
|
||||
@@ -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' });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user