stub: rook slider movegen
This commit is contained in:
parent
5b65f9b756
commit
23f4ff68b4
@ -4,7 +4,7 @@ pub const START_POSITION: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w
|
||||
|
||||
pub trait FromFen {
|
||||
type Error;
|
||||
fn from_fen(_: String) -> Result<Self, Self::Error>
|
||||
fn from_fen(_: &str) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
}
|
||||
@ -36,7 +36,7 @@ pub enum FenError {
|
||||
|
||||
impl FromFen for BoardState {
|
||||
type Error = FenError;
|
||||
fn from_fen(fen: String) -> Result<BoardState, FenError> {
|
||||
fn from_fen(fen: &str) -> Result<BoardState, FenError> {
|
||||
//! Parse FEN string into position.
|
||||
|
||||
/// Parser state machine.
|
||||
@ -301,7 +301,7 @@ mod tests {
|
||||
|
||||
macro_rules! make_board {
|
||||
($fen_fmt: expr) => {
|
||||
BoardState::from_fen(format!($fen_fmt)).unwrap()
|
||||
BoardState::from_fen(&format!($fen_fmt)).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
@ -437,7 +437,7 @@ mod tests {
|
||||
|
||||
for fen1 in test_cases {
|
||||
println!("fen1: {fen1:?}");
|
||||
let fen2 = BoardState::from_fen(fen1.to_string()).unwrap().to_fen();
|
||||
let fen2 = BoardState::from_fen(fen1).unwrap().to_fen();
|
||||
|
||||
assert_eq!(fen1.to_string(), fen2, "FEN not equivalent")
|
||||
}
|
||||
|
86
src/lib.rs
86
src/lib.rs
@ -1,5 +1,7 @@
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
pub mod fen;
|
||||
pub mod movegen;
|
||||
|
||||
@ -107,7 +109,7 @@ impl ColPiece {
|
||||
/// Square index newtype.
|
||||
///
|
||||
/// A1 is (0, 0) -> 0, A2 is (0, 1) -> 2, and H8 is (7, 7) -> 63.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct Square(usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -127,6 +129,28 @@ impl TryFrom<usize> for Square {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! sq_try_from {
|
||||
($T: ty) => {
|
||||
impl TryFrom<$T> for Square {
|
||||
type Error = SquareError;
|
||||
|
||||
fn try_from(value: $T) -> Result<Self, Self::Error> {
|
||||
if let Ok(upper_bound) = <$T>::try_from(N_SQUARES) {
|
||||
if (0..upper_bound).contains(&value) {
|
||||
return Ok(Square(value as usize));
|
||||
}
|
||||
}
|
||||
Err(SquareError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sq_try_from!(i32);
|
||||
sq_try_from!(isize);
|
||||
sq_try_from!(i8);
|
||||
|
||||
impl From<Square> for usize {
|
||||
fn from(value: Square) -> Self {
|
||||
value.0
|
||||
@ -138,6 +162,10 @@ impl Square {
|
||||
let ret = BOARD_WIDTH * r + c;
|
||||
ret.try_into()
|
||||
}
|
||||
fn from_row_col_signed(r: isize, c: isize) -> Result<Self, SquareError> {
|
||||
let ret = (BOARD_WIDTH as isize) * r + c;
|
||||
ret.try_into()
|
||||
}
|
||||
fn to_row_col(self) -> (usize, usize) {
|
||||
//! Get row, column from index
|
||||
let div = self.0 / BOARD_WIDTH;
|
||||
@ -155,10 +183,14 @@ impl Square {
|
||||
let file = letters[col];
|
||||
format!("{file}{rank}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Square {
|
||||
type Err = SquareError;
|
||||
|
||||
/// Convert typical human-readable form (e.g. `e4`) to square index.
|
||||
fn from_algebraic(value: &str) -> Result<Self, SquareError> {
|
||||
let bytes = value.as_bytes();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let bytes = s.as_bytes();
|
||||
let col = match bytes[0] as char {
|
||||
'a' => 0,
|
||||
'b' => 1,
|
||||
@ -289,8 +321,13 @@ struct Player {
|
||||
}
|
||||
|
||||
impl Player {
|
||||
/// Get board for a specific piece.
|
||||
fn board(&mut self, pc: Piece) -> &mut Bitboard {
|
||||
/// Get board (non-mutable) for a specific piece.
|
||||
fn board(&self, pc: Piece) -> &Bitboard {
|
||||
&self.bit[pc as usize]
|
||||
}
|
||||
|
||||
/// Get board (mutable) for a specific piece.
|
||||
fn board_mut(&mut self, pc: Piece) -> &mut Bitboard {
|
||||
&mut self.bit[pc as usize]
|
||||
}
|
||||
}
|
||||
@ -371,7 +408,7 @@ impl BoardState {
|
||||
/// Create a new piece in a location.
|
||||
fn set_piece(&mut self, idx: Square, pc: ColPiece) {
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board(pc.into()).on_idx(idx);
|
||||
pl.board_mut(pc.into()).on_idx(idx);
|
||||
*self.mail.sq_mut(idx) = Some(pc);
|
||||
}
|
||||
|
||||
@ -381,7 +418,7 @@ impl BoardState {
|
||||
fn del_piece(&mut self, idx: Square) -> Result<ColPiece, NoPieceError> {
|
||||
if let Some(pc) = *self.mail.sq_mut(idx) {
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board(pc.into()).off_idx(idx);
|
||||
pl.board_mut(pc.into()).off_idx(idx);
|
||||
*self.mail.sq_mut(idx) = None;
|
||||
Ok(pc)
|
||||
} else {
|
||||
@ -422,13 +459,46 @@ impl core::fmt::Display for BoardState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_square_casts() {
|
||||
let fail_cases = [-1, 64, 0x7FFFFFFF, 257, 256, 128, 65, -3, !0x7FFFFFFF];
|
||||
for tc in fail_cases {
|
||||
macro_rules! try_type {
|
||||
($T: ty) => {
|
||||
if let Ok(conv) = <$T>::try_from(tc) {
|
||||
assert!(matches!(Square::try_from(conv), Err(SquareError::OutOfBounds)))
|
||||
}
|
||||
};
|
||||
}
|
||||
try_type!(i32);
|
||||
try_type!(i8);
|
||||
try_type!(isize);
|
||||
try_type!(usize);
|
||||
}
|
||||
|
||||
let good_cases = 0..N_SQUARES;
|
||||
for tc in good_cases {
|
||||
macro_rules! try_type {
|
||||
($T: ty) => {
|
||||
let conv = <$T>::try_from(tc).unwrap();
|
||||
let res = Square::try_from(conv).unwrap();
|
||||
assert_eq!(res.0, tc);
|
||||
};
|
||||
}
|
||||
try_type!(i32);
|
||||
try_type!(i8);
|
||||
try_type!(isize);
|
||||
try_type!(usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_from_algebraic() {
|
||||
let test_cases = [("a1", 0), ("a8", 56), ("h1", 7), ("h8", 63)];
|
||||
for (sqr, idx) in test_cases {
|
||||
assert_eq!(Square::try_from(idx).unwrap().to_algebraic(), sqr);
|
||||
assert_eq!(
|
||||
Square::from_algebraic(sqr).unwrap(),
|
||||
sqr.parse::<Square>().unwrap(),
|
||||
Square::try_from(idx).unwrap()
|
||||
);
|
||||
}
|
||||
|
157
src/movegen.rs
157
src/movegen.rs
@ -18,15 +18,14 @@ struct Node {
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Node {
|
||||
pos: BoardState::from_fen(START_POSITION.to_string())
|
||||
.expect("Starting FEN should be valid"),
|
||||
pos: BoardState::from_fen(START_POSITION).expect("Starting FEN should be valid"),
|
||||
prev: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Piece enum specifically for promotions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum PromotePiece {
|
||||
Rook,
|
||||
Bishop,
|
||||
@ -45,17 +44,18 @@ impl From<PromotePiece> for Piece {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pseudo-legal move.
|
||||
///
|
||||
/// No checking is done when constructing this.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum MoveType {
|
||||
/// Pawn promotes to another piece.
|
||||
Promotion(PromotePiece),
|
||||
/// Capture, or push move. Includes castling and en-passant too.
|
||||
Normal,
|
||||
}
|
||||
/// Move data common to all move types.
|
||||
struct Move {
|
||||
/// Pseudo-legal move.
|
||||
///
|
||||
/// No checking is done when constructing this.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Move {
|
||||
src: Square,
|
||||
dest: Square,
|
||||
move_type: MoveType,
|
||||
@ -232,7 +232,7 @@ pub trait ToUCIAlgebraic {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MoveAlgebraicError {
|
||||
pub enum MoveAlgebraicError {
|
||||
/// String is invalid length; refuse to parse
|
||||
InvalidLength(usize),
|
||||
/// Invalid character at given index.
|
||||
@ -250,14 +250,14 @@ impl FromUCIAlgebraic for Move {
|
||||
return Err(MoveAlgebraicError::InvalidLength(value_len));
|
||||
}
|
||||
|
||||
let src_sq = match Square::from_algebraic(&value[0..=1]) {
|
||||
let src_sq = match value[0..=1].parse::<Square>() {
|
||||
Ok(sq) => sq,
|
||||
Err(e) => {
|
||||
return Err(MoveAlgebraicError::SquareError(0, e));
|
||||
}
|
||||
};
|
||||
|
||||
let dest_sq = match Square::from_algebraic(&value[2..=3]) {
|
||||
let dest_sq = match value[2..=3].parse::<Square>() {
|
||||
Ok(sq) => sq,
|
||||
Err(e) => {
|
||||
return Err(MoveAlgebraicError::SquareError(0, e));
|
||||
@ -285,11 +285,144 @@ impl FromUCIAlgebraic for Move {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pseudo-legal move generation.
|
||||
///
|
||||
/// "Pseudo-legal" here means that moving into check is allowed, and capturing friendly pieces is
|
||||
/// allowed. These will be filtered out in the legal move generation step.
|
||||
pub trait PseudoMoveGen {
|
||||
type MoveIterable;
|
||||
fn gen_pseudo_moves(self) -> Self::MoveIterable;
|
||||
}
|
||||
|
||||
enum SliderDirection {
|
||||
/// Rook movement
|
||||
Straight,
|
||||
/// Bishop movement
|
||||
Diagonal,
|
||||
/// Queen/king movement
|
||||
Star,
|
||||
}
|
||||
/// Generate slider moves for a given square.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `board`: Board to generate moves with.
|
||||
/// * `src`: Square on which the slider piece is on.
|
||||
/// * `move_list`: Vector to append generated moves to.
|
||||
/// * `slide_type`: Directions the piece is allowed to go in.
|
||||
/// * `keep_going`: Allow sliding more than one square (true for everything except king).
|
||||
fn move_slider(
|
||||
board: &BoardState,
|
||||
src: Square,
|
||||
move_list: &mut Vec<Move>,
|
||||
slide_type: SliderDirection,
|
||||
keep_going: bool,
|
||||
) {
|
||||
let dirs_straight = [(0, 1), (1, 0), (-1, 0), (0, -1)];
|
||||
let dirs_diag = [(1, 1), (1, -1), (-1, 1), (-1, -1)];
|
||||
let dirs_star = [
|
||||
(1, 1),
|
||||
(1, -1),
|
||||
(-1, 1),
|
||||
(-1, -1),
|
||||
(0, 1),
|
||||
(1, 0),
|
||||
(-1, 0),
|
||||
(0, -1),
|
||||
];
|
||||
|
||||
let dirs = match slide_type {
|
||||
SliderDirection::Straight => dirs_straight.iter(),
|
||||
SliderDirection::Diagonal => dirs_diag.iter(),
|
||||
SliderDirection::Star => dirs_star.iter(),
|
||||
};
|
||||
|
||||
for dir in dirs {
|
||||
let (mut r, mut c) = src.to_row_col();
|
||||
loop {
|
||||
// increment
|
||||
let nr = r as isize + dir.0;
|
||||
let nc = c as isize + dir.1;
|
||||
|
||||
if let Ok(dest) = Square::from_row_col_signed(nr, nc) {
|
||||
r = nr as usize;
|
||||
c = nc as usize;
|
||||
|
||||
move_list.push(Move {
|
||||
src,
|
||||
dest,
|
||||
move_type: MoveType::Normal,
|
||||
});
|
||||
|
||||
// Stop at other pieces.
|
||||
if let Some(_cap_pc) = board.get_piece(dest) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if !keep_going {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PseudoMoveGen for BoardState {
|
||||
type MoveIterable = Vec<Move>;
|
||||
|
||||
fn gen_pseudo_moves(self) -> Self::MoveIterable {
|
||||
let mut ret = Vec::new();
|
||||
for pl in self.players {
|
||||
for sq in pl.board(Piece::Rook).into_iter() {
|
||||
move_slider(&self, sq, &mut ret, SliderDirection::Straight, true);
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Legal move generation.
|
||||
pub trait LegalMoveGen {
|
||||
type MoveIterable;
|
||||
fn gen_moves(self) -> Self::MoveIterable;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::fen::{ToFen, START_POSITION};
|
||||
|
||||
/// Test that slider pieces can move and capture.
|
||||
#[test]
|
||||
fn test_slider_movegen() {
|
||||
let test_cases = [(
|
||||
// start position
|
||||
"8/8/8/8/8/8/8/R7 w - - 0 1",
|
||||
// expected moves
|
||||
vec![(
|
||||
// source piece
|
||||
"a1",
|
||||
// destination squares
|
||||
vec![
|
||||
"a2", "a3", "a4", "a5", "a6", "a7", "a8", "b1", "c1", "d1", "e1", "f1", "g1",
|
||||
"h1",
|
||||
],
|
||||
)],
|
||||
)];
|
||||
|
||||
for (fen, expected) in test_cases {
|
||||
let board = BoardState::from_fen(fen).unwrap();
|
||||
|
||||
let mut moves = board.gen_pseudo_moves();
|
||||
moves.sort_unstable();
|
||||
let moves = moves;
|
||||
|
||||
let expected_moves = expected.iter().map(|(src, dests)| {});
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that make move and unmake move work as expected.
|
||||
///
|
||||
/// Ensure that:
|
||||
@ -405,7 +538,7 @@ mod tests {
|
||||
// make move
|
||||
eprintln!("Starting test case {i}, make move.");
|
||||
let mut node = Node {
|
||||
pos: BoardState::from_fen(start_pos.to_string()).unwrap(),
|
||||
pos: BoardState::from_fen(start_pos).unwrap(),
|
||||
prev: None,
|
||||
};
|
||||
for (move_str, expect_fen) in moves {
|
||||
|
Loading…
Reference in New Issue
Block a user