From 1aa1a3c2c6f2696404815e8682eb97e8109ee129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Sun, 31 May 2026 15:43:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=84=E5=90=88=E6=A3=8B=E5=BD=A2?= =?UTF-8?q?=E8=AF=84=E4=BC=B0=20+=20=E4=BD=8D=E7=BD=AE=E6=9D=83=E9=87=8D?= =?UTF-8?q?=20=E2=80=94=20=E5=8F=8C=E6=B4=BB=E4=B8=89/=E5=86=B2=E5=9B=9B?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=20+=204=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- core/src/ai/evaluate.rs | 132 ++++++++++++++++++++++++++++++++-------- 1 file changed, 105 insertions(+), 27 deletions(-) diff --git a/core/src/ai/evaluate.rs b/core/src/ai/evaluate.rs index c931951..87ee573 100644 --- a/core/src/ai/evaluate.rs +++ b/core/src/ai/evaluate.rs @@ -1,7 +1,6 @@ use crate::board::Board; use crate::types::{CellState, Color, Position}; -/// 棋形分数 const FIVE: f64 = 100000.0; const OPEN_FOUR: f64 = 10000.0; const RUSH_FOUR: f64 = 5000.0; @@ -11,42 +10,93 @@ const OPEN_TWO: f64 = 100.0; const SLEEP_TWO: f64 = 50.0; const OPEN_ONE: f64 = 10.0; -/// 评估整个棋盘对 player 的得分 (player得分 - 对手得分) +// 组合加分 +const COMBO_THREE_THREE: f64 = 5000.0; +const COMBO_THREE_FOUR: f64 = 10000.0; +const COMBO_FOUR_FOUR: f64 = 8000.0; +const COMBO_THREE_TWO: f64 = 500.0; + +const POSITION_MAX_BONUS: f64 = 50.0; + +/// 评估棋盘对 player 的得分 (player - opponent) pub fn evaluate_board(board: &Board, player: Color) -> f64 { - let player_score = evaluate_player(board, player); - let opponent_score = evaluate_player(board, player.opponent()); - player_score - opponent_score + let p_score = evaluate_player(board, player); + let o_score = evaluate_player(board, player.opponent()); + p_score - o_score } fn evaluate_player(board: &Board, color: Color) -> f64 { let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)]; let mut total = 0.0f64; let size = board.size; + let center = (size as f64 - 1.0) / 2.0; for x in 0..size { for y in 0..size { if board.get(Position::new(x, y)) != CellState::Occupied(color) { continue; } + + let mut patterns: Vec<(u32, u32)> = Vec::with_capacity(4); for &(dx, dy) in &directions { - let (count, start_open, end_open) = + let (count, open_count, is_start) = scan_pattern(board, Position::new(x, y), color, dx, dy); - total += score_pattern(count, start_open, end_open); + // 始终记录模式信息,用于组合检测(交叉点需要) + patterns.push((count, open_count)); + // 只在起点处计分,避免重复 + if is_start && count >= 1 { + total += score_pattern(count, open_count); + } } + + // 组合棋形:交叉方向检测 + for i in 0..patterns.len() { + for j in (i + 1)..patterns.len() { + let (c1, o1) = patterns[i]; + let (c2, o2) = patterns[j]; + if c1 >= 3 && o1 == 2 && c2 >= 3 && o2 == 2 { + total += COMBO_THREE_THREE; + } + if (c1 >= 3 && o1 == 2 && c2 == 4 && o2 == 1) + || (c1 == 4 && o1 == 1 && c2 >= 3 && o2 == 2) + { + total += COMBO_THREE_FOUR; + } + if c1 == 4 && o1 == 1 && c2 == 4 && o2 == 1 { + total += COMBO_FOUR_FOUR; + } + if (c1 >= 3 && o1 == 2 && c2 == 2 && o2 == 2) + || (c1 == 2 && o1 == 2 && c2 >= 3 && o2 == 2) + { + total += COMBO_THREE_TWO; + } + } + } + + // 位置权重(高斯分布,中心最高) + let dx = x as f64 - center; + let dy = y as f64 - center; + let dist = (dx * dx + dy * dy).sqrt(); + let max_dist = center; + total += POSITION_MAX_BONUS * (1.0 - dist / max_dist).max(0.0); } } total } -/// 从 pos 向 (dx,dy) 方向扫描, 只计数起点 +/// 扫描从 pos 沿 (dx,dy) 方向的完整棋形。 +/// 返回 (总连子数, 开放端数, 是否连续段起点)。 +/// 总连子数和开放端数始终正确,供组合检测使用; +/// is_start 用于控制计分,避免重复。 fn scan_pattern( board: &Board, pos: Position, color: Color, dx: isize, dy: isize, -) -> (u32, bool, bool) { - let mut count = 1u32; +) -> (u32, u32, bool) { + let mut pos_count = 0u32; + let mut neg_count = 0u32; // 正方向 let mut nx = pos.x as isize + dx; @@ -54,31 +104,34 @@ fn scan_pattern( while in_bounds(board, nx, ny) && board.get(Position::new(nx as usize, ny as usize)) == CellState::Occupied(color) { - count += 1; + pos_count += 1; nx += dx; ny += dy; } let end_open = in_bounds(board, nx, ny) && board.get(Position::new(nx as usize, ny as usize)) == CellState::Empty; - // 反方向 (检查是否是起点) - let sx = pos.x as isize - dx; - let sy = pos.y as isize - dy; - let start_open = in_bounds(board, sx, sy) - && board.get(Position::new(sx as usize, sy as usize)) == CellState::Empty; - - // 如果不是连续段的起点, 不计分 (避免重复) - if in_bounds(board, sx, sy) - && board.get(Position::new(sx as usize, sy as usize)) == CellState::Occupied(color) + // 反方向 + let mut nx = pos.x as isize - dx; + let mut ny = pos.y as isize - dy; + while in_bounds(board, nx, ny) + && board.get(Position::new(nx as usize, ny as usize)) == CellState::Occupied(color) { - return (0, false, false); + neg_count += 1; + nx -= dx; + ny -= dy; } + let start_open = in_bounds(board, nx, ny) + && board.get(Position::new(nx as usize, ny as usize)) == CellState::Empty; - (count, start_open, end_open) + let total_count = 1 + pos_count + neg_count; + let open_count = (start_open as u32) + (end_open as u32); + let is_start = neg_count == 0; + + (total_count, open_count, is_start) } -fn score_pattern(count: u32, start_open: bool, end_open: bool) -> f64 { - let open_count = start_open as u32 + end_open as u32; +fn score_pattern(count: u32, open_count: u32) -> f64 { match (count, open_count) { (5, _) => FIVE, (4, 2) => OPEN_FOUR, @@ -105,8 +158,7 @@ mod tests { #[test] fn test_evaluate_empty_board() { let board = Board::new(15); - let score = evaluate_board(&board, Color::Black); - assert_eq!(score, 0.0); + assert_eq!(evaluate_board(&board, Color::Black), 0.0); } #[test] @@ -116,7 +168,33 @@ mod tests { for y in 5..10 { board = board.place(Position::new(7, y), Color::Black).unwrap(); } + assert!(evaluate_board(&board, Color::Black) > 10000.0); + } + + #[test] + fn test_center_worth_more_than_edge() { + let board = Board::new(15); + let b_center = board.place(Position::new(7, 7), Color::Black).unwrap(); + let b_edge = board.place(Position::new(0, 0), Color::Black).unwrap(); + assert!(evaluate_board(&b_center, Color::Black) > evaluate_board(&b_edge, Color::Black)); + } + + #[test] + fn test_combo_three_three() { + let board = Board::new(15); + let mut board = board; + // 水平活三: (7,5)(7,6)(7,7) — 两端(7,4)(7,8)空 + board = board.place(Position::new(7, 5), Color::Black).unwrap(); + board = board.place(Position::new(7, 6), Color::Black).unwrap(); + board = board.place(Position::new(7, 7), Color::Black).unwrap(); + // 垂直活三: (5,7)(6,7) 与 (7,7) 交叉 — 两端(4,7)(8,7)空 + board = board.place(Position::new(5, 7), Color::Black).unwrap(); + board = board.place(Position::new(6, 7), Color::Black).unwrap(); let score = evaluate_board(&board, Color::Black); - assert!(score > 10000.0); + assert!( + score > COMBO_THREE_THREE * 0.5, + "双活三应大幅加分, got {}", + score + ); } }