From aa562d4ab6f4490951c302b2a8c87a3949bcaee9 Mon Sep 17 00:00:00 2001 From: jackjohn7 <70782491+jackjohn7@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:03:56 -0500 Subject: [PATCH] more tests --- src/ai.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++--------- src/game.rs | 10 ++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/src/ai.rs b/src/ai.rs index b770e54..3efa2c2 100644 --- a/src/ai.rs +++ b/src/ai.rs @@ -245,9 +245,8 @@ fn alphabeta_with_printing_inner( #[cfg(test)] mod tests { use super::*; - use crate::board::view::View; use crate::board::{BitBoard, Board, Score}; - use crate::game::Game; + use crate::game::{Game, Team}; use rand::prelude::IndexedRandom; use rand::rngs::StdRng; @@ -259,25 +258,72 @@ mod tests { *moves.choose(rng).unwrap() } + fn game_after(moves: &[Board]) -> Game { + let mut game = Game::default(); + for &mv in moves { + game.safe_play(mv).expect("Move should be valid"); + } + game + } + + fn assert_ai_move_is_legal(game: &Game, depth: u8) -> Board { + let available = game.available(); + let best_move = alphabeta(game.clone(), depth, i8::MIN + 1, i8::MAX - 1).0; + assert_ne!(best_move, 0, "AI should return a move when one exists"); + assert_eq!( + best_move & available, + best_move, + "AI returned an illegal move" + ); + best_move + } + #[test] // just a sanity check to ensure that my AI performs up to snuff with another popular engine fn opening() { let mut game = Game::default(); - - println!("{}", game.board().render(View::RankAsc, vec![])); game.play(D3); let (best_move, _) = alphabeta(game.clone(), 12, i8::MIN + 1, i8::MAX - 1); - println!( - "dis:\n{}", - BitBoard { - boards: [0, best_move] - } - ); - game.play(best_move); - println!("{}", game.board().render(View::RankAsc, vec![])); assert_eq!(best_move, C3); } + #[test] + fn ai_returns_legal_moves_across_curated_positions() { + let cases = vec![ + (game_after(&[]), 4), + (game_after(&[D3]), 4), + (game_after(&[F5, F6, E6, F4]), 4), + ]; + + for (game, depth) in cases { + let available = game.available(); + if available == 0 { + continue; + } + let mv = assert_ai_move_is_legal(&game, depth); + assert_ne!(mv, 0); + } + } + + #[test] + fn ai_prefers_forced_corner() { + let board = BitBoard::from_jon("5bw//////").expect("Valid board"); + let game = Game::from_parts(Team::Black, board); + assert_eq!(game.available(), H8); + let mv = assert_ai_move_is_legal(&game, 3); + assert_eq!(mv, H8); + } + + #[test] + fn ai_passes_when_no_moves_exist() { + let board = BitBoard::from_jon("wwwwwwww/wwwwwwww/////").expect("Valid board"); + let game = Game::from_parts(Team::Black, board); + assert_eq!(game.available(), 0); + let (mv, eval) = alphabeta(game.clone(), 4, i8::MIN + 1, i8::MAX - 1); + assert_eq!(mv, 0); + assert_eq!(eval, game.score().diff()); + } + // I found that, despite the AI clobbering me, the AI could not // compete with itself very well. I'm honestly not quite sure why that is. #[test] @@ -310,6 +356,7 @@ mod tests { } else { random_move(&game, &mut rng) }; + assert_eq!(mv & game.available(), mv, "AI generated an illegal move"); game.play(mv); } diff --git a/src/game.rs b/src/game.rs index 8eb8b53..800e7eb 100644 --- a/src/game.rs +++ b/src/game.rs @@ -71,6 +71,16 @@ impl Game { } } +#[cfg(test)] +impl Game { + pub(crate) fn from_parts(current_team: Team, board: BitBoard) -> Self { + Self { + current_team, + board, + } + } +} + #[cfg(test)] mod tests { use super::*;