From d54a6d88ad739ba229666fd97f5b47ebd7700a11 Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Sun, 24 Nov 2024 22:05:32 -0600 Subject: [PATCH 01/14] Fix Cargo.lock. --- Cargo.lock | 136 ++++++++++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce13e03..5657680 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,37 +12,6 @@ dependencies = [ ] [[package]] - -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -80,9 +49,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cc" -version = "1.1.37" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] @@ -115,13 +84,38 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crypto-common" version = "0.1.6" @@ -142,6 +136,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "generic-array" version = "0.14.7" @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "log" @@ -203,25 +203,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] - -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", - name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -283,9 +264,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -297,14 +278,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", +] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -314,9 +314,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "sha2" @@ -357,9 +357,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -400,9 +400,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "vampirc-uci" From b1dc5ee1ed4a7420ba919fbe609389d3682a6c68 Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:18:09 -0600 Subject: [PATCH 02/14] Added position updates according to move history. --- src/enginemanager.rs | 22 ++++++++ src/gamemanager/mod.rs | 1 + src/main.rs | 14 +++-- src/types.rs | 2 +- src/ucimanager.rs | 119 ++++++++++++++++++++++++++++++----------- 5 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 src/enginemanager.rs diff --git a/src/enginemanager.rs b/src/enginemanager.rs new file mode 100644 index 0000000..a615091 --- /dev/null +++ b/src/enginemanager.rs @@ -0,0 +1,22 @@ +use vampirc_uci::UciMove; + +use crate::{gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}}; + +pub struct Engine { + pub tbl: NoArc, + pub move_history: Vec, + pub board: GameManager, + pub set_new_game: bool, +} + +impl Default for Engine { + fn default() -> Engine{ + Engine { + tbl: NoArc::new(MoveTable::default()), + board: GameManager::default(), + move_history: Vec::::new(), + set_new_game: false, + } + } +} + diff --git a/src/gamemanager/mod.rs b/src/gamemanager/mod.rs index 61e7521..e0d800c 100644 --- a/src/gamemanager/mod.rs +++ b/src/gamemanager/mod.rs @@ -11,6 +11,7 @@ pub mod legal_moves; pub mod pseudolegal_moves; /// This is a representation of a chess game and the various states of each element. +#[derive(Clone)] pub struct GameManager { /*FEN Notes: * active color - get whose turn it is to move {w, b} diff --git a/src/main.rs b/src/main.rs index a263703..d62abf6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,25 @@ #![allow(dead_code)] +use enginemanager::Engine; use gamemanager::{legal_moves::perft::perft, GameManager}; -use movetable::{noarc, MoveTable}; +use movetable::{noarc::{self, NoArc}, MoveTable}; mod bitboard; mod gamemanager; mod movetable; mod types; mod ucimanager; +mod enginemanager; fn main() { - let tbl = noarc::NoArc::new(MoveTable::default()); + //let tbl = noarc::NoArc::new(MoveTable::default()); + //let gm = GameManager::default(); + //println!("Searched {} nodes.", perft(0, 6, gm, &tbl)); + + //ucimanager::communicate(); + let e: Engine = Engine::default(); + ucimanager::communicate(e); - let gm = GameManager::default(); - println!("Searched {} nodes.", perft(0, 6, gm, &tbl)); } #[cfg(test)] diff --git a/src/types.rs b/src/types.rs index 0cfc545..7223fd3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -408,7 +408,7 @@ impl Square { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum MoveType { QuietMove, DoublePawnPush, diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 028b00a..dc3d3e8 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,9 +1,10 @@ use std::io::{self, BufRead}; -use vampirc_uci::{UciFen, UciMessage, UciMove, UciSquare}; - -pub fn communicate() { - println!("Waiting for messages..."); +use vampirc_uci::{UciFen, UciMessage, UciMove, UciPiece, UciSquare}; +use crate::{enginemanager::Engine, gamemanager::GameManager}; +use crate::types::MoveType; +pub fn communicate(mut e: Engine) { + //Waiting for messages... for line in io::stdin().lock().lines() { let msg = vampirc_uci::parse_one(&line.unwrap()); @@ -11,36 +12,92 @@ pub fn communicate() { UciMessage::Uci => { println!("id name Swordfish"); println!("id author Emilio Zuniga, Ethan Barry, Eric Oliver, Grace Kizer, & Zachary Wilson"); - // if we want configurable options, then we would list them here println!("uciok"); } UciMessage::IsReady => { - // a function should initalize the movetable for the engine - // as well as set up any internal parameters println!("readyok") } UciMessage::UciNewGame => { - // a function call here should clear the move history - // and starting FEN of any ongoing game - // - the history should just be set to an empty vec - // - the saved inital FEN should be reset to a None value (we will need to create this) - // then, the engine should wait for the GUI - // to send the inital position - // - that position should be saved as the starting FEN + e.set_new_game = true; } UciMessage::Position { startpos, fen, moves, } => { - - // if the saved inital position is a None value, - // then, set the above position as the inital position - // - if startpos is given as true, then just use standard board's FEN - // - otherwise, grab the given FEN - - // if so, setup default position - // if not, check moves & resume from last move + if e.set_new_game { + if startpos { + e.board = GameManager::default(); + e.move_history = moves; + + for m in e.move_history { + let h_from = m.from.to_string(); + let h_to = m.to.to_string(); + //determine if there was a promotion + //determine the promotion type + let legal_moves = e.board.legal_moves(&e.tbl); + //|data| data.1.to_str() == h_from && data.2.to_str() == h_to + let updated_data = legal_moves.iter().find(|data| + data.1.to_str() == h_from + && data.2.to_str() == h_to + && match m.promotion { + Some(p) => + match p { + UciPiece::Knight => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::NPromoCapture + } else { + data.3 == MoveType::NPromotion + }, + UciPiece::Bishop => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::BPromoCapture + } else { + data.3 == MoveType::BPromotion + }, + UciPiece::Rook => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::RPromoCapture + } else { + data.3 == MoveType::RPromotion + }, + UciPiece::Queen => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::QPromoCapture + } else { + data.3 == MoveType::QPromotion + }, + _ => panic!("We should never promote to a Pawn or King"), + }, + None => true, + } + ).unwrap(); + + e.board = updated_data.4.clone(); + } + + e.set_new_game = false; + } else { + e.board = GameManager::from_fen_str(fen.unwrap().as_str()); + e.move_history = moves; + //for move in move_history { + // GameManager.make_move(move) + //} + e.set_new_game = false; + } + } else { //if we're using the current position + //new game is not being set up + //if (position does not match current information) + //setup position accordingly + } } UciMessage::Go { time_control, @@ -50,23 +107,21 @@ pub fn communicate() { _ => (), } match search_control { - _ => todo!(), + _ => (), } - // engine actually does thinking/computing here - // sends info regularly until "stop" message is received + //engine starts calculating here + //TODO: send info regularly until "stop" msg received } UciMessage::Stop => { - // engine should print info about last depth searched to - // then, send "bestmove 'move'" (we may include ponder here) - todo!() + println!("{}", e.board.to_fen_string()); + //engine should print info about last depth searched to + //println!("bestmove {}", UciMove); } - UciMessage::Quit => todo!(), //engine should shutdown + UciMessage::Quit => break, //engine should shutdown - _ => println!("Received message: {msg}"), + _ => eprintln!("Received message: {msg}"), } } - - println!("We made it out the loop"); } fn crate_investigation() { From 00e5b65cb6074cf71934649d1f3a98517e26e3d3 Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:20:14 -0600 Subject: [PATCH 03/14] Progress save. --- src/types.rs | 142 +++++++++++++++++++++++----------------------- src/ucimanager.rs | 69 ++++++++++++++++++---- 2 files changed, 129 insertions(+), 82 deletions(-) diff --git a/src/types.rs b/src/types.rs index 7223fd3..27ca967 100644 --- a/src/types.rs +++ b/src/types.rs @@ -333,77 +333,77 @@ impl Square { /// * `returns` - a `&str` in the format [A-H]{1}[1-8]{1} indicating the position given by the `Square` pub fn to_str(&self) -> &str { match self { - Square::A8 => "A8", - Square::B8 => "B8", - Square::C8 => "C8", - Square::D8 => "D8", - Square::E8 => "E8", - Square::F8 => "F8", - Square::G8 => "G8", - Square::H8 => "H8", - - Square::A7 => "A7", - Square::B7 => "B7", - Square::C7 => "C7", - Square::D7 => "D7", - Square::E7 => "E7", - Square::F7 => "F7", - Square::G7 => "G7", - Square::H7 => "H7", - - Square::A6 => "A6", - Square::B6 => "B6", - Square::C6 => "C6", - Square::D6 => "D6", - Square::E6 => "E6", - Square::F6 => "F6", - Square::G6 => "G6", - Square::H6 => "H6", - - Square::A5 => "A5", - Square::B5 => "B5", - Square::C5 => "C5", - Square::D5 => "D5", - Square::E5 => "E5", - Square::F5 => "F5", - Square::G5 => "G5", - Square::H5 => "H5", - - Square::A4 => "A4", - Square::B4 => "B4", - Square::C4 => "C4", - Square::D4 => "D4", - Square::E4 => "E4", - Square::F4 => "F4", - Square::G4 => "G4", - Square::H4 => "H4", - - Square::A3 => "A3", - Square::B3 => "B3", - Square::C3 => "C3", - Square::D3 => "D3", - Square::E3 => "E3", - Square::F3 => "F3", - Square::G3 => "G3", - Square::H3 => "H3", - - Square::A2 => "A2", - Square::B2 => "B2", - Square::C2 => "C2", - Square::D2 => "D2", - Square::E2 => "E2", - Square::F2 => "F2", - Square::G2 => "G2", - Square::H2 => "H2", - - Square::A1 => "A1", - Square::B1 => "B1", - Square::C1 => "C1", - Square::D1 => "D1", - Square::E1 => "E1", - Square::F1 => "F1", - Square::G1 => "G1", - Square::H1 => "H1", + Square::A8 => "a8", + Square::B8 => "b8", + Square::C8 => "c8", + Square::D8 => "d8", + Square::E8 => "e8", + Square::F8 => "f8", + Square::G8 => "g8", + Square::H8 => "h8", + + Square::A7 => "a7", + Square::B7 => "b7", + Square::C7 => "c7", + Square::D7 => "d7", + Square::E7 => "e7", + Square::F7 => "f7", + Square::G7 => "g7", + Square::H7 => "h7", + + Square::A6 => "a6", + Square::B6 => "b6", + Square::C6 => "c6", + Square::D6 => "d6", + Square::E6 => "e6", + Square::F6 => "f6", + Square::G6 => "g6", + Square::H6 => "h6", + + Square::A5 => "a5", + Square::B5 => "b5", + Square::C5 => "c5", + Square::D5 => "d5", + Square::E5 => "e5", + Square::F5 => "f5", + Square::G5 => "g5", + Square::H5 => "h5", + + Square::A4 => "a4", + Square::B4 => "b4", + Square::C4 => "c4", + Square::D4 => "d4", + Square::E4 => "e4", + Square::F4 => "f4", + Square::G4 => "g4", + Square::H4 => "h4", + + Square::A3 => "a3", + Square::B3 => "b3", + Square::C3 => "c3", + Square::D3 => "d3", + Square::E3 => "e3", + Square::F3 => "f3", + Square::G3 => "g3", + Square::H3 => "h3", + + Square::A2 => "a2", + Square::B2 => "b2", + Square::C2 => "c2", + Square::D2 => "d2", + Square::E2 => "e2", + Square::F2 => "f2", + Square::G2 => "g2", + Square::H2 => "h2", + + Square::A1 => "a1", + Square::B1 => "b1", + Square::C1 => "c1", + Square::D1 => "d1", + Square::E1 => "e1", + Square::F1 => "f1", + Square::G1 => "g1", + Square::H1 => "h1", } } } diff --git a/src/ucimanager.rs b/src/ucimanager.rs index dc3d3e8..640f541 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,7 +1,7 @@ use std::io::{self, BufRead}; use vampirc_uci::{UciFen, UciMessage, UciMove, UciPiece, UciSquare}; use crate::{enginemanager::Engine, gamemanager::GameManager}; -use crate::types::MoveType; +use crate::types::{MoveType, Square}; pub fn communicate(mut e: Engine) { //Waiting for messages... @@ -31,15 +31,12 @@ pub fn communicate(mut e: Engine) { e.move_history = moves; for m in e.move_history { - let h_from = m.from.to_string(); - let h_to = m.to.to_string(); - //determine if there was a promotion - //determine the promotion type + let h_from = Square::from_str(&m.from.to_string()).unwrap(); + let h_to = Square::from_str(&m.to.to_string()).unwrap(); let legal_moves = e.board.legal_moves(&e.tbl); - //|data| data.1.to_str() == h_from && data.2.to_str() == h_to let updated_data = legal_moves.iter().find(|data| - data.1.to_str() == h_from - && data.2.to_str() == h_to + data.1 == h_from + && data.2 == h_to && match m.promotion { Some(p) => match p { @@ -88,12 +85,62 @@ pub fn communicate(mut e: Engine) { } else { e.board = GameManager::from_fen_str(fen.unwrap().as_str()); e.move_history = moves; - //for move in move_history { - // GameManager.make_move(move) - //} + + for m in e.move_history { + let h_from = Square::from_str(&m.from.to_string()).unwrap(); + let h_to = Square::from_str(&m.to.to_string()).unwrap(); + let legal_moves = e.board.legal_moves(&e.tbl); + let updated_data = legal_moves.iter().find(|data| + data.1 == h_from + && data.2 == h_to + && match m.promotion { + Some(p) => + match p { + UciPiece::Knight => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::NPromoCapture + } else { + data.3 == MoveType::NPromotion + }, + UciPiece::Bishop => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::BPromoCapture + } else { + data.3 == MoveType::BPromotion + }, + UciPiece::Rook => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::RPromoCapture + } else { + data.3 == MoveType::RPromotion + }, + UciPiece::Queen => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::QPromoCapture + } else { + data.3 == MoveType::QPromotion + }, + _ => panic!("We should never promote to a Pawn or King"), + }, + None => true, + } + ).unwrap(); + + e.board = updated_data.4.clone(); + } + e.set_new_game = false; } } else { //if we're using the current position + //new game is not being set up //if (position does not match current information) //setup position accordingly From c2db8d343031bb530963fde6cb08f54ee865562d Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:02:14 -0600 Subject: [PATCH 04/14] Cleaned up duplicate code. --- src/gamemanager/pseudolegal_moves/mod.rs | 4 +- src/main.rs | 8 - src/ucimanager.rs | 180 +++++++---------------- 3 files changed, 58 insertions(+), 134 deletions(-) diff --git a/src/gamemanager/pseudolegal_moves/mod.rs b/src/gamemanager/pseudolegal_moves/mod.rs index 1119ed4..6404525 100644 --- a/src/gamemanager/pseudolegal_moves/mod.rs +++ b/src/gamemanager/pseudolegal_moves/mod.rs @@ -25,8 +25,8 @@ pub fn pseudolegal_moves( bitboard: BitBoard, castling_rights: CastlingRecord, en_passant_target: &str, - halfmoves: u32, - fullmoves: u32, + _halfmoves: u32, + _fullmoves: u32, movetable: &NoArc, ) -> Vec { let mut pseudolegal_moves: Vec = Vec::new(); diff --git a/src/main.rs b/src/main.rs index d62abf6..792dfe8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,6 @@ #![allow(dead_code)] use enginemanager::Engine; -use gamemanager::{legal_moves::perft::perft, GameManager}; -use movetable::{noarc::{self, NoArc}, MoveTable}; mod bitboard; mod gamemanager; @@ -12,14 +10,8 @@ mod ucimanager; mod enginemanager; fn main() { - //let tbl = noarc::NoArc::new(MoveTable::default()); - //let gm = GameManager::default(); - //println!("Searched {} nodes.", perft(0, 6, gm, &tbl)); - - //ucimanager::communicate(); let e: Engine = Engine::default(); ucimanager::communicate(e); - } #[cfg(test)] diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 640f541..eebe9fb 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,5 +1,5 @@ use std::io::{self, BufRead}; -use vampirc_uci::{UciFen, UciMessage, UciMove, UciPiece, UciSquare}; +use vampirc_uci::{UciMessage, UciPiece}; use crate::{enginemanager::Engine, gamemanager::GameManager}; use crate::types::{MoveType, Square}; @@ -25,122 +25,69 @@ pub fn communicate(mut e: Engine) { fen, moves, } => { + if e.set_new_game { if startpos { e.board = GameManager::default(); - e.move_history = moves; - - for m in e.move_history { - let h_from = Square::from_str(&m.from.to_string()).unwrap(); - let h_to = Square::from_str(&m.to.to_string()).unwrap(); - let legal_moves = e.board.legal_moves(&e.tbl); - let updated_data = legal_moves.iter().find(|data| - data.1 == h_from - && data.2 == h_to - && match m.promotion { - Some(p) => - match p { - UciPiece::Knight => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::NPromoCapture - } else { - data.3 == MoveType::NPromotion - }, - UciPiece::Bishop => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::BPromoCapture - } else { - data.3 == MoveType::BPromotion - }, - UciPiece::Rook => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::RPromoCapture - } else { - data.3 == MoveType::RPromotion - }, - UciPiece::Queen => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::QPromoCapture - } else { - data.3 == MoveType::QPromotion - }, - _ => panic!("We should never promote to a Pawn or King"), - }, - None => true, - } - ).unwrap(); - - e.board = updated_data.4.clone(); - } - - e.set_new_game = false; } else { e.board = GameManager::from_fen_str(fen.unwrap().as_str()); - e.move_history = moves; - - for m in e.move_history { - let h_from = Square::from_str(&m.from.to_string()).unwrap(); - let h_to = Square::from_str(&m.to.to_string()).unwrap(); - let legal_moves = e.board.legal_moves(&e.tbl); - let updated_data = legal_moves.iter().find(|data| - data.1 == h_from - && data.2 == h_to - && match m.promotion { - Some(p) => - match p { - UciPiece::Knight => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::NPromoCapture - } else { - data.3 == MoveType::NPromotion - }, - UciPiece::Bishop => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::BPromoCapture - } else { - data.3 == MoveType::BPromotion - }, - UciPiece::Rook => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::RPromoCapture - } else { - data.3 == MoveType::RPromotion - }, - UciPiece::Queen => - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::QPromoCapture - } else { - data.3 == MoveType::QPromotion - }, - _ => panic!("We should never promote to a Pawn or King"), - }, - None => true, - } - ).unwrap(); + } + e.move_history = moves; - e.board = updated_data.4.clone(); - } + for m in e.move_history { + let h_from = Square::from_str(&m.from.to_string()).unwrap(); + let h_to = Square::from_str(&m.to.to_string()).unwrap(); + let legal_moves = e.board.legal_moves(&e.tbl); + let updated_data = legal_moves.iter().find(|data| + data.1 == h_from + && data.2 == h_to + && match m.promotion { + Some(p) => + match p { + UciPiece::Knight => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::NPromoCapture + } else { + data.3 == MoveType::NPromotion + }, + UciPiece::Bishop => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::BPromoCapture + } else { + data.3 == MoveType::BPromotion + }, + UciPiece::Rook => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::RPromoCapture + } else { + data.3 == MoveType::RPromotion + }, + UciPiece::Queen => + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::QPromoCapture + } else { + data.3 == MoveType::QPromotion + }, + _ => panic!("We should never promote to a Pawn or King"), + }, + None => true, + } + ).unwrap(); - e.set_new_game = false; + e.board = updated_data.4.clone(); } - } else { //if we're using the current position - + + e.set_new_game = false; + } else { + //if we're using the current position //new game is not being set up //if (position does not match current information) //setup position accordingly @@ -170,18 +117,3 @@ pub fn communicate(mut e: Engine) { } } } - -fn crate_investigation() { - let fen: UciFen = UciFen(String::from("k7/8/8/4n3/8/3N4/RN6/K7 w - - 0 1")); - let move_list: Vec = vec![UciMove::from_to( - UciSquare::from('e', 2), - UciSquare::from('e', 4), - )]; - let msg: UciMessage = UciMessage::Position { - startpos: false, - fen: Some(fen), - moves: move_list, - }; - - println!("UCI Message: {msg}"); -} From 7fd665e181be121793a75b98f8068e37acc9f57d Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:56:08 -0600 Subject: [PATCH 05/14] Removed redundancies. --- src/enginemanager.rs | 12 ------------ src/main.rs | 6 +----- src/ucimanager.rs | 17 +++++++++++++---- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/enginemanager.rs b/src/enginemanager.rs index a615091..a047f37 100644 --- a/src/enginemanager.rs +++ b/src/enginemanager.rs @@ -8,15 +8,3 @@ pub struct Engine { pub board: GameManager, pub set_new_game: bool, } - -impl Default for Engine { - fn default() -> Engine{ - Engine { - tbl: NoArc::new(MoveTable::default()), - board: GameManager::default(), - move_history: Vec::::new(), - set_new_game: false, - } - } -} - diff --git a/src/main.rs b/src/main.rs index 792dfe8..f544808 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,4 @@ #![allow(dead_code)] - -use enginemanager::Engine; - mod bitboard; mod gamemanager; mod movetable; @@ -10,8 +7,7 @@ mod ucimanager; mod enginemanager; fn main() { - let e: Engine = Engine::default(); - ucimanager::communicate(e); + ucimanager::communicate(); } #[cfg(test)] diff --git a/src/ucimanager.rs b/src/ucimanager.rs index eebe9fb..796ead1 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,10 +1,19 @@ use std::io::{self, BufRead}; -use vampirc_uci::{UciMessage, UciPiece}; -use crate::{enginemanager::Engine, gamemanager::GameManager}; +use vampirc_uci::{UciMessage, UciPiece, UciMove}; +use crate::{enginemanager::Engine, gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}}; use crate::types::{MoveType, Square}; -pub fn communicate(mut e: Engine) { +pub fn communicate(){ + + let mut e: Engine = Engine { + tbl: NoArc::new(MoveTable::default()), + board: GameManager::default(), + move_history: Vec::::new(), + set_new_game: false, + }; + //Waiting for messages... + for line in io::stdin().lock().lines() { let msg = vampirc_uci::parse_one(&line.unwrap()); @@ -25,7 +34,6 @@ pub fn communicate(mut e: Engine) { fen, moves, } => { - if e.set_new_game { if startpos { e.board = GameManager::default(); @@ -87,6 +95,7 @@ pub fn communicate(mut e: Engine) { e.set_new_game = false; } else { + //if we're using the current position //new game is not being set up //if (position does not match current information) From ef56c1bd8e2688c4e8f809167cb39822079063e1 Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Wed, 27 Nov 2024 15:33:20 -0600 Subject: [PATCH 06/14] Move legal_moves to mod.rs. --- src/gamemanager/{legal_moves.rs => legal_moves/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/gamemanager/{legal_moves.rs => legal_moves/mod.rs} (100%) diff --git a/src/gamemanager/legal_moves.rs b/src/gamemanager/legal_moves/mod.rs similarity index 100% rename from src/gamemanager/legal_moves.rs rename to src/gamemanager/legal_moves/mod.rs From ee86b5da256e5eef4ed36d064643a827018fbf27 Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Wed, 27 Nov 2024 15:37:28 -0600 Subject: [PATCH 07/14] Move match functions into separate files. Now we can use `diff` to see whether they have the same structure or not. --- src/gamemanager/legal_moves/black.rs | 470 ++++++++++++++ src/gamemanager/legal_moves/mod.rs | 934 +-------------------------- src/gamemanager/legal_moves/white.rs | 473 ++++++++++++++ 3 files changed, 946 insertions(+), 931 deletions(-) create mode 100644 src/gamemanager/legal_moves/black.rs create mode 100644 src/gamemanager/legal_moves/white.rs diff --git a/src/gamemanager/legal_moves/black.rs b/src/gamemanager/legal_moves/black.rs new file mode 100644 index 0000000..5c17942 --- /dev/null +++ b/src/gamemanager/legal_moves/black.rs @@ -0,0 +1,470 @@ +use crate::{ + bitboard::*, + gamemanager::*, + types::{CastlingRights, MoveType, PieceType, Square}, +}; + +impl GameManager { + /// Extracted from legal_moves(). + pub(super) fn black_match_block( + &self, + piecetype: PieceType, + movetype: MoveType, + from: Square, + to: Square, + ) -> GameManager { + // For each type of piece, there are at least two moves that can be made, Quiet and Capture. + // A quiet move needs to update only a handful of things, namely the move clocks, bitboard + // of the moving piece type, and white_to_move boolean. + // + // A capture needs to update the above, and all enemy bitboards. If the capture is also a + // pawn move, then it may be a pawn promotion, which means two friendly and all enemy boards + // would be updated. + // + // If the move is a special move, it may also need to update: + // - en_passant_target if it is a DoublePawnPush or EPCapture, + // - castling_rights if it is a KingCastle or QueenCastle move. + // + // THESE SHOULD HOLD FOR ALL CODE BLOCKS BELOW! CHECK THIS IN REVIEW, VERY CAREFULLY, OR ELSE! + let retval = match piecetype { + PieceType::Bishop => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + bishops_black: (self.bitboard.bishops_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + bishops_black: (self.bitboard.bishops_black ^ from.to_u64()) + | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Bishops will never make another type of move."), + }, + PieceType::Rook => { + // NOTE: Color-dependent logic. + let new_castling_rights = if from == Square::A8 { + CastlingRecord { + black: match self.castling_rights.black { + CastlingRights::Both => CastlingRights::Kingside, + CastlingRights::Queenside => CastlingRights::Neither, + _ => self.castling_rights.black, + }, + ..self.castling_rights + } + } else if from == Square::H8 { + CastlingRecord { + black: match self.castling_rights.black { + CastlingRights::Both => CastlingRights::Queenside, + CastlingRights::Kingside => CastlingRights::Neither, + _ => self.castling_rights.black, + }, + ..self.castling_rights + } + } else { + self.castling_rights + }; + + match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + rooks_black: (self.bitboard.rooks_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: new_castling_rights, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + rooks_black: (self.bitboard.rooks_black ^ from.to_u64()) + | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: new_castling_rights, + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Rooks will never make another type of move."), + } + } + PieceType::King => match movetype { + // Handle checks on king-side and queen-side castling differently! + MoveType::KingCastle => GameManager { + bitboard: BitBoard { + // Check castling spaces for attacks. + king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), + rooks_black: (self.bitboard.rooks_black ^ Square::H8.to_u64()) + | Square::F8.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + black: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::QueenCastle => GameManager { + bitboard: BitBoard { + king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), + rooks_black: (self.bitboard.rooks_black ^ Square::A8.to_u64()) + | Square::D8.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + black: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + king_black: (self.bitboard.king_black ^ from.to_u64()) | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: CastlingRecord { + black: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + black: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + _ => unreachable!("Kings will never make another type of move."), + }, + PieceType::Knight => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + knights_black: (self.bitboard.knights_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + knights_black: (self.bitboard.knights_black ^ from.to_u64()) + | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Knights will never make another type of move."), + }, + PieceType::Pawn => match movetype { + // On a promotion to X, delete the pawn, and place a(n) X on square 'to'. + MoveType::BPromotion => GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + bishops_black: self.bitboard.bishops_black | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::RPromotion => GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + rooks_black: self.bitboard.rooks_black | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::NPromotion => GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + knights_black: self.bitboard.knights_black | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::QPromotion => GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + queens_black: self.bitboard.queens_black | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + // On a promotion capture to X delete all enemy pieces + // at 'to' and place a new X on 'to'. + MoveType::BPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + bishops_black: self.bitboard.bishops_black | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::NPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + knights_black: self.bitboard.knights_black | to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::RPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + rooks_black: self.bitboard.rooks_black | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::QPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), + queens_black: self.bitboard.queens_black | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::EPCapture => { + // Color-dependent logic. + use Square::*; + let to_square = match to { + A3 => A4.to_u64(), + B3 => B4.to_u64(), + C3 => C4.to_u64(), + D3 => D4.to_u64(), + E3 => E4.to_u64(), + F3 => F4.to_u64(), + G3 => G4.to_u64(), + H3 => H4.to_u64(), + _ => unreachable!( + "We will never have a non-rank-3 square as a valid en passant target." + ), + }; + + GameManager { + bitboard: BitBoard { + // Move to the target square, behind the targeted piece. + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), + pawns_white: self.bitboard.pawns_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::DoublePawnPush => { + // Color-dependent logic. + // Update en_passant_target to square behind the double push. + use Square::*; + let target_coord = match to { + A5 => A6.to_str(), + B5 => B6.to_str(), + C5 => C6.to_str(), + D5 => D6.to_str(), + E5 => E6.to_str(), + F5 => F6.to_str(), + G5 => G6.to_str(), + H5 => H6.to_str(), + _ => unreachable!( + "We will never have a non-rank-5 square as a valid `to` coordinate here." + ), + }; + + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: String::from(target_coord), + ..*self + } + } + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + + ..*self + } + } + _ => { + eprintln!("{:?}", movetype); + unreachable!("Pawns will never make another type of move.") + } + }, + PieceType::Queen => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + queens_black: (self.bitboard.queens_black ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + queens_black: (self.bitboard.queens_black ^ from.to_u64()) | to_square, + pawns_white: self.bitboard.pawns_white & !to_square, + rooks_white: self.bitboard.rooks_white & !to_square, + knights_white: self.bitboard.knights_white & !to_square, + bishops_white: self.bitboard.bishops_white & !to_square, + queens_white: self.bitboard.queens_white & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Queens will never make another type of move."), + }, + PieceType::Super => { + unreachable!("We will never generate pseudolegal Super moves.") + } + }; + + assert!( + retval.bitboard.king_black.is_power_of_two(), + "{} {:?} {:?} {:#X} {:#X}\n", + retval.bitboard.to_string(), + retval.castling_rights.black, + retval.castling_rights.white, + retval.bitboard.king_black, + retval.bitboard.king_white + ); + assert!( + retval.bitboard.king_white.is_power_of_two(), + "{} {:?} {:?} {:#X} {:#X}\n", + retval.bitboard.to_string(), + retval.castling_rights.black, + retval.castling_rights.white, + retval.bitboard.king_black, + retval.bitboard.king_white + ); + + retval + } +} diff --git a/src/gamemanager/legal_moves/mod.rs b/src/gamemanager/legal_moves/mod.rs index 3fae3b9..6091c33 100644 --- a/src/gamemanager/legal_moves/mod.rs +++ b/src/gamemanager/legal_moves/mod.rs @@ -1,12 +1,13 @@ //! This module handles filtering of pseudolegal moves, and returns only legal moves from any game state. use crate::{ - bitboard::*, gamemanager::*, - types::{CastlingRights, Color, MoveType, PieceType, Square}, + types::{Color, MoveType, PieceType, Square}, }; +mod black; pub mod perft; +mod white; impl GameManager { /// Returns all legal moves possible from this GameManager's state. @@ -153,935 +154,6 @@ impl GameManager { legal_moves } - - /// Extracted from the large match block above. - pub(super) fn black_match_block( - &self, - piecetype: PieceType, - movetype: MoveType, - from: Square, - to: Square, - ) -> GameManager { - // For each type of piece, there are at least two moves that can be made, Quiet and Capture. - // A quiet move needs to update only a handful of things, namely the move clocks, bitboard - // of the moving piece type, and white_to_move boolean. - // - // A capture needs to update the above, and all enemy bitboards. If the capture is also a - // pawn move, then it may be a pawn promotion, which means two friendly and all enemy boards - // would be updated. - // - // If the move is a special move, it may also need to update: - // - en_passant_target if it is a DoublePawnPush or EPCapture, - // - castling_rights if it is a KingCastle or QueenCastle move. - // - // THESE SHOULD HOLD FOR ALL CODE BLOCKS BELOW! CHECK THIS IN REVIEW, VERY CAREFULLY, OR ELSE! - let retval = match piecetype { - PieceType::Bishop => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - bishops_black: (self.bitboard.bishops_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - bishops_black: (self.bitboard.bishops_black ^ from.to_u64()) - | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Bishops will never make another type of move."), - }, - PieceType::Rook => { - // NOTE: Color-dependent logic. - let new_castling_rights = if from == Square::A8 { - CastlingRecord { - black: match self.castling_rights.black { - CastlingRights::Both => CastlingRights::Kingside, - CastlingRights::Queenside => CastlingRights::Neither, - _ => self.castling_rights.black, - }, - ..self.castling_rights - } - } else if from == Square::H8 { - CastlingRecord { - black: match self.castling_rights.black { - CastlingRights::Both => CastlingRights::Queenside, - CastlingRights::Kingside => CastlingRights::Neither, - _ => self.castling_rights.black, - }, - ..self.castling_rights - } - } else { - self.castling_rights - }; - - match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - rooks_black: (self.bitboard.rooks_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: new_castling_rights, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - rooks_black: (self.bitboard.rooks_black ^ from.to_u64()) - | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: new_castling_rights, - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Rooks will never make another type of move."), - } - } - PieceType::King => match movetype { - // Handle checks on king-side and queen-side castling differently! - MoveType::KingCastle => GameManager { - bitboard: BitBoard { - // Check castling spaces for attacks. - king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), - rooks_black: (self.bitboard.rooks_black ^ Square::H8.to_u64()) - | Square::F8.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - black: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::QueenCastle => GameManager { - bitboard: BitBoard { - king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), - rooks_black: (self.bitboard.rooks_black ^ Square::A8.to_u64()) - | Square::D8.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - black: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - king_black: (self.bitboard.king_black ^ from.to_u64()) | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: CastlingRecord { - black: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - king_black: (self.bitboard.king_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - black: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - _ => unreachable!("Kings will never make another type of move."), - }, - PieceType::Knight => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - knights_black: (self.bitboard.knights_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - knights_black: (self.bitboard.knights_black ^ from.to_u64()) - | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Knights will never make another type of move."), - }, - PieceType::Pawn => match movetype { - // On a promotion to X, delete the pawn, and place a(n) X on square 'to'. - MoveType::BPromotion => GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - bishops_black: self.bitboard.bishops_black | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::RPromotion => GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - rooks_black: self.bitboard.rooks_black | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::NPromotion => GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - knights_black: self.bitboard.knights_black | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::QPromotion => GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - queens_black: self.bitboard.queens_black | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - // On a promotion capture to X delete all enemy pieces - // at 'to' and place a new X on 'to'. - MoveType::BPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - bishops_black: self.bitboard.bishops_black | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::NPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - knights_black: self.bitboard.knights_black | to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::RPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - rooks_black: self.bitboard.rooks_black | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::QPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()), - queens_black: self.bitboard.queens_black | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::EPCapture => { - // Color-dependent logic. - use Square::*; - let to_square = match to { - A3 => A4.to_u64(), - B3 => B4.to_u64(), - C3 => C4.to_u64(), - D3 => D4.to_u64(), - E3 => E4.to_u64(), - F3 => F4.to_u64(), - G3 => G4.to_u64(), - H3 => H4.to_u64(), - _ => unreachable!( - "We will never have a non-rank-3 square as a valid en passant target." - ), - }; - - GameManager { - bitboard: BitBoard { - // Move to the target square, behind the targeted piece. - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), - pawns_white: self.bitboard.pawns_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::DoublePawnPush => { - // Color-dependent logic. - // Update en_passant_target to square behind the double push. - use Square::*; - let target_coord = match to { - A5 => A6.to_str(), - B5 => B6.to_str(), - C5 => C6.to_str(), - D5 => D6.to_str(), - E5 => E6.to_str(), - F5 => F6.to_str(), - G5 => G6.to_str(), - H5 => H6.to_str(), - _ => unreachable!( - "We will never have a non-rank-5 square as a valid `to` coordinate here." - ), - }; - - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: String::from(target_coord), - ..*self - } - } - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_black: (self.bitboard.pawns_black ^ from.to_u64()) | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - - ..*self - } - } - _ => { - eprintln!("{:?}", movetype); - unreachable!("Pawns will never make another type of move.") - } - }, - PieceType::Queen => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - queens_black: (self.bitboard.queens_black ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - queens_black: (self.bitboard.queens_black ^ from.to_u64()) | to_square, - pawns_white: self.bitboard.pawns_white & !to_square, - rooks_white: self.bitboard.rooks_white & !to_square, - knights_white: self.bitboard.knights_white & !to_square, - bishops_white: self.bitboard.bishops_white & !to_square, - queens_white: self.bitboard.queens_white & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Queens will never make another type of move."), - }, - PieceType::Super => { - unreachable!("We will never generate pseudolegal Super moves.") - } - }; - - assert!( - retval.bitboard.king_black.is_power_of_two(), - "{} {:?} {:?} {:#X} {:#X}\n", - retval.bitboard.to_string(), - retval.castling_rights.black, - retval.castling_rights.white, - retval.bitboard.king_black, - retval.bitboard.king_white - ); - assert!( - retval.bitboard.king_white.is_power_of_two(), - "{} {:?} {:?} {:#X} {:#X}\n", - retval.bitboard.to_string(), - retval.castling_rights.black, - retval.castling_rights.white, - retval.bitboard.king_black, - retval.bitboard.king_white - ); - - retval - } - - /// Extracted from the large match block above. - pub(super) fn white_match_block( - &self, - piecetype: PieceType, - movetype: MoveType, - from: Square, - to: Square, - ) -> GameManager { - assert!(self.bitboard.king_black.is_power_of_two()); - assert!(self.bitboard.king_white.is_power_of_two()); - // For each type of piece, there are at least two moves that can be made, Quiet and Capture. - // A quiet move needs to update only a handful of things, namely the move clocks, bitboard - // of the moving piece type, and black_to_move boolean. - // - // A capture needs to update the above, and all enemy bitboards. If the capture is also a - // pawn move, then it may be a pawn promotion, which means two friendly and all enemy boards - // would be updated. - // - // If the move is a special move, it may also need to update: - // - en_passant_target if it is a DoublePawnPush or EPCapture, - // - castling_rights if it is a KingCastle or QueenCastle move. - // - // THESE SHOULD HOLD FOR ALL CODE BLOCKS BELOW! CHECK THIS IN REVIEW, VERY CAREFULLY, OR ELSE! - let retval = match piecetype { - PieceType::Bishop => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - bishops_white: (self.bitboard.bishops_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - bishops_white: (self.bitboard.bishops_white ^ from.to_u64()) - | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - - ..*self - } - } - _ => unreachable!("Bishops will never make another type of move."), - }, - PieceType::Rook => { - // NOTE: Color-dependent logic. - let new_castling_rights = if from == Square::A1 { - CastlingRecord { - white: match self.castling_rights.white { - CastlingRights::Both => CastlingRights::Kingside, - CastlingRights::Queenside => CastlingRights::Neither, - _ => self.castling_rights.white, - }, - ..self.castling_rights - } - } else if from == Square::H1 { - CastlingRecord { - white: match self.castling_rights.white { - CastlingRights::Both => CastlingRights::Queenside, - CastlingRights::Kingside => CastlingRights::Neither, - _ => self.castling_rights.white, - }, - ..self.castling_rights - } - } else { - self.castling_rights - }; - - match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - rooks_white: (self.bitboard.rooks_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: new_castling_rights, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - rooks_white: (self.bitboard.rooks_white ^ from.to_u64()) - | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: new_castling_rights, - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Rooks will never make another type of move."), - } - } - PieceType::King => match movetype { - // Handle checks on king-side and queen-side castling differently! - MoveType::KingCastle => GameManager { - bitboard: BitBoard { - // Check castling spaces for attacks. - king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), - rooks_white: (self.bitboard.rooks_white ^ Square::H1.to_u64()) - | Square::F1.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - white: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::QueenCastle => GameManager { - bitboard: BitBoard { - king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), - rooks_white: (self.bitboard.rooks_white ^ Square::A1.to_u64()) - | Square::D1.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - white: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - king_white: (self.bitboard.king_white ^ from.to_u64()) | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: CastlingRecord { - white: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: CastlingRecord { - white: CastlingRights::Neither, - ..self.castling_rights - }, - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - _ => unreachable!("Kings will never make another type of move."), - }, - PieceType::Knight => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - knights_white: (self.bitboard.knights_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - knights_white: (self.bitboard.knights_white ^ from.to_u64()) - | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Knights will never make another type of move."), - }, - PieceType::Pawn => match movetype { - // On a promotion to X, delete the pawn, and place a(n) X on square 'to'. - MoveType::BPromotion => GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - bishops_white: self.bitboard.bishops_white | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::RPromotion => GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - rooks_white: self.bitboard.rooks_white | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::NPromotion => GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - knights_white: self.bitboard.knights_white | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::QPromotion => GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - queens_white: self.bitboard.queens_white | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - // On a promotion capture to X delete all enemy pieces - // at 'to' and place a new X on 'to'. - MoveType::BPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - bishops_white: self.bitboard.bishops_white | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::NPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - knights_white: self.bitboard.knights_white | to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::RPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - rooks_white: self.bitboard.rooks_white | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::QPromoCapture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), - queens_white: self.bitboard.queens_white | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::EPCapture => { - // Color-dependent logic. - use Square::*; - let to_square = match to { - A6 => A5.to_u64(), - B6 => B5.to_u64(), - C6 => C5.to_u64(), - D6 => D5.to_u64(), - E6 => E5.to_u64(), - F6 => F5.to_u64(), - G6 => G5.to_u64(), - H6 => H5.to_u64(), - _ => unreachable!( - "We will never have a non-rank-6 square as a valid en passant target." - ), - }; - - GameManager { - bitboard: BitBoard { - // Move to the target square, behind the targeted piece. - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), - pawns_black: self.bitboard.pawns_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - MoveType::DoublePawnPush => { - // Color-dependent logic. - // Update en_passant_target to square behind the double push. - use Square::*; - let target_coord = match to { - A4 => A3.to_str(), - B4 => B3.to_str(), - C4 => C3.to_str(), - D4 => D3.to_str(), - E4 => E3.to_str(), - F4 => F3.to_str(), - G4 => G3.to_str(), - H4 => H3.to_str(), - _ => unreachable!( - "We will never have a non-rank-4 square as a valid `to` coordinate here." - ), - }; - - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: String::from(target_coord), - ..*self - } - } - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => { - eprintln!("{:?}", movetype); - unreachable!("Pawns will never make another type of move.") - } - }, - PieceType::Queen => match movetype { - MoveType::QuietMove => GameManager { - bitboard: BitBoard { - queens_white: (self.bitboard.queens_white ^ from.to_u64()) | to.to_u64(), - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - }, - MoveType::Capture => { - let to_square = to.to_u64(); - GameManager { - bitboard: BitBoard { - queens_white: (self.bitboard.queens_white ^ from.to_u64()) | to_square, - pawns_black: self.bitboard.pawns_black & !to_square, - rooks_black: self.bitboard.rooks_black & !to_square, - knights_black: self.bitboard.knights_black & !to_square, - bishops_black: self.bitboard.bishops_black & !to_square, - queens_black: self.bitboard.queens_black & !to_square, - ..self.bitboard - }, - castling_rights: self.castling_rights.clone(), - en_passant_target: self.en_passant_target.clone(), - ..*self - } - } - _ => unreachable!("Queens will never make another type of move."), - }, - PieceType::Super => { - unreachable!("We will never generate pseudolegal Super moves.") - } - }; - - assert!( - retval.bitboard.king_black.is_power_of_two(), - "{} {:?} {:?} {:#X} {:#X}\n", - retval.bitboard.to_string(), - retval.castling_rights.black, - retval.castling_rights.white, - retval.bitboard.king_black, - retval.bitboard.king_white - ); - assert!( - retval.bitboard.king_white.is_power_of_two(), - "{} {:?} {:?} {:#X} {:#X}\n", - retval.bitboard.to_string(), - retval.castling_rights.black, - retval.castling_rights.white, - retval.bitboard.king_black, - retval.bitboard.king_white - ); - - retval - } } #[cfg(test)] diff --git a/src/gamemanager/legal_moves/white.rs b/src/gamemanager/legal_moves/white.rs new file mode 100644 index 0000000..e7ab0ad --- /dev/null +++ b/src/gamemanager/legal_moves/white.rs @@ -0,0 +1,473 @@ +use crate::{ + bitboard::*, + gamemanager::*, + types::{CastlingRights, MoveType, PieceType, Square}, +}; + +impl GameManager { + /// Extracted from the large match block above. + pub(super) fn white_match_block( + &self, + piecetype: PieceType, + movetype: MoveType, + from: Square, + to: Square, + ) -> GameManager { + assert!(self.bitboard.king_black.is_power_of_two()); + assert!(self.bitboard.king_white.is_power_of_two()); + // For each type of piece, there are at least two moves that can be made, Quiet and Capture. + // A quiet move needs to update only a handful of things, namely the move clocks, bitboard + // of the moving piece type, and black_to_move boolean. + // + // A capture needs to update the above, and all enemy bitboards. If the capture is also a + // pawn move, then it may be a pawn promotion, which means two friendly and all enemy boards + // would be updated. + // + // If the move is a special move, it may also need to update: + // - en_passant_target if it is a DoublePawnPush or EPCapture, + // - castling_rights if it is a KingCastle or QueenCastle move. + // + // THESE SHOULD HOLD FOR ALL CODE BLOCKS BELOW! CHECK THIS IN REVIEW, VERY CAREFULLY, OR ELSE! + let retval = match piecetype { + PieceType::Bishop => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + bishops_white: (self.bitboard.bishops_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + bishops_white: (self.bitboard.bishops_white ^ from.to_u64()) + | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + + ..*self + } + } + _ => unreachable!("Bishops will never make another type of move."), + }, + PieceType::Rook => { + // NOTE: Color-dependent logic. + let new_castling_rights = if from == Square::A1 { + CastlingRecord { + white: match self.castling_rights.white { + CastlingRights::Both => CastlingRights::Kingside, + CastlingRights::Queenside => CastlingRights::Neither, + _ => self.castling_rights.white, + }, + ..self.castling_rights + } + } else if from == Square::H1 { + CastlingRecord { + white: match self.castling_rights.white { + CastlingRights::Both => CastlingRights::Queenside, + CastlingRights::Kingside => CastlingRights::Neither, + _ => self.castling_rights.white, + }, + ..self.castling_rights + } + } else { + self.castling_rights + }; + + match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + rooks_white: (self.bitboard.rooks_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: new_castling_rights, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + rooks_white: (self.bitboard.rooks_white ^ from.to_u64()) + | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: new_castling_rights, + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Rooks will never make another type of move."), + } + } + PieceType::King => match movetype { + // Handle checks on king-side and queen-side castling differently! + MoveType::KingCastle => GameManager { + bitboard: BitBoard { + // Check castling spaces for attacks. + king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), + rooks_white: (self.bitboard.rooks_white ^ Square::H1.to_u64()) + | Square::F1.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + white: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::QueenCastle => GameManager { + bitboard: BitBoard { + king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), + rooks_white: (self.bitboard.rooks_white ^ Square::A1.to_u64()) + | Square::D1.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + white: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + king_white: (self.bitboard.king_white ^ from.to_u64()) | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: CastlingRecord { + white: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + king_white: (self.bitboard.king_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: CastlingRecord { + white: CastlingRights::Neither, + ..self.castling_rights + }, + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + _ => unreachable!("Kings will never make another type of move."), + }, + PieceType::Knight => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + knights_white: (self.bitboard.knights_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + knights_white: (self.bitboard.knights_white ^ from.to_u64()) + | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Knights will never make another type of move."), + }, + PieceType::Pawn => match movetype { + // On a promotion to X, delete the pawn, and place a(n) X on square 'to'. + MoveType::BPromotion => GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + bishops_white: self.bitboard.bishops_white | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::RPromotion => GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + rooks_white: self.bitboard.rooks_white | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::NPromotion => GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + knights_white: self.bitboard.knights_white | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::QPromotion => GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + queens_white: self.bitboard.queens_white | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + // On a promotion capture to X delete all enemy pieces + // at 'to' and place a new X on 'to'. + MoveType::BPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + bishops_white: self.bitboard.bishops_white | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::NPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + knights_white: self.bitboard.knights_white | to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::RPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + rooks_white: self.bitboard.rooks_white | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::QPromoCapture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()), + queens_white: self.bitboard.queens_white | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::EPCapture => { + // Color-dependent logic. + use Square::*; + let to_square = match to { + A6 => A5.to_u64(), + B6 => B5.to_u64(), + C6 => C5.to_u64(), + D6 => D5.to_u64(), + E6 => E5.to_u64(), + F6 => F5.to_u64(), + G6 => G5.to_u64(), + H6 => H5.to_u64(), + _ => unreachable!( + "We will never have a non-rank-6 square as a valid en passant target." + ), + }; + + GameManager { + bitboard: BitBoard { + // Move to the target square, behind the targeted piece. + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), + pawns_black: self.bitboard.pawns_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + MoveType::DoublePawnPush => { + // Color-dependent logic. + // Update en_passant_target to square behind the double push. + use Square::*; + let target_coord = match to { + A4 => A3.to_str(), + B4 => B3.to_str(), + C4 => C3.to_str(), + D4 => D3.to_str(), + E4 => E3.to_str(), + F4 => F3.to_str(), + G4 => G3.to_str(), + H4 => H3.to_str(), + _ => unreachable!( + "We will never have a non-rank-4 square as a valid `to` coordinate here." + ), + }; + + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: String::from(target_coord), + ..*self + } + } + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + pawns_white: (self.bitboard.pawns_white ^ from.to_u64()) | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => { + eprintln!("{:?}", movetype); + unreachable!("Pawns will never make another type of move.") + } + }, + PieceType::Queen => match movetype { + MoveType::QuietMove => GameManager { + bitboard: BitBoard { + queens_white: (self.bitboard.queens_white ^ from.to_u64()) | to.to_u64(), + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + }, + MoveType::Capture => { + let to_square = to.to_u64(); + GameManager { + bitboard: BitBoard { + queens_white: (self.bitboard.queens_white ^ from.to_u64()) | to_square, + pawns_black: self.bitboard.pawns_black & !to_square, + rooks_black: self.bitboard.rooks_black & !to_square, + knights_black: self.bitboard.knights_black & !to_square, + bishops_black: self.bitboard.bishops_black & !to_square, + queens_black: self.bitboard.queens_black & !to_square, + ..self.bitboard + }, + castling_rights: self.castling_rights.clone(), + en_passant_target: self.en_passant_target.clone(), + ..*self + } + } + _ => unreachable!("Queens will never make another type of move."), + }, + PieceType::Super => { + unreachable!("We will never generate pseudolegal Super moves.") + } + }; + + assert!( + retval.bitboard.king_black.is_power_of_two(), + "{} {:?} {:?} {:#X} {:#X}\n", + retval.bitboard.to_string(), + retval.castling_rights.black, + retval.castling_rights.white, + retval.bitboard.king_black, + retval.bitboard.king_white + ); + assert!( + retval.bitboard.king_white.is_power_of_two(), + "{} {:?} {:?} {:#X} {:#X}\n", + retval.bitboard.to_string(), + retval.castling_rights.black, + retval.castling_rights.white, + retval.bitboard.king_black, + retval.bitboard.king_white + ); + + retval + } +} From 9b02811222708ac276d68737c2ebc2265225f33c Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Wed, 27 Nov 2024 21:41:55 -0600 Subject: [PATCH 08/14] Add foundation for search. --- src/bitboard.rs | 1 + src/gamemanager/evaluation.rs | 7 +++ src/gamemanager/legal_moves/mod.rs | 1 + src/gamemanager/legal_moves/perft.rs | 1 + src/gamemanager/legal_moves/search.rs | 68 ++++++++++++++++++++++++ src/gamemanager/mod.rs | 2 + src/gamemanager/pseudolegal_moves/mod.rs | 6 +-- src/main.rs | 11 ++-- src/movetable/mod.rs | 1 + src/types.rs | 13 ++--- 10 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 src/gamemanager/evaluation.rs create mode 100644 src/gamemanager/legal_moves/search.rs diff --git a/src/bitboard.rs b/src/bitboard.rs index 59c7908..4e1de3a 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -180,6 +180,7 @@ impl BitBoard { /// of this `PieceType` of this `Color`\ /// * `color` - the `Color` of the pieces\ /// * `pieces` - the `PieceType` of the pieces + #[allow(dead_code)] pub fn get_bitboard(&self, color: Color, piece: PieceType) -> u64 { match color { Color::White => match piece { diff --git a/src/gamemanager/evaluation.rs b/src/gamemanager/evaluation.rs new file mode 100644 index 0000000..1cc9a17 --- /dev/null +++ b/src/gamemanager/evaluation.rs @@ -0,0 +1,7 @@ +use super::GameManager; + +impl GameManager { + pub fn evaluate(&self) -> i32 { + todo!() + } +} diff --git a/src/gamemanager/legal_moves/mod.rs b/src/gamemanager/legal_moves/mod.rs index 6091c33..af73f4d 100644 --- a/src/gamemanager/legal_moves/mod.rs +++ b/src/gamemanager/legal_moves/mod.rs @@ -7,6 +7,7 @@ use crate::{ mod black; pub mod perft; +pub mod search; mod white; impl GameManager { diff --git a/src/gamemanager/legal_moves/perft.rs b/src/gamemanager/legal_moves/perft.rs index 5f5f35c..366a54c 100644 --- a/src/gamemanager/legal_moves/perft.rs +++ b/src/gamemanager/legal_moves/perft.rs @@ -12,6 +12,7 @@ pub fn perft(depth: u16, maxdepth: u16, gm: GameManager, tbl: &NoArc) } } +#[allow(dead_code)] pub fn printing_perft(depth: u16, maxdepth: u16, gm: GameManager, tbl: &NoArc) { //use crate::types::Square::*; for mv in gm.legal_moves(tbl) { diff --git a/src/gamemanager/legal_moves/search.rs b/src/gamemanager/legal_moves/search.rs new file mode 100644 index 0000000..810ab96 --- /dev/null +++ b/src/gamemanager/legal_moves/search.rs @@ -0,0 +1,68 @@ +use rayon::prelude::*; + +use crate::types::Move; + +use super::{GameManager, MoveTable, NoArc}; + +/// A Negamax search routine that runs in parallel. +pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> (Move, GameManager) { + // First we need all the legal moves, numbered, and an equal number of scores + // initialized in a list. + + let moves = gm.legal_moves(tbl); + + if moves.len() == 0 { + panic!("IDK how to handle checkmate or stalemate; help!"); + } + + let alpha = i32::MIN; + let beta = i32::MAX; + + let labelled_scores: Vec<(i32, (Move, GameManager))> = moves + .into_par_iter() + .map(|mv| { + ( + negamax(1, maxdepth, alpha, beta, &mv.4, tbl), + ((mv.0, mv.1, mv.2, mv.3), mv.4), + ) + }) + .collect(); + + let mut scored_moves: Vec<(i32, (Move, GameManager))> = labelled_scores + .into_iter() + .map(|(s, movetuple)| (s, (movetuple.0, movetuple.1))) + .collect(); + + scored_moves.sort_by(|a, b| a.0.cmp(&b.0)); + + scored_moves.pop().expect("There'll be a move!").1 +} + +fn negamax( + depth: u16, + maxdepth: u16, + mut alpha: i32, + beta: i32, + gm: &GameManager, + tbl: &NoArc, +) -> i32 { + if depth == maxdepth { + gm.evaluate() + } else { + let moves = gm.legal_moves(tbl); + if moves.len() == 0 { + return i32::MIN; // It's a pretty bad outcome to have no moves, + // but stalemates shouldn't count so hard against us. + } + let mut best = i32::MIN; + for mv in moves { + let value = -negamax(depth + 1, maxdepth, alpha, beta, &mv.4, tbl); + best = i32::max(best, value); + alpha = i32::max(alpha, best); + if alpha >= beta { + break; + } + } + best + } +} diff --git a/src/gamemanager/mod.rs b/src/gamemanager/mod.rs index 54d6ac0..a6f12b5 100644 --- a/src/gamemanager/mod.rs +++ b/src/gamemanager/mod.rs @@ -7,6 +7,7 @@ use bitboard::BitBoard; use pseudolegal_moves::pseudolegal_moves; use regex::Regex; +pub mod evaluation; pub mod legal_moves; pub mod pseudolegal_moves; @@ -73,6 +74,7 @@ impl GameManager { } } + #[allow(dead_code)] /// A utility method generating a complete FEN string representation of the game /// * `returns` - a `String` representing the game state in FEN pub fn to_fen_string(&self) -> String { diff --git a/src/gamemanager/pseudolegal_moves/mod.rs b/src/gamemanager/pseudolegal_moves/mod.rs index 3d53a8d..1195a29 100644 --- a/src/gamemanager/pseudolegal_moves/mod.rs +++ b/src/gamemanager/pseudolegal_moves/mod.rs @@ -14,7 +14,7 @@ use crate::{ bitboard::BitBoard, gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}, - types::{CastlingRecord, Color, Move, Square}, + types::{CastlingRecord, Color, Move}, }; /// Returns a [`Vec`] of pseudolegal moves encoded as a [`Move`](Move) type, @@ -25,8 +25,8 @@ pub fn pseudolegal_moves( bitboard: BitBoard, castling_rights: CastlingRecord, en_passant_target: &str, - halfmoves: u32, - fullmoves: u32, + _halfmoves: u32, + _fullmoves: u32, movetable: &NoArc, ) -> Vec { let mut pseudolegal_moves: Vec = Vec::new(); diff --git a/src/main.rs b/src/main.rs index 5f9b0e3..079c9e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,4 @@ -#![allow(dead_code)] - -use gamemanager::{ - legal_moves::perft::{perft, printing_perft}, - GameManager, -}; +use gamemanager::{legal_moves::search::root_negamax, GameManager}; use movetable::{noarc, MoveTable}; mod bitboard; @@ -18,8 +13,8 @@ fn main() { let gm = GameManager::from_fen_str( "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", ); - println!("Searched {} nodes.", perft(0, 6, gm, &tbl)); - //printing_perft(0, 1, gm, &tbl); + let bestmove = root_negamax(3, gm, &tbl).0; + println!("Best move: {}{}", bestmove.1.to_str(), bestmove.2.to_str()) } #[cfg(test)] diff --git a/src/movetable/mod.rs b/src/movetable/mod.rs index 744f296..64110ff 100644 --- a/src/movetable/mod.rs +++ b/src/movetable/mod.rs @@ -553,6 +553,7 @@ impl MoveTable { /// * `piece` - the `PieceType` /// * `square` - the x and y coordinates of the piece's position /// * `returns` - a `u64` bitboard representing the pseudo legal move of that piece possible from that square + #[allow(dead_code)] pub fn get_moves_as_bitboard(&self, color: Color, piece: PieceType, position: u64) -> u64 { let moverays = &self.get_moves(color, piece, position); let mut board = 0_u64; diff --git a/src/types.rs b/src/types.rs index 7223fd3..cb5b696 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,12 +3,12 @@ use std::fmt::Display; /// An `enum` to represent which type the piece is. This provides indexing for our hash table of moves. #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub enum PieceType { - Queen, - Rook, - Bishop, - Knight, - King, - Pawn, + Queen = 500, + Rook = 300, + Bishop = 250, + Knight = 200, + King = 1000, + Pawn = 100, Super, } @@ -433,6 +433,7 @@ impl MoveType { /// 2nd bit: capture /// 3rd bit: special 1 /// 4th bit: special 0 + #[allow(dead_code)] pub fn to_str(&self) -> &str { match self { MoveType::QuietMove => "0000", From 70c7b9045ee3b17bd4ca2ef55b433c9e89eb6545 Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Wed, 27 Nov 2024 22:04:21 -0600 Subject: [PATCH 09/14] Basic eval and search! --- src/bitboard.rs | 22 ++++++++++++++++++++++ src/gamemanager/evaluation.rs | 10 +++++++++- src/gamemanager/legal_moves/search.rs | 13 +++++-------- src/main.rs | 6 ++---- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/bitboard.rs b/src/bitboard.rs index 4e1de3a..90a757c 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -44,6 +44,28 @@ impl Default for BitBoard { } impl BitBoard { + /// Gets the total piece weights for a given side. + pub fn piece_mass(&self, color: Color) -> i32 { + match color { + Color::Black => { + self.king_black.count_ones() as i32 * PieceType::King as i32 + + self.queens_black.count_ones() as i32 * PieceType::Queen as i32 + + self.rooks_black.count_ones() as i32 * PieceType::Rook as i32 + + self.bishops_black.count_ones() as i32 * PieceType::Bishop as i32 + + self.knights_black.count_ones() as i32 * PieceType::Knight as i32 + + self.pawns_black.count_ones() as i32 * PieceType::Pawn as i32 + } + Color::White => { + self.king_white.count_ones() as i32 * PieceType::King as i32 + + self.queens_white.count_ones() as i32 * PieceType::Queen as i32 + + self.rooks_white.count_ones() as i32 * PieceType::Rook as i32 + + self.bishops_white.count_ones() as i32 * PieceType::Bishop as i32 + + self.knights_white.count_ones() as i32 * PieceType::Knight as i32 + + self.pawns_white.count_ones() as i32 * PieceType::Pawn as i32 + } + } + } + /// A utility method for generating a `BitBoard` from a FEN string\ /// * `fen` - a `&str` representing the board token of a FEN string\ /// * `returns` - a `BitBoard` as generated from the FEN token diff --git a/src/gamemanager/evaluation.rs b/src/gamemanager/evaluation.rs index 1cc9a17..a74835d 100644 --- a/src/gamemanager/evaluation.rs +++ b/src/gamemanager/evaluation.rs @@ -1,7 +1,15 @@ +use crate::types::Color; + use super::GameManager; impl GameManager { pub fn evaluate(&self) -> i32 { - todo!() + if self.white_to_move { + // Evaluate for black; better or worse after its move? + self.bitboard.piece_mass(Color::Black) + } else { + // Ditto for white. + self.bitboard.piece_mass(Color::White) + } } } diff --git a/src/gamemanager/legal_moves/search.rs b/src/gamemanager/legal_moves/search.rs index 810ab96..84817eb 100644 --- a/src/gamemanager/legal_moves/search.rs +++ b/src/gamemanager/legal_moves/search.rs @@ -6,17 +6,14 @@ use super::{GameManager, MoveTable, NoArc}; /// A Negamax search routine that runs in parallel. pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> (Move, GameManager) { - // First we need all the legal moves, numbered, and an equal number of scores - // initialized in a list. - let moves = gm.legal_moves(tbl); if moves.len() == 0 { panic!("IDK how to handle checkmate or stalemate; help!"); } - let alpha = i32::MIN; - let beta = i32::MAX; + let alpha = i32::MIN + 1; + let beta = i32::MAX - 1; let labelled_scores: Vec<(i32, (Move, GameManager))> = moves .into_par_iter() @@ -51,10 +48,10 @@ fn negamax( } else { let moves = gm.legal_moves(tbl); if moves.len() == 0 { - return i32::MIN; // It's a pretty bad outcome to have no moves, - // but stalemates shouldn't count so hard against us. + return i32::MIN + 1; // It's a pretty bad outcome to have no moves, + // but stalemates shouldn't count so hard against us. } - let mut best = i32::MIN; + let mut best = i32::MIN + 1; for mv in moves { let value = -negamax(depth + 1, maxdepth, alpha, beta, &mv.4, tbl); best = i32::max(best, value); diff --git a/src/main.rs b/src/main.rs index 079c9e1..4d3ef24 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,10 +10,8 @@ mod ucimanager; fn main() { let tbl = noarc::NoArc::new(MoveTable::default()); - let gm = GameManager::from_fen_str( - "r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1", - ); - let bestmove = root_negamax(3, gm, &tbl).0; + let gm = GameManager::from_fen_str("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1"); + let bestmove = root_negamax(4, gm, &tbl).0; println!("Best move: {}{}", bestmove.1.to_str(), bestmove.2.to_str()) } From cb3f9ffa8adb2dcfdc6a9b79c4e7a92836b8a40a Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Wed, 27 Nov 2024 23:31:26 -0600 Subject: [PATCH 10/14] Added search termination flag. --- src/ucimanager.rs | 117 ++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 796ead1..1925457 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -1,10 +1,16 @@ -use std::io::{self, BufRead}; -use vampirc_uci::{UciMessage, UciPiece, UciMove}; -use crate::{enginemanager::Engine, gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}}; use crate::types::{MoveType, Square}; +use crate::{ + enginemanager::Engine, + gamemanager::GameManager, + movetable::{noarc::NoArc, MoveTable}, +}; +use std::io::{self, BufRead}; +use std::sync::atomic::AtomicBool; +use vampirc_uci::{UciMessage, UciMove, UciPiece}; + +pub fn communicate() { + let mut flag = AtomicBool::new(false); -pub fn communicate(){ - let mut e: Engine = Engine { tbl: NoArc::new(MoveTable::default()), board: GameManager::default(), @@ -12,8 +18,6 @@ pub fn communicate(){ set_new_game: false, }; - //Waiting for messages... - for line in io::stdin().lock().lines() { let msg = vampirc_uci::parse_one(&line.unwrap()); @@ -34,94 +38,87 @@ pub fn communicate(){ fen, moves, } => { - if e.set_new_game { - if startpos { - e.board = GameManager::default(); - } else { - e.board = GameManager::from_fen_str(fen.unwrap().as_str()); - } - e.move_history = moves; + //For now, we'll reinitalize the engine's data + //(minus movetable) each time we receive a + //'position' command. + if startpos { + e.board = GameManager::default(); + } else { + e.board = GameManager::from_fen_str(fen.unwrap().as_str()); + } + e.move_history = moves; - for m in e.move_history { - let h_from = Square::from_str(&m.from.to_string()).unwrap(); - let h_to = Square::from_str(&m.to.to_string()).unwrap(); - let legal_moves = e.board.legal_moves(&e.tbl); - let updated_data = legal_moves.iter().find(|data| - data.1 == h_from - && data.2 == h_to - && match m.promotion { - Some(p) => - match p { - UciPiece::Knight => + for m in e.move_history { + let h_from = Square::from_str(&m.from.to_string()).unwrap(); + let h_to = Square::from_str(&m.to.to_string()).unwrap(); + let legal_moves = e.board.legal_moves(&e.tbl); + let updated_data = legal_moves + .iter() + .find(|data| { + data.1 == h_from + && data.2 == h_to + && match m.promotion { + Some(p) => match p { + UciPiece::Knight => { if m.from.rank != m.to.rank { //if the ranks are not the same //then this was a promoting pawn capture data.3 == MoveType::NPromoCapture } else { data.3 == MoveType::NPromotion - }, - UciPiece::Bishop => + } + } + UciPiece::Bishop => { if m.from.rank != m.to.rank { //if the ranks are not the same //then this was a promoting pawn capture data.3 == MoveType::BPromoCapture } else { data.3 == MoveType::BPromotion - }, - UciPiece::Rook => + } + } + UciPiece::Rook => { if m.from.rank != m.to.rank { //if the ranks are not the same //then this was a promoting pawn capture data.3 == MoveType::RPromoCapture } else { data.3 == MoveType::RPromotion - }, - UciPiece::Queen => + } + } + UciPiece::Queen => { if m.from.rank != m.to.rank { //if the ranks are not the same //then this was a promoting pawn capture data.3 == MoveType::QPromoCapture } else { data.3 == MoveType::QPromotion - }, + } + } _ => panic!("We should never promote to a Pawn or King"), }, - None => true, - } - ).unwrap(); + None => true, + } + }) + .unwrap(); - e.board = updated_data.4.clone(); - } - - e.set_new_game = false; - } else { - - //if we're using the current position - //new game is not being set up - //if (position does not match current information) - //setup position accordingly + e.board = updated_data.4.clone(); } + + e.set_new_game = false; } UciMessage::Go { - time_control, - search_control, + time_control: _, + search_control: _, } => { - match time_control { - _ => (), - } - match search_control { - _ => (), - } - //engine starts calculating here - //TODO: send info regularly until "stop" msg received + //engine start search on current position... + //engine regularly sends info } UciMessage::Stop => { - println!("{}", e.board.to_fen_string()); - //engine should print info about last depth searched to - //println!("bestmove {}", UciMove); + *flag.get_mut() = true; + println!("bestmove (move name here)"); } - UciMessage::Quit => break, //engine should shutdown - + UciMessage::Quit => break, _ => eprintln!("Received message: {msg}"), } } From 2c72dd36647984afed066901b92c5a2602932bb9 Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Thu, 28 Nov 2024 08:13:31 -0600 Subject: [PATCH 11/14] Weights on movetypes. --- src/gamemanager/evaluation.rs | 24 ++++++++++++++++++++---- src/gamemanager/legal_moves/search.rs | 9 +++++---- src/main.rs | 2 +- src/types.rs | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/gamemanager/evaluation.rs b/src/gamemanager/evaluation.rs index a74835d..d5e3087 100644 --- a/src/gamemanager/evaluation.rs +++ b/src/gamemanager/evaluation.rs @@ -1,15 +1,31 @@ -use crate::types::Color; +use crate::types::{Color, MoveType, PieceType}; use super::GameManager; impl GameManager { - pub fn evaluate(&self) -> i32 { + pub fn evaluate(&self, movetype: MoveType) -> i32 { if self.white_to_move { // Evaluate for black; better or worse after its move? - self.bitboard.piece_mass(Color::Black) + self.bitboard.piece_mass(Color::Black) + movetype_weight(movetype) } else { // Ditto for white. - self.bitboard.piece_mass(Color::White) + self.bitboard.piece_mass(Color::White) + movetype_weight(movetype) } } } + +fn movetype_weight(mt: MoveType) -> i32 { + use PieceType::*; + match mt { + MoveType::QPromotion => Queen as i32, + MoveType::QPromoCapture => Queen as i32 + 50, + MoveType::RPromotion => Rook as i32, + MoveType::RPromoCapture => Rook as i32 + 50, + MoveType::BPromotion => Bishop as i32, + MoveType::BPromoCapture => Bishop as i32 + 50, + MoveType::NPromotion => Knight as i32, + MoveType::NPromoCapture => Knight as i32 + 50, + MoveType::EPCapture => Pawn as i32, + _ => 0, + } +} diff --git a/src/gamemanager/legal_moves/search.rs b/src/gamemanager/legal_moves/search.rs index 84817eb..cfa0a81 100644 --- a/src/gamemanager/legal_moves/search.rs +++ b/src/gamemanager/legal_moves/search.rs @@ -1,6 +1,6 @@ use rayon::prelude::*; -use crate::types::Move; +use crate::types::{Move, MoveType}; use super::{GameManager, MoveTable, NoArc}; @@ -19,7 +19,7 @@ pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> ( .into_par_iter() .map(|mv| { ( - negamax(1, maxdepth, alpha, beta, &mv.4, tbl), + negamax(1, maxdepth, alpha, beta, mv.3, &mv.4, tbl), ((mv.0, mv.1, mv.2, mv.3), mv.4), ) }) @@ -40,11 +40,12 @@ fn negamax( maxdepth: u16, mut alpha: i32, beta: i32, + movetype: MoveType, gm: &GameManager, tbl: &NoArc, ) -> i32 { if depth == maxdepth { - gm.evaluate() + gm.evaluate(movetype) } else { let moves = gm.legal_moves(tbl); if moves.len() == 0 { @@ -53,7 +54,7 @@ fn negamax( } let mut best = i32::MIN + 1; for mv in moves { - let value = -negamax(depth + 1, maxdepth, alpha, beta, &mv.4, tbl); + let value = -negamax(depth + 1, maxdepth, alpha, beta, mv.3, &mv.4, tbl); best = i32::max(best, value); alpha = i32::max(alpha, best); if alpha >= beta { diff --git a/src/main.rs b/src/main.rs index 4d3ef24..aa61eb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ mod ucimanager; fn main() { let tbl = noarc::NoArc::new(MoveTable::default()); - let gm = GameManager::from_fen_str("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1"); + let gm = GameManager::from_fen_str("8/2p5/3p4/1P5r/1K3k2/8/4P1P1/8 w - - 0 3"); let bestmove = root_negamax(4, gm, &tbl).0; println!("Best move: {}{}", bestmove.1.to_str(), bestmove.2.to_str()) } diff --git a/src/types.rs b/src/types.rs index cb5b696..66ecf3a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -408,7 +408,7 @@ impl Square { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum MoveType { QuietMove, DoublePawnPush, From 2fb6c6b02d7f07160624a4002756489732f5cbcf Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:26:33 -0600 Subject: [PATCH 12/14] Implemented UCI. --- src/ucimanager.rs | 49 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 1925457..7338af6 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -4,13 +4,15 @@ use crate::{ gamemanager::GameManager, movetable::{noarc::NoArc, MoveTable}, }; -use std::io::{self, BufRead}; -use std::sync::atomic::AtomicBool; +use std::io; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; use vampirc_uci::{UciMessage, UciMove, UciPiece}; pub fn communicate() { - let mut flag = AtomicBool::new(false); - + let mut stop_engine = false; + let start_search_flag = Arc::new(AtomicBool::new(false)); let mut e: Engine = Engine { tbl: NoArc::new(MoveTable::default()), board: GameManager::default(), @@ -18,8 +20,13 @@ pub fn communicate() { set_new_game: false, }; - for line in io::stdin().lock().lines() { - let msg = vampirc_uci::parse_one(&line.unwrap()); + while !stop_engine { + let mut text = String::new(); + + io::stdin() + .read_line(&mut text) + .expect("Failed to read line"); + let msg = vampirc_uci::parse_one(&text); match msg { UciMessage::Uci => { @@ -111,15 +118,33 @@ pub fn communicate() { time_control: _, search_control: _, } => { - //engine start search on current position... - //engine regularly sends info + start_search_flag.store(true, Ordering::Relaxed); + let clone_flag = start_search_flag.clone(); + + thread::spawn(move || { + //dud move searching here + let mut counter = 0; + while clone_flag.load(Ordering::Relaxed) { + thread::sleep(std::time::Duration::from_millis(500)); + println!("info depth {counter}"); + counter += 1; + } + }); } UciMessage::Stop => { - *flag.get_mut() = true; - println!("bestmove (move name here)"); + start_search_flag.store(false, Ordering::Relaxed); + //dud move selection here: + let bestmove = e.board.legal_moves(&e.tbl).get(0).unwrap().clone(); + let bestmove_algebraic = format!("{}{}", bestmove.1.to_str(), bestmove.2.to_str()); + println!("bestmove {bestmove_algebraic}"); + } + UciMessage::Quit => { + start_search_flag.store(false, Ordering::Relaxed); + stop_engine = true; + } + _ => { + println!("Some other message was received."); } - UciMessage::Quit => break, - _ => eprintln!("Received message: {msg}"), } } } From fcd25ab000e671287d931d32818c62eb58dd04f9 Mon Sep 17 00:00:00 2001 From: Ethan Barry Date: Fri, 29 Nov 2024 11:25:01 -0600 Subject: [PATCH 13/14] Improve search & evaluation. This adds quiesence search, heatmap piece position evaluation, and a number of other improvements. Search speed wasn't greatly affected. --- src/gamemanager/evaluation.rs | 31 ------- src/gamemanager/evaluation/heatmaps.rs | 106 +++++++++++++++++++++++ src/gamemanager/evaluation/mod.rs | 113 +++++++++++++++++++++++++ src/gamemanager/legal_moves/search.rs | 72 +++++++++++++--- src/main.rs | 5 +- src/types.rs | 11 +-- 6 files changed, 287 insertions(+), 51 deletions(-) delete mode 100644 src/gamemanager/evaluation.rs create mode 100644 src/gamemanager/evaluation/heatmaps.rs create mode 100644 src/gamemanager/evaluation/mod.rs diff --git a/src/gamemanager/evaluation.rs b/src/gamemanager/evaluation.rs deleted file mode 100644 index d5e3087..0000000 --- a/src/gamemanager/evaluation.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::types::{Color, MoveType, PieceType}; - -use super::GameManager; - -impl GameManager { - pub fn evaluate(&self, movetype: MoveType) -> i32 { - if self.white_to_move { - // Evaluate for black; better or worse after its move? - self.bitboard.piece_mass(Color::Black) + movetype_weight(movetype) - } else { - // Ditto for white. - self.bitboard.piece_mass(Color::White) + movetype_weight(movetype) - } - } -} - -fn movetype_weight(mt: MoveType) -> i32 { - use PieceType::*; - match mt { - MoveType::QPromotion => Queen as i32, - MoveType::QPromoCapture => Queen as i32 + 50, - MoveType::RPromotion => Rook as i32, - MoveType::RPromoCapture => Rook as i32 + 50, - MoveType::BPromotion => Bishop as i32, - MoveType::BPromoCapture => Bishop as i32 + 50, - MoveType::NPromotion => Knight as i32, - MoveType::NPromoCapture => Knight as i32 + 50, - MoveType::EPCapture => Pawn as i32, - _ => 0, - } -} diff --git a/src/gamemanager/evaluation/heatmaps.rs b/src/gamemanager/evaluation/heatmaps.rs new file mode 100644 index 0000000..bf6b86a --- /dev/null +++ b/src/gamemanager/evaluation/heatmaps.rs @@ -0,0 +1,106 @@ +pub struct Heatmap { + pub pawns_start: [i32; 64], + pub pawns_end: [i32; 64], + pub knights: [i32; 64], + pub bishops: [i32; 64], + pub rooks: [i32; 64], + pub queens: [i32; 64], + pub kings_start: [i32; 64], + pub kings_end: [i32; 64], +} + +impl Default for Heatmap { + /// Returns the heatmaps for white. + /// Use .rev() to get the heatmaps for black. + fn default() -> Self { + let pawns_start = [ + 0, 0, 0, 0, 0, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 10, 10, 20, 30, 30, 20, 10, 10, + 5, 5, 10, 25, 25, 10, 5, 5, 0, 0, 0, 20, 20, 0, 0, 0, 5, -5, -10, 0, 0, -10, -5, 5, 5, + 10, 10, -20, -20, 10, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let pawns_end = [ + 0, 0, 0, 0, 0, 0, 0, 0, 80, 80, 80, 80, 80, 80, 80, 80, 50, 50, 50, 50, 50, 50, 50, 50, + 30, 30, 30, 30, 30, 30, 30, 30, 20, 20, 20, 20, 20, 20, 20, 20, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let knights = [ + -50, -40, -30, -30, -30, -30, -40, -50, -40, -20, 0, 0, 0, 0, -20, -40, -30, 0, 10, 15, + 15, 10, 0, -30, -30, 5, 15, 20, 20, 15, 5, -30, -30, 0, 15, 20, 20, 15, 0, -30, -30, 5, + 10, 15, 15, 10, 5, -30, -40, -20, 0, 5, 5, 0, -20, -40, -50, -40, -30, -30, -30, -30, + -40, -50, + ]; + + let bishops = [ + -20, -10, -10, -10, -10, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, -10, 0, 5, 10, 10, + 5, 0, -10, -10, 5, 5, 10, 10, 5, 5, -10, -10, 0, 10, 10, 10, 10, 0, -10, -10, 10, 10, + 10, 10, 10, 10, -10, -10, 5, 0, 0, 0, 0, 5, -10, -20, -10, -10, -10, -10, -10, -10, + -20, + ]; + + let rooks = [ + 0, 0, 0, 0, 0, 0, 0, 0, 5, 10, 10, 10, 10, 10, 10, 5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, + 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, 0, 0, -5, -5, 0, 0, 0, 0, + 0, 0, -5, 0, 0, 0, 5, 5, 0, 0, 0, + ]; + + let queens = [ + -20, -10, -10, -5, -5, -10, -10, -20, -10, 0, 0, 0, 0, 0, 0, -10, -10, 0, 5, 5, 5, 5, + 0, -10, -5, 0, 5, 5, 5, 5, 0, -5, 0, 0, 5, 5, 5, 5, 0, -5, -10, 5, 5, 5, 5, 5, 0, -10, + -10, 0, 5, 0, 0, 0, 0, -10, -20, -10, -10, -5, -5, -10, -10, -20, + ]; + + let kings_start = [ + -80, -70, -70, -70, -70, -70, -70, -80, -60, -60, -60, -60, -60, -60, -60, -60, -40, + -50, -50, -60, -60, -50, -50, -40, -30, -40, -40, -50, -50, -40, -40, -30, -20, -30, + -30, -40, -40, -30, -30, -20, -10, -20, -20, -20, -20, -20, -20, -10, 20, 20, -5, -5, + -5, -5, 20, 20, 20, 30, 10, 0, 0, 10, 30, 20, + ]; + + let kings_end = [ + -20, -10, -10, -10, -10, -10, -10, -20, -5, 0, 5, 5, 5, 5, 0, -5, -10, -5, 20, 30, 30, + 20, -5, -10, -15, -10, 35, 45, 45, 35, -10, -15, -20, -15, 30, 40, 40, 30, -15, -20, + -25, -20, 20, 25, 25, 20, -20, -25, -30, -25, 0, 0, 0, 0, -25, -30, -50, -30, -30, -30, + -30, -30, -30, -50, + ]; + + Self { + pawns_start, + pawns_end, + knights, + bishops, + rooks, + queens, + kings_start, + kings_end, + } + } +} + +impl Heatmap { + /// Returns the heatmap for black, if the provided + /// heatmap is for white, and vice versa. + pub fn rev(&self) -> Self { + Self { + pawns_start: reverse_array(self.pawns_start), + pawns_end: reverse_array(self.pawns_end), + knights: reverse_array(self.knights), + bishops: reverse_array(self.bishops), + rooks: reverse_array(self.rooks), + queens: reverse_array(self.queens), + kings_start: reverse_array(self.kings_start), + kings_end: reverse_array(self.kings_end), + } + } +} + +fn reverse_array(arr: [i32; 64]) -> [i32; 64] { + let mut ret = [0; 64]; + + // All heatmaps are symmetric about the central vertical axis. + for (idx, v) in arr.into_iter().rev().enumerate() { + ret[idx] = v; + } + ret +} diff --git a/src/gamemanager/evaluation/mod.rs b/src/gamemanager/evaluation/mod.rs new file mode 100644 index 0000000..3ab2dce --- /dev/null +++ b/src/gamemanager/evaluation/mod.rs @@ -0,0 +1,113 @@ +use heatmaps::Heatmap; + +use crate::{ + bitboard::BitBoard, + types::{Color, MoveType, PieceType}, +}; + +use super::GameManager; + +mod heatmaps; + +const START_MASS: i32 = PieceType::Queen as i32 + + PieceType::Rook as i32 * 2 + + PieceType::Bishop as i32 * 2 + + PieceType::Knight as i32 * 2 + + PieceType::Pawn as i32 * 8; + +impl GameManager { + pub fn evaluate(&self, movetype: MoveType) -> i32 { + if self.white_to_move { + // Evaluate for black; better or worse after its move? + let mass_score = self.bitboard.piece_mass(Color::Black) + movetype_weight(movetype); + let heatmap = heatmaps::Heatmap::default(); + + let endgame_weight = mass_score * 100 / START_MASS; + + let position_score = + eval_heatmaps(Color::Black, self.bitboard, heatmap, endgame_weight); + + position_score + mass_score + } else { + // Ditto for white. + let mass_score = self.bitboard.piece_mass(Color::White) + movetype_weight(movetype); + let heatmap = heatmaps::Heatmap::default().rev(); + + let endgame_weight = mass_score * 100 / START_MASS; + + let position_score = + eval_heatmaps(Color::White, self.bitboard, heatmap, endgame_weight); + + position_score + mass_score + } + } +} + +fn movetype_weight(mt: MoveType) -> i32 { + use MoveType::*; + use PieceType::*; + match mt { + QPromotion => Queen as i32, + QPromoCapture => Queen as i32 + 50, + RPromotion => Rook as i32, + RPromoCapture => Rook as i32 + 50, + BPromotion => Bishop as i32, + BPromoCapture => Bishop as i32 + 50, + NPromotion => Knight as i32, + NPromoCapture => Knight as i32 + 50, + EPCapture => Pawn as i32, + Capture => 400, + _ => 0, + } +} + +fn eval_heatmaps(color: Color, board: BitBoard, map: Heatmap, endgame_weight: i32) -> i32 { + let base_value = match color { + Color::Black => { + eval_heatmap(map.knights, board.knights_black) + + eval_heatmap(map.bishops, board.bishops_black) + + eval_heatmap(map.rooks, board.rooks_black) + + eval_heatmap(map.queens, board.queens_black) + } + Color::White => { + eval_heatmap(map.knights, board.knights_white) + + eval_heatmap(map.bishops, board.bishops_white) + + eval_heatmap(map.rooks, board.rooks_white) + + eval_heatmap(map.queens, board.queens_white) + } + }; + + let weighted_value = match color { + Color::Black => { + eval_heatmap(map.pawns_start, board.pawns_black) * (100 - endgame_weight) + + eval_heatmap(map.pawns_end, board.pawns_black) * endgame_weight + + eval_heatmap(map.kings_start, board.king_black) * (100 - endgame_weight) + + eval_heatmap(map.kings_end, board.king_black) * endgame_weight + } + Color::White => { + eval_heatmap(map.pawns_start, board.pawns_white) * (100 - endgame_weight) + + eval_heatmap(map.pawns_end, board.pawns_white) * endgame_weight + + eval_heatmap(map.kings_start, board.king_white) * (100 - endgame_weight) + + eval_heatmap(map.kings_end, board.king_white) * endgame_weight + } + }; + + base_value + weighted_value +} + +fn eval_heatmap(table: [i32; 64], bits: u64) -> i32 { + let mut score = 0; + let split_bits = split_bits(bits); + for idx in 0..64 { + score += table[idx] * split_bits[idx] as i32; + } + score +} + +fn split_bits(int: u64) -> [u8; 64] { + let mut bits = [0u8; 64]; + for idx in 0..64 { + bits[idx] = ((int >> idx) & 1) as u8; + } + bits +} diff --git a/src/gamemanager/legal_moves/search.rs b/src/gamemanager/legal_moves/search.rs index cfa0a81..281a580 100644 --- a/src/gamemanager/legal_moves/search.rs +++ b/src/gamemanager/legal_moves/search.rs @@ -1,11 +1,11 @@ use rayon::prelude::*; -use crate::types::{Move, MoveType}; +use crate::types::{Move, MoveType, PieceType, Square}; use super::{GameManager, MoveTable, NoArc}; /// A Negamax search routine that runs in parallel. -pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> (Move, GameManager) { +pub fn root_negamax(depth: u16, gm: GameManager, tbl: &NoArc) -> (Move, GameManager) { let moves = gm.legal_moves(tbl); if moves.len() == 0 { @@ -19,7 +19,7 @@ pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> ( .into_par_iter() .map(|mv| { ( - negamax(1, maxdepth, alpha, beta, mv.3, &mv.4, tbl), + -negamax(depth, -beta, -alpha, mv.3, &mv.4, tbl), ((mv.0, mv.1, mv.2, mv.3), mv.4), ) }) @@ -32,35 +32,81 @@ pub fn root_negamax(maxdepth: u16, gm: GameManager, tbl: &NoArc) -> ( scored_moves.sort_by(|a, b| a.0.cmp(&b.0)); + // Little bit of debugging code. + let _ = scored_moves + .iter() + .for_each(|m| println!("{}: {}{}", m.0, m.1 .0 .1.to_str(), m.1 .0 .2.to_str())); + scored_moves.pop().expect("There'll be a move!").1 } fn negamax( depth: u16, - maxdepth: u16, mut alpha: i32, beta: i32, movetype: MoveType, gm: &GameManager, tbl: &NoArc, ) -> i32 { - if depth == maxdepth { - gm.evaluate(movetype) + if depth == 0 { + capture_search(alpha, beta, movetype, gm, tbl) } else { let moves = gm.legal_moves(tbl); + if moves.len() == 0 { return i32::MIN + 1; // It's a pretty bad outcome to have no moves, // but stalemates shouldn't count so hard against us. } - let mut best = i32::MIN + 1; + for mv in moves { - let value = -negamax(depth + 1, maxdepth, alpha, beta, mv.3, &mv.4, tbl); - best = i32::max(best, value); - alpha = i32::max(alpha, best); - if alpha >= beta { - break; + // Call negamax and negate it's return value. Enemy's alpha is our -beta & v.v. + let score = -negamax(depth - 1, -beta, -alpha, mv.3, &mv.4, tbl); + + if score >= beta { + return beta; + } + if score > alpha { + alpha = score; + } + } + alpha + } +} + +fn capture_search( + mut alpha: i32, + beta: i32, + movetype: MoveType, + gm: &GameManager, + tbl: &NoArc, +) -> i32 { + let mut eval = gm.evaluate(movetype); + + if eval >= beta { + beta + } else { + alpha = alpha.max(eval); + + let captures: Vec<(PieceType, Square, Square, MoveType, GameManager)> = gm + .legal_moves(tbl) + .into_iter() + .filter(|m| { + use MoveType::*; + match m.3 { + Capture | NPromoCapture | BPromoCapture | RPromoCapture | QPromoCapture + | EPCapture => true, + _ => false, + } + }) + .collect(); + + for capture in captures { + eval = -capture_search(-beta, -alpha, capture.3, &capture.4, tbl); + if eval >= beta { + return beta; } + alpha = alpha.max(eval); } - best + alpha } } diff --git a/src/main.rs b/src/main.rs index aa61eb5..05ff1ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,9 @@ mod ucimanager; fn main() { let tbl = noarc::NoArc::new(MoveTable::default()); - let gm = GameManager::from_fen_str("8/2p5/3p4/1P5r/1K3k2/8/4P1P1/8 w - - 0 3"); - let bestmove = root_negamax(4, gm, &tbl).0; + let gm = + GameManager::from_fen_str("3r3r/pkppqp2/1nN1pbp1/3P4/1p2P3/8/PPPBNPpP/1K1R2R1 b - - 3 6"); + let bestmove = root_negamax(3, gm, &tbl).0; println!("Best move: {}{}", bestmove.1.to_str(), bestmove.2.to_str()) } diff --git a/src/types.rs b/src/types.rs index 66ecf3a..a2528a3 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,13 +1,14 @@ use std::fmt::Display; /// An `enum` to represent which type the piece is. This provides indexing for our hash table of moves. +/// It also carries the evaluation weights of the pieces, which influence our eval. fn. #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub enum PieceType { - Queen = 500, - Rook = 300, - Bishop = 250, - Knight = 200, - King = 1000, + King, + Queen = 900, + Rook = 500, + Bishop = 320, + Knight = 300, Pawn = 100, Super, } From bfb3129d1ebc68df98b12f54420271b38dc180df Mon Sep 17 00:00:00 2001 From: CursedCactusJack <165201142+CursedCactusJack@users.noreply.github.com> Date: Fri, 29 Nov 2024 17:43:18 -0600 Subject: [PATCH 14/14] Minor polish. --- src/enginemanager.rs | 2 +- src/ucimanager.rs | 134 +++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 71 deletions(-) diff --git a/src/enginemanager.rs b/src/enginemanager.rs index a047f37..aff4771 100644 --- a/src/enginemanager.rs +++ b/src/enginemanager.rs @@ -6,5 +6,5 @@ pub struct Engine { pub tbl: NoArc, pub move_history: Vec, pub board: GameManager, - pub set_new_game: bool, + //pub set_new_game: bool, } diff --git a/src/ucimanager.rs b/src/ucimanager.rs index 7338af6..74fea06 100644 --- a/src/ucimanager.rs +++ b/src/ucimanager.rs @@ -17,7 +17,7 @@ pub fn communicate() { tbl: NoArc::new(MoveTable::default()), board: GameManager::default(), move_history: Vec::::new(), - set_new_game: false, + //set_new_game: false, }; while !stop_engine { @@ -38,7 +38,7 @@ pub fn communicate() { println!("readyok") } UciMessage::UciNewGame => { - e.set_new_game = true; + //e.set_new_game = true; } UciMessage::Position { startpos, @@ -53,66 +53,13 @@ pub fn communicate() { } else { e.board = GameManager::from_fen_str(fen.unwrap().as_str()); } - e.move_history = moves; + e.move_history = moves.clone(); - for m in e.move_history { - let h_from = Square::from_str(&m.from.to_string()).unwrap(); - let h_to = Square::from_str(&m.to.to_string()).unwrap(); - let legal_moves = e.board.legal_moves(&e.tbl); - let updated_data = legal_moves - .iter() - .find(|data| { - data.1 == h_from - && data.2 == h_to - && match m.promotion { - Some(p) => match p { - UciPiece::Knight => { - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::NPromoCapture - } else { - data.3 == MoveType::NPromotion - } - } - UciPiece::Bishop => { - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::BPromoCapture - } else { - data.3 == MoveType::BPromotion - } - } - UciPiece::Rook => { - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::RPromoCapture - } else { - data.3 == MoveType::RPromotion - } - } - UciPiece::Queen => { - if m.from.rank != m.to.rank { - //if the ranks are not the same - //then this was a promoting pawn capture - data.3 == MoveType::QPromoCapture - } else { - data.3 == MoveType::QPromotion - } - } - _ => panic!("We should never promote to a Pawn or King"), - }, - None => true, - } - }) - .unwrap(); - - e.board = updated_data.4.clone(); + for m in moves { + e.board = make_move(&e.board, &e.tbl, m); } - e.set_new_game = false; + //e.set_new_game = false; } UciMessage::Go { time_control: _, @@ -122,21 +69,11 @@ pub fn communicate() { let clone_flag = start_search_flag.clone(); thread::spawn(move || { - //dud move searching here - let mut counter = 0; - while clone_flag.load(Ordering::Relaxed) { - thread::sleep(std::time::Duration::from_millis(500)); - println!("info depth {counter}"); - counter += 1; - } + //search() - pass in clone_flag to check whether search termination was ordered }); } UciMessage::Stop => { start_search_flag.store(false, Ordering::Relaxed); - //dud move selection here: - let bestmove = e.board.legal_moves(&e.tbl).get(0).unwrap().clone(); - let bestmove_algebraic = format!("{}{}", bestmove.1.to_str(), bestmove.2.to_str()); - println!("bestmove {bestmove_algebraic}"); } UciMessage::Quit => { start_search_flag.store(false, Ordering::Relaxed); @@ -148,3 +85,60 @@ pub fn communicate() { } } } + +fn make_move(board: &GameManager, tbl: &NoArc, m: UciMove) -> GameManager{ + let h_from = Square::from_str(&m.from.to_string()).unwrap(); + let h_to = Square::from_str(&m.to.to_string()).unwrap(); + let legal_moves = board.legal_moves(tbl); + let updated_data = legal_moves + .iter() + .find(|data| { + data.1 == h_from + && data.2 == h_to + && match m.promotion { + Some(p) => match p { + UciPiece::Knight => { + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::NPromoCapture + } else { + data.3 == MoveType::NPromotion + } + } + UciPiece::Bishop => { + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::BPromoCapture + } else { + data.3 == MoveType::BPromotion + } + } + UciPiece::Rook => { + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::RPromoCapture + } else { + data.3 == MoveType::RPromotion + } + } + UciPiece::Queen => { + if m.from.rank != m.to.rank { + //if the ranks are not the same + //then this was a promoting pawn capture + data.3 == MoveType::QPromoCapture + } else { + data.3 == MoveType::QPromotion + } + } + _ => panic!("We should never promote to a Pawn or King"), + }, + None => true, + } + }) + .unwrap(); + + updated_data.4.clone() +}