### Search Tricks Source: https://docs.rs/rschess/latest/help.html Examples of advanced search queries in Rustdoc. ```text fn:my_function vec -> usize -> vec String, enum:Cow -> bool "my_item" -> [u8] [] -> Option vec::Vec ``` -------------------------------- ### Move Struct and Methods Source: https://docs.rs/rschess/latest/src/rschess/move_.rs.html Defines the Move struct for representing a chess move and its associated methods for getting squares, special move types, and converting to/from UCI format. ```rust use super::{helpers, InvalidUciError, PieceType}; use std::fmt; /// The structure for a chess move, in the format (_source square_, _destination square_, _castling/promotion/en passant_) #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] pub struct Move(pub(crate) usize, pub(crate) usize, pub(crate) Option); impl Move { /// Returns the source square of the move in the format (_file_, _rank_). pub fn from_square(&self) -> (char, char) { helpers::idx_to_sq(self.0) } /// Returns the destination square of the move in the format (_file_, _rank_). pub fn to_square(&self) -> (char, char) { helpers::idx_to_sq(self.1) } /// Returns the type of special move (castling/promotion/en passant) if this move is a special move (otherwise `None`). pub fn special_move_type(&self) -> Option { self.2 } /// Creates a `Move` object from its UCI representation. pub fn from_uci(uci: &str) -> Result { let uci_len = uci.len(); if ![4, 5].contains(&uci_len) { return Err(InvalidUciError::Length); } let from_square = (uci.chars().next().unwrap(), uci.chars().nth(1).unwrap()); let to_square = (uci.chars().nth(2).unwrap(), uci.chars().nth(3).unwrap()); let promotion = uci.chars().nth(4); if !(('a'..='h').contains(&from_square.0) && ('1'..='8').contains(&from_square.1)) { return Err(InvalidUciError::InvalidSquareName(from_square.0, from_square.1)); } if !(('a'..='h').contains(&to_square.0) && ('1'..='8').contains(&to_square.1)) { return Err(InvalidUciError::InvalidSquareName(to_square.0, to_square.1)); } let (src, dest) = (helpers::sq_to_idx(from_square.0, from_square.1), helpers::sq_to_idx(to_square.0, to_square.1)); let promotion = match promotion { Some(p) => Some({ let pt = PieceType::try_from(p).map_err(|_| InvalidUciError::InvalidPieceType(p))?; if pt == PieceType::K { return Err(InvalidUciError::InvalidPieceType(p)); } else { pt } }), _ => None, }; Ok(Self( src, dest, match promotion { Some(p) => Some(SpecialMoveType::Promotion(p)), _ => Some(SpecialMoveType::Unclear), }, )) } /// Returns the UCI representation of the move. pub fn to_uci(&self) -> String { let ((srcf, srcr), (destf, destr)) = (helpers::idx_to_sq(self.0), helpers::idx_to_sq(self.1)); format!( "{srcf}{srcr}{destf}{destr}{}", match self.2 { Some(SpecialMoveType::Promotion(pt)) => char::from(pt).to_ascii_lowercase().to_string(), _ => String::new(), } ) } } impl fmt::Display for Move { /// Converts the move to a UCI string. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_uci()) } } ``` -------------------------------- ### Keyboard Shortcuts Source: https://docs.rs/rschess/latest/help.html List of keyboard shortcuts for navigating and interacting with Rustdoc. ```text `?` Show this help dialog `S` / `/` Focus the search field `↑` Move up in search results `↓` Move down in search results `←` / `→` Switch result tab (when results focused) `⏎` Go to active search result `+` / `=` Expand all sections `-` Collapse all sections `_` Collapse all sections, including impl blocks ``` -------------------------------- ### Move to SAN conversion logic Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This snippet demonstrates the logic for converting a chess move into its Standard Algebraic Notation (SAN) representation, handling various piece types, special moves like en passant and castling, and disambiguation. ```rust } else if new_content.is_check() { "+" } else { "" }; let piece_type; match src_occ { Some(Piece(pt, _)) => match pt { PieceType::P => { return Ok(format!( "{}{} " match spec { Some(SpecialMoveType::EnPassant) => format!("{srcf}x{destf}{destr}"), _ => format!( "{}{}", match dest_occ { Some(_) => format!("{srcf}x{destf}{destr}",), None => format!("{destf}{destr}"), }, match spec { Some(SpecialMoveType::Promotion(piece_type)) => format!("={}", char::from(piece_type)), _ => String::new(), } ), }, )) } PieceType::K => { return Ok(format!( "{}{} " match spec { Some(SpecialMoveType::CastlingKingside) => "O-O".to_owned(), Some(SpecialMoveType::CastlingQueenside) => "O-O-O".to_owned(), _ => format!( "K{}{destf}{destr}", match dest_occ { Some(_) => "x", None => "", } ), }, )) } pt => { san.push(char::from(pt)); piece_type = pt; } }, _ => panic!("the universe is malfunctioning"), } if legal .iter() .filter(|m| { if m.1 == dest { if let Some(Piece(pt, _)) = content[m.0] { pt == piece_type } else { false } } else { false } }) .count() > 1 { if legal .iter() .filter(|m| { if m.1 == dest { if let Some(Piece(pt, _)) = content[m.0] { pt == piece_type && helpers::squares_in_file(srcf).contains(&m.0) } else { false } } else { false } }) .count() > 1 { if legal .iter() .filter(|m| { if m.1 == dest { if let Some(Piece(pt, _)) = content[m.0] { pt == piece_type && helpers::squares_in_rank(srcr).contains(&m.0) } else { false } } else { false } }) .count() > 1 { san.push(srcf); } san.push(srcr); } else { san.push(srcf); } } Ok(format!( "{san}{}{destf}{destr}{suffix}", match dest_occ { Some(_) => "x", None => "", } )) } ``` -------------------------------- ### Making a Move Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This snippet demonstrates the logic within the `make_move` function, which updates the board state after a legal move is made. It handles piece movement, castling rights, en passant targets, and pawn double-step promotions. ```Rust let move_ = match helpers::as_legal(move_, &self.gen_non_illegal_moves()) { Some(m) => m, _ => return Err(IllegalMoveError(move_)), }; let castling_rights_idx_offset = if self.side.is_white() { 0 } else { 2 }; let Self { content, mut side, mut castling_rights, .. } = self; let mut ep_target = None; let Move(move_src, move_dest, ..) = move_; let moved_piece = content[move_src]; match moved_piece { Some(Piece(PieceType::K, _)) => (castling_rights[castling_rights_idx_offset], castling_rights[castling_rights_idx_offset + 1]) = (None, None), Some(Piece(PieceType::P, _)) => { if (std::cmp::max(move_src, move_dest) - std::cmp::min(move_src, move_dest)) == 16 { ep_target = Some(if side.is_white() { move_src + 8 } else { move_src - 8 }); } } _ => (), } for maybe_rook in [move_src, move_dest] { let maybe_right = castling_rights.iter().enumerate().find(|(_, right)| right.is_some() && right.unwrap() == maybe_rook); if maybe_right.is_some() { castling_rights[maybe_right.unwrap().0] = None; } } side = !side; let new_content = helpers::change_content(content, &move_, &self.castling_rights); Ok(Self { content: new_content, side, castling_rights, ep_target, }) ``` -------------------------------- ### Legal Move Cache Initialization Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html A static, lazily initialized mutex-guarded HashMap to cache legal moves for chess positions. ```rust 1use super::{helpers, Color, IllegalMoveError, InvalidSanMoveError, Move, Piece, PieceType, SpecialMoveType}; use std:: collections::HashMap, fmt, sync::{Mutex, OnceLock}; /// Returns the cached positions and their legal moves. fn legal_move_cache() -> &'static Mutex>> { static LEGAL_MOVE_CACHE: OnceLock>>> = OnceLock::new(); LEGAL_MOVE_CACHE.get_or_init(|| Mutex::new(HashMap::new())) } ``` -------------------------------- ### rschess/lib.rs Source: https://docs.rs/rschess/latest/src/rschess/lib.rs.html A Rust chess library with the aim to be as feature-rich as possible ```rust //! A Rust chess library with the aim to be as feature-rich as possible //! //! Examples are available on the [GitHub repository page](https://github.com/Python3-8/rschess). mod board; pub mod errors; mod fen; mod game_result; mod helpers; #[cfg(feature = "img")] pub mod img; mod move_; #[cfg(feature = "pgn")] pub mod pgn; mod piece; mod position; pub use board::*; pub(crate) use errors::*; pub use fen::Fen; pub use game_result::*; pub use move_::*; pub use piece::*; pub use position::*; use std::{fmt, ops::Not}; /// Converts a square index (`0..64`) to a square name, returning an error if the square index is invalid. pub fn idx_to_sq(idx: usize) -> Result<(char, char), InvalidSquareIndexError> { if !(0..64).contains(&idx) { return Err(InvalidSquareIndexError(idx)); } Ok(helpers::idx_to_sq(idx)) } /// Converts a square name to a square index, returning an error if the square name is invalid. pub fn sq_to_idx(file: char, rank: char) -> Result { if !(('a'..='h').contains(&file) && ('1'..='8').contains(&rank)) { return Err(InvalidSquareNameError(file, rank)); } Ok(helpers::sq_to_idx(file, rank)) } /// Represents a side/color. #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] pub enum Color { White, Black, } impl Color { /// Checks if the color is white. pub fn is_white(&self) -> bool { matches!(self, Self::White) } /// Checks if the color is black. pub fn is_black(&self) -> bool { matches!(self, Self::Black) } } impl TryFrom<&str> for Color { type Error = InvalidColorCharacterError; /// Attempts to convert a color character in a string slice to a `Color` ("w" is white, and "b" is black). fn try_from(string: &str) -> Result { match string { "w" => Ok(Self::White), "b" => Ok(Self::Black), _ => Err(InvalidColorCharacterError(string.to_string())), } } } impl From for char { /// Converts a `Color` to a color character (white is 'w', and black is 'b'). fn from(c: Color) -> char { match c { Color::White => 'w', Color::Black => 'b', } } } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", char::from(*self)) } } impl Not for Color { type Output = Self; fn not(self) -> Self { match self { Self::White => Self::Black, Self::Black => Self::White, } } } #[cfg(test)] mod test; ``` -------------------------------- ### Pawn Move Generation Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Generates pseudolegal moves for a pawn, including forward moves, captures, en passant, and double pawn pushes. ```rust 487 PieceType::P => { 488 let mut possible_dests = Vec::new(); 489 if side.is_white() { 490 if content[i + 8].is_none() { 491 possible_dests.push((i + 8, false)); 492 if (8..16).contains(&i) && content[i + 16].is_none() { 493 possible_dests.push((i + 16, false)) 494 } 495 } 496 if helpers::long_range_can_move(i, 7) { 497 if let Some(Piece(_, color)) = content[i + 7] { 498 if color.is_black() { 499 possible_dests.push((i + 7, false)); 500 } 501 } else if ep_target.is_some() && ep_target.unwrap() == i + 7 { 502 possible_dests.push((i + 7, true)); 503 } 504 } 505 if helpers::long_range_can_move(i, 9) { 506 if let Some(Piece(_, color)) = content[i + 9] { 507 if color.is_black() { 508 possible_dests.push((i + 9, false)); 509 } 510 } else if ep_target.is_some() && ep_target.unwrap() == i + 9 { 511 possible_dests.push((i + 9, true)); 512 } 513 } 514 } else { 515 if content[i - 8].is_none() { 516 possible_dests.push((i - 8, false)); 517 if (48..56).contains(&i) && content[i - 16].is_none() { 518 possible_dests.push((i - 16, false)) 519 } 520 } 521 if helpers::long_range_can_move(i, -9) { 522 if let Some(Piece(_, color)) = content[i - 9] { 523 if color.is_white() { 524 possible_dests.push((i - 9, false)); 525 } 526 } else if ep_target.is_some() && ep_target.unwrap() == i - 9 { 527 possible_dests.push((i - 9, true)); 528 } 529 } 530 if helpers::long_range_can_move(i, -7) { 531 if let Some(Piece(_, color)) = content[i - 7] { 532 if color.is_white() { 533 possible_dests.push((i - 7, false)); 534 } 535 } else if ep_target.is_some() && ep_target.unwrap() == i - 7 { 536 possible_dests.push((i - 7, true)); 537 } 538 } 539 } ``` -------------------------------- ### Board Source: https://docs.rs/rschess/latest/rschess/struct.Board.html The structure for a chessboard/game ```rust pub struct Board { /* private fields */ } ``` -------------------------------- ### Castling Logic Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Handles the logic for generating pseudolegal moves for castling, considering both kingside and queenside castling. ```rust 450 0 => pseudolegal_moves.push(Move(i, ooo_sq, Some(SpecialMoveType::CastlingQueenside))), 451 1 => { 452 if helpers::find_all_pieces(ooo_sq..i, content)[0] == r { 453 pseudolegal_moves.push(Move(i, ooo_sq, Some(SpecialMoveType::CastlingQueenside))) 454 } 455 } 456 _ => (), 457 } 458 } 459 pseudolegal_moves ``` -------------------------------- ### Pretty Printing the Board Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This snippet shows the `pretty_print` method, which generates a string representation of the chess board. It handles different perspectives (white or black on the bottom) and can output either Unicode chess characters or ASCII characters. ```Rust pub fn pretty_print(&self, perspective: Color, ascii: bool) -> String { let mut string = String::new(); let mut content = self.content; let ranks: Vec<_> = if perspective.is_white() { content.chunks(8).rev().enumerate().collect() } else { content.reverse(); content.chunks(8).rev().enumerate().collect() }; let mut file_names = ["a", "b", "c", "d", "e", "f", "g", "h"]; if perspective.is_black() { file_names.reverse(); } string += &(" ".to_owned() + "┌" + &"───┬".repeat(7) + "───┐\n"); for (ranki, rank) in ranks { if ranki != 0 { string += &(" ".to_owned() + "├" + &"───┼".repeat(7) + "───┤\n"); } string += &format!("{} │", if perspective.is_white() { 8 - ranki } else { ranki + 1 },); for occupant in rank.iter() { string += &format!( " {} │", if let Some(p) = occupant { if ascii { (*p).into() } else { p.to_string().chars().next().unwrap() } } else { ' ' } ); } string.push('\n'); } string += &(" ".to_owned() + "└" + &"───┴".repeat(7) + "───┘\n"); let mut files = vec![" "]; files.extend(file_names); string += &(files.join(" ") + " "); string } ``` -------------------------------- ### Make Move SAN Source: https://docs.rs/rschess/latest/src/rschess/board.rs.html Attempts to interpret the SAN representation of a move and play it on the board, returning an error if it is invalid or illegal. ```rust pub fn make_move_san(&mut self, san: &str) -> Result<(), InvalidSanMoveError> { let move_ = self.san_to_move(san)?; self.make_move(move_).map_err(|_| InvalidSanMoveError(san.to_owned())) } ``` -------------------------------- ### Board Methods Source: https://docs.rs/rschess/latest/src/rschess/board.rs.html Methods for accessing game state like resigned side, draw agreement, initial FEN, and generating SAN movetext. ```rust ``` -------------------------------- ### Knight Move Generation Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Generates pseudolegal moves for a knight, considering all possible L-shaped moves and checking for valid destinations. ```rust 461 PieceType::N => { 462 let b_r_axes = [(7, [-1, 8]), (9, [8, 1]), (-7, [1, -8]), (-9, [-8, -1])]; 463 let mut dest_squares = Vec::new(); 464 for (b_axis, r_axes) in b_r_axes { 465 if !helpers::long_range_can_move(i, b_axis) { 466 continue; 467 } 468 let b_dest = i as isize + b_axis; 469 for r_axis in r_axes { 470 if !helpers::long_range_can_move(b_dest as usize, r_axis) { 471 continue; 472 } 473 dest_squares.push((b_dest + r_axis) as usize); 474 } 475 } 476 pseudolegal_moves.extend( 477 dest_squares 478 .into_iter() 479 .filter(|&dest| match content[dest] { 480 Some(Piece(_, color)) => color != *side, 481 _ => true, 482 }) 483 .map(|dest| Move(i, dest, None)), 484 ); 485 pseudolegal_moves 486 } ``` -------------------------------- ### Display Implementation Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Pretty-prints the position from the perspective of the side whose turn it is to move. ```rust impl fmt::Display for Position { /// Pretty-prints the position from the perspective of the side whose turn it is to move. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.pretty_print(self.side, false)) } } ``` -------------------------------- ### Make Moves SAN Source: https://docs.rs/rschess/latest/src/rschess/board.rs.html Attempts to play the given line of SAN moves (separated by spaces, **excluding move numbers**) on the board, returning an error if any move is illegal. If an error is returned, the board is left unchanged, i.e. no moves are played on the board. ```rust pub fn make_moves_san(&mut self, line: &str) -> Result<(), InvalidSanMoveError> { let mut board = self.clone(); for san in line.split_ascii_whitespace() { board.make_move_san(san)?; } *self = board; Ok(()) } ``` -------------------------------- ### As Legal Move Source: https://docs.rs/rschess/latest/src/rschess/helpers.rs.html Checks if a given move is legal, with special handling for unclear moves. ```rust pub fn as_legal(move_: Move, legal: &[Move]) -> Option { if legal.contains(&move_) { Some(move_) } else if move_.2 == Some(SpecialMoveType::Unclear) { match legal.iter().find(|m| (m.0, m.1) == (move_.0, move_.1) && !matches!(m.2, Some(SpecialMoveType::Promotion(_)))) { Some(&m) => Some(m), _ => None, } } else { None } } ``` -------------------------------- ### Change Board Content Source: https://docs.rs/rschess/latest/src/rschess/helpers.rs.html Updates the board's piece content based on a given move, handling captures, castling, en passant, and promotions. ```Rust pub fn change_content(content: &[Option; 64], move_: &Move, castling_rights: &[Option]) -> [Option; 64] { let mut content = *content; let Move(src, dest, spec) = move_; (content[*src], content[*dest]) = (None, content[*src]); match spec { Some(SpecialMoveType::CastlingKingside | SpecialMoveType::CastlingQueenside) => match *dest { 6 => { let krook = castling_rights[0].unwrap(); (content[krook], content[5]) = (None, content[krook]); } 2 => { let qrook = castling_rights[1].unwrap(); (content[qrook], content[3]) = (None, content[qrook]); } 62 => { let krook = castling_rights[2].unwrap(); (content[krook], content[61]) = (None, content[krook]); } 58 => { let qrook = castling_rights[3].unwrap(); (content[qrook], content[59]) = (None, content[qrook]); } _ => panic!("the universe is malfunctioning"), }, Some(SpecialMoveType::EnPassant) => match dest { 16..=23 => content[dest + 8] = None, 40..=47 => content[dest - 8] = None, _ => panic!("the universe is malfunctioning"), }, Some(SpecialMoveType::Promotion(piece_type)) => { if let Some(Piece(_, color)) = content[*dest] { content[*dest] = Some(Piece(*piece_type, color)); } } _ => (), } content } ``` -------------------------------- ### Count Material on the Board Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Counts the material on the board, categorizing pieces into Knights, Bishops (with color complexity), and others. Kings are ignored, and pawns are implicitly handled by other material counts. ```rust pub(crate) fn count_material(&self) -> Vec { let mut material = Vec::new(); for sq in 0..64 { if let Some(Piece(piece_type, _)) = self.content[sq] { match piece_type { PieceType::K => (), PieceType::N => material.push(Material::Knight), PieceType::B => material.push(Material::Bishop(helpers::color_complex_of(sq))), _ => material.push(Material::Other), } } } material } ``` -------------------------------- ### Generate Pseudolegal Moves Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Generates all pseudolegal moves for the current position. Pseudolegal moves are moves that are legal according to piece movement rules but may leave the king in check. ```Rust pub fn gen_pseudolegal_moves(&self) -> Vec { let mut pseudolegal_moves = Vec::new(); for i in 0..64 { pseudolegal_moves.append(&mut self.gen_pseudolegal_moves_sq(i)); } pseudolegal_moves } ``` -------------------------------- ### Is Ongoing Source: https://docs.rs/rschess/latest/src/rschess/board.rs.html Checks whether the game is still ongoing. ```rust pub fn is_ongoing(&self) -> bool { self.ongoing } ``` -------------------------------- ### GameResult Source: https://docs.rs/rschess/latest/rschess/enum.GameResult.html Represents game results. ```rust pub enum GameResult { Wins(Color, WinType), Draw(DrawType), } ``` -------------------------------- ### Generate Pseudolegal Moves from Square Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Generates pseudolegal moves from a specific square. The square index `i` can be converted from a square name using the `sq_to_idx` function. ```Rust pub fn gen_pseudolegal_moves_sq(&self, i: usize) -> Vec { let Self { content, castling_rights, ep_target, side, } = self; let mut pseudolegal_moves = Vec::new(); if let Some(piece) = self.content[i] { if piece.1 != *side { return pseudolegal_moves; } match piece.0 { PieceType::K => { let mut possible_dests = Vec::new(); for axis in [1, 8, 7, 9] { if helpers::long_range_can_move(i, axis as isize) { possible_dests.push(i + axis); } if helpers::long_range_can_move(i, -(axis as isize)) { possible_dests.push(i - axis); } } possible_dests.retain(|&dest| match content[dest] { Some(Piece(_, color)) => color != *side, _ => true, }); pseudolegal_moves.extend(possible_dests.into_iter().map(|d| Move(i, d, None))); let castling_rights_idx_offset = if side.is_white() { 0 } else { 2 }; let (oo_sq, ooo_sq) = if side.is_white() { (6, 2) } else { (62, 58) }; let (kingside, queenside) = (castling_rights[castling_rights_idx_offset], castling_rights[castling_rights_idx_offset + 1]); if let Some(r) = kingside { match helpers::count_pieces(i + 1..=oo_sq, content) { 0 => pseudolegal_moves.push(Move(i, oo_sq, Some(SpecialMoveType::CastlingKingside))), 1 => { if helpers::find_all_pieces(i + 1..=oo_sq, content)[0] == r { pseudolegal_moves.push(Move(i, oo_sq, Some(SpecialMoveType::CastlingKingside))) } } _ => (), } } if let Some(r) = queenside { match helpers::count_pieces(ooo_sq..i, content) { ``` -------------------------------- ### Generating Legal Moves from a Square Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This snippet shows `gen_non_illegal_moves_sq`, which generates legal moves from a specific square. It first generates pseudolegal moves and then filters them to ensure they do not leave the king in check. ```Rust pub fn gen_non_illegal_moves_sq(&self, i: usize) -> Vec { let Self { content, side, castling_rights, .. } = self; self.gen_pseudolegal_moves_sq(i) .into_iter() .filter(|move_| { if let Move(src, dest, Some(SpecialMoveType::CastlingKingside | SpecialMoveType::CastlingQueenside)) = move_ { for sq in *std::cmp::min(src, dest)..=*std::cmp::max(src, dest) { if self.controls_square(sq, !*side) { return false; } } return true; } true }) .collect() } ``` -------------------------------- ### WinType Enum Source: https://docs.rs/rschess/latest/rschess/enum.WinType.html The WinType enum definition. ```rust pub enum WinType { Checkmate, Resignation, } ``` -------------------------------- ### FEN Parsing Logic Source: https://docs.rs/rschess/latest/src/rschess/fen.rs.html This code snippet demonstrates the validation process for FEN strings, including checking for the correct number of kings, determining the active color, and validating castling rights based on king and rook positions. ```rust 108 rankn -= 1; 109 } 110 if !(wk_seen && bk_seen) { 111 return Err(InvalidFenError::BoardData("a valid chess position must have one white king and one black king".to_owned())); 112 } 113 let turn = fields[1]; 114 let side = match Color::try_from(turn) { 115 Ok(c) => c, 116 _ => return Err(InvalidFenError::ActiveColor), 117 }; 118 if helpers::king_capture_pseudolegal(&content, side) { 119 return Err(InvalidFenError::BoardData("when one side is in check, it cannot be the other side's turn to move".to_owned())); 120 } 121 let castling = fields[2]; 122 let len_castling = castling.len(); 123 if !((1..=4).contains(&len_castling)) { 124 return Err(InvalidFenError::CastlingRights("expected castling rights to be 1 to 4 characters long".to_owned())); 125 } 126 let mut castling_rights_old = [false; 4]; 127 if castling != "-" { 128 for ch in castling.chars() { 129 match ch { 130 'K' => { 131 if wk_pos > 6 { 132 return Err(InvalidFenError::CastlingRights("white king must be from a1 to g1 to have kingside castling rights".to_owned())); 133 } 134 if castling_rights_old[0] { 135 return Err(InvalidFenError::CastlingRights("found more than one occurrence of 'K'".to_owned())); 136 } 137 castling_rights_old[0] = true; 138 } 139 'Q' => { 140 if !(1..=7).contains(&wk_pos) { 141 return Err(InvalidFenError::CastlingRights("white king must be from b1 to h1 to have queenside castling rights".to_owned())); 142 } 143 if castling_rights_old[1] { 144 return Err(InvalidFenError::CastlingRights("found more than one occurrence of 'Q'".to_owned())); 145 } 146 castling_rights_old[1] = true; 147 } 148 'k' => { 149 if !(56..=62).contains(&bk_pos) { 150 return Err(InvalidFenError::CastlingRights("black king must be from a8 to g8 to have kingside castling rights".to_owned())); 151 } 152 if castling_rights_old[2] { 153 return Err(InvalidFenError::CastlingRights("found more than one occurrence of 'k'".to_owned())); 154 } 155 castling_rights_old[2] = true; 156 } 157 'q' => { 158 if !(57..=63).contains(&bk_pos) { 159 return Err(InvalidFenError::CastlingRights("black king must be from b8 to h8 to have queenside castling rights".to_owned())); 160 } 161 if castling_rights_old[3] { 162 return Err(InvalidFenError::CastlingRights("found more than one occurrence of 'q'".to_owned())); 163 } 164 castling_rights_old[3] = true; 165 } 166 _ => return Err(InvalidFenError::CastlingRights("expected '-' or a subset of 'KQkq'".to_owned())), 167 } 168 } 169 } 170 let count_rooks = |rng, color| helpers::count_piece(rng, Piece(PieceType::R, color), &content); 171 if castling_rights_old[0] && count_rooks(wk_pos + 1..8, Color::White) != 1 { 172 return Err(InvalidFenError::CastlingRights("white must have exactly one king's rook to have kingside castling rights".to_owned())); 173 } 174 if castling_rights_old[1] && count_rooks(0..wk_pos, Color::White) != 1 { 175 return Err(InvalidFenError::CastlingRights("white must have exactly one queen's rook to have queenside castling rights".to_owned())); 176 } 177 if castling_rights_old[2] && count_rooks(bk_pos + 1..64, Color::Black) != 1 { 178 return Err(InvalidFenError::CastlingRights("black must have exactly one king's rook to have kingside castling rights".to_owned())); 179 } 180 if castling_rights_old[3] && count_rooks(56..bk_pos, Color::Black) != 1 { 181 return Err(InvalidFenError::CastlingRights("black must have exactly one queen's rook to have queenside castling rights".to_owned())); 182 } 183 let find_rook = |rng, color| helpers::find_pieces(Piece(PieceType::R, color), rng, &content)[0]; 184 let mut castling_rights = [None; 4]; 185 if castling_rights_old[0] { 186 castling_rights[0] = Some(find_rook(wk_pos + 1..8, Color::White)); 187 } 188 if castling_rights_old[1] { ``` -------------------------------- ### Squares in File Source: https://docs.rs/rschess/latest/src/rschess/helpers.rs.html Returns a list of the indices of all squares in a given file (column). ```rust /// Returns a list of the indices of all the squares in a file. pub fn squares_in_file(file: char) -> Vec { let mut vec = Vec::new(); let bottom = sq_to_idx(file, '1'); for i in 0..8 { vec.push(bottom + 8 * i); } vec } ``` -------------------------------- ### Move struct Source: https://docs.rs/rschess/latest/rschess/struct.Move.html The structure for a chess move, in the format (_source square_ , _destination square_ , _castling/promotion/en passant_) ```rust pub struct Move(/* private fields */); ``` -------------------------------- ### Generate Pseudolegal Moves for Long-Range Pieces Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This function generates all possible moves for long-range pieces (Queens, Rooks, Bishops) from a given square, considering board boundaries and blocking pieces. ```rust pub(crate) fn gen_long_range_piece_pseudolegal_moves(&self, sq: usize, piece_type: PieceType) -> Vec { let Self { content, side, .. } = self; let axes = match piece_type { PieceType::Q => vec![1, 8, 7, 9], PieceType::R => vec![1, 8], PieceType::B => vec![7, 9], _ => panic!("not a long-range piece"), }; let mut dest_squares = Vec::new(); for axis in axes { 'axis: for axis_direction in [-axis, axis] { let mut current_sq = sq as isize; while helpers::long_range_can_move(current_sq as usize, axis_direction) { let mut skip = false; current_sq += axis_direction; if let Some(Piece(_, color)) = content[current_sq as usize] { if color == *side { continue 'axis; } else { skip = true; } } dest_squares.push(current_sq as usize); if skip { continue 'axis; } } } } dest_squares.into_iter().map(|dest| Move(sq, dest, None)).collect() } ``` -------------------------------- ### Make Move UCI Source: https://docs.rs/rschess/latest/src/rschess/board.rs.html Attempts to parse the UCI representation of a move and play it on the board, returning an error if the move is invalid or illegal. ```rust pub fn make_move_uci(&mut self, uci: &str) -> Result<(), InvalidUciMoveError> { let move_ = Move::from_uci(uci).map_err(|_| InvalidUciMoveError::InvalidUci(uci.to_owned()))?; self.make_move(move_).map_err(|_| InvalidUciMoveError::IllegalMove(uci.to_owned())) } ``` -------------------------------- ### Piece Struct and Implementations Source: https://docs.rs/rschess/latest/src/rschess/piece.rs.html Defines the `Piece` struct, which represents a chess piece with its type and color, and implements conversions from and to characters, as well as display formatting. ```Rust use super::{Color, InvalidPieceCharacterError}; use std::{collections::HashMap, fmt}; /// Represents a piece in the format (_piece type_, _color_). #[derive(Eq, PartialEq, Hash, Copy, Clone, Debug)] pub struct Piece(pub(crate) PieceType, pub(crate) Color); impl Piece { /// Returns the type of piece. pub fn piece_type(&self) -> PieceType { self.0 } /// Returns the color of the piece. pub fn color(&self) -> Color { self.1 } } impl TryFrom for Piece { type Error = InvalidPieceCharacterError; /// Attempts to convert a piece character to a `Piece`. fn try_from(value: char) -> Result { Ok(Self(PieceType::try_from(value)?, if value.is_ascii_uppercase() { Color::White } else { Color::Black })) } } impl From for char { /// Converts a `Piece` to a piece character. fn from(piece: Piece) -> char { let ch = piece.0.into(); match piece.1 { Color::White => ch, Color::Black => ch.to_ascii_lowercase(), } } } impl fmt::Display for Piece { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let codepoints = HashMap::from([ (PieceType::K, 0x2654), (PieceType::Q, 0x2655), (PieceType::R, 0x2656), (PieceType::B, 0x2657), (PieceType::N, 0x2658), (PieceType::P, 0x2659), ]); let Self(t, c) = self; write!(f, "{}", char::from_u32((codepoints.get(t).unwrap() + if c.is_white() { 0 } else { 6 }) as u32).unwrap()) } } ``` -------------------------------- ### Generating Legal Moves Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html This snippet illustrates the `gen_non_illegal_moves` function, which generates all legal moves for the current position. It utilizes a cache to store and retrieve previously computed legal moves for efficiency. ```Rust pub fn gen_non_illegal_moves(&self) -> Vec { if let Some(v) = legal_move_cache().lock().unwrap().get(self) { return v.clone(); } let v = (0..64).fold(Vec::new(), |v, i| [v, self.gen_non_illegal_moves_sq(i)].concat()); legal_move_cache().lock().unwrap().insert(self.clone(), v.clone()); v } ``` -------------------------------- ### FEN Display Implementation Source: https://docs.rs/rschess/latest/src/rschess/fen.rs.html Implements the `Display` trait for the `Fen` struct, allowing it to be formatted into a FEN string. It concatenates the position, halfmove clock, and fullmove number, separated by spaces. ```Rust 239impl fmt::Display for Fen { 240 /// Returns an FEN string representing this object. 241 /// If standard FEN is inadequate for representing castling rights, a mixture of standard FEN and Shredder-FEN will be generated. 242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 243 write!(f, "{}", [self.position.to_fen(), self.halfmove_clock.to_string(), self.fullmove_number.to_string()].join(" ")) 244 } 245} ``` -------------------------------- ### Side to Move Source: https://docs.rs/rschess/latest/src/rschess/position.rs.html Returns which side's turn it is to move. ```rust /// Returns which side's turn it is to move. pub fn side_to_move(&self) -> Color { self.side } ``` -------------------------------- ### Piece Finding Functions Source: https://docs.rs/rschess/latest/src/rschess/helpers.rs.html Functions to find the indices of specific pieces or all pieces within a given range on the board. ```Rust pub fn find_pieces(piece: Piece, rng: R, content: &[Option; 64]) -> Vec where R: RangeBounds + Iterator, { let piece = Some(piece); rng.filter(|&sq| content[sq] == piece).collect() } pub fn find_all_pieces(rng: R, content: &[Option; 64]) -> Vec where R: RangeBounds + Iterator, { rng.filter(|&sq| content[sq].is_some()).collect() } ``` -------------------------------- ### Struct Position Source: https://docs.rs/rschess/latest/rschess/struct.Position.html The structure for a chess position ```rust pub struct Position { /* private fields */ } ```