From 851cf308fb5a781ce8dc9242567818f815f9ecc3 Mon Sep 17 00:00:00 2001 From: Saphereye Date: Tue, 24 Sep 2024 19:58:34 +0530 Subject: [PATCH] feat: added fuzzy testing --- Cargo.lock | 193 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + fuzzy_testing/main.py | 62 ++++++++++++ reference_testing/main.py | 14 --- src/definitions/board.rs | 38 ++++---- src/definitions/move_.rs | 2 +- src/main.rs | 93 +++++------------- 7 files changed, 299 insertions(+), 104 deletions(-) create mode 100644 fuzzy_testing/main.py delete mode 100644 reference_testing/main.py diff --git a/Cargo.lock b/Cargo.lock index c80095a..619a74b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,55 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -14,6 +63,52 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "getrandom" version = "0.2.15" @@ -25,6 +120,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "libc" version = "0.2.158" @@ -92,9 +199,16 @@ dependencies = [ name = "shard" version = "0.1.0" dependencies = [ + "clap", "rand", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.77" @@ -112,12 +226,91 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 63ff765..2cb47b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.18", features = ["derive"] } rand = "0.8.5" diff --git a/fuzzy_testing/main.py b/fuzzy_testing/main.py new file mode 100644 index 0000000..545e217 --- /dev/null +++ b/fuzzy_testing/main.py @@ -0,0 +1,62 @@ +import chess +import subprocess +import random + +def generate_random_fen(): + """Generates a random legal board position (FEN string).""" + board = chess.Board() + move_count = random.randint(0, 100) # Random number of moves to play + + for _ in range(move_count): + if board.is_game_over(): + break + legal_moves = list(board.legal_moves) + board.push(random.choice(legal_moves)) # Play random legal move + + return board.fen() + +def get_legal_moves_from_executable(fen): + """Runs the executable with the given FEN string and gets legal moves.""" + # Modify this command to point to your executable's path + result = subprocess.run(['/home/adarsh/Coding/shard/target/release/shard', fen], capture_output=True, text=True) + + # Assuming the executable outputs moves as space-separated UCI strings + moves = result.stdout.strip().split() + return moves + +def run_fuzzy_test(): + """Performs fuzzy testing by comparing executable output with python-chess.""" + i = 0 + subprocess.run(["cargo", "build", "--release"]) + while True: + print(f"Test iteration {i + 1}") + + # Generate random FEN + random_fen = generate_random_fen() + print("Testing FEN:", random_fen) + + # Get legal moves using python-chess + board = chess.Board(random_fen) + python_moves = [move.uci() for move in board.legal_moves] + + # Get legal moves from executable + executable_moves = get_legal_moves_from_executable(random_fen) + + # Compare results + python_moves_set = set(python_moves) + executable_moves_set = set(executable_moves) + + if python_moves_set != executable_moves_set: + print(f"Discrepancy found in test {i + 1}!") + print(f"FEN: {random_fen}") + print(f"python-chess moves: {sorted(python_moves_set)}") + print(f"Executable moves: {sorted(executable_moves_set)}") + print(f"Moves in python-chess not present in executable: {sorted(python_moves_set - executable_moves_set)}") + print(f"Moves in executable not present in python-chess: {sorted(executable_moves_set - python_moves_set)}") + break + else: + print(f"Test {i + 1} passed!\n") + i += 1 + +if __name__ == "__main__": + run_fuzzy_test() diff --git a/reference_testing/main.py b/reference_testing/main.py deleted file mode 100644 index 92da53c..0000000 --- a/reference_testing/main.py +++ /dev/null @@ -1,14 +0,0 @@ -import chess - -board = chess.Board("k7/8/8/1q1Pp1K1/8/8/8/8 w - e6 0 2") -print(board) - -truth = set([i.uci() for i in board.legal_moves]) -test_string = "d5d6 d5e6 g5g4 g5h4 g5h5 g5f6 g5g6 g5h6" -test = set(test_string.split()) -print(truth, '\n', test) - -print("Size of truth: ", len(truth)) -print("Size of test: ", len(test)) - -print("Extra moves: ", (test | truth) - (test & truth)) diff --git a/src/definitions/board.rs b/src/definitions/board.rs index 9b8223a..df54b7c 100644 --- a/src/definitions/board.rs +++ b/src/definitions/board.rs @@ -175,6 +175,7 @@ fn flip_board(square_index: usize) -> usize { } impl_type_for_board!(&str); +impl_type_for_board!(String); impl Default for Board { fn default() -> Self { @@ -757,7 +758,7 @@ impl Board { && !self.is_square_attacked(&Square::D1, &Color::Black) { #[rustfmt::skip] - moves.push(Move::new(Square::A1, Square::D1, Piece::None, false, false, Piece::None, CastleType::QueenSide, Piece::WR)); + moves.push(Move::new(Square::E1, Square::C1, Piece::None, false, false, Piece::None, CastleType::QueenSide, Piece::WK)); } // Rook castle king side @@ -770,7 +771,7 @@ impl Board { && !self.is_square_attacked(&Square::G1, &Color::Black) { #[rustfmt::skip] - moves.push(Move::new(Square::H1, Square::F1, Piece::None, false, false, Piece::None, CastleType::KingSide, Piece::WR)); + moves.push(Move::new(Square::E1, Square::G1, Piece::None, false, false, Piece::None, CastleType::KingSide, Piece::WK)); } } @@ -1026,9 +1027,10 @@ impl Board { && !self.is_square_attacked(&Square::E8, &Color::White) && !self.is_square_attacked(&Square::C8, &Color::White) && !self.is_square_attacked(&Square::D8, &Color::White) + // Cheking is rook is attacked after castle, FIXME { #[rustfmt::skip] - moves.push(Move::new(Square::A8, Square::D8, Piece::None, false, false, Piece::None, CastleType::QueenSide, Piece::BR)); + moves.push(Move::new(Square::E8, Square::C8, Piece::None, false, false, Piece::None, CastleType::QueenSide, Piece::BK)); } // Rook castle king side @@ -1041,7 +1043,7 @@ impl Board { && !self.is_square_attacked(&Square::G8, &Color::White) { #[rustfmt::skip] - moves.push(Move::new(Square::H8, Square::F8, Piece::None, false, false, Piece::None, CastleType::KingSide, Piece::BR)); + moves.push(Move::new(Square::E8, Square::G8, Piece::None, false, false, Piece::None, CastleType::KingSide, Piece::BK)); } } @@ -1218,19 +1220,19 @@ impl Board { match move_.castle { CastleType::KingSide => { if self.side_to_move == Color::White { - self.transfer_piece(Square::E1, Square::G1); + self.transfer_piece(Square::H1, Square::F1); self.castle_permission &= !(Castling::WK as u32); } else { - self.transfer_piece(Square::E8, Square::G8); + self.transfer_piece(Square::H8, Square::F8); self.castle_permission &= !(Castling::BK as u32); } } CastleType::QueenSide => { if self.side_to_move == Color::White { - self.transfer_piece(Square::E1, Square::C1); + self.transfer_piece(Square::A1, Square::D1); self.castle_permission &= !(Castling::WQ as u32); } else { - self.transfer_piece(Square::E8, Square::C8); + self.transfer_piece(Square::A8, Square::D8); self.castle_permission &= !(Castling::BQ as u32); } } @@ -1395,22 +1397,22 @@ impl Board { } pub fn perft_test(&mut self, max_depth: u32) -> u64 { - let start_time = Instant::now(); + // let start_time = Instant::now(); let mut total_nodes = 0; for current_depth in 0..=max_depth { let nodes = self.perft(current_depth); - println!("Depth {}: {} nodes", current_depth, nodes); + // println!("Depth {}: {} nodes", current_depth, nodes); total_nodes = nodes; } - let elapsed_time = start_time.elapsed(); - println!( - "Total nodes: {}, total time elapsed: {:.2?}, time per node: {:.2?}, NPS: {:.0} n/s", - total_nodes, - elapsed_time, - Duration::from_secs_f64((elapsed_time.as_secs_f64() / (total_nodes as f64))), - (total_nodes as f64) / (elapsed_time.as_secs_f64()) - ); + // let elapsed_time = start_time.elapsed(); + // println!( + // "Total nodes: {}, total time elapsed: {:.2?}, time per node: {:.2?}, NPS: {:.0} n/s", + // total_nodes, + // elapsed_time, + // Duration::from_secs_f64((elapsed_time.as_secs_f64() / (total_nodes as f64))), + // (total_nodes as f64) / (elapsed_time.as_secs_f64()) + // ); total_nodes } diff --git a/src/definitions/move_.rs b/src/definitions/move_.rs index 973ce68..48f8ba4 100644 --- a/src/definitions/move_.rs +++ b/src/definitions/move_.rs @@ -48,7 +48,7 @@ impl Display for Move { notation.push_str(&self.from.to_string()); notation.push_str(&self.to.to_string()); if self.promoted_piece != Piece::None { - notation.push_str(&self.promoted_piece.to_string()); + notation.push_str(&self.promoted_piece.to_string().to_lowercase()); } write!(f, "{}", notation) diff --git a/src/main.rs b/src/main.rs index 06d5f90..5eb9aec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,20 @@ mod definitions; mod utils; +use clap::Parser; use definitions::board::Board; use definitions::castling::CastleType; use definitions::move_::Move; use definitions::piece::Piece; use definitions::square::Square; +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Args { + fen: String, +} + fn main() { - // println!("Hello, world!"); - // let mut board = BitBoard::default(); - // board.set(Square::D2); - // board.set(Square::D3); - // board.set(Square::D4); - // println!("{:?}", board); - // println!("{}", board.count()); - // println!("{:?}", board.pop()); - // println!("{}", board); - // - // FIXME: Position 3, 4 has errors at level >=3 - // TODO: update scores - // + let args = Args::parse(); // Fixing: // - en passant capture checks opponent: // - castling (including losing cr due to rook capture): @@ -31,65 +26,21 @@ fn main() { // let mut board = Board::new("rnbqkbnr/ppp1p1pp/8/4P3/2p2p2/5P2/PPPPN1PP/RNBQK2R w KQkq - 0 6"); // Castling // let mut board = Board::new("8/P3k3/8/8/4p3/8/3K4/8 w - - 0 1"); // Endgame // let mut board = Board::new("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 "); - let mut board = Board::new("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1"); - - // TODO check if king comes under attack after move + // let mut board = Board::new("8/8/1k6/8/2pP4/8/5BK1/8 b - d3 0 1"); + let mut board = Board::new(args.fen); // println!("{}", board); - // let mut count = 0; - // let mut moves = String::new(); - // for move_ in board.get_legal_moves() { - // board.make_move(move_); - // println!("{}\n{}", move_, board); - // moves.push_str(&format!("{} ", move_)); - // board.undo_move(); - // count += 1; - // } - // moves.push_str("\n"); + let mut count = 0; + let mut moves = String::new(); + for move_ in board.get_legal_moves() { + board.make_move(move_); + // println!("{}\n{}", move_, board); + moves.push_str(&format!("{} ", move_)); + board.undo_move(); + count += 1; + } + moves.push_str("\n"); // println!("{}", count); - // println!("{}", moves); + println!("{}", moves); - board.perft_test(6); - // println!(); - // board.make_move(castle_move); - // println!("{}", board); - // board.undo_move(); - // println!("{}", board); - // println!("{:0X}", Board::default().get_hash()); - // println!( - // "{:0X}", - // Board::new("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").get_hash() - // ); - - // let board: Board = Board::new("rnbqkbnr/pp2pppp/2p5/3pP3/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 1"); - // println!("{}", board); - // dbg!( - // board.pieces[Square::D4 as usize], - // board.pieces[Square::D5 as usize], - // board.pieces[35], - // board.pieces[Square::D5.new_coor(-1, -1).unwrap()], - // board.pieces[Square::D5.new_coor(1, -1).unwrap()], - // board.pieces[Square::D5.new_coor(-1, -1).unwrap()], - // board.pieces[Square::D5.new_coor(1, 1).unwrap()], - // ); - // // dbg!(board.is_square_attacked(Square::D4)); - // dbg!(board.is_square_attacked(Square::C4)); - // dbg!(board.is_square_attacked(Square::C5)); - // dbg!(board.piece_list[Piece::BP as usize]); - // // board.update_piece_metadata(); - // // println!("{}", board); - // for move_ in board.generate_moves() { - // println!("{}", move_); - // } - // println!("{:?}", board); - // - // let move_ = Move::new( - // Square::D2, - // Square::D4, - // Piece::BN, - // false, - // false, - // Piece::WR, - // false, - // ); - // println!("{}", move_); + // println!("{}", board.perft_test(6)); }