mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
feat: 接入 LLM AI 引擎到 GUI,GameConfig 支持 useLlm 切换 AI 类型
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -121,6 +121,14 @@ pub struct GameConfig {
|
|||||||
pub is_server: bool,
|
pub is_server: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub remote_address: String,
|
pub remote_address: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub use_llm: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub llm_endpoint: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub llm_api_key: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub llm_model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GameConfig {
|
impl Default for GameConfig {
|
||||||
@@ -134,6 +142,10 @@ impl Default for GameConfig {
|
|||||||
player_color: Color::Black,
|
player_color: Color::Black,
|
||||||
is_server: false,
|
is_server: false,
|
||||||
remote_address: String::new(),
|
remote_address: String::new(),
|
||||||
|
use_llm: false,
|
||||||
|
llm_endpoint: String::new(),
|
||||||
|
llm_api_key: String::new(),
|
||||||
|
llm_model: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-14
@@ -1,5 +1,6 @@
|
|||||||
use gobang_core::ai::search::AlphaBetaAi;
|
use gobang_core::ai::search::AlphaBetaAi;
|
||||||
use gobang_core::ai::AiEngine;
|
use gobang_core::ai::AiEngine;
|
||||||
|
use gobang_core::llm::LlmAi;
|
||||||
use gobang_core::board::Board;
|
use gobang_core::board::Board;
|
||||||
use gobang_core::rules;
|
use gobang_core::rules;
|
||||||
use gobang_core::types::*;
|
use gobang_core::types::*;
|
||||||
@@ -11,7 +12,7 @@ pub struct AppState {
|
|||||||
pub board: Mutex<Option<Board>>,
|
pub board: Mutex<Option<Board>>,
|
||||||
pub game_mode: Mutex<GameMode>,
|
pub game_mode: Mutex<GameMode>,
|
||||||
pub config: Mutex<GameConfig>,
|
pub config: Mutex<GameConfig>,
|
||||||
pub ai_engine: Mutex<Option<AlphaBetaAi>>,
|
pub ai_engine: Mutex<Option<Box<dyn AiEngine + Send>>>,
|
||||||
pub current_color: Mutex<Color>,
|
pub current_color: Mutex<Color>,
|
||||||
pub game_over: Mutex<bool>,
|
pub game_over: Mutex<bool>,
|
||||||
}
|
}
|
||||||
@@ -42,7 +43,15 @@ pub fn new_game(mode: GameMode, config: GameConfig, state: State<AppState>) -> R
|
|||||||
|
|
||||||
// 初始化 AI (如果是人机模式)
|
// 初始化 AI (如果是人机模式)
|
||||||
if is_vs_ai {
|
if is_vs_ai {
|
||||||
let ai = AlphaBetaAi::new(config.ai_difficulty as usize);
|
let ai: Box<dyn AiEngine + Send> = if config.use_llm {
|
||||||
|
Box::new(LlmAi::new(
|
||||||
|
&config.llm_endpoint,
|
||||||
|
&config.llm_api_key,
|
||||||
|
&config.llm_model,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Box::new(AlphaBetaAi::new(config.ai_difficulty as usize))
|
||||||
|
};
|
||||||
*state.ai_engine.lock().map_err(|e| e.to_string())? = Some(ai);
|
*state.ai_engine.lock().map_err(|e| e.to_string())? = Some(ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,24 +130,21 @@ pub fn undo(steps: u32, state: State<AppState>) -> Result<(), String> {
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn ai_move(state: State<AppState>) -> Result<Option<(usize, usize)>, String> {
|
pub fn ai_move(state: State<AppState>) -> Result<Option<(usize, usize)>, String> {
|
||||||
let (board_clone, color, ai_clone) = {
|
// 预先提取棋盘和当前颜色,释放 board/color 锁
|
||||||
|
let (board_clone, color) = {
|
||||||
let board_opt = state.board.lock().map_err(|e| e.to_string())?;
|
let board_opt = state.board.lock().map_err(|e| e.to_string())?;
|
||||||
let board = board_opt.as_ref().ok_or("游戏未开始")?.clone();
|
let board = board_opt.as_ref().ok_or("游戏未开始")?.clone();
|
||||||
let color = *state.current_color.lock().map_err(|e| e.to_string())?;
|
let color = *state.current_color.lock().map_err(|e| e.to_string())?;
|
||||||
let ai_guard = state.ai_engine.lock().map_err(|e| e.to_string())?;
|
(board, color)
|
||||||
let ai = ai_guard.as_ref().ok_or("AI 未初始化")?.clone();
|
|
||||||
(board, color, ai)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
// 持有 ai_engine 锁同步调用 best_move(阻塞 IPC 命令,但只影响 AI 走棋)
|
||||||
std::thread::spawn(move || {
|
let ai_guard = state.ai_engine.lock().map_err(|e| e.to_string())?;
|
||||||
let result = ai_clone.best_move(&board_clone, color);
|
let ai = ai_guard.as_ref().ok_or("AI 未初始化")?;
|
||||||
let _ = tx.send(result);
|
let result = ai.best_move(&board_clone, color);
|
||||||
});
|
drop(ai_guard);
|
||||||
|
|
||||||
rx.recv_timeout(std::time::Duration::from_secs(30))
|
Ok(result.map(|p| (p.x, p.y)))
|
||||||
.map_err(|_| "AI 计算超时".to_string())
|
|
||||||
.map(|r| r.map(|p| (p.x, p.y)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export interface GameConfig {
|
|||||||
playerColor: Color;
|
playerColor: Color;
|
||||||
isServer: boolean;
|
isServer: boolean;
|
||||||
remoteAddress: string;
|
remoteAddress: string;
|
||||||
|
useLlm?: boolean;
|
||||||
|
llmEndpoint?: string;
|
||||||
|
llmApiKey?: string;
|
||||||
|
llmModel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MoveResult {
|
export interface MoveResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user