stub: movegen
This commit is contained in:
parent
1914d812e4
commit
77838fd417
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/target
|
||||
TODO.txt
|
||||
|
@ -1,9 +0,0 @@
|
||||
use chess_inator::Position;
|
||||
use chess_inator::fen::FromFen;
|
||||
|
||||
fn main() {
|
||||
let fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
|
||||
let board = Position::from_fen(fen.into()).unwrap();
|
||||
println!("{}", board);
|
||||
println!("{:#?}", board);
|
||||
}
|
34
src/fen.rs
34
src/fen.rs
@ -1,4 +1,4 @@
|
||||
use crate::{Position, Index, Color, ColPiece};
|
||||
use crate::{BoardState, Square, Color, ColPiece};
|
||||
use crate::{BOARD_WIDTH, BOARD_HEIGHT};
|
||||
|
||||
pub trait FromFen {
|
||||
@ -29,9 +29,9 @@ pub enum FenError {
|
||||
InternalError(usize),
|
||||
}
|
||||
|
||||
impl FromFen for Position {
|
||||
impl FromFen for BoardState {
|
||||
type Error = FenError;
|
||||
fn from_fen(fen: String) -> Result<Position, FenError> {
|
||||
fn from_fen(fen: String) -> Result<BoardState, FenError> {
|
||||
//! Parse FEN string into position.
|
||||
|
||||
/// Parser state machine.
|
||||
@ -55,7 +55,7 @@ impl FromFen for Position {
|
||||
FullMove,
|
||||
}
|
||||
|
||||
let mut pos = Position::default();
|
||||
let mut pos = BoardState::default();
|
||||
|
||||
let mut parser_state = FenState::Piece(0, 0);
|
||||
let mut next_state = FenState::Space;
|
||||
@ -105,7 +105,7 @@ impl FromFen for Position {
|
||||
let pc = ColPiece::try_from(pc_char).or(bad_char!(i))?;
|
||||
|
||||
pos.set_piece(
|
||||
Index::from_row_col(real_row, col)
|
||||
Square::from_row_col(real_row, col)
|
||||
.or(Err(FenError::InternalError(i)))?,
|
||||
pc,
|
||||
);
|
||||
@ -174,7 +174,7 @@ impl FromFen for Position {
|
||||
parse_space_and_goto!(FenState::HalfMove);
|
||||
}
|
||||
'a'..='h' => {
|
||||
pos.ep_square = Some(Index(c as usize - 'a' as usize));
|
||||
pos.ep_square = Some(Square(c as usize - 'a' as usize));
|
||||
parser_state = FenState::EnPassantFile;
|
||||
}
|
||||
_ => return bad_char!(i),
|
||||
@ -182,8 +182,8 @@ impl FromFen for Position {
|
||||
}
|
||||
FenState::EnPassantFile => {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
pos.ep_square = Some(Index(
|
||||
usize::from(pos.ep_square.unwrap_or(Index(0)))
|
||||
pos.ep_square = Some(Square(
|
||||
usize::from(pos.ep_square.unwrap_or(Square(0)))
|
||||
+ (digit as usize - 1) * 8,
|
||||
));
|
||||
} else {
|
||||
@ -193,7 +193,7 @@ impl FromFen for Position {
|
||||
}
|
||||
FenState::HalfMove => {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
if pos.half_moves > Position::MAX_MOVES {
|
||||
if pos.half_moves > BoardState::MAX_MOVES {
|
||||
return Err(FenError::TooManyMoves);
|
||||
}
|
||||
pos.half_moves *= 10;
|
||||
@ -206,7 +206,7 @@ impl FromFen for Position {
|
||||
}
|
||||
FenState::FullMove => {
|
||||
if let Some(digit) = c.to_digit(10) {
|
||||
if pos.half_moves > Position::MAX_MOVES {
|
||||
if pos.half_moves > BoardState::MAX_MOVES {
|
||||
return Err(FenError::TooManyMoves);
|
||||
}
|
||||
pos.full_moves *= 10;
|
||||
@ -237,22 +237,22 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fen_pieces() {
|
||||
let fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1";
|
||||
let board = Position::from_fen(fen.into()).unwrap();
|
||||
let board = BoardState::from_fen(fen.into()).unwrap();
|
||||
assert_eq!(
|
||||
(0..N_SQUARES)
|
||||
.map(Index)
|
||||
.map(Square)
|
||||
.map(|i| board.get_piece(i))
|
||||
.map(ColPiece::opt_to_char)
|
||||
.collect::<String>(),
|
||||
"RNBQKBNRPPPP.PPP............P...................pppppppprnbqkbnr"
|
||||
);
|
||||
assert_eq!(board.ep_square.unwrap(), Index(20));
|
||||
assert_eq!(board.ep_square.unwrap(), Square(20));
|
||||
assert_eq!(board.turn, Color::Black);
|
||||
}
|
||||
|
||||
macro_rules! make_board{
|
||||
($fen_fmt: expr) => {
|
||||
Position::from_fen(format!($fen_fmt)).unwrap()
|
||||
BoardState::from_fen(format!($fen_fmt)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ mod tests {
|
||||
let test_cases = [("e3", 20), ("h8", 63), ("a8", 56), ("h4", 31), ("a1", 0)];
|
||||
for (sqr, idx) in test_cases {
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - {sqr} 0 0");
|
||||
assert_eq!(board.ep_square.unwrap(), Index(idx));
|
||||
assert_eq!(board.ep_square.unwrap(), Square(idx));
|
||||
}
|
||||
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - - 0 0");
|
||||
@ -338,7 +338,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fen_half_move_counter() {
|
||||
for i in 0..=Position::MAX_MOVES {
|
||||
for i in 0..=BoardState::MAX_MOVES {
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - - {i} 0");
|
||||
assert_eq!(board.half_moves, i);
|
||||
assert_eq!(board.full_moves, 0);
|
||||
@ -347,7 +347,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fen_move_counter() {
|
||||
for i in 0..=Position::MAX_MOVES {
|
||||
for i in 0..=BoardState::MAX_MOVES {
|
||||
let board = make_board!("8/8/8/8/8/8/8/8 w - - 0 {i}");
|
||||
assert_eq!(board.half_moves, 0);
|
||||
assert_eq!(board.full_moves, i);
|
||||
|
64
src/lib.rs
64
src/lib.rs
@ -1,6 +1,8 @@
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
pub mod fen;
|
||||
pub mod movegen;
|
||||
use std::rc::Rc;
|
||||
|
||||
const BOARD_WIDTH: usize = 8;
|
||||
const BOARD_HEIGHT: usize = 8;
|
||||
@ -9,11 +11,21 @@ const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
enum Color {
|
||||
#[default]
|
||||
White,
|
||||
Black,
|
||||
White = 0,
|
||||
Black = 1,
|
||||
}
|
||||
const N_COLORS: usize = 2;
|
||||
|
||||
impl Color {
|
||||
/// Return opposite color (does not assign).
|
||||
pub fn flip(self) -> Self {
|
||||
match self {
|
||||
Color::White => Color::Black,
|
||||
Color::Black => Color::White,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Piece {
|
||||
Rook,
|
||||
@ -88,29 +100,29 @@ impl ColPiece {
|
||||
///
|
||||
/// A1 is (0, 0) -> 0, A2 is (0, 1) -> 2, and H8 is (7, 7) -> 63.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct Index(usize);
|
||||
struct Square(usize);
|
||||
|
||||
enum IndexError {
|
||||
OutOfBounds,
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for Index {
|
||||
impl TryFrom<usize> for Square {
|
||||
type Error = IndexError;
|
||||
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
if (0..N_SQUARES).contains(&value) {
|
||||
Ok(Index(value))
|
||||
Ok(Square(value))
|
||||
} else {
|
||||
Err(IndexError::OutOfBounds)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Index> for usize {
|
||||
fn from(value: Index) -> Self {
|
||||
impl From<Square> for usize {
|
||||
fn from(value: Square) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl Index {
|
||||
impl Square {
|
||||
fn from_row_col(r: usize, c: usize) -> Result<Self, IndexError> {
|
||||
//! Get index of square based on row and column.
|
||||
let ret = BOARD_WIDTH * r + c;
|
||||
@ -147,16 +159,16 @@ impl From<Piece> for char {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct Bitboard(u64);
|
||||
|
||||
impl Bitboard {
|
||||
pub fn on_idx(&mut self, idx: Index) {
|
||||
pub fn on_idx(&mut self, idx: Square) {
|
||||
//! Set the square at an index to on.
|
||||
self.0 |= 1 << usize::from(idx);
|
||||
}
|
||||
|
||||
pub fn off_idx(&mut self, idx: Index) {
|
||||
pub fn off_idx(&mut self, idx: Square) {
|
||||
//! Set the square at an index to off.
|
||||
self.0 &= !(1 << usize::from(idx));
|
||||
}
|
||||
@ -165,7 +177,7 @@ impl Bitboard {
|
||||
/// Array form board.
|
||||
///
|
||||
/// Complements bitboards, notably for "what piece is at this square?" queries.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Mailbox([Option<ColPiece>; N_SQUARES]);
|
||||
|
||||
impl Default for Mailbox {
|
||||
@ -176,12 +188,12 @@ impl Default for Mailbox {
|
||||
|
||||
impl Mailbox {
|
||||
/// Get mutable reference to square at index.
|
||||
fn sq_mut(&mut self, idx: Index) -> &mut Option<ColPiece> {
|
||||
fn sq_mut(&mut self, idx: Square) -> &mut Option<ColPiece> {
|
||||
&mut self.0[usize::from(idx)]
|
||||
}
|
||||
|
||||
/// Get non-mutable reference to square at index.
|
||||
fn sq(&self, idx: Index) -> &Option<ColPiece> {
|
||||
fn sq(&self, idx: Square) -> &Option<ColPiece> {
|
||||
&self.0[usize::from(idx)]
|
||||
}
|
||||
}
|
||||
@ -189,7 +201,7 @@ impl Mailbox {
|
||||
/// Piece bitboards and state for one player.
|
||||
///
|
||||
/// Default is all empty.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
struct Player {
|
||||
/// Bitboards for individual pieces. Piece -> locations.
|
||||
bit: [Bitboard; N_PIECES],
|
||||
@ -203,7 +215,7 @@ impl Player {
|
||||
}
|
||||
|
||||
/// Castling rights for one player
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct CastlingRights {
|
||||
/// Kingside
|
||||
k: bool,
|
||||
@ -211,11 +223,11 @@ pub struct CastlingRights {
|
||||
q: bool,
|
||||
}
|
||||
|
||||
/// Game state.
|
||||
/// Immutable game state, unique to a position.
|
||||
///
|
||||
/// Default is empty.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Position {
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct BoardState {
|
||||
/// Player bitboards
|
||||
players: [Player; N_COLORS],
|
||||
|
||||
@ -225,7 +237,7 @@ pub struct Position {
|
||||
/// En-passant square.
|
||||
///
|
||||
/// (If a pawn moves twice, this is one square in front of the start position.)
|
||||
ep_square: Option<Index>,
|
||||
ep_square: Option<Square>,
|
||||
|
||||
/// Castling rights
|
||||
castle: [CastlingRights; N_COLORS],
|
||||
@ -240,21 +252,21 @@ pub struct Position {
|
||||
turn: Color,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
impl BoardState {
|
||||
/// Get mutable reference to a player.
|
||||
fn pl_mut(&mut self, col: Color) -> &mut Player {
|
||||
&mut self.players[col as usize]
|
||||
}
|
||||
|
||||
/// Create a new piece in a location.
|
||||
fn set_piece(&mut self, idx: Index, pc: ColPiece) {
|
||||
fn set_piece(&mut self, idx: Square, pc: ColPiece) {
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board(pc.into()).on_idx(idx);
|
||||
*self.mail.sq_mut(idx) = Some(pc);
|
||||
}
|
||||
|
||||
/// Delete the piece in a location, if it exists.
|
||||
fn del_piece(&mut self, idx: Index) {
|
||||
fn del_piece(&mut self, idx: Square) {
|
||||
if let Some(pc) = *self.mail.sq_mut(idx) {
|
||||
let pl = self.pl_mut(pc.col);
|
||||
pl.board(pc.into()).off_idx(idx);
|
||||
@ -263,7 +275,7 @@ impl Position {
|
||||
}
|
||||
|
||||
/// Get the piece at a location.
|
||||
fn get_piece(&self, idx: Index) -> Option<ColPiece> {
|
||||
fn get_piece(&self, idx: Square) -> Option<ColPiece> {
|
||||
*self.mail.sq(idx)
|
||||
}
|
||||
|
||||
@ -271,12 +283,12 @@ impl Position {
|
||||
const MAX_MOVES: usize = 9_999;
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Position {
|
||||
impl core::fmt::Display for BoardState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut str = String::with_capacity(N_SQUARES + BOARD_HEIGHT);
|
||||
for row in (0..BOARD_HEIGHT).rev() {
|
||||
for col in 0..BOARD_WIDTH {
|
||||
let idx = Index::from_row_col(row, col).or(Err(std::fmt::Error))?;
|
||||
let idx = Square::from_row_col(row, col).or(Err(std::fmt::Error))?;
|
||||
let pc = self.get_piece(idx);
|
||||
str.push(ColPiece::opt_to_char(pc));
|
||||
}
|
||||
|
68
src/movegen.rs
Normal file
68
src/movegen.rs
Normal file
@ -0,0 +1,68 @@
|
||||
//! Move generation.
|
||||
|
||||
use crate::{Color, Square, BoardState};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Game tree node.
|
||||
struct Node {
|
||||
/// Immutable position data.
|
||||
pos: BoardState,
|
||||
/// Backlink to previous node.
|
||||
prev: Option<Rc<Node>>,
|
||||
}
|
||||
|
||||
/// Piece enum specifically for promotions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum PromotePiece {
|
||||
Rook,
|
||||
Bishop,
|
||||
Knight,
|
||||
Queen,
|
||||
}
|
||||
|
||||
/// Move data common to all move types.
|
||||
struct MoveData {
|
||||
src: Square,
|
||||
dest: Square,
|
||||
}
|
||||
/// Pseudo-legal move.
|
||||
enum Move {
|
||||
/// Pawn promotes to another piece.
|
||||
Promotion { data: MoveData, piece: PromotePiece },
|
||||
/// King castles with rook.
|
||||
Castle { data: MoveData },
|
||||
/// Capture, or push move.
|
||||
Normal { data: MoveData },
|
||||
/// This move is an en-passant capture.
|
||||
EnPassant { data: MoveData },
|
||||
}
|
||||
|
||||
impl Move {
|
||||
/// Make move and return new position.
|
||||
///
|
||||
/// Old position is saved in a backlink.
|
||||
pub fn make(self, old_node: Node) -> Node {
|
||||
let old_pos = old_node.pos;
|
||||
let mut node = Node {
|
||||
prev: Some(Rc::new(old_node)),
|
||||
pos: old_pos,
|
||||
};
|
||||
node.pos.turn = node.pos.turn.flip();
|
||||
if node.pos.turn == Color::White {
|
||||
node.pos.full_moves += 1;
|
||||
}
|
||||
|
||||
match self {
|
||||
Move::Promotion { data, piece } => todo!(),
|
||||
Move::Castle { data } => todo!(),
|
||||
Move::Normal { data } => {
|
||||
let pc = node.pos.get_piece(data.src).unwrap();
|
||||
node.pos.del_piece(data.src);
|
||||
node.pos.set_piece(data.dest, pc);
|
||||
}
|
||||
Move::EnPassant { data } => todo!(),
|
||||
}
|
||||
|
||||
node
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user