feat: 组合棋形评估 + 位置权重 — 双活三/冲四检测 + 4 测试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 15:43:08 +08:00
parent e6690a35fe
commit 1aa1a3c2c6
+105 -27
View File
@@ -1,7 +1,6 @@
use crate::board::Board; use crate::board::Board;
use crate::types::{CellState, Color, Position}; use crate::types::{CellState, Color, Position};
/// 棋形分数
const FIVE: f64 = 100000.0; const FIVE: f64 = 100000.0;
const OPEN_FOUR: f64 = 10000.0; const OPEN_FOUR: f64 = 10000.0;
const RUSH_FOUR: f64 = 5000.0; const RUSH_FOUR: f64 = 5000.0;
@@ -11,42 +10,93 @@ const OPEN_TWO: f64 = 100.0;
const SLEEP_TWO: f64 = 50.0; const SLEEP_TWO: f64 = 50.0;
const OPEN_ONE: f64 = 10.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 { pub fn evaluate_board(board: &Board, player: Color) -> f64 {
let player_score = evaluate_player(board, player); let p_score = evaluate_player(board, player);
let opponent_score = evaluate_player(board, player.opponent()); let o_score = evaluate_player(board, player.opponent());
player_score - opponent_score p_score - o_score
} }
fn evaluate_player(board: &Board, color: Color) -> f64 { fn evaluate_player(board: &Board, color: Color) -> f64 {
let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)]; let directions: [(isize, isize); 4] = [(0, 1), (1, 0), (1, 1), (1, -1)];
let mut total = 0.0f64; let mut total = 0.0f64;
let size = board.size; let size = board.size;
let center = (size as f64 - 1.0) / 2.0;
for x in 0..size { for x in 0..size {
for y in 0..size { for y in 0..size {
if board.get(Position::new(x, y)) != CellState::Occupied(color) { if board.get(Position::new(x, y)) != CellState::Occupied(color) {
continue; continue;
} }
let mut patterns: Vec<(u32, u32)> = Vec::with_capacity(4);
for &(dx, dy) in &directions { 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); 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 total
} }
/// 从 pos (dx,dy) 方向扫描, 只计数起点 /// 扫描从 pos 沿 (dx,dy) 方向的完整棋形。
/// 返回 (总连子数, 开放端数, 是否连续段起点)。
/// 总连子数和开放端数始终正确,供组合检测使用;
/// is_start 用于控制计分,避免重复。
fn scan_pattern( fn scan_pattern(
board: &Board, board: &Board,
pos: Position, pos: Position,
color: Color, color: Color,
dx: isize, dx: isize,
dy: isize, dy: isize,
) -> (u32, bool, bool) { ) -> (u32, u32, bool) {
let mut count = 1u32; let mut pos_count = 0u32;
let mut neg_count = 0u32;
// 正方向 // 正方向
let mut nx = pos.x as isize + dx; let mut nx = pos.x as isize + dx;
@@ -54,31 +104,34 @@ fn scan_pattern(
while in_bounds(board, nx, ny) while in_bounds(board, nx, ny)
&& board.get(Position::new(nx as usize, ny as usize)) == CellState::Occupied(color) && board.get(Position::new(nx as usize, ny as usize)) == CellState::Occupied(color)
{ {
count += 1; pos_count += 1;
nx += dx; nx += dx;
ny += dy; ny += dy;
} }
let end_open = in_bounds(board, nx, ny) let end_open = in_bounds(board, nx, ny)
&& board.get(Position::new(nx as usize, ny as usize)) == CellState::Empty; && board.get(Position::new(nx as usize, ny as usize)) == CellState::Empty;
// 反方向 (检查是否是起点) // 反方向
let sx = pos.x as isize - dx; let mut nx = pos.x as isize - dx;
let sy = pos.y as isize - dy; let mut ny = pos.y as isize - dy;
let start_open = in_bounds(board, sx, sy) while in_bounds(board, nx, ny)
&& board.get(Position::new(sx as usize, sy as usize)) == CellState::Empty; && board.get(Position::new(nx as usize, ny as usize)) == CellState::Occupied(color)
// 如果不是连续段的起点, 不计分 (避免重复)
if in_bounds(board, sx, sy)
&& board.get(Position::new(sx as usize, sy 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 { fn score_pattern(count: u32, open_count: u32) -> f64 {
let open_count = start_open as u32 + end_open as u32;
match (count, open_count) { match (count, open_count) {
(5, _) => FIVE, (5, _) => FIVE,
(4, 2) => OPEN_FOUR, (4, 2) => OPEN_FOUR,
@@ -105,8 +158,7 @@ mod tests {
#[test] #[test]
fn test_evaluate_empty_board() { fn test_evaluate_empty_board() {
let board = Board::new(15); let board = Board::new(15);
let score = evaluate_board(&board, Color::Black); assert_eq!(evaluate_board(&board, Color::Black), 0.0);
assert_eq!(score, 0.0);
} }
#[test] #[test]
@@ -116,7 +168,33 @@ mod tests {
for y in 5..10 { for y in 5..10 {
board = board.place(Position::new(7, y), Color::Black).unwrap(); 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); let score = evaluate_board(&board, Color::Black);
assert!(score > 10000.0); assert!(
score > COMBO_THREE_THREE * 0.5,
"双活三应大幅加分, got {}",
score
);
} }
} }