diff --git a/src/board.rs b/src/board.rs index 7ca4cbb..154a5b3 100644 --- a/src/board.rs +++ b/src/board.rs @@ -1,5 +1,6 @@ use crate::moves::Move; use crate::{constants::*, conversion::*, evaluate}; +use std::collections; use std::collections::hash_map::DefaultHasher; use std::hash::Hasher; #[derive(Clone)] @@ -49,8 +50,8 @@ impl Board { ]; let colour_array = [ - [2, 2, 2, 2, 2, 2, 2, 2], - [2, 2, 2, 2, 2, 2, 2, 2], + [-1, -1, -1, -1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1, -1, -1, -1], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], @@ -94,10 +95,6 @@ impl Board { } pub fn get_fen(&self) -> String { - // TODO - - // let mut fen = String::new(); - return "".to_string(); } pub fn reset_board(&mut self) { @@ -112,8 +109,8 @@ impl Board { [4, 2, 3, 5, 6, 3, 2, 4], ]; self.colour_array = [ - [2, 2, 2, 2, 2, 2, 2, 2], - [2, 2, 2, 2, 2, 2, 2, 2], + [-1, -1, -1, -1, -1, -1, -1, -1], + [-1, -1, -1, -1, -1, -1, -1, -1], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], @@ -278,8 +275,6 @@ impl Board { self.set_piece_and_colour(castle_from_to_square.0, EMPTY, EMPTY); } - self.add_hash_of_current_position(); - // set side to move to opposite self.side_to_move = if self.side_to_move == WHITE { BLACK @@ -287,6 +282,8 @@ impl Board { WHITE }; + self.add_hash_of_current_position(); + self.ply += 1; } @@ -306,11 +303,6 @@ impl Board { return Err("cannot move empty colour".to_string()); } - println!( - "from: {:?},from piece: {:?}, to: {:?}, to piece: {}", - move_to_do.from, move_to_do.to, move_to_do.from_piece, move_to_do.to_piece - ); - self.make_move(&move_to_do); return Ok(move_to_do); } @@ -385,8 +377,23 @@ impl Board { } // add pawn back from en passant } - - pub fn convert_notation_to_move(&self, mut chess_move: String) -> Result { + pub fn is_piece_type_on_board_for_side(&self, piece: i8, colour: i8) -> bool { + // go through each piece on the board, by colour to only get moves for side to move. + for (row_index, row) in self.board_array.iter().enumerate() { + for (column_index, piece_type) in row.iter().enumerate() { + if piece_type != &piece { + continue; + } + let piece_colour = self.get_piece_colour((row_index, column_index)); + if piece_colour != colour { + continue; + } + return true; + } + } + return false; + } + pub fn convert_notation_to_move(&self, chess_move: String) -> Result { let mut converted_move = Move::default(); // convert "e1g1" "e1c1" "e8g8" "e8c8" into O-O or O-O-O @@ -395,35 +402,19 @@ impl Board { || ((chess_move == "e8g8" && self.can_castle_h8) || (chess_move == "e8c8" && self.can_castle_h1)) { - chess_move = match chess_move.as_str() { - "e1g1" => "O-O".to_string(), - "e1c1" => "O-O-O".to_string(), - "e8g8" => "O-O".to_string(), - "e8c8" => "O-O-O".to_string(), - _ => return Err("Invalid castling move".to_string()), - }; - } - - // castling is a special case - if ((self.side_to_move == WHITE && self.can_castle_a1 && self.can_castle_h1) - || (self.side_to_move == BLACK && self.can_castle_a8 && self.can_castle_h8)) - && (chess_move == "O-O" || chess_move == "O-O-O") - { + // chess_move = match chess_move.as_str() { + // "e1g1" => "O-O".to_string(), + // "e1c1" => "O-O-O".to_string(), + // "e8g8" => "O-O".to_string(), + // "e8c8" => "O-O-O".to_string(), + // _ => return Err("Invalid castling move".to_string()), + // }; + // castling is a special case let from_to_squares = match chess_move.as_str() { - "O-O" => { - if self.side_to_move == WHITE { - ((7, 4), (7, 6), (7, 7), (7, 5)) - } else { - ((0, 4), (0, 6), (0, 7), (0, 5)) - } - } - "O-O-O" => { - if self.side_to_move == WHITE { - ((7, 4), (7, 2), (7, 0), (7, 3)) - } else { - ((0, 4), (0, 2), (0, 9), (0, 3)) - } - } + "e1g1" => ((7, 4), (7, 6), (7, 7), (7, 5)), + "e8g8" => ((0, 4), (0, 6), (0, 7), (0, 5)), + "e1c1" => ((7, 4), (7, 2), (7, 0), (7, 3)), + "e8c8" => ((0, 4), (0, 2), (0, 9), (0, 3)), _ => return Err("Invalid castling move".to_string()), }; // depends on side to move where piece is @@ -434,8 +425,6 @@ impl Board { to: from_to_squares.1, to_piece: EMPTY, to_colour: EMPTY, - - // notation_move: chess_move.clone(), promotion_to: None, en_passant: false, castle_from_to_square: Some((from_to_squares.2, from_to_squares.3)), @@ -585,22 +574,26 @@ impl Board { return if count >= 3 { true } else { false }; } - pub fn get_king_location(&self, side: i8) -> (usize, usize) { + pub fn get_king_location(&self, side: i8) -> Option<(usize, usize)> { // find king for side // go through each piece on the board, by colour to only get moves for side to move. for (row_index, row) in self.board_array.iter().enumerate() { for (column_index, piece) in row.iter().enumerate() { + if *piece != KING { + continue; + } + let location = (row_index, column_index); let square_colour = &self.get_piece_colour((row_index, column_index)); - if square_colour != &side || *piece != KING { + if square_colour != &side { continue; } - return location; + return Some(location); } } - panic!("King not found!"); + return None; } } pub fn print_board(board: &Board) { @@ -646,7 +639,7 @@ pub fn print_board(board: &Board) { } let colour = match square { 1 => "W", - 2 => "B", + -1 => "B", 0 => " ", _ => " ", }; diff --git a/src/constants.rs b/src/constants.rs index 1335ef3..8d37716 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -7,7 +7,7 @@ pub const QUEEN: i8 = 5; pub const KING: i8 = 6; pub const WHITE: i8 = 1; -pub const BLACK: i8 = 2; +pub const BLACK: i8 = -1; pub const EMPTY: i8 = 0; pub const BOARD_COORDINATES: [[&str; 8]; 8] = [ @@ -24,7 +24,7 @@ pub const BOARD_COORDINATES: [[&str; 8]; 8] = [ // MVV_VLA[victim][attacker] // Most Valued Victim, Least Valued Attacker pub const MVV_LVA: [[u8; 7]; 7] = [ - [0, 0, 0, 0, 0, 0, 0], // victim K, attacker K, Q, R, B, N, P, None + [60, 61, 62, 63, 64, 65, 0], // victim K, attacker K, Q, R, B, N, P, None [10, 11, 12, 13, 14, 15, 0], // victim P, attacker K, Q, R, B, N, P, None [20, 21, 22, 23, 24, 25, 0], // victim N, attacker K, Q, R, B, N, P, None [30, 31, 32, 33, 34, 35, 0], // victim B, attacker K, Q, R, B, N, P, None @@ -32,129 +32,79 @@ pub const MVV_LVA: [[u8; 7]; 7] = [ [50, 51, 52, 53, 54, 55, 0], // victim Q, attacker K, Q, R, B, N, P, None [0, 0, 0, 0, 0, 0, 0], // victim None, attacker K, Q, R, B, N, P, None ]; -// pub const MG_PAWN_TABLE: [[i32; 8]; 8] = [ -// [0, 0, 0, 0, 0, 0, 0, 0], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 2, 2, 1, 1, 1], -// [1, 1, 2, 1, 1, 2, 1, 1], -// [1, 1, 1, -1, -1, 1, 1, 1], -// [0, 0, 0, 0, 0, 0, 0, 0], -// ]; -// pub const MG_KNIGHT_TABLE: [[i32; 8]; 8] = [ -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// ]; -// pub const MG_BISHOP_TABLE: [[i32; 8]; 8] = [ -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// ]; - -// pub const MG_ROOK_TABLE: [[i32; 8]; 8] = [ -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// ]; -// pub const MG_QUEEN_TABLE: [[i32; 8]; 8] = [ -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// ]; -// pub const MG_KING_TABLE: [[i32; 8]; 8] = [ -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// [1, 1, 1, 1, 1, 1, 1, 1], -// ]; - pub const MG_PAWN_TABLE: [[i32; 8]; 8] = [ [0, 0, 0, 0, 0, 0, 0, 0], - [98, 134, 61, 95, 68, 126, 34, -11], - [-6, 7, 26, 31, 65, 56, 25, -20], - [-14, 13, 6, 21, 23, 12, 17, -23], - [-27, -2, -5, 12, 17, 6, 10, -25], - [-26, -4, -4, -10, 3, 3, 33, -12], - [-35, -1, -20, -23, -15, 24, 38, -22], + [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], ]; pub const MG_KNIGHT_TABLE: [[i32; 8]; 8] = [ - [-167, -89, -34, -49, 61, -97, -15, -107], - [-73, -41, 72, 36, 23, 62, 7, -17], - [-47, 60, 37, 65, 84, 129, 73, 44], - [-9, 17, 19, 53, 37, 69, 18, 22], - [-13, 4, 16, 13, 28, 19, 21, -8], - [-23, -9, 12, 10, 19, 17, 25, -16], - [-29, -53, -12, -3, -1, 18, -14, -19], - [-105, -21, -58, -33, -17, -28, -19, -23], + [-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], ]; pub const MG_BISHOP_TABLE: [[i32; 8]; 8] = [ - [-29, 4, -82, -37, -25, -42, 7, -8], - [-26, 16, -18, -13, 30, 59, 18, -47], - [-16, 37, 43, 40, 35, 50, 37, -2], - [-4, 5, 19, 50, 37, 37, 7, -2], - [-6, 13, 13, 26, 34, 12, 10, 4], - [0, 15, 15, 15, 14, 27, 18, 10], - [4, 15, 16, 0, 7, 21, 33, 1], - [-33, -3, -14, -21, -13, -12, -39, -21], + [-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], ]; pub const MG_ROOK_TABLE: [[i32; 8]; 8] = [ - [32, 42, 32, 51, 63, 9, 31, 43], - [27, 32, 58, 62, 80, 67, 26, 44], - [-5, 19, 26, 36, 17, 45, 61, 16], - [-24, -11, 7, 26, 24, 35, -8, -20], - [-36, -26, -12, -1, 9, -7, 6, -23], - [-45, -25, -16, -17, 3, 0, -5, -33], - [-44, -16, -20, -9, -1, 11, -6, -71], - [-19, -13, 1, 17, 16, 7, -37, -26], + [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], ]; pub const MG_QUEEN_TABLE: [[i32; 8]; 8] = [ - [-28, 0, 29, 12, 59, 44, 43, 45], - [-24, -39, -5, 1, -16, 57, 28, 54], - [-13, -17, 7, 8, 29, 56, 47, 57], - [-27, -27, -16, -16, -1, 17, -2, 1], - [-9, -26, -9, -10, -2, -4, 3, -3], - [-14, 2, -11, -2, -5, 2, 14, 5], - [-35, -8, 11, 2, 8, 15, -3, 1], - [-1, -18, -9, 10, -15, -25, -31, -50], + [-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], ]; pub const MG_KING_TABLE: [[i32; 8]; 8] = [ - [-65, 23, 16, -15, -56, -34, 2, 13], - [29, -1, -20, -7, -8, -4, -38, -29], - [-9, 24, 2, -16, -20, 6, 22, -22], - [-17, -20, -12, -27, -30, -25, -14, -36], - [-49, -1, -27, -39, -46, -44, -33, -51], - [-14, -14, -22, -46, -44, -30, -15, -27], - [1, 7, -8, -64, -43, -16, 9, 8], - [-15, 36, 12, -54, 8, -28, 24, 14], + [-30, -40, -40, -50, -50, -40, -40, -30], + [-30, -40, -40, -50, -50, -40, -40, -30], + [-30, -40, -40, -50, -50, -40, -40, -30], + [-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, 0, 0, 0, 0, 20, 20], + [20, 30, 10, 0, 0, 10, 30, 20], ]; + +pub const EG_KING_TABLE: [[i32; 8]; 8] = [ + [-50, -40, -30, -20, -20, -30, -40, -50], + [-30, -20, -10, 0, 0, -10, -20, -30], + [-30, -10, 20, 30, 30, 20, -10, -30], + [-30, -10, 30, 40, 40, 30, -10, -30], + [-30, -10, 30, 40, 40, 30, -10, -30], + [-30, -10, 20, 30, 30, 20, -10, -30], + [-30, -30, 0, 0, 0, 0, -30, -30], + [-50, -30, -30, -30, -30, -30, -30, -50], +]; + pub const BENCH_FENS: [&str; 50] = [ "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq a6 0 14", "4rrk1/2p1b1p1/p1p3q1/4p3/2P2n1p/1P1NR2P/PB3PP1/3R1QK1 b - - 2 24", diff --git a/src/conversion.rs b/src/conversion.rs index 79a13df..0b7d662 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -1,5 +1,8 @@ use crate::board::Board; use crate::constants; +use crate::constants::BLACK; +use crate::constants::WHITE; +use crate::evaluate; use crate::moves::*; // use crate::evaluate; pub fn convert_fen_to_board(fen: &str) -> Board { @@ -31,9 +34,11 @@ pub fn convert_fen_to_board(fen: &str) -> Board { } if character.is_alphabetic() { - // board.board_array[current_row][current_column] = - // convert_alphabetic_to_piece(character); - let piece_colour = if character.is_uppercase() { 1 } else { 2 }; + let piece_colour = if character.is_uppercase() { + WHITE + } else { + BLACK + }; board.set_piece_and_colour( (current_row, current_column), @@ -58,8 +63,8 @@ pub fn convert_fen_to_board(fen: &str) -> Board { 1 => { // side to move match section { - "w" => board.side_to_move = 1, - "b" => board.side_to_move = 2, + "w" => board.side_to_move = WHITE, + "b" => board.side_to_move = BLACK, "-" => {} _ => todo!(), // probably panic } @@ -137,7 +142,7 @@ pub fn convert_fen_to_board(fen: &str) -> Board { return board; } -pub fn get_piece_square_value(location: (usize, usize), piece_type: i8, colour: i8) -> i32 { +pub fn get_piece_square_value_mg(location: (usize, usize), piece_type: i8, colour: i8) -> i32 { if colour == constants::WHITE { return match piece_type { constants::PAWN => constants::MG_PAWN_TABLE[location.0][location.1], @@ -160,7 +165,29 @@ pub fn get_piece_square_value(location: (usize, usize), piece_type: i8, colour: }; } } - +pub fn get_piece_square_value_eg(location: (usize, usize), piece_type: i8, colour: i8) -> i32 { + if colour == constants::WHITE { + return match piece_type { + constants::PAWN => constants::MG_PAWN_TABLE[location.0][location.1], + constants::KNIGHT => constants::MG_KNIGHT_TABLE[location.0][location.1], + constants::BISHOP => constants::MG_BISHOP_TABLE[location.0][location.1], + constants::ROOK => constants::MG_ROOK_TABLE[location.0][location.1], + constants::QUEEN => constants::MG_QUEEN_TABLE[location.0][location.1], + constants::KING => constants::EG_KING_TABLE[location.0][location.1], + _ => 0, + }; + } else { + return match piece_type { + constants::PAWN => constants::MG_PAWN_TABLE[7 - (location.0)][7 - (location.1)], + constants::KNIGHT => constants::MG_KNIGHT_TABLE[7 - (location.0)][7 - (location.1)], + constants::BISHOP => constants::MG_BISHOP_TABLE[7 - (location.0)][7 - (location.1)], + constants::ROOK => constants::MG_ROOK_TABLE[7 - (location.0)][7 - (location.1)], + constants::QUEEN => constants::MG_QUEEN_TABLE[7 - (location.0)][7 - (location.1)], + constants::KING => constants::EG_KING_TABLE[7 - (location.0)][7 - (location.1)], + _ => 0, + }; + } +} pub fn convert_alphabetic_to_piece(character: char) -> i8 { match character.to_ascii_uppercase() { 'K' => constants::KING, @@ -218,6 +245,10 @@ pub fn convert_notation_to_location(chess_move: &str) -> Option<(usize, usize)> } return Some(location); } + +pub fn normalise_score_to_cp(score: i32) -> f64 { + return (2.00 * (1.00 / (1.00 + (-score as f64 / 1000.00).exp())) - 1.00).clamp(-1.00, 1.00); +} /// convert current board state into fen pub fn convert_board_to_fen(_board: &Board) -> String { let fen_string = String::new(); diff --git a/src/evaluate.rs b/src/evaluate.rs index cbbca32..f593e91 100644 --- a/src/evaluate.rs +++ b/src/evaluate.rs @@ -1,34 +1,93 @@ +use core::num; + use crate::board::Board; use crate::{constants::*, conversion, movegen::*}; +#[derive(Debug, Clone, Copy)] +pub struct PieceValues { + pub pawn: i32, + pub knight: i32, + pub bishop: i32, + pub rook: i32, + pub queen: i32, + pub king: i32, +} +pub const PIECE_VALUES: PieceValues = PieceValues { + pawn: 100, + knight: 320, + bishop: 330, + rook: 500, + queen: 900, + king: 20000, +}; +pub fn is_endgame(board: &Board) -> bool { + // if no queens + if !board.is_piece_type_on_board_for_side(QUEEN, WHITE) + && !board.is_piece_type_on_board_for_side(QUEEN, BLACK) + { + return true; + } + // if if only queen on either side + return false; +} pub fn evaluate(board: &Board, searching_side: i8) -> i32 { let mut score: i32 = 0; + if board.get_king_location(searching_side).is_none() { + return i32::MIN / 2 + board.ply; + } + let opponent = if searching_side == WHITE { + BLACK + } else { + WHITE + }; + if board.get_king_location(opponent).is_none() { + return i32::MAX / 2 - board.ply; + } + + score -= get_safety_score( + board, + board.get_king_location(searching_side).unwrap(), + searching_side, + ); - // go through each piece on the board, by colour to only get moves for side to move. for (external_row_index, row) in board.colour_array.iter().enumerate() { for (external_column_index, colour) in row.iter().enumerate() { - let mut is_protected: bool = false; - // let location = (row_index, column_index); let square = board.get_piece((external_row_index, external_column_index)); + let mut is_protected = false; + let mut is_attacked = false; if square == EMPTY { continue; } - let mut score_for_piece_type = conversion::get_piece_square_value( - (external_row_index, external_column_index), - square, - *colour, - ); + let mut score_for_piece_type = if is_endgame(board) { + conversion::get_piece_square_value_eg( + (external_row_index, external_column_index), + square, + *colour, + ) + } else { + conversion::get_piece_square_value_mg( + (external_row_index, external_column_index), + square, + *colour, + ) + }; + score_for_piece_type += match square { - PAWN => 100, - KNIGHT => 200, - BISHOP => 250, - ROOK => 400, - QUEEN => 800, - KING => 10000, + PAWN => PIECE_VALUES.pawn, + KNIGHT => PIECE_VALUES.knight, + BISHOP => PIECE_VALUES.bishop, + ROOK => PIECE_VALUES.rook, + QUEEN => PIECE_VALUES.queen, + KING => PIECE_VALUES.king, _ => 0, }; + + if square == KING && is_in_check(board, *colour, None) { + score_for_piece_type = PIECE_VALUES.king / 2; + } + for (row_index, row) in board.colour_array.iter().enumerate() { for (column_index, square_colour) in row.iter().enumerate() { let piece_type = board.get_piece((row_index, column_index)); @@ -36,34 +95,48 @@ pub fn evaluate(board: &Board, searching_side: i8) -> i32 { if (row_index, column_index) == (external_row_index, external_column_index) { continue; } - is_protected = is_attacked_by_piece_from_square( - board, - (row_index, column_index), - piece_type, - (external_row_index, external_column_index), - *square_colour, - ); - if is_protected { + // for each piece check if its hung or not + let enemy_colour = if *colour == WHITE { BLACK } else { WHITE }; + + if !is_protected { + is_protected = is_attacked_by_piece_from_square( + board, + (row_index, column_index), + piece_type, + (external_row_index, external_column_index), + *square_colour, + ); + } + if !is_attacked { + is_attacked = is_attacked_by_piece_from_square( + board, + (row_index, column_index), + piece_type, + (external_row_index, external_column_index), + enemy_colour, + ); + } + if is_protected && is_attacked { break; } } - if is_protected { + if is_protected && is_attacked { break; } } - if !is_protected { + // if not protected, half the piece value. + if !is_protected || is_attacked { score_for_piece_type = (score_for_piece_type as f64 * 0.5) as i32; } - // if for other side, make negative. if colour != &searching_side { score_for_piece_type *= -1; } - score += score_for_piece_type as i32; } } + return score; // count and addup pieces. } @@ -86,7 +159,7 @@ pub fn is_in_check( board, (row_index, column_index), piece_type, - king_location, + king_location.unwrap(), opponent_colour, ); @@ -110,9 +183,72 @@ pub fn is_in_check( } } return false; +} +pub fn get_safety_score(board: &Board, square: (usize, usize), side_to_check: i8) -> i32 { + let mut safety_score = 0; + let mut number_of_attackers = 0; + + let piece_attack_weight = [1, 20, 20, 40, 80, 1]; + let square_direction = [ + (1, 0), + (1, -1), + (0, -1), + (-1, -1), + (-1, 0), + (-1, 1), + (0, 1), + (1, 1), + (0, 0), + ]; + // for the square, get it and all the surrounding squares locations. + // for each of those squares, check if it is attacked. + let opponent_colour = if side_to_check == WHITE { BLACK } else { WHITE }; - // in case of castling, check if they attack the intermediary squares. + for direction in square_direction.iter() { + let square_to_check = (square.0 as i8 + direction.0, square.1 as i8 + direction.1); + if square_to_check.0 < 0 + || square_to_check.0 > 7 + || square_to_check.1 < 0 + || square_to_check.1 > 7 + { + continue; + } + let mut square_checked = false; + for (row_index, row) in board.colour_array.iter().enumerate() { + for (column_index, square_colour) in row.iter().enumerate() { + if square_colour != &opponent_colour { + continue; + } + + let piece_type = board.get_piece((row_index, column_index)); + + let outcome = is_attacked_by_piece_from_square( + board, + (row_index, column_index), + piece_type, + (square_to_check.0 as usize, square_to_check.1 as usize), + opponent_colour, + ); + + if outcome { + number_of_attackers += 1; + safety_score += piece_attack_weight[piece_type as usize - 1]; + square_checked = true; + } + if square_checked { + break; + } + } + + if square_checked { + break; + } + } + } + return number_of_attackers * safety_score; } +// in case of castling, check if they attack the intermediary squares. + // its given a square, and an enemy piece. // and if the square is attacked by the enemy piece it returns true. pub fn is_attacked_by_piece_from_square( @@ -252,7 +388,7 @@ mod tests { let board = conversion::convert_fen_to_board("Q1k5/8/1K6/8/8/5B2/8/8 b - - 0 64"); let eval = evaluate::evaluate(&board, 1); - assert!(eval > 1000, "position does not favour white!"); + assert!(eval > 100, "position does not favour white!"); } #[test] @@ -268,7 +404,7 @@ mod tests { let board = conversion::convert_fen_to_board("1k6/7p/4q3/3n4/3K4/2q5/7P/8 w - - 2 50"); let eval = evaluate::evaluate(&board, 2); - assert!(eval > 1000, "position does not favour black!"); + assert!(eval > 100, "position does not favour black!"); } // test black favoured position favour black diff --git a/src/moveGen.rs b/src/moveGen.rs index c08cdbf..a458059 100644 --- a/src/moveGen.rs +++ b/src/moveGen.rs @@ -1,9 +1,4 @@ -use crate::{ - board::*, - constants::*, - // conversion::*, - moves::*, -}; +use crate::{board::*, constants::*, moves::*}; use std::vec; pub fn get_pawn_attacks( @@ -17,7 +12,7 @@ pub fn get_pawn_attacks( let (row, column) = square; let direction_of_pawns: isize = match side_to_generate_for { 1 => -1, - 2 => 1, + -1 => 1, _ => 0, }; let pawn_attack_steps: [(isize, isize); 2] = @@ -58,10 +53,14 @@ pub fn generate_pawn_moves( let mut blocked = false; let direction_of_pawns: i8 = match side_to_generate_for { 1 => -1, - 2 => 1, + -1 => 1, _ => 0, }; - let _enemy_color = if side_to_generate_for == 1 { 2 } else { 1 }; + let _enemy_color = if side_to_generate_for == WHITE { + BLACK + } else { + WHITE + }; // know if double jump allowed if from starting row let starting_row = if side_to_generate_for == 1 { 6 } else { 1 }; diff --git a/src/search.rs b/src/search.rs index 91cdd86..79ef5fa 100644 --- a/src/search.rs +++ b/src/search.rs @@ -18,12 +18,15 @@ pub struct SearchEngine { pub start: Instant, pub move_nodes: Vec, pub depth: i8, + pub current_depth: i8, pub wtime: u128, + pub movetime: u128, pub btime: u128, pub winc: u128, pub binc: u128, pub use_time_management: bool, pub searching_side: i8, + pub move_overhead: u128, } pub fn order_moves(moves: &mut Vec) { @@ -34,9 +37,7 @@ pub fn order_moves(moves: &mut Vec) { move_to_score.sort_score += value; } - moves.sort_by(|a, b| a.sort_score.cmp(&b.sort_score)); - // sort moves - // captures first + moves.sort_by(|a, b| b.sort_score.cmp(&a.sort_score)); } impl SearchEngine { pub fn new() -> Self { @@ -45,7 +46,10 @@ impl SearchEngine { start: Instant::now(), move_nodes: Vec::new(), depth: 3, + current_depth: 1, winc: 0, + movetime: 0, + move_overhead: 10, wtime: 0, binc: 0, btime: 0, @@ -55,6 +59,10 @@ impl SearchEngine { } pub fn get_allowed_time(&self, side: i8) -> u128 { if self.use_time_management { + if self.movetime > 0 { + return self.movetime - 2 * self.move_overhead; + } + let time_left = if side == WHITE { self.wtime } else { @@ -62,8 +70,8 @@ impl SearchEngine { }; let increment = if side == WHITE { self.winc } else { self.binc }; - - return time_left / 30 + increment; + println!("{} {}", self.move_overhead, increment); + return (time_left / 30 + increment - 2 * self.move_overhead) as u128; } else { return 10000; } @@ -79,6 +87,7 @@ impl SearchEngine { // the move needs to record its own evaluation if depth == 0 { self.nodes += 1; + return evaluate::evaluate(&board, self.searching_side); }; @@ -87,6 +96,7 @@ impl SearchEngine { return evaluate::evaluate(&board, self.searching_side); } } + // generate moves for current depth of board let mut moves_for_current_depth = movegen::generate_pseudo_legal_moves(board, board.side_to_move, false); @@ -126,13 +136,38 @@ impl SearchEngine { } } + pub fn negamax(&mut self, board: &mut Board, depth: i8, mut alpha: i32, mut beta: i32) -> i32 { + let mut max_eval = i32::MIN; + if depth == 0 { + self.nodes += 1; + return evaluate::evaluate(&board, self.searching_side); + }; + + let mut moves_for_current_depth = + movegen::generate_pseudo_legal_moves(board, board.side_to_move, false); + + order_moves(&mut moves_for_current_depth); + + for generated_move in moves_for_current_depth.iter() { + board.make_move(generated_move); + let eval = -self.negamax(board, depth - 1, alpha, beta); + board.un_make_move(generated_move); + + max_eval = std::cmp::max(max_eval, eval); + alpha = std::cmp::max(alpha, eval); + if beta <= alpha { + break; + } + } + return max_eval; + } + pub fn search(&mut self, board: &mut Board) -> (Move, Vec) { // adding in iterative deepening? let mut searching = true; let mut best_move = Move::default(); let mut best_score = i32::MIN; let mut best_moves = Vec::new(); - let mut local_depth = 1; self.searching_side = board.side_to_move; self.nodes = 0; @@ -151,12 +186,6 @@ impl SearchEngine { for generated_move in moves_for_current_depth.iter_mut() { board.make_move(generated_move); - if board.has_positions_repeated() { - generated_move.illegal_move = true; - board.un_make_move(generated_move); - continue; - } - // check not moving self into check if evaluate::is_in_check( board, @@ -168,8 +197,14 @@ impl SearchEngine { continue; } + if board.has_positions_repeated() { + generated_move.illegal_move = true; + board.un_make_move(generated_move); + continue; + } + generated_move.search_score = - self.minimax(board, local_depth, true, i32::MIN, i32::MAX); + self.minimax(board, self.current_depth, true, i32::MIN, i32::MAX); board.un_make_move(generated_move); } @@ -183,12 +218,8 @@ impl SearchEngine { } } - if local_depth < self.depth - || (!(self.start.elapsed().as_millis() - > self.get_allowed_time(self.searching_side)) - && self.use_time_management) - { - local_depth += 1; + if self.current_depth < self.depth || self.use_time_management { + self.current_depth += 1; } else { searching = false; } @@ -204,10 +235,11 @@ impl SearchEngine { } for generated_move in moves_for_current_depth.iter() { - // println!( - // "move {:?}, score{}", - // generated_move.from, generated_move.search_score, - // ); + // print illegal infor + if generated_move.illegal_move { + println!("illegal move"); + } + if generated_move.search_score > best_score { best_score = generated_move.search_score; best_move = generated_move.clone(); @@ -227,17 +259,23 @@ impl SearchEngine { if depth == 0 { return 1; } - // println!("ply {} side {}", board.ply, board.side_to_move); + let current_side = board.side_to_move; let currently_in_check = evaluate::is_in_check(board, current_side, None); - let moves_for_current_depth = + let mut moves_for_current_depth = movegen::generate_pseudo_legal_moves(board, board.side_to_move, currently_in_check); - for generated_move in moves_for_current_depth.iter() { + for generated_move in moves_for_current_depth.iter_mut() { board.make_move(generated_move); + if board.has_positions_repeated() { + generated_move.illegal_move = true; + board.un_make_move(generated_move); + continue; + } + if evaluate::is_in_check( board, current_side, diff --git a/src/uci.rs b/src/uci.rs index b6fd605..35b5fe9 100644 --- a/src/uci.rs +++ b/src/uci.rs @@ -31,6 +31,7 @@ pub enum CommandTypes { MakeUnMake, Bench, GetFen, + Hash, Empty, Invalid, // Quit, // @@ -66,6 +67,7 @@ impl CommunicationManager { "uci" => CommandTypes::Uci, "isready" => CommandTypes::IsReady, "position" => CommandTypes::Position, + "hash" => CommandTypes::Hash, "go" => CommandTypes::Go, "search" => CommandTypes::Search, "quit" => CommandTypes::Quit, @@ -130,7 +132,13 @@ impl CommunicationManager { } pub fn evaluate(&self) { println!( - "{}", + "evaluate for {}: {}", + self.board.side_to_move, + evaluate::evaluate(&self.board, self.board.side_to_move) // self.board.get_running_evaluation() + ); + println!( + "evaluate with negamax for {}: {}", + self.board.side_to_move, evaluate::evaluate(&self.board, self.board.side_to_move) // self.board.get_running_evaluation() ); } @@ -265,20 +273,27 @@ impl CommunicationManager { "depth" => { self.engine.depth = command_text_split.next().unwrap().parse::().unwrap(); } + "movetime" => { + self.engine.movetime = + command_text_split.next().unwrap().parse::().unwrap(); + self.engine.use_time_management = true; + } + _ => {} } } let moves = self.engine.search(&mut self.board); - let time_taken_micros = self.engine.start.elapsed().as_millis(); + let time_taken_seconds = self.engine.start.elapsed().as_secs_f32(); - println!("{}", moves.1.len()); + println!( - "nodes: {}, time:{:?}, nodes per second: {}", + "info depth {} time {} nodes {} nps {} score cp {:.2}", + self.engine.current_depth, + self.engine.start.elapsed().as_millis(), self.engine.nodes, - time_taken_micros, - self.engine.nodes as f32 / time_taken_seconds + self.engine.nodes as f32 / time_taken_seconds, + moves.0.search_score, ); - println!( "bestmove {}", conversion::convert_array_location_to_notation( @@ -349,6 +364,18 @@ pub fn run() { CommandTypes::IsReady => println!("readyok"), CommandTypes::Go => manager.go(&buffer), CommandTypes::GetFen => println!("{}", manager.board.get_fen()), + CommandTypes::Hash => { + println!( + "hash: {}, has repeated: {}", + manager.board.hash_board_state(), + manager.board.has_positions_repeated() + ); + + for hash in manager.board.hash_of_previous_positions.iter() { + println!("hash: {}", hash); + } + } + CommandTypes::Help => { println!("{}", HELP); println!("{}", CHOO_CHOO_TRAIN)