mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 00:45:55 +08:00
feat: 组合棋形评估 + 位置权重 — 双活三/冲四检测 + 4 测试
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+105
-27
@@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user