make clippy happy

This commit is contained in:
c0repwn3r 2023-02-15 13:22:04 -05:00
parent 0fc6af54b9
commit bb2b2b02f8
Signed by: core
GPG Key ID: FDBF740DADDCEECF
7 changed files with 185 additions and 54 deletions

View File

@ -13,12 +13,16 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provides structs and functions for managing representations of a chess board.
use crate::boardfield::{Boardfield, BoardfieldOps};
use crate::error::FENParseError;
use crate::piece::{PieceColor, PieceOnBoard, PieceType};
use crate::utils::{algebraic_to_boardloc};
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[allow(clippy::struct_excessive_bools)]
/// A struct to represent a chessboard and the pieces and state associated with it.
pub struct Board {
bitfield: Boardfield,
// While this is a less efficient memory layout, having an array of where all the pieces are will make move generation significantly faster.
@ -38,6 +42,7 @@ pub struct Board {
}
impl Board {
/// Convert the current state of the board into a 41-byte packed board ID. This packed representation contains all piece data and flags associated with the chessboard and is sufficient for unique identification and compact storage of chessboards.
pub fn to_board_id(&self) -> [u8; 41] {
let mut res = [0u8; 41];
@ -76,22 +81,23 @@ impl Board {
}
res[32] = flags_byte;
let en_passant_target: [u8; 4] = match self.en_passant_target {
Some(target) => {
(target as u32).to_le_bytes()
}
None => {
u32::MAX.to_le_bytes()
}
};
#[allow(clippy::cast_possible_truncation)] {
let en_passant_target: [u8; 4] = self.en_passant_target.map_or_else(|| u32::MAX.to_le_bytes(), |target| (target as i32).to_le_bytes());
res[33..37].copy_from_slice(&en_passant_target);
res[37..39].copy_from_slice(&(self.halfmove_counter as u16).to_le_bytes());
res[39..41].copy_from_slice(&(self.move_counter as u16).to_le_bytes());
}
res
}
/// Create a new `Board` from a FEN string.
/// # Errors
/// This function will return an error if an invalid FEN string is passed.
/// # Panics
/// This function, while containing code that can panic, will never panic as the string is bounds checked.
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::match_on_vec_items)]
pub fn from_fen(fen: &str) -> Result<Self, FENParseError> {
let components = fen.split(' ').collect::<Vec<&str>>();
if components.len() != 6 {
@ -146,7 +152,11 @@ impl Board {
file += 1;
}
_ if char.is_numeric() => {
let num = char.to_digit(10).unwrap() as isize;
let num;
// see above (.is_numeric())
#[allow(clippy::unwrap_used)] {
num = char.to_digit(10).unwrap() as isize;
}
if !(1..=8).contains(&num) {
return Err(FENParseError::CannotSkipToOutsideOfBoard { got: num, which_is: num });
@ -185,7 +195,7 @@ impl Board {
}
}
fn create_fen_piece(c: char, rank: isize, file: isize) -> Result<PieceOnBoard, FENParseError> {
const fn create_fen_piece(c: char, rank: isize, file: isize) -> Result<PieceOnBoard, FENParseError> {
Ok(PieceOnBoard {
loc: (rank + file * 8) - 1,
value: match c {
@ -193,20 +203,20 @@ fn create_fen_piece(c: char, rank: isize, file: isize) -> Result<PieceOnBoard, F
'n' => PieceColor::Black as u8 | PieceType::Knight as u8,
'b' => PieceColor::Black as u8 | PieceType::Bishop as u8,
'q' => PieceColor::Black as u8 | PieceType::Queen as u8,
'k' => PieceColor::Black as u8 | PieceType::Knight as u8,
'k' => PieceColor::Black as u8 | PieceType::King as u8,
'p' => PieceColor::Black as u8 | PieceType::Pawn as u8,
'R' => PieceColor::White as u8 | PieceType::Rook as u8,
'N' => PieceColor::White as u8 | PieceType::Knight as u8,
'B' => PieceColor::White as u8 | PieceType::Bishop as u8,
'Q' => PieceColor::White as u8 | PieceType::Queen as u8,
'K' => PieceColor::White as u8 | PieceType::Knight as u8,
'K' => PieceColor::White as u8 | PieceType::King as u8,
'P' => PieceColor::White as u8 | PieceType::Pawn as u8,
_ => return Err(FENParseError::InvalidPieceCharacter { got: c })
},
})
}
fn create_bitfield_piece(c: char) -> Result<u8, FENParseError> {
const fn create_bitfield_piece(c: char) -> Result<u8, FENParseError> {
match c {
'r' => Ok(PieceColor::Black as u8 | PieceType::Rook as u8),
'n' => Ok(PieceColor::Black as u8 | PieceType::Knight as u8),
@ -227,6 +237,8 @@ fn create_bitfield_piece(c: char) -> Result<u8, FENParseError> {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
mod tests {
use crate::board::{Board, create_bitfield_piece, create_fen_piece};
use crate::boardfield::BoardfieldOps;
@ -276,6 +288,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
pub fn fen_parse_testing() {
let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap();

View File

@ -13,15 +13,25 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Operations for working with a packed square-wise representation of a chessboard.
use crate::boardloc;
use crate::piece::{PieceColor, PieceType};
/// A Boardfield is a packed, square-wise representation of a chessboard. It **does not** include any extra metadata such as castling rights.
pub type Boardfield = [u8; 32];
/// A trait to contain the operations available on a `Boardfield`
pub trait BoardfieldOps {
/// Create a new, entirely empty `Boardfield`.
fn new() -> Self where Self: Sized;
/// Create a new `Boardfield` from the standard chess starting position.
fn startpos() -> Self where Self: Sized;
/// Get the piece value stored at the provided boardloc.
fn get_pos(&self, boardloc: isize) -> u8;
/// Set the piece stored at the provided boardloc to the given piece value.
fn set_pos(&mut self, boardloc: isize, piece: u8);
/// Get the entire boardfield as a 32-length bytearray.
fn get_field(&self) -> [u8; 32];
}
impl BoardfieldOps for Boardfield {
@ -76,13 +86,13 @@ impl BoardfieldOps for Boardfield {
field
}
#[allow(clippy::cast_sign_loss)]
fn get_pos(&self, boardloc: isize) -> u8 {
// bf1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
// bf2: 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
if boardloc > 63 {
panic!("boardloc out of range");
}
assert!(boardloc <= 63, "boardloc out of range");
assert!(boardloc > 0, "boardloc out of range");
let field = self[(boardloc / 2) as usize];
let shift = 4 * (boardloc % 2);
@ -90,10 +100,10 @@ impl BoardfieldOps for Boardfield {
(field & (0b1111 << shift)) >> shift
}
#[allow(clippy::cast_sign_loss)]
fn set_pos(&mut self, boardloc: isize, piece: u8) {
if boardloc > 63 {
panic!("boardloc out of range {}", boardloc);
}
assert!(boardloc <= 63, "boardloc out of range {boardloc}");
assert!(boardloc > 0, "boardloc out of range {boardloc}");
let field = self[(boardloc / 2) as usize];
let shift = 4 * (boardloc % 2);
@ -136,6 +146,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn bitfield_board() {
let field = Boardfield::startpos();

View File

@ -13,18 +13,52 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Module to contain the various types of errors used by Bamboo.
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::num::ParseIntError;
#[derive(Debug)]
/// An error type to represent the possible errors while parsing a FEN string
pub enum FENParseError {
InvalidNumberOfComponents { got: isize },
InvalidPlayerToMove { got: String },
EnPassantTargetParseError { e: AlgebraicNotationError },
InvalidMoveCounter { e: ParseIntError },
InvalidPieceCharacter { got: char },
CannotSkipToOutsideOfBoard { got: isize, which_is: isize },
/// A FEN string is expected to have six space-delimited components, but there were less or more than 6 components provided.
InvalidNumberOfComponents {
/// How many components were actually found in the string
got: isize
},
/// The Player To Move field should either be a `w`, indicating white to move, or `b`, indicating black to move, but neither were found.
InvalidPlayerToMove {
/// The actual Player To Move field found in the FEN string.
got: String
},
/// An error occured parsing the en passant target square.
EnPassantTargetParseError {
/// The actual error that occured
e: AlgebraicNotationError
},
/// An error occured parsing the fullmove or halfmove color.
InvalidMoveCounter {
/// The parsing error that occured
e: ParseIntError
},
/// While parsing the piece data, an unexpected character was found
InvalidPieceCharacter {
/// The unexpected character that was found
got: char
},
/// Chess boards are 8x8 but this FEN string attempted to place a piece outside of the board.
CannotSkipToOutsideOfBoard {
/// The skip value that was found
got: isize,
/// The location that skip value would place a piece at
which_is: isize
},
}
impl Error for FENParseError {}
@ -32,29 +66,42 @@ impl Error for FENParseError {}
impl Display for FENParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidNumberOfComponents { got } => write!(f, "Invalid number of components: got {}, expected 6", got),
Self::InvalidPlayerToMove { got } => write!(f, "Invalid player to move: expected one of `wb`, got `{}`", got),
Self::EnPassantTargetParseError { e } => write!(f, "Error parsing en passant target: {}", e),
Self::InvalidMoveCounter { e } => write!(f, "Invalid move counter: {}", e),
Self::InvalidPieceCharacter { got } => write!(f, "Invalid piece character: expected one of `rnbqkpRNBQKP`, got `{}`", got),
Self::CannotSkipToOutsideOfBoard { got, which_is } => write!(f, "Cannot skip files to outside the board (tried to skip {} positions, which would be at position {})", got, which_is)
Self::InvalidNumberOfComponents { got } => write!(f, "Invalid number of components: got {got}, expected 6"),
Self::InvalidPlayerToMove { got } => write!(f, "Invalid player to move: expected one of `wb`, got `{got}`"),
Self::EnPassantTargetParseError { e } => write!(f, "Error parsing en passant target: {e}"),
Self::InvalidMoveCounter { e } => write!(f, "Invalid move counter: {e}"),
Self::InvalidPieceCharacter { got } => write!(f, "Invalid piece character: expected one of `rnbqkpRNBQKP`, got `{got}`"),
Self::CannotSkipToOutsideOfBoard { got, which_is } => write!(f, "Cannot skip files to outside the board (tried to skip {got} positions, which would be at position {which_is})")
}
}
}
#[derive(Debug)]
/// Represents an error that occured parsing an algebraic notation coordinate.
pub enum AlgebraicNotationError {
InvalidRank { got: String },
InvalidFile { got: String },
InvalidLength { got: isize }
/// An invalid rank (1-8) was encountered
InvalidRank {
/// The rank that was provided
got: String
},
/// An invalid file (a-h) was encountered
InvalidFile {
/// The file that was provided
got: String
},
/// Algebraic notation is two characters long, but this string was not two characters long
InvalidLength {
/// The length of the provided string
got: isize
}
}
impl Error for AlgebraicNotationError {}
impl Display for AlgebraicNotationError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AlgebraicNotationError::InvalidRank { got } => write!(f, "Invalid rank: expected one of `012345678`, got `{}`", got),
AlgebraicNotationError::InvalidFile { got } => write!(f, "Invalid file: expected one of `abcdefgh`, got `{}`", got),
AlgebraicNotationError::InvalidLength { got } => write!(f, "Invalid length, expected 2, got `{}`", got)
Self::InvalidRank { got } => write!(f, "Invalid rank: expected one of `12345678`, got `{got}`"),
Self::InvalidFile { got } => write!(f, "Invalid file: expected one of `abcdefgh`, got `{got}`"),
Self::InvalidLength { got } => write!(f, "Invalid length, expected 2, got `{got}`")
}
}
}

View File

@ -13,6 +13,21 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! # Bamboo
//! Bamboo is a pure-rust chess engine that uses a machine learning model to provide NNUE-like board evaluation.
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#![deny(clippy::missing_errors_doc)]
#![deny(clippy::missing_panics_doc)]
#![deny(clippy::missing_safety_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::module_name_repetitions)]
pub mod boardfield;
pub mod piece;
pub mod board;

View File

@ -13,7 +13,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Functions, structs, traits and macros for working with `BambooNN`.
#[derive(Default, Debug)]
/// A feed-forward machine learning model used to evaluate board positions
pub struct BNNModel {
}

View File

@ -13,23 +13,28 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Structs and methods for working with chess pieces.
use crate::piece::PieceType::{Bishop, King, Knight, Pawn, Queen, Rook};
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)]
/// Represents a piece on the chessboard. Used for the piece-based evaluation in `Board`.
pub struct PieceOnBoard {
/// The boardloc this piece is at
pub loc: isize,
/// The value of the piece
pub value: Piece
}
impl PieceOnBoard {
fn empty(loc: isize) -> Self {
PieceOnBoard {
const fn empty(loc: isize) -> Self {
Self {
loc,
value: 0u8
}
}
fn new(loc: isize, value: Piece) -> Self {
PieceOnBoard {
const fn new(loc: isize, value: Piece) -> Self {
Self {
loc,
value
}
@ -38,37 +43,56 @@ impl PieceOnBoard {
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
#[repr(u8)]
/// The type of a chess piece, used for `u8`/four-bit piece representations
pub enum PieceType {
/// Represents a pawn
Pawn = 1,
/// Represents a knight
Knight = 2,
/// Represents a bishop
Bishop = 3,
/// Represents a rook
Rook = 4,
/// Represents a queen
Queen = 5,
/// Represents a king
King = 6
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
#[repr(u8)]
/// Represents the color of a piece, used for `u8`/four-bit piece representations
pub enum PieceColor {
/// Represents a white piece
White = 8,
/// Represents a black piece
Black = 0
}
impl PieceColor {
pub fn invert(&self) -> Self {
/// Get the opposite of this color, e.g. for white return black and vice versa.
#[must_use]
pub const fn invert(&self) -> Self {
match self {
Self::White => PieceColor::Black,
Self::Black => PieceColor::White
Self::White => Self::Black,
Self::Black => Self::White
}
}
}
/// Represents a four-bit packed chess piece.
pub type Piece = u8;
/// A trait for the operations that can be done on a `Piece`
pub trait PieceOps {
/// Determines if this piece is white. Inverse of `is_black`
fn is_white(value: u8) -> bool;
/// Determines if this piece is black. Inverse of `is_black`
fn is_black(value: u8) -> bool;
/// Get the `PieceType` of this piece. This returns an option, as a Piece can be any `u8`, but not all of them are actual piece types.
fn get_type(value: u8) -> Option<PieceType>;
/// Gets the color of this piece. This effectively just checks if the fourth bit is set, and as such does not return an option.
fn get_color(value: u8) -> PieceColor;
}
impl PieceOps for Piece {
@ -76,7 +100,7 @@ impl PieceOps for Piece {
if value < 8 {
false
} else {
value & PieceColor::White as u8 != 0
value & PieceColor::White as Self != 0
}
}
@ -106,6 +130,7 @@ impl PieceOps for Piece {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod piece_tests {
use crate::piece::{Piece, PieceColor, PieceOnBoard, PieceOps, PieceType};
@ -159,6 +184,7 @@ mod piece_tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn piece_color_checks() {
assert!(!Piece::is_white(PieceType::Pawn as u8 | PieceColor::Black as u8));
assert!(Piece::is_white(PieceType::Pawn as u8 | PieceColor::White as u8));

View File

@ -13,8 +13,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Provides various utility functions and macros
use crate::error::AlgebraicNotationError;
/// Convert an algebraic notation, such as `a1`, to the corresponding boardloc.
/// # Errors
/// This function will return an error if the algebraic string is invalid.
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_wrap)]
pub fn algebraic_to_boardloc(algebraic: &str) -> Result<isize, AlgebraicNotationError> {
if algebraic.chars().count() != 2 {
return Err(AlgebraicNotationError::InvalidLength { got: algebraic.chars().count() as isize })
@ -38,12 +45,19 @@ pub fn algebraic_to_boardloc(algebraic: &str) -> Result<isize, AlgebraicNotation
Ok(rank + file * 8)
}
/// An array for converting boardloc to algebraic notation. Do not use this directly, use `boardloc_to_algebraic`.
pub const BOARDLOC_TO_ALG: [&str; 64] = ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "g1", "g2", "g3", "g4", "g5", "g6", "g7", "g8", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8"];
pub fn boardloc_to_algebraic<'a>(boardloc: isize) -> &'a str {
/// Convert a 0-63 boardloc into an algebraic notation string.
/// # Panics
/// This function will **panic** if you provide an invalid boardloc. Please bounds check it first.
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_wrap)]
pub const fn boardloc_to_algebraic<'a>(boardloc: isize) -> &'a str {
BOARDLOC_TO_ALG[boardloc as usize]
}
/// A macro to shorten `algebraic_to_boardloc`. This will **panic** if you provide an invalid algebraic notation.
#[macro_export]
macro_rules! boardloc {
($alg:expr) => {
@ -51,6 +65,7 @@ macro_rules! boardloc {
};
}
/// A macro to shorten `boardloc_to_algebraic`. This will **panic** if you provide an invalid (out of boudns) boardloc.
#[macro_export]
macro_rules! algebraic {
($boardloc:expr) => {
@ -59,6 +74,7 @@ macro_rules! algebraic {
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::utils::{algebraic_to_boardloc, boardloc_to_algebraic};
@ -73,7 +89,7 @@ mod tests {
#[should_panic]
fn boardloc_invalid_file() {
if let Err(e) = algebraic_to_boardloc("j8") {
println!("{}", e);
println!("{e}");
panic!();
}
}
@ -82,7 +98,7 @@ mod tests {
#[should_panic]
fn boardloc_invalid_length() {
if let Err(e) = algebraic_to_boardloc("jsdf8") {
println!("{}", e);
println!("{e}");
panic!();
}
}
@ -91,7 +107,7 @@ mod tests {
#[should_panic]
fn boardloc_invalid_rank() {
if let Err(e) = algebraic_to_boardloc("a9") {
println!("{}", e);
println!("{e}");
panic!();
}
}