diff --git a/core/src/ai/mod.rs b/core/src/ai/mod.rs index f622fed..3845562 100644 --- a/core/src/ai/mod.rs +++ b/core/src/ai/mod.rs @@ -9,7 +9,7 @@ pub trait AiEngine: Send + Sync { pub mod evaluate; pub mod killer; +pub mod opening; pub mod search; pub mod trans_table; pub mod vcf; -pub mod opening; diff --git a/core/src/ai/opening.rs b/core/src/ai/opening.rs index ec470d2..3a9a5e6 100644 --- a/core/src/ai/opening.rs +++ b/core/src/ai/opening.rs @@ -112,7 +112,10 @@ mod tests { let book = OpeningBook::new(); let board = Board::new(15); // 开局库在走子后才能匹配,空棋盘作为兜底结果也合理 - assert!(book.lookup(board.hash()).is_none(), "空棋盘不应匹配(需至少一手)"); + assert!( + book.lookup(board.hash()).is_none(), + "空棋盘不应匹配(需至少一手)" + ); } #[test] @@ -126,18 +129,10 @@ mod tests { let book = OpeningBook::new(); let board = Board::new(15); // 花月前4手: 黑(7,7) 白(7,8) 黑(6,7) 白(6,6) - let board = board - .place(Position::new(7, 7), Color::Black) - .unwrap(); - let board = board - .place(Position::new(7, 8), Color::White) - .unwrap(); - let board = board - .place(Position::new(6, 7), Color::Black) - .unwrap(); - let board = board - .place(Position::new(6, 6), Color::White) - .unwrap(); + let board = board.place(Position::new(7, 7), Color::Black).unwrap(); + let board = board.place(Position::new(7, 8), Color::White).unwrap(); + let board = board.place(Position::new(6, 7), Color::Black).unwrap(); + let board = board.place(Position::new(6, 6), Color::White).unwrap(); assert!(book.lookup(board.hash()).is_some(), "花月前4手应匹配"); } } diff --git a/core/src/ai/search.rs b/core/src/ai/search.rs index 2683359..f229486 100644 --- a/core/src/ai/search.rs +++ b/core/src/ai/search.rs @@ -62,9 +62,8 @@ impl AiEngine for AlphaBetaAi { break; } - let (pos, completed) = self.search_depth( - board, color, depth, &mut tt, &mut killer, start, time_limit, - ); + let (pos, completed) = + self.search_depth(board, color, depth, &mut tt, &mut killer, start, time_limit); if let Some(p) = pos { best_pos = p; @@ -81,9 +80,14 @@ impl AiEngine for AlphaBetaAi { impl AlphaBetaAi { fn search_depth( - &self, board: &Board, color: Color, depth: u32, - tt: &mut TransTable, killer: &mut KillerTable, - start: Instant, time_limit: Duration, + &self, + board: &Board, + color: Color, + depth: u32, + tt: &mut TransTable, + killer: &mut KillerTable, + start: Instant, + time_limit: Duration, ) -> (Option, bool) { let candidates = board.get_candidate_moves(); if candidates.is_empty() { @@ -103,8 +107,11 @@ impl AlphaBetaAi { .filter(|&&p| !rules::is_forbidden(board, p, color)) .filter_map(|&p| { board.place(p, color).ok().map(|b| { - if b.check_win(p) { (p, f64::INFINITY) } - else { (p, evaluate_board(&b, color)) } + if b.check_win(p) { + (p, f64::INFINITY) + } else { + (p, evaluate_board(&b, color)) + } }) }) .collect(); @@ -112,9 +119,13 @@ impl AlphaBetaAi { scored.sort_by(|a, b| { let a_k = killer_moves.contains(&Some(a.0)); let b_k = killer_moves.contains(&Some(b.0)); - if a_k && !b_k { std::cmp::Ordering::Less } - else if !a_k && b_k { std::cmp::Ordering::Greater } - else { b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal) } + if a_k && !b_k { + std::cmp::Ordering::Less + } else if !a_k && b_k { + std::cmp::Ordering::Greater + } else { + b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal) + } }); for (pos, _) in scored { @@ -128,8 +139,15 @@ impl AlphaBetaAi { return (Some(pos), true); } let score = -self.negamax( - &new_board, depth - 1, -beta, -alpha, color.opponent(), - tt, killer, start, time_limit, + &new_board, + depth - 1, + -beta, + -alpha, + color.opponent(), + tt, + killer, + start, + time_limit, ); if score > best_score { best_score = score; @@ -145,9 +163,16 @@ impl AlphaBetaAi { } fn negamax( - &self, board: &Board, depth: u32, mut alpha: f64, beta: f64, color: Color, - tt: &mut TransTable, killer: &mut KillerTable, - start: Instant, time_limit: Duration, + &self, + board: &Board, + depth: u32, + mut alpha: f64, + beta: f64, + color: Color, + tt: &mut TransTable, + killer: &mut KillerTable, + start: Instant, + time_limit: Duration, ) -> f64 { if start.elapsed() >= time_limit { return evaluate_board(board, color); @@ -160,10 +185,15 @@ impl AlphaBetaAi { match entry.bound { BoundType::Exact => return entry.score as f64, BoundType::LowerBound => alpha = alpha.max(entry.score as f64), - BoundType::UpperBound => - if (entry.score as f64) <= alpha { return entry.score as f64; }, + BoundType::UpperBound => { + if (entry.score as f64) <= alpha { + return entry.score as f64; + } + } + } + if alpha >= beta { + return entry.score as f64; } - if alpha >= beta { return entry.score as f64; } } if depth == 0 { @@ -182,8 +212,11 @@ impl AlphaBetaAi { .filter(|&p| !rules::is_forbidden(board, p, color)) .filter_map(|p| { board.place(p, color).ok().map(|b| { - if b.check_win(p) { (p, f64::INFINITY) } - else { (p, evaluate_board(&b, color)) } + if b.check_win(p) { + (p, f64::INFINITY) + } else { + (p, evaluate_board(&b, color)) + } }) }) .collect(); @@ -191,9 +224,13 @@ impl AlphaBetaAi { scored.sort_by(|a, b| { let a_k = killer_moves.contains(&Some(a.0)); let b_k = killer_moves.contains(&Some(b.0)); - if a_k && !b_k { std::cmp::Ordering::Less } - else if !a_k && b_k { std::cmp::Ordering::Greater } - else { b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal) } + if a_k && !b_k { + std::cmp::Ordering::Less + } else if !a_k && b_k { + std::cmp::Ordering::Greater + } else { + b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal) + } }); let mut max_val = f64::NEG_INFINITY; @@ -206,12 +243,25 @@ impl AlphaBetaAi { if let Ok(new_board) = board.place(pos, color) { if new_board.check_win(pos) { - tt.store(hash, depth as u8, f64::INFINITY as i32, BoundType::Exact, Some(pos)); + tt.store( + hash, + depth as u8, + f64::INFINITY as i32, + BoundType::Exact, + Some(pos), + ); return f64::INFINITY; } let val = -self.negamax( - &new_board, depth - 1, -beta, -alpha, color.opponent(), - tt, killer, start, time_limit, + &new_board, + depth - 1, + -beta, + -alpha, + color.opponent(), + tt, + killer, + start, + time_limit, ); if val > max_val { max_val = val; @@ -227,9 +277,13 @@ impl AlphaBetaAi { } } - let bound = if max_val <= alpha_orig { BoundType::UpperBound } - else if max_val >= beta { BoundType::LowerBound } - else { BoundType::Exact }; + let bound = if max_val <= alpha_orig { + BoundType::UpperBound + } else if max_val >= beta { + BoundType::LowerBound + } else { + BoundType::Exact + }; tt.store(hash, depth as u8, max_val as i32, bound, best_move); max_val @@ -268,7 +322,9 @@ mod tests { let mv = ai.best_move(&board, Color::Black).unwrap(); assert!( (mv.x == 7 && mv.y == 2) || (mv.x == 7 && mv.y == 7), - "AI should win, got ({},{})", mv.x, mv.y + "AI should win, got ({},{})", + mv.x, + mv.y ); } }