fix: 代码审查修复 — serde camelCase/CSP/TS检查/replay/undo/AI禁手/星位/未使用依赖

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 12:51:53 +08:00
parent bb4f393229
commit ffcc7a7675
27 changed files with 9032 additions and 181 deletions
-2
View File
@@ -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
View File
@@ -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;
}
+6
View File
@@ -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
View File
@@ -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();
+5
View File
@@ -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(),
}
}
}