diff --git a/engine/src/bitboard.rs b/engine/src/bitboard.rs index 1486484..9779d93 100644 --- a/engine/src/bitboard.rs +++ b/engine/src/bitboard.rs @@ -3,7 +3,8 @@ mod utils; mod legality; mod checkinfo; mod attacks; -mod bitmove; mod movebuffer; +mod movegen; -pub mod board; \ No newline at end of file +pub mod board; +pub(in super) mod bitmove; \ No newline at end of file diff --git a/engine/src/bitboard/attackmaps.rs b/engine/src/bitboard/attackmaps.rs index f7fe35f..b99a4db 100644 --- a/engine/src/bitboard/attackmaps.rs +++ b/engine/src/bitboard/attackmaps.rs @@ -94,6 +94,64 @@ pub static RAY_TABLE: Lazy<[[u64; 8]; 64]> = Lazy::new(|| { return table; }); +// ROOK_MOVE_MASK[] +pub static ROOK_MOVE_MASK: Lazy<[u64; 64]> = Lazy::new(|| { + let mut table = [0u64; 64]; + + for sq in 0..64 { + for dir in [0, 2, 4, 6] { + table[sq] |= RAY_TABLE[sq][dir]; + } + } + table +}); + +// BISHOP_MOVE_MASK[] +pub static BISHOP_MOVE_MASK: Lazy<[u64; 64]> = Lazy::new(|| { + let mut table = [0u64; 64]; + + for sq in 0..64 { + for dir in [1, 3, 5, 7] { + table[sq] |= RAY_TABLE[sq][dir]; + } + } + table +}); + +// KING_SAFETY_ROOK_MASK[] +pub static KING_SAFETY_ROOK_MASK: Lazy<[u64; 64]> = Lazy::new(|| { + let mut table = [0u64; 64]; + + for sq in 0..64 { + let mut mask = KING_ATTACK_MAP[sq]; + + while mask != 0 { + let next_sq = mask.trailing_zeros(); + table[sq] |= ROOK_MOVE_MASK[next_sq as usize]; + mask &= !(1 << next_sq); + } + } + + table +}); + +// KING_SAFETY_BISHOP_MASK[] +pub static KING_SAFETY_BISHOP_MASK: Lazy<[u64; 64]> = Lazy::new(|| { + let mut table = [0u64; 64]; + + for sq in 0..64 { + let mut mask = KING_ATTACK_MAP[sq]; + + while mask != 0 { + let next_sq = mask.trailing_zeros(); + table[sq] |= BISHOP_MOVE_MASK[next_sq as usize]; + mask &= !(1 << next_sq); + } + } + + table +}); + // <----- TESTS -----> diff --git a/engine/src/bitboard/attacks.rs b/engine/src/bitboard/attacks.rs index a348d96..63b0c32 100644 --- a/engine/src/bitboard/attacks.rs +++ b/engine/src/bitboard/attacks.rs @@ -5,6 +5,8 @@ impl Board { const RANK_2: u64 = 0x0000_0000_0000_FF00; const RANK_7: u64 = 0x00FF_0000_0000_0000; + const A_FILE: u64 = 0x0101_0101_0101_0101; + const H_FILE: u64 = 0x8080_8080_8080_8080; pub fn get_pseudo_pawn_moves(&self, sq: u32) -> u64 { let pawn: u64 = 1 << sq; @@ -71,6 +73,111 @@ impl Board { pub fn get_pseudo_queen_moves(&self, sq: u32) -> u64 { return self.get_pseudo_bishop_moves(sq) | self.get_pseudo_rook_moves(sq); } + + #[inline] + pub fn get_pseudo_bishop_moves_ignore_king(&self, sq: u32) -> u64 { + let mut moves = 0u64; + let sq = sq as usize; + let king = self.bitboards[5 + 6*self.side_to_move as usize]; + let occupancy = self.occupancy[2] & !king; + moves |= get_raycast_from_square_in_direction(occupancy, sq, 1); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 3); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 5); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 7); + + return moves; + } + #[inline] + pub fn get_pseudo_rook_moves_ignore_king(&self, sq: u32) -> u64 { + let mut moves: u64 = 0u64; + let sq = sq as usize; + let king = self.bitboards[5 + 6*self.side_to_move as usize]; + let occupancy = self.occupancy[2] & !king; + moves |= get_raycast_from_square_in_direction(occupancy, sq, 0); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 2); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 4); + moves |= get_raycast_from_square_in_direction(occupancy, sq, 6); + + return moves; + } + + #[inline] + pub fn is_square_attacked(&self, king_sq: u32) -> bool { + let offset: usize = 6 * self.side_to_move as usize; + + // rook-queen checks (+) + let mut threat_mask: u64 = self.get_pseudo_rook_moves(king_sq); + let mut attacker_mask: u64 = self.bitboards[10 - offset] | self.bitboards[9 - offset]; + if threat_mask & attacker_mask != 0 { return true; } + + // bishop-queen checks (x) + threat_mask = self.get_pseudo_bishop_moves(king_sq); + attacker_mask = self.bitboards[10 - offset] | self.bitboards[8 - offset]; + if threat_mask & attacker_mask != 0 { return true; } + + // knight checks (L) + threat_mask = KNIGHT_ATTACK_MAP[king_sq as usize]; + attacker_mask = self.bitboards[7 - offset]; + if threat_mask & attacker_mask != 0 { return true; } + + // pawn checks (v) + threat_mask = PAWN_ATTACK_MAP[king_sq as usize][self.side_to_move as usize]; + attacker_mask = self.bitboards[6 - offset]; + return threat_mask & attacker_mask != 0; + } + pub fn get_safe_king_squares(&self) -> u64 { + let offset: usize = 6 * (1 - self.side_to_move as usize); + let king_sq = self.bitboards[11 - offset].trailing_zeros() as usize; + let bishop_mask = KING_SAFETY_BISHOP_MASK[king_sq]; + let rook_mask = KING_SAFETY_ROOK_MASK[king_sq]; + let mut attack_map: u64 = 0u64; + + let mut board: u64 = self.bitboards[offset]; + if self.side_to_move() == 0 { + attack_map |= (board >> 9 & !Self::H_FILE) | (board >> 7 & !Self::A_FILE); + } + else { + attack_map |= (board << 9 & !Self::A_FILE) | (board << 7 & !Self::H_FILE); + } + + board = self.bitboards[offset + 1]; + while board != 0 { + let piece_sq: u32 = board.trailing_zeros(); + board &= !(1 << piece_sq); + + attack_map |= self.get_pseudo_knight_moves(piece_sq); + } + + board = self.bitboards[offset + 2] & bishop_mask; + while board != 0 { + let piece_sq: u32 = board.trailing_zeros(); + board &= !(1 << piece_sq); + + attack_map |= self.get_pseudo_bishop_moves_ignore_king(piece_sq); + } + + board = self.bitboards[offset + 3] & rook_mask; + while board != 0 { + let piece_sq: u32 = board.trailing_zeros(); + board &= !(1 << piece_sq); + + attack_map |= self.get_pseudo_rook_moves_ignore_king(piece_sq); + } + + board = self.bitboards[offset + 4] & (bishop_mask | rook_mask); + while board != 0 { + let piece_sq: u32 = board.trailing_zeros(); + board &= !(1 << piece_sq); + + attack_map |= self.get_pseudo_rook_moves_ignore_king(piece_sq) | self.get_pseudo_bishop_moves_ignore_king(piece_sq); + } + + board = self.bitboards[offset + 5]; + let piece_sq: u32 = board.trailing_zeros(); + attack_map |= self.get_pseudo_king_moves(piece_sq); + + return !attack_map; + } } #[inline(always)] diff --git a/engine/src/bitboard/legality.rs b/engine/src/bitboard/legality.rs index f42fdc1..f81973e 100644 --- a/engine/src/bitboard/legality.rs +++ b/engine/src/bitboard/legality.rs @@ -93,6 +93,13 @@ impl Board { } } + #[inline] + pub(in super) fn get_pin_masked_moves(&self, moves: u64, sq: u32) -> u64 { + let sq: usize = sq as usize; + if self.pinned_squares[sq] == 4 { return moves; } + let dir: u8 = self.pinned_squares[sq]; + return moves & (RAY_TABLE[sq][dir as usize] | RAY_TABLE[sq][4 + dir as usize]); + } } // <----- TESTS -----> diff --git a/engine/src/bitboard/movegen.rs b/engine/src/bitboard/movegen.rs new file mode 100644 index 0000000..b702f4a --- /dev/null +++ b/engine/src/bitboard/movegen.rs @@ -0,0 +1,67 @@ +mod pawns; +mod knights; +mod bishops; +mod rooks; +mod queens; +mod kings; + +use super::board::Board; +use super::movebuffer::MoveBuffer; +use super::bitmove::BitMove; +use super::checkinfo::CheckInfo; +use super::utils::*; + +impl Board { + + const NO_FILTER: u64 = 0xFFFF_FFFF_FFFF_FFFF; + + pub fn collect_moves(&mut self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) -> bool { + buffer.clear(); + self.calc_pinned_squares(); + let check_info = self.check_test(); + + match check_info.check_count { + 0 => self.collect_all_moves(buffer, temp_buffer), + 1 => self.collect_moves_single_check(buffer, temp_buffer, &check_info), + 2 => self.collect_king_evasion(buffer, temp_buffer), + _ => panic!("More than 2 checking pieces found as the same time!") + } + return check_info.check_count > 0; + } + + pub(in super) fn collect_all_moves(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) { + let safe_squares = self.get_safe_king_squares(); + + self.add_pawn_moves(buffer, temp_buffer, Self::NO_FILTER); + self.add_knight_moves(buffer, temp_buffer, Self::NO_FILTER); + self.add_bishop_moves(buffer, temp_buffer, Self::NO_FILTER); + self.add_rook_moves(buffer, temp_buffer, Self::NO_FILTER); + self.add_queen_moves(buffer, temp_buffer, Self::NO_FILTER); + self.add_king_moves(buffer, temp_buffer, safe_squares); + self.add_king_castles(buffer, safe_squares); + + buffer.append(temp_buffer); + temp_buffer.clear(); + } + pub(in super) fn collect_moves_single_check(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer, check_info: &CheckInfo) { + let safe_squares = self.get_safe_king_squares(); + + self.add_pawn_moves(buffer, temp_buffer, check_info.move_mask); + self.add_knight_moves(buffer, temp_buffer, check_info.move_mask); + self.add_bishop_moves(buffer, temp_buffer, check_info.move_mask); + self.add_rook_moves(buffer, temp_buffer, check_info.move_mask); + self.add_queen_moves(buffer, temp_buffer, check_info.move_mask); + self.add_king_moves(buffer, temp_buffer, safe_squares); + + buffer.append(temp_buffer); + temp_buffer.clear(); + } + pub(in super) fn collect_king_evasion(&self, buffer: &mut MoveBuffer, temp_buffer: &mut MoveBuffer) { + let safe_squares = self.get_safe_king_squares(); + + self.add_king_moves(buffer, temp_buffer, safe_squares); + + buffer.append(&temp_buffer); + temp_buffer.clear(); + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/bishops.rs b/engine/src/bitboard/movegen/bishops.rs new file mode 100644 index 0000000..3d41916 --- /dev/null +++ b/engine/src/bitboard/movegen/bishops.rs @@ -0,0 +1,55 @@ +use super::*; + +impl Board { + + pub fn add_bishop_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + let piece_index = 2 + self.side_to_move * 6; + let mut bishops = self.bitboards[piece_index as usize]; + let empty = !self.occupancy[2]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while bishops != 0 { + let from_sq = pop_lsb(&mut bishops); + let raw_move_map = self.get_pseudo_bishop_moves(from_sq) & move_mask; + let move_map = self.get_pin_masked_moves(raw_move_map, from_sq); + + let mut quiet_map = move_map & empty; + let mut capture_map = move_map & opponents; + + while quiet_map != 0 { + let to_sq = pop_lsb(&mut quiet_map); + quiet_buffer.add(BitMove::quiet( + from_sq as u8, + to_sq as u8, + None + )); + } + while capture_map != 0 { + let to_sq = pop_lsb(&mut capture_map); + capture_buffer.add(BitMove::capture( + from_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_bishop_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut bishops: u64 = self.bitboards[2 + offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while bishops != 0 { + let next_sq: u32 = pop_lsb(&mut bishops); + let mut attacks: u64 = self.get_pseudo_bishop_moves(next_sq) & opponents & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + + while attacks != 0 { + let to_sq = pop_lsb(&mut attacks); + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + } + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/kings.rs b/engine/src/bitboard/movegen/kings.rs new file mode 100644 index 0000000..60e7daf --- /dev/null +++ b/engine/src/bitboard/movegen/kings.rs @@ -0,0 +1,88 @@ +use super::*; + +impl Board { + + pub fn add_king_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + let piece_index = 5 + self.side_to_move * 6; + let mut kings = self.bitboards[piece_index as usize]; + let empty = !self.occupancy[2]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while kings != 0 { + let from_sq = pop_lsb(&mut kings); + let move_map = self.get_pseudo_king_moves(from_sq) & move_mask; + + let mut quiet_map = move_map & empty; + let mut capture_map = move_map & opponents; + + while quiet_map != 0 { + let to_sq = pop_lsb(&mut quiet_map); + quiet_buffer.add(BitMove::quiet( + from_sq as u8, + to_sq as u8, + None + )); + } + while capture_map != 0 { + let to_sq = pop_lsb(&mut capture_map); + capture_buffer.add(BitMove::capture( + from_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_king_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut kings: u64 = self.bitboards[5 + offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while kings != 0 { + let next_sq: u32 = pop_lsb(&mut kings); + let mut attacks: u64 = self.get_pseudo_king_moves(next_sq) & opponents & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + + while attacks != 0 { + let to_sq = pop_lsb(&mut attacks); + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_king_castles(&self, buffer: &mut MoveBuffer, move_mask: u64) { + if self.castling_rights & (0b11 << (2 - 2 * self.side_to_move)) == 0 { + return; + } + + let offset = 5 + 6 * self.side_to_move as u8; + let castle_offset = 2 - 2 * self.side_to_move as u8; + let castling_rights = self.castling_rights & 3 << castle_offset; + let occupied = self.occupancy[2]; + let king_sq = self.bitboards[offset as usize].trailing_zeros(); + + let queenside_mask = 0b111 << (king_sq - 3); + let kingside_mask = 0b11 << (king_sq + 1); + + if (castling_rights & 1 << castle_offset) != 0 + && queenside_mask & occupied == 0 + && !move_mask & 0b11 << (king_sq - 2) == 0 + && !self.is_square_attacked(king_sq - 2) { + buffer.add(BitMove::castle( + king_sq as u8, + (king_sq - 2) as u8 + )); + } + if (castling_rights & 2 << castle_offset) != 0 + && kingside_mask & occupied == 0 + && !move_mask & 0b11 << (king_sq + 1) == 0 + && !self.is_square_attacked(king_sq + 2) { + buffer.add(BitMove::castle( + king_sq as u8, + (king_sq + 2) as u8 + )); + } + + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/knights.rs b/engine/src/bitboard/movegen/knights.rs new file mode 100644 index 0000000..4c0efce --- /dev/null +++ b/engine/src/bitboard/movegen/knights.rs @@ -0,0 +1,55 @@ +use super::*; + +impl Board { + + pub fn add_knight_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + let piece_index = 1 + self.side_to_move * 6; + let mut knights = self.bitboards[piece_index as usize]; + let empty = !self.occupancy[2]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while knights != 0 { + let from_sq = pop_lsb(&mut knights); + let raw_move_map = self.get_pseudo_knight_moves(from_sq) & move_mask; + let move_map = self.get_pin_masked_moves(raw_move_map, from_sq); + + let mut quiet_map = move_map & empty; + let mut capture_map = move_map & opponents; + + while quiet_map != 0 { + let to_sq = pop_lsb(&mut quiet_map); + quiet_buffer.add(BitMove::quiet( + from_sq as u8, + to_sq as u8, + None + )); + } + while capture_map != 0 { + let to_sq = pop_lsb(&mut capture_map); + capture_buffer.add(BitMove::capture( + from_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_knight_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut knights: u64 = self.bitboards[1 + offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while knights != 0 { + let next_sq: u32 = pop_lsb(&mut knights); + let mut attacks: u64 = self.get_pseudo_knight_moves(next_sq) & opponents & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + + while attacks != 0 { + let to_sq = pop_lsb(&mut attacks); + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + } + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/pawns.rs b/engine/src/bitboard/movegen/pawns.rs new file mode 100644 index 0000000..444b387 --- /dev/null +++ b/engine/src/bitboard/movegen/pawns.rs @@ -0,0 +1,76 @@ +use super::*; + +impl Board { + + pub fn add_pawn_quiets(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset: u8 = self.side_to_move * 6; + let mut pawns: u64 = self.bitboards[offset as usize]; + while pawns != 0 { + let next_sq = pop_lsb(&mut pawns); + + let mut quiets: u64 = self.get_pseudo_pawn_moves(next_sq) & move_mask; + quiets = self.get_pin_masked_moves(quiets, next_sq); + while quiets != 0 { + let to_sq = quiets.trailing_zeros(); + + if (self.side_to_move == 0 && quiets.trailing_zeros() / 8 == 7) + || (self.side_to_move == 1 && quiets.trailing_zeros() / 8 == 0) { + for piece_type in [4, 3, 2, 1] { + buffer.add(BitMove::quiet( + next_sq as u8, + to_sq as u8, + Some(piece_type) + )); + } + } + else { + buffer.add(BitMove::quiet( + next_sq as u8, + to_sq as u8, + None + )); + } + quiets &= !(1 << to_sq); + } + } + } + pub fn add_pawn_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut pawns: u64 = self.bitboards[offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while pawns != 0 { + let next_sq = pop_lsb(&mut pawns); + + let mut attacks: u64 = self.get_pseudo_pawn_captures(next_sq) & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + attacks &= opponents; + + while attacks != 0 { + let to_sq = attacks.trailing_zeros(); + + if (self.side_to_move == 0 && attacks.trailing_zeros() / 8 == 7) + || (self.side_to_move == 1 && attacks.trailing_zeros() / 8 == 0) { + for piece_type in [4, 3, 2, 1] { + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + Some(piece_type) + )); + } + } + else { + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + attacks &= !(1 << to_sq); + } + } + } + pub fn add_pawn_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + self.add_pawn_captures(capture_buffer, move_mask); + self.add_pawn_quiets(quiet_buffer, move_mask); + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/queens.rs b/engine/src/bitboard/movegen/queens.rs new file mode 100644 index 0000000..960f49a --- /dev/null +++ b/engine/src/bitboard/movegen/queens.rs @@ -0,0 +1,55 @@ +use super::*; + +impl Board { + + pub fn add_queen_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + let piece_index = 4 + self.side_to_move * 6; + let mut queens = self.bitboards[piece_index as usize]; + let empty = !self.occupancy[2]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while queens != 0 { + let from_sq = pop_lsb(&mut queens); + let raw_move_map = self.get_pseudo_queen_moves(from_sq) & move_mask; + let move_map = self.get_pin_masked_moves(raw_move_map, from_sq); + + let mut quiet_map = move_map & empty; + let mut capture_map = move_map & opponents; + + while quiet_map != 0 { + let to_sq = pop_lsb(&mut quiet_map); + quiet_buffer.add(BitMove::quiet( + from_sq as u8, + to_sq as u8, + None + )); + } + while capture_map != 0 { + let to_sq = pop_lsb(&mut capture_map); + capture_buffer.add(BitMove::capture( + from_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_queen_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut queens: u64 = self.bitboards[4 + offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while queens != 0 { + let next_sq: u32 = pop_lsb(&mut queens); + let mut attacks: u64 = self.get_pseudo_queen_moves(next_sq) & opponents & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + + while attacks != 0 { + let to_sq = pop_lsb(&mut attacks); + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + } + } +} \ No newline at end of file diff --git a/engine/src/bitboard/movegen/rooks.rs b/engine/src/bitboard/movegen/rooks.rs new file mode 100644 index 0000000..749e2d8 --- /dev/null +++ b/engine/src/bitboard/movegen/rooks.rs @@ -0,0 +1,55 @@ +use super::*; + +impl Board { + + pub fn add_rook_moves(&self, capture_buffer: &mut MoveBuffer, quiet_buffer: &mut MoveBuffer, move_mask: u64) { + let piece_index = 3 + self.side_to_move * 6; + let mut rooks = self.bitboards[piece_index as usize]; + let empty = !self.occupancy[2]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while rooks != 0 { + let from_sq = pop_lsb(&mut rooks); + let raw_move_map = self.get_pseudo_rook_moves(from_sq) & move_mask; + let move_map = self.get_pin_masked_moves(raw_move_map, from_sq); + + let mut quiet_map = move_map & empty; + let mut capture_map = move_map & opponents; + + while quiet_map != 0 { + let to_sq = pop_lsb(&mut quiet_map); + quiet_buffer.add(BitMove::quiet( + from_sq as u8, + to_sq as u8, + None + )); + } + while capture_map != 0 { + let to_sq = pop_lsb(&mut capture_map); + capture_buffer.add(BitMove::capture( + from_sq as u8, + to_sq as u8, + None + )); + } + } + } + pub fn add_rook_captures(&self, buffer: &mut MoveBuffer, move_mask: u64) { + let offset = 6 * self.side_to_move as usize; + let mut rooks: u64 = self.bitboards[3 + offset]; + let opponents = self.occupancy[1 - self.side_to_move as usize]; + while rooks != 0 { + let next_sq: u32 = pop_lsb(&mut rooks); + let mut attacks: u64 = self.get_pseudo_rook_moves(next_sq) & opponents & move_mask; + attacks = self.get_pin_masked_moves(attacks, next_sq); + + while attacks != 0 { + let to_sq = pop_lsb(&mut attacks); + buffer.add(BitMove::capture( + next_sq as u8, + to_sq as u8, + None + )); + } + } + } +} \ No newline at end of file diff --git a/engine/src/bitboard/utils.rs b/engine/src/bitboard/utils.rs index f7f83fe..81cc68f 100644 --- a/engine/src/bitboard/utils.rs +++ b/engine/src/bitboard/utils.rs @@ -1,13 +1,13 @@ #[inline(always)] -pub fn pop_lsb(value: &mut u64) -> usize { - let idx = value.trailing_zeros() as usize; +pub fn pop_lsb(value: &mut u64) -> u32 { + let idx = value.trailing_zeros(); *value &= !(1 << idx); return idx; } #[inline(always)] -pub fn pop_msb(value: &mut u64) -> usize { - let idx = 63 - value.leading_zeros() as usize; +pub fn pop_msb(value: &mut u64) -> u32 { + let idx = 63 - value.leading_zeros(); *value &= !(1 << idx); return idx; } @@ -72,7 +72,7 @@ mod tests { 0xBEAC_DBE0_903A_AC00, 0x01E8_C895_A6F0_0000 ]; - let expected_values: [usize; 6] = [63, 0, 4, 2, 10, 20]; + let expected_values: [u32; 6] = [63, 0, 4, 2, 10, 20]; // tests for index in 0..6 { @@ -92,7 +92,7 @@ mod tests { 0x0000_C1C3_201C_0DB1, 0x0000_0203_0DE4_E944 ]; - let expected_values: [usize; 6] = [63, 0, 61, 57, 47, 41]; + let expected_values: [u32; 6] = [63, 0, 61, 57, 47, 41]; // tests for index in 0..6 { diff --git a/engine/src/boardsquare.rs b/engine/src/boardsquare.rs index 2587406..2e0b57f 100644 --- a/engine/src/boardsquare.rs +++ b/engine/src/boardsquare.rs @@ -27,5 +27,22 @@ impl BoardSquare { } return Self { x: x, y: y }; } + + pub(in super) fn from_index(idx: u8) -> Self { + let file = idx % 8; + let rank = idx / 8; + + #[cfg(debug_assertions)] + { + if !(0..8).contains(&rank) { + println!("Warning: internal engine issue, given index is not on the board!"); + } + } + + return Self {x: file as usize, y: rank as usize}; + } + pub(in super) fn to_index(&self) -> u8 { + return (8 * self.y + self.x) as u8; + } } diff --git a/engine/src/chessmove.rs b/engine/src/chessmove.rs index ae46a6a..4fa1319 100644 --- a/engine/src/chessmove.rs +++ b/engine/src/chessmove.rs @@ -1,12 +1,11 @@ -use crate::piecetype; +use crate::{bitboard::{bitmove::{BitMove, BitMoveType}, board::Board}}; use super::boardsquare::BoardSquare; -use super::movetype::MoveType; use super::piecetype::PieceType; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct ChessMove { +/*pub struct ChessMove { pub move_type: MoveType, pub piece_type: PieceType, pub from_square: BoardSquare, @@ -14,6 +13,36 @@ pub struct ChessMove { pub rook_from: BoardSquare, pub rook_to: BoardSquare, pub promotion_piece: Option, +}*/ +pub enum ChessMove { + Quiet { + piece_type: PieceType, + from_square: BoardSquare, + to_square: BoardSquare, + promotion_piece: Option + }, + Capture { + piece_type: PieceType, + from_square: BoardSquare, + to_square: BoardSquare, + captured_piece: PieceType, + promotion_piece: Option + }, + Castle { + king_type: PieceType, + king_from: BoardSquare, + king_to: BoardSquare, + rook_type: PieceType, + rook_from: BoardSquare, + rook_to: BoardSquare + }, + EnPassant { + pawn_type: PieceType, + from_square: BoardSquare, + to_square: BoardSquare, + captured_piece: PieceType, + captured_from: BoardSquare + } } impl ChessMove { @@ -23,14 +52,11 @@ impl ChessMove { to_square: BoardSquare, promotion_piece: Option, ) -> Self { - return Self { - move_type: MoveType::Quiet, - piece_type: piece_type, - from_square: from_square, - to_square: to_square, - rook_from: BoardSquare::new(), - rook_to: BoardSquare::new(), - promotion_piece: promotion_piece, + return Self::Quiet { + piece_type, + from_square, + to_square, + promotion_piece }; } @@ -38,34 +64,129 @@ impl ChessMove { piece_type: PieceType, from_square: BoardSquare, to_square: BoardSquare, + captured_piece: PieceType, promotion_piece: Option, ) -> Self { - return Self { - move_type: MoveType::Capture, - piece_type: piece_type, - from_square: from_square, - to_square: to_square, - rook_from: BoardSquare::new(), - rook_to: BoardSquare::new(), - promotion_piece: promotion_piece, + return Self::Capture { + piece_type, + from_square, + to_square, + captured_piece, + promotion_piece }; } pub fn castle( - piece_type: PieceType, - from_square: BoardSquare, - to_square: BoardSquare, + king_type: PieceType, + king_from: BoardSquare, + king_to: BoardSquare, + rook_type: PieceType, rook_from: BoardSquare, rook_to: BoardSquare, ) -> Self { - return Self { - move_type: MoveType::Quiet, - piece_type: piece_type, - from_square: from_square, - to_square: to_square, - rook_from: rook_from, - rook_to: rook_to, - promotion_piece: None, + return Self::Castle { + king_type, + king_from, + king_to, + rook_type, + rook_from, + rook_to }; } + + pub(in super) fn from_bitmove(bitmove: &BitMove, board: Board) -> Self { + match bitmove.move_type() { + BitMoveType::Quiet => { + let from_square_index = bitmove.from_square(); + let piece_type = PieceType::from_index(board.piece_board(from_square_index)); + let from_square = BoardSquare::from_index(from_square_index); + let to_square = BoardSquare::from_index(bitmove.to_square()); + let promotion_piece = match bitmove.promotion_piece() { + Some(piece) => Some(PieceType::from_index(piece)), + None => None + }; + + return ChessMove::Quiet { piece_type, from_square, to_square, promotion_piece } + }, + BitMoveType::Capture => { + let from_square_index = bitmove.from_square(); + let to_square_index = bitmove.to_square(); + let piece_type = PieceType::from_index(board.piece_board(from_square_index)); + let from_square = BoardSquare::from_index(from_square_index); + let to_square = BoardSquare::from_index(to_square_index); + let captured_piece = PieceType::from_index(board.piece_board(to_square_index)); + let promotion_piece = match bitmove.promotion_piece() { + Some(piece) => Some(PieceType::from_index(piece)), + None => None + }; + + return ChessMove::Capture { piece_type, from_square, to_square, captured_piece, promotion_piece } + }, + BitMoveType::Castle => { + let from_square_index = bitmove.from_square(); + let to_square_index = bitmove.to_square(); + let king_type = PieceType::from_index(board.piece_board(from_square_index)); + let king_from = BoardSquare::from_index(from_square_index); + let king_to = BoardSquare::from_index(to_square_index); + let promotion_piece = match bitmove.promotion_piece() { + Some(piece) => Some(PieceType::from_index(piece)), + None => None + }; + let rook_type = if bitmove.from_square() < 32 { PieceType::WhiteRook } else { PieceType::BlackRook }; + let rook_from_index = if bitmove.to_square() > bitmove.from_square() { + bitmove.from_square() + 3 + } else { + bitmove.from_square() - 4 + }; + let rook_from = BoardSquare::from_index(rook_from_index); + let rook_to_index = if bitmove.to_square() > bitmove.from_square() { + bitmove.from_square() + 1 + } else { + bitmove.from_square() - 1 + }; + let rook_to = BoardSquare::from_index(rook_to_index); + + return ChessMove::Castle { king_type, king_from, king_to, rook_type, rook_from, rook_to } + }, + BitMoveType::EnPassant => { + panic!("ChessMove::from_bitmove was left unimplemented"); + } + } + } + pub(in super) fn to_bitmove(&self) -> BitMove { + let bitmove = match self { + ChessMove::Quiet { piece_type, from_square, to_square, promotion_piece } => { + let promotion_piece = match promotion_piece { + Some(piece) => Some(piece.to_index()), + None => None + }; + return BitMove::quiet( + from_square.to_index(), + to_square.to_index(), + promotion_piece + ); + }, + ChessMove::Capture { piece_type, from_square, to_square, captured_piece, promotion_piece } => { + let promotion_piece = match promotion_piece { + Some(piece) => Some(piece.to_index()), + None => None + }; + return BitMove::capture( + from_square.to_index(), + to_square.to_index(), + promotion_piece + ); + }, + ChessMove::Castle { king_type, king_from, king_to, rook_type, rook_from, rook_to } => { + return BitMove::castle( + king_from.to_index(), + king_to.to_index() + ); + }, + ChessMove::EnPassant { pawn_type, from_square, to_square, captured_piece, captured_from } => { + panic!("ChessMove::to_bitmove was left unimplemented"); + } + }; + return bitmove; + } } diff --git a/engine/src/movetype.rs b/engine/src/movetype.rs index 3c531dd..c7fa6d9 100644 --- a/engine/src/movetype.rs +++ b/engine/src/movetype.rs @@ -1,5 +1,6 @@ use serde::Deserialize; use serde::Serialize; +use super::bitboard::bitmove::BitMoveType; #[derive(Serialize, Deserialize)] pub enum MoveType { @@ -9,3 +10,22 @@ pub enum MoveType { EnPassant, } +impl MoveType { + pub(in super) fn from_bitmovetype(move_type: BitMoveType) -> Self { + return match move_type { + BitMoveType::Quiet => Self::Quiet, + BitMoveType::Capture => Self::Capture, + BitMoveType::Castle => Self::Castle, + BitMoveType::EnPassant => Self::EnPassant, + _ => panic!("invalid move_type index! should NEVER appear") + } + } + pub(in super) fn to_bitmoveType(&self) -> BitMoveType { + return match self { + &MoveType::Quiet => BitMoveType::Quiet, + &MoveType::Capture => BitMoveType::Capture, + &MoveType::Castle => BitMoveType::Castle, + &MoveType::EnPassant => BitMoveType::EnPassant + }; + } +} \ No newline at end of file diff --git a/engine/src/piecetype.rs b/engine/src/piecetype.rs index b79d341..92a7e7b 100644 --- a/engine/src/piecetype.rs +++ b/engine/src/piecetype.rs @@ -16,3 +16,39 @@ pub enum PieceType { BlackKing, } +impl PieceType { + + pub(in super) fn from_index(idx: u8) -> Self { + return match idx { + 0 => PieceType::WhitePawn, + 1 => PieceType::WhiteKnight, + 2 => PieceType::WhiteBishop, + 3 => PieceType::WhiteRook, + 4 => PieceType::WhiteQueen, + 5 => PieceType::WhiteKing, + 6 => PieceType::BlackPawn, + 7 => PieceType::BlackKnight, + 8 => PieceType::BlackBishop, + 9 => PieceType::BlackRook, + 10 => PieceType::BlackQueen, + 11 => PieceType::BlackKing, + _ => panic!("invalid piece index! should NEVER appear") + } + } + pub(in super) fn to_index(&self) -> u8 { + return match self { + &PieceType::WhitePawn => 0, + &PieceType::WhiteKnight => 1, + &PieceType::WhiteBishop => 2, + &PieceType::WhiteRook => 3, + &PieceType::WhiteQueen => 4, + &PieceType::WhiteKing => 5, + &PieceType::BlackPawn => 6, + &PieceType::BlackKnight => 7, + &PieceType::BlackBishop => 8, + &PieceType::BlackRook => 9, + &PieceType::BlackQueen => 10, + &PieceType::BlackKing => 11 + } + } +} \ No newline at end of file diff --git a/server/Cargo.lock b/server/Cargo.lock index 1037c3d..027f4a3 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -768,6 +768,7 @@ dependencies = [ "env_logger", "futures-util", "log", + "futures-util", "rand 0.9.2", "serde", "serde_json", diff --git a/server/src/connection.rs b/server/src/connection.rs index d4342e5..828f0cf 100644 --- a/server/src/connection.rs +++ b/server/src/connection.rs @@ -1,10 +1,12 @@ use crate::connection::ClientEvent::*; +use crate::matchmaking; use engine::chessmove::ChessMove; use engine::gameend::GameEnd::{self, *}; use engine::{get_available_moves, is_game_over}; use futures_util::{SinkExt, StreamExt}; use log::{error, info, warn}; use serde::{Deserialize, Serialize}; +use std::char::from_u32_unchecked; use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use tokio::net::TcpStream; @@ -18,6 +20,10 @@ pub type ConnectionMap = Arc>>; pub type MatchMap = Arc>>; pub type WaitingQueue = Arc>>; +pub async fn clean_up_match(matches: &MatchMap, match_id: &Uuid) { + matches.lock().await.remove(&match_id); +} + // Helper functions to create new instances pub fn new_connection_map() -> ConnectionMap { warn!("Created new connection map"); @@ -65,6 +71,9 @@ pub enum ServerMessage2 { color: String, opponent_name: String, }, + Ok { + response: Result<(), String>, + }, } #[derive(Serialize, Deserialize)] @@ -78,11 +87,6 @@ enum ClientEvent { RequestLegalMoves { fen: String }, } -#[derive(Serialize, Deserialize, Debug)] -pub struct EventResponse { - pub response: Result<(), String>, -} - #[derive(Debug)] pub struct PlayerConnection { pub id: Uuid, @@ -207,12 +211,7 @@ pub async fn handle_connection( } //respone to client - // TODO: switch over to server message 2? - let response: EventResponse = EventResponse { - response: core::result::Result::Ok(()), - }; - - info!("response: {:?}", response); + let response = ServerMessage2::Ok { response: Ok(()) }; let mut conn_map = connections.lock().await; let _ = send_message_to_player_connection( @@ -264,9 +263,11 @@ pub async fn handle_connection( .await; { - match engine::is_game_over( + let is_game_end = engine::is_game_over( &matches.lock().await.get(&match_id).unwrap().board_state, - ) { + ); + + match is_game_end { Some(res) => { warn!("A player won the match: {}", &match_id); let message = ServerMessage2::GameEnd { winner: res }; @@ -277,6 +278,7 @@ pub async fn handle_connection( &serde_json::to_string(&message).unwrap(), ) .await; + clean_up_match(&matches, &match_id); } None => { info!("No winner match continues. Id: {}", &match_id); @@ -295,8 +297,65 @@ pub async fn handle_connection( info!("Sent moves to player: {}", player_id); } Resign => { - // TODO: set game over and turn on game end ui, then delete the match warn!("Resigned!"); + let (fuck, fuck_id): (ServerMessage2, &Uuid) = { + let matches = matches.lock().await; + + let curr_match = matches + .get( + &connections + .lock() + .await + .get(&player_id) + .unwrap() + .current_match + .unwrap(), + ) + .unwrap(); + + if player_id == curr_match.player_white { + ( + ServerMessage2::GameEnd { + winner: GameEnd::BlackWon("Resigned".to_string()), + }, + &connections + .lock() + .await + .get(&player_id) + .unwrap() + .current_match + .unwrap(), + ) + } else { + ( + ServerMessage2::GameEnd { + winner: GameEnd::WhiteWon("Resigned".to_string()), + }, + &connections + .lock() + .await + .get(&player_id) + .unwrap() + .current_match + .unwrap(), + ) + } + }; + + broadcast_to_match( + &connections, + &matches, + connections + .lock() + .await + .get(&player_id) + .unwrap() + .current_match + .unwrap(), + &serde_json::to_string(&fuck).unwrap(), + ) + .await; + clean_up_match(&matches, fuck_id); } _ => { warn!("Not known client event"); diff --git a/server/src/matchmaking.rs b/server/src/matchmaking.rs index 524842d..b1514c9 100644 --- a/server/src/matchmaking.rs +++ b/server/src/matchmaking.rs @@ -26,6 +26,10 @@ impl MatchmakingSystem { } } + pub async fn clean_up(&self, match_id: Uuid) { + self.matches.lock().await.remove(&match_id); + } + async fn try_create_match(&self) { info!("Checking for new matches!"); let mut queue = self.waiting_queue.lock().await;