mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 00:45:55 +08:00
feat(core): AI 棋形评分模块 — 连五/活四/冲四/活三等棋形打分
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
|||||||
|
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;
|
||||||
|
const OPEN_THREE: f64 = 1000.0;
|
||||||
|
const SLEEP_THREE: f64 = 500.0;
|
||||||
|
const OPEN_TWO: f64 = 100.0;
|
||||||
|
const SLEEP_TWO: f64 = 50.0;
|
||||||
|
const OPEN_ONE: f64 = 10.0;
|
||||||
|
|
||||||
|
/// 评估整个棋盘对 player 的得分 (player得分 - 对手得分)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for x in 0..size {
|
||||||
|
for y in 0..size {
|
||||||
|
if board.get(Position::new(x, y)) != CellState::Occupied(color) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for &(dx, dy) in &directions {
|
||||||
|
let (count, start_open, end_open) =
|
||||||
|
scan_pattern(board, Position::new(x, y), color, dx, dy);
|
||||||
|
total += score_pattern(count, start_open, end_open);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从 pos 向 (dx,dy) 方向扫描, 只计数起点
|
||||||
|
fn scan_pattern(
|
||||||
|
board: &Board,
|
||||||
|
pos: Position,
|
||||||
|
color: Color,
|
||||||
|
dx: isize,
|
||||||
|
dy: isize,
|
||||||
|
) -> (u32, bool, bool) {
|
||||||
|
let mut count = 1u32;
|
||||||
|
|
||||||
|
// 正方向
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return (0, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
(count, start_open, end_open)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn score_pattern(count: u32, start_open: bool, end_open: bool) -> f64 {
|
||||||
|
let open_count = start_open as u32 + end_open as u32;
|
||||||
|
match (count, open_count) {
|
||||||
|
(5, _) => FIVE,
|
||||||
|
(4, 2) => OPEN_FOUR,
|
||||||
|
(4, 1) => RUSH_FOUR,
|
||||||
|
(3, 2) => OPEN_THREE,
|
||||||
|
(3, 1) => SLEEP_THREE,
|
||||||
|
(2, 2) => OPEN_TWO,
|
||||||
|
(2, 1) => SLEEP_TWO,
|
||||||
|
(1, 2) => OPEN_ONE,
|
||||||
|
_ => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_bounds(board: &Board, x: isize, y: isize) -> bool {
|
||||||
|
x >= 0 && y >= 0 && (x as usize) < board.size && (y as usize) < board.size
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::board::Board;
|
||||||
|
use crate::types::{Color, Position};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_evaluate_empty_board() {
|
||||||
|
let board = Board::new(15);
|
||||||
|
let score = evaluate_board(&board, Color::Black);
|
||||||
|
assert_eq!(score, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_five_in_a_row_high_score() {
|
||||||
|
let board = Board::new(15);
|
||||||
|
let mut board = board;
|
||||||
|
for y in 5..10 {
|
||||||
|
board = board.place(Position::new(7, y), Color::Black).unwrap();
|
||||||
|
}
|
||||||
|
let score = evaluate_board(&board, Color::Black);
|
||||||
|
assert!(score > 10000.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
use crate::board::Board;
|
||||||
|
use crate::types::{Color, Position};
|
||||||
|
|
||||||
|
/// AI 引擎统一接口
|
||||||
|
pub trait AiEngine: Send + Sync {
|
||||||
|
/// 返回 AI 的最佳落子位置, 无位置返回 None
|
||||||
|
fn best_move(&self, board: &Board, color: Color) -> Option<Position>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod evaluate;
|
||||||
|
pub mod search;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
//! AI 搜索模块 — 占位符, 待实现 Alpha-Beta 搜索
|
||||||
|
//! TODO: 在 Task 6 中实现完整搜索逻辑
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Gobang core library — 纯游戏逻辑,零 GUI 依赖
|
// Gobang core library — 纯游戏逻辑,零 GUI 依赖
|
||||||
|
|
||||||
|
pub mod ai;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod rules;
|
pub mod rules;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
Reference in New Issue
Block a user