diff --git a/Cargo.toml b/Cargo.toml index a8d39533a..a6c0ae90d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,9 @@ license = "MIT" documentation = "https://jordanbray.github.io/chess/chess/index.html" [dependencies] -arrayvec = "0.5.1" -nodrop = "0.1.14" -failure = "0.1.6" +arrayvec = { version = "0.7.2", default-features = false } +nodrop = { version = "0.1.14", default_features = false } +failure = { version = "0.1.6", optional = true } [profile.release] opt-level = 3 @@ -32,3 +32,16 @@ opt-level = 3 [build-dependencies] rand = { version = "0.7.2", default_features = false, features = ["small_rng"] } failure = "0.1.6" + +[features] +default = ["std"] +std = [ "dep:failure" ] + +# always optimize build script, because it takes a long time to run unoptimized +[profile.dev.build-override] +opt-level = 3 +[profile.release.build-override] +opt-level = 3 +[profile.test.build-override] +opt-level = 3 + diff --git a/src/bitboard.rs b/src/bitboard.rs index 4d90dcc75..b58bdb52c 100644 --- a/src/bitboard.rs +++ b/src/bitboard.rs @@ -254,18 +254,17 @@ impl Not for &BitBoard { impl fmt::Display for BitBoard { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut s: String = "".to_owned(); for x in 0..64 { if self.0 & (1u64 << x) == (1u64 << x) { - s.push_str("X "); + write!(f, "X ")?; } else { - s.push_str(". "); + write!(f, ". ")?; } if x % 8 == 7 { - s.push_str("\n"); + write!(f, "\n")?; } } - write!(f, "{}", s) + Ok(()) } } diff --git a/src/board.rs b/src/board.rs index ff5031227..809ee19cf 100644 --- a/src/board.rs +++ b/src/board.rs @@ -95,6 +95,7 @@ impl Board { /// ``` #[deprecated(since = "3.1.0", note = "please use `Board::from_str(fen)?` instead")] #[inline] + #[cfg(feature="std")] pub fn from_fen(fen: String) -> Option { Board::from_str(&fen).ok() } diff --git a/src/board_builder.rs b/src/board_builder.rs index da2f67953..57f7cbc6e 100644 --- a/src/board_builder.rs +++ b/src/board_builder.rs @@ -5,7 +5,8 @@ use crate::error::Error; use crate::file::{File, ALL_FILES}; use crate::piece::Piece; use crate::rank::{Rank, ALL_RANKS}; -use crate::square::{Square, ALL_SQUARES}; +use crate::square::{Square, ALL_SQUARES, NUM_SQUARES}; +use arrayvec::ArrayVec; use std::fmt; use std::ops::{Index, IndexMut}; @@ -288,7 +289,7 @@ impl fmt::Display for BoardBuilder { } if let Some((piece, color)) = self.pieces[square] { - write!(f, "{}", piece.to_string(color))?; + write!(f, "{}", piece.with_color(color))?; } else { count += 1; } @@ -315,12 +316,12 @@ impl fmt::Display for BoardBuilder { write!( f, "{}", - self.castle_rights[Color::White.to_index()].to_string(Color::White) + self.castle_rights[Color::White.to_index()].with_color(Color::White) )?; write!( f, "{}", - self.castle_rights[Color::Black.to_index()].to_string(Color::Black) + self.castle_rights[Color::Black.to_index()].with_color(Color::Black) )?; if self.castle_rights[0] == CastleRights::NoRights && self.castle_rights[1] == CastleRights::NoRights @@ -353,17 +354,19 @@ impl FromStr for BoardBuilder { let mut cur_file = File::A; let mut fen = &mut BoardBuilder::new(); - let tokens: Vec<&str> = value.split(' ').collect(); - if tokens.len() < 4 { - return Err(Error::InvalidFen { + #[cfg(feature="std")] + let invalid = || Error::InvalidFen { fen: value.to_string(), - }); - } + }; + #[cfg(not(feature="std"))] + let invalid = || Error::InvalidFen; + + let mut tokens = value.split(' '); - let pieces = tokens[0]; - let side = tokens[1]; - let castles = tokens[2]; - let ep = tokens[3]; + let pieces = tokens.next().ok_or(invalid())?; + let side = tokens.next().ok_or(invalid())?; + let castles = tokens.next().ok_or(invalid())?; + let ep = tokens.next().ok_or(invalid())?; for x in pieces.chars() { match x { @@ -436,9 +439,7 @@ impl FromStr for BoardBuilder { cur_file = cur_file.right(); } _ => { - return Err(Error::InvalidFen { - fen: value.to_string(), - }); + return Err(invalid()); } } } @@ -446,9 +447,7 @@ impl FromStr for BoardBuilder { "w" | "W" => fen = fen.side_to_move(Color::White), "b" | "B" => fen = fen.side_to_move(Color::Black), _ => { - return Err(Error::InvalidFen { - fen: value.to_string(), - }) + return Err(invalid()) } } @@ -482,7 +481,7 @@ impl FromStr for BoardBuilder { impl From<&Board> for BoardBuilder { fn from(board: &Board) -> Self { - let mut pieces = vec![]; + let mut pieces = ArrayVec::<_, NUM_SQUARES>::new(); for sq in ALL_SQUARES.iter() { if let Some(piece) = board.piece_on(*sq) { let color = board.color_on(*sq).unwrap(); diff --git a/src/castle_rights.rs b/src/castle_rights.rs index 14674ea80..ec56a3c85 100644 --- a/src/castle_rights.rs +++ b/src/castle_rights.rs @@ -1,4 +1,5 @@ use std::hint::unreachable_unchecked; +use std::fmt; use crate::bitboard::{BitBoard, EMPTY}; use crate::color::Color; @@ -118,6 +119,15 @@ impl CastleRights { } } + fn to_str(&self) -> &'static str { + match *self { + CastleRights::NoRights => "", + CastleRights::KingSide => "k", + CastleRights::QueenSide => "q", + CastleRights::Both => "kq", + } + } + /// Convert the castle rights to an FEN compatible string. /// /// ``` @@ -128,13 +138,9 @@ impl CastleRights { /// assert_eq!(CastleRights::KingSide.to_string(Color::White), "K"); /// assert_eq!(CastleRights::QueenSide.to_string(Color::Black), "q"); /// ``` + #[cfg(feature="std")] pub fn to_string(&self, color: Color) -> String { - let result = match *self { - CastleRights::NoRights => "", - CastleRights::KingSide => "k", - CastleRights::QueenSide => "q", - CastleRights::Both => "kq", - }; + let result = self.to_str(); if color == Color::White { result.to_uppercase() @@ -152,4 +158,28 @@ impl CastleRights { _ => unsafe { unreachable_unchecked() }, } } + + pub fn with_color(&self, color: Color) -> CastleRightsWithColor { + CastleRightsWithColor { castle_rights: *self, color } + } +} + +pub struct CastleRightsWithColor { + castle_rights: CastleRights, + color: Color, +} + +impl fmt::Display for CastleRightsWithColor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self.castle_rights.to_str(); + + if self.color == Color::White { + for c in s.chars() { + write!(f, "{}", c.to_uppercase())? + } + Ok(()) + } else { + write!(f, "{}", s) + } + } } diff --git a/src/error.rs b/src/error.rs index 185b83a72..affd8cfd5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,9 @@ +#[cfg(feature="std")] use failure::Fail; /// Sometimes, bad stuff happens. #[derive(Clone, Debug, Fail)] +#[cfg(feature="std")] pub enum Error { /// The FEN string is invalid #[fail(display = "Invalid FEN string: {}", fen)] @@ -33,3 +35,15 @@ pub enum Error { #[fail(display = "The string specified does not contain a valid file")] InvalidFile, } + +#[derive(Clone, Debug)] +#[cfg(not(feature="std"))] +pub enum Error { + InvalidFen, + InvalidBoard, + InvalidSquare, + InvalidSanMove, + InvalidUciMove, + InvalidRank, + InvalidFile, +} diff --git a/src/lib.rs b/src/lib.rs index a9da30c57..2d99e3482 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![doc(html_root_url = "https://jordanbray.github.io/chess/")] +#![cfg_attr(not(feature="std"), no_std)] //! # Rust Chess Library //! This is a chess move generation library for rust. It is designed to be fast, so that it can be //! used in a chess engine or UI without performance issues. @@ -18,13 +19,18 @@ //! ``` //! +#[cfg(not(feature="std"))] +extern crate core as std; + mod board; pub use crate::board::*; mod bitboard; pub use crate::bitboard::{BitBoard, EMPTY}; +#[cfg(feature="std")] mod cache_table; +#[cfg(feature="std")] pub use crate::cache_table::*; mod castle_rights; @@ -66,7 +72,9 @@ pub use crate::movegen::MoveGen; mod zobrist; +#[cfg(feature="std")] mod game; +#[cfg(feature="std")] pub use crate::game::{Action, Game, GameResult}; mod board_builder; diff --git a/src/movegen/movegen.rs b/src/movegen/movegen.rs index 8706bc38f..5c485a181 100644 --- a/src/movegen/movegen.rs +++ b/src/movegen/movegen.rs @@ -27,7 +27,7 @@ impl SquareAndBitBoard { } } -pub type MoveList = NoDrop>; +pub type MoveList = NoDrop>; /// An incremental move generator /// @@ -95,7 +95,7 @@ impl MoveGen { fn enumerate_moves(board: &Board) -> MoveList { let checkers = *board.checkers(); let mask = !board.color_combined(board.side_to_move()); - let mut movelist = NoDrop::new(ArrayVec::<[SquareAndBitBoard; 18]>::new()); + let mut movelist = NoDrop::new(ArrayVec::::new()); if checkers == EMPTY { PawnType::legals::(&mut movelist, &board, mask); diff --git a/src/piece.rs b/src/piece.rs index c199e9a02..d918280a3 100644 --- a/src/piece.rs +++ b/src/piece.rs @@ -38,6 +38,17 @@ impl Piece { *self as usize } + fn piece_char(&self) -> char { + match *self { + Piece::Pawn => 'p', + Piece::Knight => 'n', + Piece::Bishop => 'b', + Piece::Rook => 'r', + Piece::Queen => 'q', + Piece::King => 'k', + } + } + /// Convert a piece with a color to a string. White pieces are uppercase, black pieces are /// lowercase. /// @@ -48,6 +59,7 @@ impl Piece { /// assert_eq!(Piece::Knight.to_string(Color::Black), "n"); /// ``` #[inline] + #[cfg(feature="std")] pub fn to_string(&self, color: Color) -> String { let piece = format!("{}", self); if color == Color::White { @@ -56,21 +68,30 @@ impl Piece { piece } } + + #[inline] + pub fn with_color(&self, color: Color) -> PieceWithColor { + PieceWithColor { piece: *self, color } + } } impl fmt::Display for Piece { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match *self { - Piece::Pawn => "p", - Piece::Knight => "n", - Piece::Bishop => "b", - Piece::Rook => "r", - Piece::Queen => "q", - Piece::King => "k", - } - ) + write!(f, "{}", self.piece_char()) + } +} + +pub struct PieceWithColor { + piece: Piece, + color: Color, +} + +impl fmt::Display for PieceWithColor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.color == Color::White { + write!(f, "{}", self.piece.piece_char().to_uppercase()) + } else { + write!(f, "{}", self.piece.piece_char()) + } } } diff --git a/src/square.rs b/src/square.rs index 218f3fa24..c272ca282 100644 --- a/src/square.rs +++ b/src/square.rs @@ -385,6 +385,7 @@ impl Square { since = "3.1.0", note = "please use `Square::from_str(square)?` instead" )] + #[cfg(feature="std")] pub fn from_string(s: String) -> Option { Square::from_str(&s).ok() } @@ -984,23 +985,28 @@ impl FromStr for Square { if s.len() < 2 { return Err(Error::InvalidSquare); } - let ch: Vec = s.chars().collect(); - match ch[0] { - 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} - _ => { - return Err(Error::InvalidSquare); + + let mut i = s.chars(); + if let (Some(c1), Some(c2)) = (i.next(), i.next()) { + match c1 { + 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' => {} + _ => { + return Err(Error::InvalidSquare); + } } - } - match ch[1] { - '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} - _ => { - return Err(Error::InvalidSquare); + match c2 { + '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' => {} + _ => { + return Err(Error::InvalidSquare); + } } + Ok(Square::make_square( + Rank::from_index((c2 as usize) - ('1' as usize)), + File::from_index((c1 as usize) - ('a' as usize)), + )) + } else { + Err(Error::InvalidSquare) } - Ok(Square::make_square( - Rank::from_index((ch[1] as usize) - ('1' as usize)), - File::from_index((ch[0] as usize) - ('a' as usize)), - )) } }