mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
fix: 代码审查修复 — serde camelCase/CSP/TS检查/replay/undo/AI禁手/星位/未使用依赖
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,4 @@ repository.workspace = true
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
renet = "2"
|
||||
reqwest = { version = "0.12", features = ["json", "blocking"] }
|
||||
rand = "0.8"
|
||||
|
||||
+9
-16
@@ -1,6 +1,7 @@
|
||||
use crate::ai::evaluate::evaluate_board;
|
||||
use crate::ai::AiEngine;
|
||||
use crate::board::Board;
|
||||
use crate::rules;
|
||||
use crate::types::{Color, Position};
|
||||
|
||||
/// Alpha-Beta AI 引擎
|
||||
@@ -25,6 +26,10 @@ impl AiEngine for AlphaBetaAi {
|
||||
let mut best_score = f64::NEG_INFINITY;
|
||||
|
||||
for &pos in &candidates {
|
||||
// 禁手检查: 黑棋不能走禁手位置
|
||||
if rules::is_forbidden(board, pos, color) {
|
||||
continue;
|
||||
}
|
||||
if let Ok(new_board) = board.place(pos, color) {
|
||||
if new_board.check_win(pos) {
|
||||
return Some(pos);
|
||||
@@ -48,14 +53,7 @@ impl AiEngine for AlphaBetaAi {
|
||||
}
|
||||
|
||||
impl AlphaBetaAi {
|
||||
fn negamax(
|
||||
&self,
|
||||
board: &Board,
|
||||
depth: usize,
|
||||
mut alpha: f64,
|
||||
beta: f64,
|
||||
color: Color,
|
||||
) -> f64 {
|
||||
fn negamax(&self, board: &Board, depth: usize, mut alpha: f64, beta: f64, color: Color) -> f64 {
|
||||
if depth == 0 {
|
||||
return evaluate_board(board, color);
|
||||
}
|
||||
@@ -65,9 +63,10 @@ impl AlphaBetaAi {
|
||||
return evaluate_board(board, color);
|
||||
}
|
||||
|
||||
// 启发式排序:先评估每步棋,优先搜索高分走法
|
||||
// 启发式排序:先评估每步棋,优先搜索高分走法 (跳过禁手)
|
||||
let mut scored: Vec<(Position, f64)> = candidates
|
||||
.into_iter()
|
||||
.filter(|&pos| !rules::is_forbidden(board, pos, color))
|
||||
.filter_map(|pos| {
|
||||
board.place(pos, color).ok().map(|b| {
|
||||
if b.check_win(pos) {
|
||||
@@ -87,13 +86,7 @@ impl AlphaBetaAi {
|
||||
if new_board.check_win(pos) {
|
||||
return f64::INFINITY;
|
||||
}
|
||||
let val = -self.negamax(
|
||||
&new_board,
|
||||
depth - 1,
|
||||
-beta,
|
||||
-alpha,
|
||||
color.opponent(),
|
||||
);
|
||||
let val = -self.negamax(&new_board, depth - 1, -beta, -alpha, color.opponent());
|
||||
if val > max_val {
|
||||
max_val = val;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,12 @@ impl LlmAi {
|
||||
}
|
||||
|
||||
impl AiEngine for LlmAi {
|
||||
/// 获取 AI 最佳走法。
|
||||
///
|
||||
/// TODO: 当前使用阻塞 HTTP 客户端 (`reqwest::blocking`),
|
||||
/// 在 GUI 线程调用会冻结界面。上层应在独立线程
|
||||
/// (`std::thread::spawn` 或 `tauri::async_runtime::spawn_blocking`) 中调用此方法,
|
||||
/// 或改用 async 版本。
|
||||
fn best_move(&self, board: &Board, color: Color) -> Option<Position> {
|
||||
let prompt = Self::board_to_prompt(board, color);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
|
||||
+15
-12
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::board::Board;
|
||||
use crate::types::{Color, Position};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 对局棋谱
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -42,15 +42,19 @@ impl GameRecord {
|
||||
Color::Black => black.to_string(),
|
||||
Color::White => white.to_string(),
|
||||
});
|
||||
let moves = board.history().iter().map(|m| RecordMove {
|
||||
x: m.position.x,
|
||||
y: m.position.y,
|
||||
color: match m.color {
|
||||
Color::Black => "Black".into(),
|
||||
Color::White => "White".into(),
|
||||
},
|
||||
turn: m.turn,
|
||||
}).collect();
|
||||
let moves = board
|
||||
.history()
|
||||
.iter()
|
||||
.map(|m| RecordMove {
|
||||
x: m.position.x,
|
||||
y: m.position.y,
|
||||
color: match m.color {
|
||||
Color::Black => "Black".into(),
|
||||
Color::White => "White".into(),
|
||||
},
|
||||
turn: m.turn,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
version: "2.0".to_string(),
|
||||
@@ -101,8 +105,7 @@ mod tests {
|
||||
let board = board.place(Position::new(7, 7), Color::Black).unwrap();
|
||||
let board = board.place(Position::new(7, 8), Color::White).unwrap();
|
||||
|
||||
let record =
|
||||
GameRecord::from_board(&board, "Human", "AI-Lv3", Some(Color::Black));
|
||||
let record = GameRecord::from_board(&board, "Human", "AI-Lv3", Some(Color::Black));
|
||||
let json = serde_json::to_string_pretty(&record).unwrap();
|
||||
|
||||
let loaded: GameRecord = serde_json::from_str(&json).unwrap();
|
||||
|
||||
@@ -99,6 +99,7 @@ pub struct GameResult {
|
||||
|
||||
/// 游戏模式 (Tauri IPC 兼容 — 纯标签, 不含字段)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum GameMode {
|
||||
Local,
|
||||
VsAi,
|
||||
@@ -108,6 +109,7 @@ pub enum GameMode {
|
||||
|
||||
/// 游戏配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GameConfig {
|
||||
pub board_size: usize,
|
||||
pub use_forbidden_rules: bool,
|
||||
@@ -116,6 +118,8 @@ pub struct GameConfig {
|
||||
pub ai_difficulty: u32,
|
||||
pub player_color: Color,
|
||||
pub is_server: bool,
|
||||
#[serde(default)]
|
||||
pub remote_address: String,
|
||||
}
|
||||
|
||||
impl Default for GameConfig {
|
||||
@@ -128,6 +132,7 @@ impl Default for GameConfig {
|
||||
ai_difficulty: 3,
|
||||
player_color: Color::Black,
|
||||
is_server: false,
|
||||
remote_address: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user