notation: rename module to fen
Move UCI movement stuff to the Move struct impl.
This commit is contained in:
parent
e114138a48
commit
e4d2b20e23
|
@ -253,7 +253,7 @@ impl Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Debug only: count number of pieces on board.
|
/// Debug only: count number of pieces on board.
|
||||||
pub fn num_pieces(&self) -> u8 {
|
pub(crate) fn num_pieces(&self) -> u8 {
|
||||||
let cbb = self.combined();
|
let cbb = self.combined();
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
while cbb > 0 {
|
while cbb > 0 {
|
||||||
|
@ -264,7 +264,7 @@ impl Board {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Debug only: write a text view of the board.
|
/// Debug only: write a text view of the board.
|
||||||
pub fn draw(&self, f: &mut dyn std::io::Write) {
|
pub(crate) fn draw(&self, f: &mut dyn std::io::Write) {
|
||||||
let cbb = self.colors[WHITE] | self.colors[BLACK];
|
let cbb = self.colors[WHITE] | self.colors[BLACK];
|
||||||
for rank in (0..8).rev() {
|
for rank in (0..8).rev() {
|
||||||
let mut rank_str = String::with_capacity(8);
|
let mut rank_str = String::with_capacity(8);
|
||||||
|
|
55
src/fen.rs
Normal file
55
src/fen.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
//! Functions to parse FEN strings.
|
||||||
|
|
||||||
|
use crate::board;
|
||||||
|
|
||||||
|
pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
|
||||||
|
/// FEN notation for positions, split into fields.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Fen {
|
||||||
|
pub placement: String,
|
||||||
|
pub color: String,
|
||||||
|
pub castling: String,
|
||||||
|
pub en_passant: String,
|
||||||
|
pub halfmove: String,
|
||||||
|
pub fullmove: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_fen(i: &str) -> Option<Fen> {
|
||||||
|
let fields: Vec<&str> = i.split_whitespace().collect();
|
||||||
|
parse_fen_fields(&fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_fen_fields(fields: &[&str]) -> Option<Fen> {
|
||||||
|
if fields.len() < 6 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
Some(Fen {
|
||||||
|
placement: fields[0].to_string(),
|
||||||
|
color: fields[1].to_string(),
|
||||||
|
castling: fields[2].to_string(),
|
||||||
|
en_passant: fields[3].to_string(),
|
||||||
|
halfmove: fields[4].to_string(),
|
||||||
|
fullmove: fields[5].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn en_passant_to_string(ep: Option<board::Square>) -> String {
|
||||||
|
ep.and_then(|p| Some(board::sq_to_string(p))).unwrap_or("-".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_fen() {
|
||||||
|
let fen_start = parse_fen(FEN_START).unwrap();
|
||||||
|
assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
|
||||||
|
assert_eq!(&fen_start.color, "w");
|
||||||
|
assert_eq!(&fen_start.castling, "KQkq");
|
||||||
|
assert_eq!(&fen_start.en_passant, "-");
|
||||||
|
assert_eq!(&fen_start.halfmove, "0");
|
||||||
|
assert_eq!(&fen_start.fullmove, "1");
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@ pub mod analysis;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod castling;
|
pub mod castling;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
|
pub mod fen;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod notation;
|
|
||||||
pub mod rules;
|
pub mod rules;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod uci;
|
pub mod uci;
|
||||||
|
|
201
src/movement.rs
201
src/movement.rs
|
@ -1,47 +1,47 @@
|
||||||
//! Move functions along with some castling helpers.
|
//! Move functions along with some castling helpers.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use crate::board::*;
|
use crate::board::*;
|
||||||
use crate::castling::*;
|
use crate::castling::*;
|
||||||
use crate::rules;
|
use crate::rules::GameState;
|
||||||
|
|
||||||
const START_WH_K_POS: Square = E1;
|
const START_WH_K_POS: Square = E1;
|
||||||
const START_BL_K_POS: Square = E8;
|
const START_BL_K_POS: Square = E8;
|
||||||
|
|
||||||
/// A movement, with before/after positions and optional promotion.
|
/// A movement, with before/after positions and optional promotion.
|
||||||
pub type Move = (Square, Square, Option<Piece>);
|
#[derive(PartialEq)]
|
||||||
|
pub struct Move {
|
||||||
/// Apply a move `m` to copies to `board` and `game_state`.
|
pub source: Square,
|
||||||
///
|
pub dest: Square,
|
||||||
/// Can be used for conveniance but it's better to write in existing
|
pub promotion: Option<Piece>,
|
||||||
/// instances as often as possible using `apply_move_to`.
|
|
||||||
pub fn apply_move(
|
|
||||||
board: &Board,
|
|
||||||
game_state: &rules::GameState,
|
|
||||||
m: &Move
|
|
||||||
) -> (Board, rules::GameState) {
|
|
||||||
let mut new_board = board.clone();
|
|
||||||
let mut new_state = game_state.clone();
|
|
||||||
apply_move_to(&mut new_board, &mut new_state, m);
|
|
||||||
(new_board, new_state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update `board` and `game_state` to reflect the move `m`.
|
impl fmt::Debug for Move {
|
||||||
///
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
/// The board is updated with correct piece placement.
|
write!(f, "{}", self.to_uci_string())
|
||||||
///
|
}
|
||||||
/// The game state is updated with the new player turn and the new
|
}
|
||||||
/// castling options.
|
|
||||||
pub fn apply_move_to(
|
|
||||||
board: &mut Board,
|
|
||||||
game_state: &mut rules::GameState,
|
|
||||||
m: &Move
|
|
||||||
) {
|
|
||||||
let (source, dest) = (m.0, m.1);
|
|
||||||
|
|
||||||
// If a rook is taken, remove its castling option. Needs to be checked before we update board.
|
pub const SAN_NULL_MOVE: &str = "0000";
|
||||||
// Note that we only check for a piece going to rook's initial position: it means the rook
|
|
||||||
// either moved previously, or it has been taken.
|
impl Move {
|
||||||
match source {
|
/// Build a move from `source` to `dest`, no promotion.
|
||||||
|
pub const fn new(source: Square, dest: Square) -> Move {
|
||||||
|
Move { source, dest, promotion: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a move from `source` to `dest`, with a promotion.
|
||||||
|
pub const fn new_promotion(source: Square, dest: Square, promotion: Piece) -> Move {
|
||||||
|
Move { source, dest, promotion: Some(promotion) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply this move to `board` and `game_state`.
|
||||||
|
pub fn apply_to(&self, board: &mut Board, game_state: &mut GameState) {
|
||||||
|
// If a rook is taken, remove its castling option. Needs to be checked before we update
|
||||||
|
// board. Note that we only check for a piece going to rook's initial position: it means
|
||||||
|
// the rook either moved previously, or it has been taken.
|
||||||
|
match self.source {
|
||||||
A1 => { game_state.castling &= !CASTLING_WH_Q; }
|
A1 => { game_state.castling &= !CASTLING_WH_Q; }
|
||||||
H1 => { game_state.castling &= !CASTLING_WH_K; }
|
H1 => { game_state.castling &= !CASTLING_WH_K; }
|
||||||
A8 => { game_state.castling &= !CASTLING_BL_Q; }
|
A8 => { game_state.castling &= !CASTLING_BL_Q; }
|
||||||
|
@ -49,11 +49,11 @@ pub fn apply_move_to(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update board and game state.
|
// Update board and game state.
|
||||||
apply_move_to_board(board, m);
|
self.apply_to_board(board);
|
||||||
game_state.color = opposite(game_state.color);
|
game_state.color = opposite(game_state.color);
|
||||||
|
|
||||||
// If the move is a castle, remove it from castling options.
|
// If the move is a castle, remove it from castling options.
|
||||||
if let Some(castle) = get_castle(m) {
|
if let Some(castle) = self.get_castle() {
|
||||||
match castle {
|
match castle {
|
||||||
CASTLING_WH_K | CASTLING_WH_Q => game_state.castling &= !CASTLING_WH_MASK,
|
CASTLING_WH_K | CASTLING_WH_Q => game_state.castling &= !CASTLING_WH_MASK,
|
||||||
CASTLING_BL_K | CASTLING_BL_Q => game_state.castling &= !CASTLING_BL_MASK,
|
CASTLING_BL_K | CASTLING_BL_Q => game_state.castling &= !CASTLING_BL_MASK,
|
||||||
|
@ -62,34 +62,34 @@ pub fn apply_move_to(
|
||||||
}
|
}
|
||||||
// Else, check if the king or a rook moved to update castling options.
|
// Else, check if the king or a rook moved to update castling options.
|
||||||
else {
|
else {
|
||||||
let color = board.get_color(dest);
|
let color = board.get_color(self.dest);
|
||||||
if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 {
|
if color == WHITE && game_state.castling & CASTLING_WH_MASK != 0 {
|
||||||
match board.get_piece(dest) {
|
match board.get_piece(self.dest) {
|
||||||
KING => {
|
KING => {
|
||||||
if source == E1 {
|
if self.source == E1 {
|
||||||
game_state.castling &= !CASTLING_WH_MASK;
|
game_state.castling &= !CASTLING_WH_MASK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ROOK => {
|
ROOK => {
|
||||||
if source == A1 {
|
if self.source == A1 {
|
||||||
game_state.castling &= !CASTLING_WH_Q;
|
game_state.castling &= !CASTLING_WH_Q;
|
||||||
} else if source == H1 {
|
} else if self.source == H1 {
|
||||||
game_state.castling &= !CASTLING_WH_K;
|
game_state.castling &= !CASTLING_WH_K;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 {
|
} else if color == BLACK && game_state.castling & CASTLING_BL_MASK != 0 {
|
||||||
match board.get_piece(dest) {
|
match board.get_piece(self.dest) {
|
||||||
KING => {
|
KING => {
|
||||||
if source == E8 {
|
if self.source == E8 {
|
||||||
game_state.castling &= !CASTLING_BL_MASK;
|
game_state.castling &= !CASTLING_BL_MASK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ROOK => {
|
ROOK => {
|
||||||
if source == A8 {
|
if self.source == A8 {
|
||||||
game_state.castling &= !CASTLING_BL_Q;
|
game_state.castling &= !CASTLING_BL_Q;
|
||||||
} else if source == H8 {
|
} else if self.source == H8 {
|
||||||
game_state.castling &= !CASTLING_BL_K;
|
game_state.castling &= !CASTLING_BL_K;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,9 @@ pub fn apply_move_to(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a move `m` into `board`.
|
/// Apply the move into `board`.
|
||||||
pub fn apply_move_to_board(board: &mut Board, m: &Move) {
|
pub fn apply_to_board(&self, board: &mut Board) {
|
||||||
if let Some(castle) = get_castle(m) {
|
if let Some(castle) = self.get_castle() {
|
||||||
match castle {
|
match castle {
|
||||||
CASTLING_WH_K => {
|
CASTLING_WH_K => {
|
||||||
board.move_square(START_WH_K_POS, G1);
|
board.move_square(START_WH_K_POS, G1);
|
||||||
|
@ -122,29 +122,28 @@ pub fn apply_move_to_board(board: &mut Board, m: &Move) {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
board.move_square(m.0, m.1);
|
board.move_square(self.source, self.dest);
|
||||||
if let Some(prom_type) = m.2 {
|
if let Some(piece) = self.promotion {
|
||||||
let color = board.get_color(m.1);
|
let color = board.get_color(self.dest);
|
||||||
board.set_square(m.1, color, prom_type);
|
board.set_square(self.dest, color, piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the corresponding castling flag for this move.
|
/// Get the corresponding castling flag for this move.
|
||||||
pub fn get_castle(m: &Move) -> Option<u8> {
|
pub fn get_castle(&self) -> Option<u8> {
|
||||||
let (source, dest) = (m.0, m.1);
|
if self.source == E1 {
|
||||||
if source == E1 {
|
if self.dest == C1 {
|
||||||
if dest == C1 {
|
|
||||||
Some(CASTLING_WH_Q)
|
Some(CASTLING_WH_Q)
|
||||||
} else if dest == G1 {
|
} else if self.dest == G1 {
|
||||||
Some(CASTLING_WH_K)
|
Some(CASTLING_WH_K)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else if source == E8 {
|
} else if self.source == E8 {
|
||||||
if dest == C8 {
|
if self.dest == C8 {
|
||||||
Some(CASTLING_BL_Q)
|
Some(CASTLING_BL_Q)
|
||||||
} else if dest == G8 {
|
} else if self.dest == G8 {
|
||||||
Some(CASTLING_BL_K)
|
Some(CASTLING_BL_K)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -157,18 +156,59 @@ pub fn get_castle(m: &Move) -> Option<u8> {
|
||||||
/// Get the move for this castle.
|
/// Get the move for this castle.
|
||||||
pub fn get_castle_move(castle: u8) -> Move {
|
pub fn get_castle_move(castle: u8) -> Move {
|
||||||
match castle {
|
match castle {
|
||||||
CASTLING_WH_Q => (E1, C1, None),
|
CASTLING_WH_Q => Move::new(E1, C1),
|
||||||
CASTLING_WH_K => (E1, G1, None),
|
CASTLING_WH_K => Move::new(E1, G1),
|
||||||
CASTLING_BL_Q => (E8, C8, None),
|
CASTLING_BL_Q => Move::new(E8, C8),
|
||||||
CASTLING_BL_K => (E8, G8, None),
|
CASTLING_BL_K => Move::new(E8, G8),
|
||||||
_ => panic!("Illegal castling requested: {:08b}", castle),
|
_ => panic!("Illegal castling requested: {:08b}", castle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse an UCI move algebraic notation string to a Move.
|
||||||
|
pub fn from_uci_string(m_str: &str) -> Move {
|
||||||
|
Move {
|
||||||
|
source: sq_from_string(&m_str[0..2]),
|
||||||
|
dest: sq_from_string(&m_str[2..4]),
|
||||||
|
promotion: if m_str.len() == 5 {
|
||||||
|
Some(match m_str.as_bytes()[4] {
|
||||||
|
b'b' => BISHOP,
|
||||||
|
b'n' => KNIGHT,
|
||||||
|
b'r' => ROOK,
|
||||||
|
b'q' => QUEEN,
|
||||||
|
_ => panic!("What is the opponent doing? This is illegal, I'm out."),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a string containing the UCI algebraic notation of this move.
|
||||||
|
pub fn to_uci_string(&self) -> String {
|
||||||
|
let mut move_string = String::new();
|
||||||
|
move_string.push_str(&sq_to_string(self.source));
|
||||||
|
move_string.push_str(&sq_to_string(self.dest));
|
||||||
|
if let Some(piece) = self.promotion {
|
||||||
|
move_string.push(match piece {
|
||||||
|
QUEEN => 'q',
|
||||||
|
BISHOP => 'b',
|
||||||
|
KNIGHT => 'n',
|
||||||
|
ROOK => 'r',
|
||||||
|
_ => panic!("What are you doing? Promote to a legal piece.")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
move_string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug only: create a space-separated string of moves.
|
||||||
|
pub(crate) fn list_to_uci_string(moves: &Vec<Move>) -> String {
|
||||||
|
moves.iter().map(|m| m.to_uci_string()).collect::<Vec<_>>().join(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::notation::parse_move;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_move_to_board() {
|
fn test_apply_move_to_board() {
|
||||||
|
@ -178,13 +218,13 @@ mod tests {
|
||||||
b.set_square(D4, WHITE, KNIGHT);
|
b.set_square(D4, WHITE, KNIGHT);
|
||||||
b.set_square(F4, BLACK, KNIGHT);
|
b.set_square(F4, BLACK, KNIGHT);
|
||||||
// Move white knight in a position attacked by black knight.
|
// Move white knight in a position attacked by black knight.
|
||||||
apply_move_to_board(&mut b, &(D4, E6, None));
|
Move::new(D4, E6).apply_to_board(&mut b);
|
||||||
assert!(b.is_empty(D4));
|
assert!(b.is_empty(D4));
|
||||||
assert_eq!(b.get_color(E6), WHITE);
|
assert_eq!(b.get_color(E6), WHITE);
|
||||||
assert_eq!(b.get_piece(E6), KNIGHT);
|
assert_eq!(b.get_piece(E6), KNIGHT);
|
||||||
assert_eq!(b.num_pieces(), 2);
|
assert_eq!(b.num_pieces(), 2);
|
||||||
// Sack it with black knight
|
// Sack it with black knight
|
||||||
apply_move_to_board(&mut b, &(F4, E6, None));
|
Move::new(F4, E6).apply_to_board(&mut b);
|
||||||
assert_eq!(b.get_color(E6), BLACK);
|
assert_eq!(b.get_color(E6), BLACK);
|
||||||
assert_eq!(b.get_piece(E6), KNIGHT);
|
assert_eq!(b.get_piece(E6), KNIGHT);
|
||||||
assert_eq!(b.num_pieces(), 1);
|
assert_eq!(b.num_pieces(), 1);
|
||||||
|
@ -193,7 +233,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_move_to_castling() {
|
fn test_apply_move_to_castling() {
|
||||||
let mut b = Board::new();
|
let mut b = Board::new();
|
||||||
let mut gs = rules::GameState::new();
|
let mut gs = GameState::new();
|
||||||
assert_eq!(gs.castling, CASTLING_MASK);
|
assert_eq!(gs.castling, CASTLING_MASK);
|
||||||
|
|
||||||
// On a starting board, start by making place for all castles.
|
// On a starting board, start by making place for all castles.
|
||||||
|
@ -208,7 +248,7 @@ mod tests {
|
||||||
b.clear_square(F8);
|
b.clear_square(F8);
|
||||||
b.clear_square(G8);
|
b.clear_square(G8);
|
||||||
// White queen-side castling.
|
// White queen-side castling.
|
||||||
apply_move_to(&mut b, &mut gs, &parse_move("e1c1"));
|
Move::new(E1, C1).apply_to(&mut b, &mut gs);
|
||||||
assert_eq!(b.get_color(C1), WHITE);
|
assert_eq!(b.get_color(C1), WHITE);
|
||||||
assert_eq!(b.get_piece(C1), KING);
|
assert_eq!(b.get_piece(C1), KING);
|
||||||
assert_eq!(b.get_color(D1), WHITE);
|
assert_eq!(b.get_color(D1), WHITE);
|
||||||
|
@ -217,7 +257,7 @@ mod tests {
|
||||||
assert!(b.is_empty(E1));
|
assert!(b.is_empty(E1));
|
||||||
assert_eq!(gs.castling, CASTLING_BL_MASK);
|
assert_eq!(gs.castling, CASTLING_BL_MASK);
|
||||||
// Black king-side castling.
|
// Black king-side castling.
|
||||||
apply_move_to(&mut b, &mut gs, &parse_move("e8g8"));
|
Move::new(E8, G8).apply_to(&mut b, &mut gs);
|
||||||
assert_eq!(b.get_color(G1), BLACK);
|
assert_eq!(b.get_color(G1), BLACK);
|
||||||
assert_eq!(b.get_piece(G1), KING);
|
assert_eq!(b.get_piece(G1), KING);
|
||||||
assert_eq!(b.get_color(F1), BLACK);
|
assert_eq!(b.get_color(F1), BLACK);
|
||||||
|
@ -230,10 +270,25 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_castle() {
|
fn test_get_castle() {
|
||||||
assert_eq!(get_castle(&parse_move("e1c1")), Some(CASTLING_WH_Q));
|
assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLING_WH_Q));
|
||||||
assert_eq!(get_castle(&parse_move("e1g1")), Some(CASTLING_WH_K));
|
assert_eq!(Move::new(E1, G1).get_castle(), Some(CASTLING_WH_K));
|
||||||
assert_eq!(get_castle(&parse_move("e8c8")), Some(CASTLING_BL_Q));
|
assert_eq!(Move::new(E8, C8).get_castle(), Some(CASTLING_BL_Q));
|
||||||
assert_eq!(get_castle(&parse_move("e8g8")), Some(CASTLING_BL_K));
|
assert_eq!(Move::new(E8, G8).get_castle(), Some(CASTLING_BL_K));
|
||||||
assert_eq!(get_castle(&parse_move("d2d4")), None);
|
assert_eq!(Move::new(D2, D4).get_castle(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_uci_string() {
|
||||||
|
assert_eq!(Move::new(A1, D4).to_uci_string(), "a1d4");
|
||||||
|
assert_eq!(Move::new(H8, A8).to_uci_string(), "h8a8");
|
||||||
|
assert_eq!(Move::new_promotion(H7, H8, QUEEN).to_uci_string(), "h7h8q");
|
||||||
|
assert_eq!(Move::new_promotion(H7, H8, KNIGHT).to_uci_string(), "h7h8n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_uci_string() {
|
||||||
|
assert_eq!(Move::from_uci_string("a1d4"), Move::new(A1, D4));
|
||||||
|
assert_eq!(Move::from_uci_string("a7a8q"), Move::new_promotion(A7, A8, QUEEN));
|
||||||
|
assert_eq!(Move::from_uci_string("a7a8r"), Move::new_promotion(A7, A8, ROOK));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
src/notation.rs
111
src/notation.rs
|
@ -1,111 +0,0 @@
|
||||||
//! Functions using various notations.
|
|
||||||
|
|
||||||
use crate::board::*;
|
|
||||||
use crate::movement::Move;
|
|
||||||
|
|
||||||
pub const NULL_MOVE: &str = "0000";
|
|
||||||
|
|
||||||
/// Create a string containing the UCI algebraic notation of this move.
|
|
||||||
pub fn move_to_string(m: &Move) -> String {
|
|
||||||
let mut move_string = String::new();
|
|
||||||
move_string.push_str(&pos_string(&m.0));
|
|
||||||
move_string.push_str(&pos_string(&m.1));
|
|
||||||
if let Some(prom) = m.2 {
|
|
||||||
move_string.push(match prom {
|
|
||||||
SQ_Q => 'q',
|
|
||||||
SQ_B => 'b',
|
|
||||||
SQ_N => 'n',
|
|
||||||
SQ_R => 'r',
|
|
||||||
_ => panic!("What are you doing? Promote to a legal piece.")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
move_string
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an UCI move algebraic notation string to a Move.
|
|
||||||
pub fn parse_move(m_str: &str) -> Move {
|
|
||||||
let prom = if m_str.len() == 5 {
|
|
||||||
Some(match m_str.as_bytes()[4] {
|
|
||||||
b'b' => SQ_B,
|
|
||||||
b'n' => SQ_N,
|
|
||||||
b'r' => SQ_R,
|
|
||||||
b'q' => SQ_Q,
|
|
||||||
_ => panic!("What is the opponent doing? This is illegal, I'm out."),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
(pos(&m_str[0..2]), pos(&m_str[2..4]), prom)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a space-separated string of moves. Used for debugging.
|
|
||||||
pub fn move_list_to_string(moves: &Vec<Move>) -> String {
|
|
||||||
moves.iter().map(|m| move_to_string(m)).collect::<Vec<_>>().join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
||||||
|
|
||||||
/// FEN notation for positions, split into fields.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Fen {
|
|
||||||
pub placement: String,
|
|
||||||
pub color: String,
|
|
||||||
pub castling: String,
|
|
||||||
pub en_passant: String,
|
|
||||||
pub halfmove: String,
|
|
||||||
pub fullmove: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_fen(i: &str) -> Option<Fen> {
|
|
||||||
let fields: Vec<&str> = i.split_whitespace().collect();
|
|
||||||
parse_fen_fields(&fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_fen_fields(fields: &[&str]) -> Option<Fen> {
|
|
||||||
if fields.len() < 6 {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
Some(Fen {
|
|
||||||
placement: fields[0].to_string(),
|
|
||||||
color: fields[1].to_string(),
|
|
||||||
castling: fields[2].to_string(),
|
|
||||||
en_passant: fields[3].to_string(),
|
|
||||||
halfmove: fields[4].to_string(),
|
|
||||||
fullmove: fields[5].to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn en_passant_to_string(ep: Option<Pos>) -> String {
|
|
||||||
ep.and_then(|p| Some(pos_string(&p))).unwrap_or("-".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_move_to_string() {
|
|
||||||
assert_eq!(move_to_string(&((0, 0), (3, 3), None)), "a1d4");
|
|
||||||
assert_eq!(move_to_string(&((7, 7), (0, 7), None)), "h8a8");
|
|
||||||
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_Q))), "h7h8q");
|
|
||||||
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_N))), "h7h8n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_move() {
|
|
||||||
assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3), None));
|
|
||||||
assert_eq!(parse_move("a7a8q"), ((0, 6), (0, 7), Some(SQ_Q)));
|
|
||||||
assert_eq!(parse_move("a7a8r"), ((0, 6), (0, 7), Some(SQ_R)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_fen() {
|
|
||||||
let fen_start = parse_fen(FEN_START).unwrap();
|
|
||||||
assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
|
|
||||||
assert_eq!(&fen_start.color, "w");
|
|
||||||
assert_eq!(&fen_start.castling, "KQkq");
|
|
||||||
assert_eq!(&fen_start.en_passant, "-");
|
|
||||||
assert_eq!(&fen_start.halfmove, "0");
|
|
||||||
assert_eq!(&fen_start.fullmove, "1");
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue