mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 00:45: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