use regex::Regex;
use std::fmt;
use super::board::Board;
use super::board::*;
pub trait Move: fmt::Display {
fn make_move(&self, board: &Board) -> Result<Board, &'static str>;
}
struct KingSideCastles {}
impl fmt::Display for KingSideCastles {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "O-O")
}
}
impl Move for KingSideCastles {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
if board.white_to_move {
if board.get_coord(4, 0).name == Piece::WK
&& board.get_coord(5, 0).name == Piece::NO
&& board.get_coord(6, 0).name == Piece::NO
&& board.get_coord(7, 0).name == Piece::WR
&& board.white_king_side_castling
{
let mut revised_board = board.clone();
revised_board.set_coord(4, 0, &Piece::NO);
revised_board.set_coord(5, 0, &Piece::WR);
revised_board.set_coord(6, 0, &Piece::WK);
revised_board.set_coord(7, 0, &Piece::NO);
revised_board.white_to_move = false;
revised_board.enpassant_target = None;
revised_board.halfmove_clock += 1;
revised_board.white_king_side_castling = false;
revised_board.white_queen_side_castling = false;
Ok(revised_board)
} else {
Err("Invalid white O-O")
}
} else {
if board.get_coord(4, 7).name == Piece::BK
&& board.get_coord(5, 7).name == Piece::NO
&& board.get_coord(6, 7).name == Piece::NO
&& board.get_coord(7, 7).name == Piece::BR
&& board.black_king_side_castling
{
let mut revised_board = board.clone();
revised_board.set_coord(4, 7, &Piece::NO);
revised_board.set_coord(5, 7, &Piece::BR);
revised_board.set_coord(6, 7, &Piece::BK);
revised_board.set_coord(7, 7, &Piece::NO);
revised_board.white_to_move = true;
revised_board.enpassant_target = None;
revised_board.halfmove_clock += 1;
revised_board.fullmove_number += 1;
revised_board.black_king_side_castling = false;
revised_board.black_queen_side_castling = false;
Ok(revised_board)
} else {
Err("Invalid black O-O")
}
}
}
}
struct QueenSideCastles {}
impl fmt::Display for QueenSideCastles {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "O-O-O")
}
}
impl Move for QueenSideCastles {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
if board.white_to_move {
if board.get_coord(4, 0).name == Piece::WK
&& board.get_coord(3, 0).name == Piece::NO
&& board.get_coord(2, 0).name == Piece::NO
&& board.get_coord(1, 0).name == Piece::NO
&& board.get_coord(0, 0).name == Piece::WR
&& board.white_queen_side_castling
{
let mut revised_board = board.clone();
revised_board.set_coord(4, 0, &Piece::NO);
revised_board.set_coord(3, 0, &Piece::WR);
revised_board.set_coord(2, 0, &Piece::WK);
revised_board.set_coord(0, 0, &Piece::NO);
revised_board.white_to_move = false;
revised_board.enpassant_target = None;
revised_board.halfmove_clock += 1;
revised_board.white_king_side_castling = false;
revised_board.white_queen_side_castling = false;
Ok(revised_board)
} else {
Err("Invalid white O-O-O")
}
} else {
if board.get_coord(4, 7).name == Piece::BK
&& board.get_coord(3, 7).name == Piece::NO
&& board.get_coord(2, 7).name == Piece::NO
&& board.get_coord(1, 7).name == Piece::NO
&& board.get_coord(0, 7).name == Piece::BR
&& board.black_queen_side_castling
{
let mut revised_board = board.clone();
revised_board.set_coord(4, 7, &Piece::NO);
revised_board.set_coord(3, 7, &Piece::BR);
revised_board.set_coord(2, 7, &Piece::BK);
revised_board.set_coord(0, 7, &Piece::NO);
revised_board.white_to_move = true;
revised_board.enpassant_target = None;
revised_board.halfmove_clock += 1;
revised_board.fullmove_number += 1;
revised_board.black_king_side_castling = false;
revised_board.black_queen_side_castling = false;
Ok(revised_board)
} else {
Err("Invalid black O-O-O")
}
}
}
}
struct PieceMove {
move_string: String,
piece: Piece,
identifier: String, destination: Square,
is_capture: bool,
}
impl fmt::Display for PieceMove {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.move_string)
}
}
impl Move for PieceMove {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
let piece = piece_for_colour (&self.piece, board.white_to_move);
let mut origin: Vec<Square> = board
.locations_within(&piece, &self.identifier)
.iter()
.filter(|loc| board.can_reach(&piece, &loc, &self.destination))
.cloned()
.collect();
if origin.len() > 1 {
origin = origin
.iter()
.filter(|loc| !board.king_left_in_check(&piece, &loc, &self.destination))
.cloned()
.collect();
}
if origin.len() != 1 || board.get_square(&origin[0]).name != piece {
Err("Invalid piece move")
} else {
let mut revised_board = board.clone();
revised_board.set_square(&origin[0], &Piece::NO);
revised_board.set_square(&self.destination, &piece);
revised_board.white_to_move = !board.white_to_move;
revised_board.enpassant_target = None;
if self.is_capture {
revised_board.halfmove_clock = 0;
} else {
revised_board.halfmove_clock += 1;
}
if !board.white_to_move {
revised_board.fullmove_number += 1
}
Ok(revised_board)
}
}
}
struct SimplePawnMove {
move_string: String,
destination: Square,
}
impl fmt::Display for SimplePawnMove {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.move_string)
}
}
impl Move for SimplePawnMove {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
let mut revised_board = board.clone();
if board.white_to_move {
revised_board.white_to_move = false;
revised_board.halfmove_clock = 0;
if self.single_step(&board) {
revised_board.set_square(&self.destination, &Piece::WP);
let square = self.previous_square(board.white_to_move);
revised_board.set_square(&square, &Piece::NO);
revised_board.enpassant_target = None;
Ok(revised_board)
} else if self.initial_step(&board) {
revised_board.set_square(&self.destination, &Piece::WP);
revised_board.set_square(&self.initial_square(board.white_to_move), &Piece::NO);
let square = self.previous_square(board.white_to_move);
revised_board.enpassant_target = Some(square);
Ok(revised_board)
} else {
Err("Invalid simple pawn move")
}
} else {
revised_board.white_to_move = true;
revised_board.halfmove_clock = 0;
revised_board.fullmove_number += 1;
if self.single_step(&board) {
revised_board.set_square(&self.destination, &Piece::BP);
let square = self.previous_square(board.white_to_move);
revised_board.set_square(&square, &Piece::NO);
revised_board.enpassant_target = None;
Ok(revised_board)
} else if self.initial_step(&board) {
revised_board.set_square(&self.destination, &Piece::BP);
revised_board.set_square(&self.initial_square(board.white_to_move), &Piece::NO);
let square = self.previous_square(board.white_to_move);
revised_board.enpassant_target = Some(square);
Ok(revised_board)
} else {
Err("Invalid simple pawn move")
}
}
}
}
impl SimplePawnMove {
fn single_step(&self, board: &Board) -> bool {
let pawn = if board.white_to_move { Piece::WP } else { Piece::BP };
let dest_square = board.get_square(&self.destination);
let square = self.previous_square(board.white_to_move);
let dest_piece = board.get_square(&square);
dest_square.name == Piece::NO && dest_piece.name == pawn
}
fn initial_step(&self, board: &Board) -> bool {
let (pawn, rank) = if board.white_to_move {
(Piece::WP, 3)
} else {
(Piece::BP, 4)
};
let dest_square = board.get_square(&self.destination);
let dest_piece = board.get_square(&self.initial_square(board.white_to_move));
dest_square.name == Piece::NO && self.destination.row == rank && dest_piece.name == pawn
}
fn initial_square(&self, white_to_move: bool) -> Square {
Square {
col: self.destination.col,
row: if white_to_move { 1 } else { 6 },
}
}
fn previous_square(&self, white_to_move: bool) -> Square {
let offset: i32 = if white_to_move { -1 } else { 1 };
Square {
col: self.destination.col,
row: ((self.destination.row as i32) + offset) as usize,
}
}
}
struct PawnCapture {
move_string: String,
source_col: String, destination: Square,
}
impl fmt::Display for PawnCapture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.move_string)
}
}
impl Move for PawnCapture {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
let origin = self.find_origin(board.white_to_move);
let mut revised_board = board.clone();
if let Some(ep) = &board.enpassant_target {
if self.destination == *ep {
revised_board.set_coord(self.destination.col, origin.row, &Piece::NO);
}
};
revised_board.set_square(&origin, &Piece::NO);
revised_board.set_square(&self.destination, &board.get_square(&origin).name);
revised_board.enpassant_target = None;
revised_board.halfmove_clock = 0;
revised_board.white_to_move = !board.white_to_move;
if revised_board.white_to_move {
revised_board.fullmove_number += 1;
}
Ok(revised_board)
}
}
impl PawnCapture {
fn find_origin(&self, white_to_move: bool) -> Square {
let offset: i32 = if white_to_move { -1 } else { 1 };
let col = match self.source_col.as_str() {
"a" => 0,
"b" => 1,
"c" => 2,
"d" => 3,
"e" => 4,
"f" => 5,
"g" => 6,
"h" => 7,
_ => panic!("Internal error: invalid square definition"),
};
Square {
col,
row: ((self.destination.row as i32) + offset) as usize,
}
}
}
struct PromotionPawnMove {
pawn_move: SimplePawnMove,
piece: Piece,
}
impl fmt::Display for PromotionPawnMove {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.pawn_move)
}
}
impl Move for PromotionPawnMove {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
let mut revised_board = self.pawn_move.make_move(&board)?;
revised_board.set_square(&self.pawn_move.destination, &self.piece);
Ok(revised_board)
}
}
struct PromotionPawnCapture {
pawn_capture: PawnCapture,
piece: Piece,
}
impl fmt::Display for PromotionPawnCapture {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.pawn_capture)
}
}
impl Move for PromotionPawnCapture {
fn make_move(&self, board: &Board) -> Result<Board, &'static str> {
let mut revised_board = self.pawn_capture.make_move(&board)?;
revised_board.set_square(&self.pawn_capture.destination, &self.piece);
Ok(revised_board)
}
}
pub struct Matches {
king_side_castles: Regex,
queen_side_castles: Regex,
piece_move: Regex,
simple_pawn_move: Regex,
pawn_capture: Regex,
promotion_pawn_move: Regex,
promotion_pawn_capture: Regex,
pub comment: Regex,
pub nag: Regex
}
impl Matches {
pub fn new() -> Matches {
Matches {
king_side_castles: Regex::new(r"^O-O\+?\z").unwrap(),
queen_side_castles: Regex::new(r"^O-O-O\+?\z").unwrap(),
piece_move: Regex::new(r"^([KQRBN])([a-h]?|[1-8]?)x?([a-h][1-8])\+?\z").unwrap(),
simple_pawn_move: Regex::new(r"^([a-h][2-7])\+?\z").unwrap(),
pawn_capture: Regex::new(r"^([a-h])x([a-h][2-7])\+?\z").unwrap(),
promotion_pawn_move: Regex::new(r"^([a-h][18])=([QqRrBbNn])\+?\z").unwrap(),
promotion_pawn_capture: Regex::new(r"^([a-h])x([a-h][18])=([QqRrBbNn])\+?\z").unwrap(),
comment: Regex::new(r"\{(.)*\}").unwrap(),
nag: Regex::new(r"$\d+").unwrap(),
}
}
pub fn is_valid(&self, move_string: &str) -> bool {
self.king_side_castles.is_match(move_string)
|| self.queen_side_castles.is_match(move_string)
|| self.piece_move.is_match(move_string)
|| self.simple_pawn_move.is_match(move_string)
|| self.pawn_capture.is_match(move_string)
|| self.promotion_pawn_move.is_match(move_string)
|| self.promotion_pawn_capture.is_match(move_string)
}
pub fn new_move(&self, move_string: &str) -> Result<Box<dyn Move>, &'static str> {
if self.king_side_castles.is_match(move_string) {
Ok(Box::new(KingSideCastles {}))
} else if self.queen_side_castles.is_match(move_string) {
Ok(Box::new(QueenSideCastles {}))
} else if self.piece_move.is_match(move_string) {
let captures = self.piece_move.captures(move_string).unwrap();
Ok(Box::new(PieceMove {
move_string: String::from(move_string),
piece: piece_from(&captures[1].to_string()),
identifier: captures[2].to_string().to_lowercase(),
destination: Square::from(&captures[3]).unwrap(),
is_capture: move_string.contains("x"),
}))
} else if self.simple_pawn_move.is_match(move_string) {
let captures = self.simple_pawn_move.captures(move_string).unwrap();
Ok(Box::new(SimplePawnMove {
move_string: String::from(move_string),
destination: Square::from(&captures[1]).unwrap(),
}))
} else if self.pawn_capture.is_match(move_string) {
let captures = self.pawn_capture.captures(move_string).unwrap();
Ok(Box::new(PawnCapture {
move_string: String::from(move_string),
source_col: captures[1].to_string().to_lowercase(),
destination: Square::from(&captures[2]).unwrap(),
}))
} else if self.promotion_pawn_move.is_match(move_string) {
let captures = self.promotion_pawn_move.captures(move_string).unwrap();
let destination = Square::from(&captures[1])?;
let destination_row = destination.row;
Ok(Box::new(PromotionPawnMove {
pawn_move: SimplePawnMove {
move_string: String::from(move_string),
destination
},
piece: if destination_row == 7 { piece_from(&captures[2].to_string().to_uppercase())
} else { piece_from(&captures[2].to_string().to_lowercase())
}
}))
} else if self.promotion_pawn_capture.is_match(move_string) {
let captures = self.promotion_pawn_capture.captures(move_string).unwrap();
let destination = Square::from(&captures[2])?;
let destination_row = destination.row;
Ok(Box::new(PromotionPawnCapture {
pawn_capture: PawnCapture {
move_string: String::from(move_string),
source_col: captures[1].to_string().to_lowercase(),
destination
},
piece: if destination_row == 7 { piece_from(&captures[3].to_string().to_uppercase())
} else { piece_from(&captures[3].to_string().to_lowercase())
}
}))
} else {
Err("Unknown move type")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const DEFNS: &[(&str, &str, &str)] = &[
(
"8/8/8/8/8/8/8/4K2R w K - 0 0",
"O-O",
"8/8/8/8/8/8/8/5RK1 b - - 1 0",
),
(
"4k2r/8/8/8/8/8/8/8 b k - 0 0",
"O-O",
"5rk1/8/8/8/8/8/8/8 w - - 1 1",
),
(
"8/8/8/8/8/8/8/R3K2R w KQ - 0 0",
"O-O-O",
"8/8/8/8/8/8/8/2KR3R b - - 1 0",
),
(
"r3k2r/8/8/8/8/8/8/8 b kq - 0 0",
"O-O-O",
"2kr3r/8/8/8/8/8/8/8 w - - 1 1",
),
(
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"e4",
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
),
(
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"a3",
"rnbqkbnr/pppppppp/8/8/8/P7/1PPPPPPP/RNBQKBNR b KQkq - 0 1",
),
(
"8/8/4P3/8/8/8/8/8 w - - 10 23",
"e7",
"8/4P3/8/8/8/8/8/8 b - - 0 23",
),
(
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
"d5",
"rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 2",
),
(
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
"g6",
"rnbqkbnr/pppppp1p/6p1/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2",
),
(
"8/8/8/8/8/2p5/8/8 b - - 10 23",
"c2",
"8/8/8/8/8/8/2p5/8 w - - 0 24",
),
(
"8/4P3/8/8/8/8/8/8 w - - 10 23",
"e8=Q",
"4Q3/8/8/8/8/8/8/8 b - - 0 23",
),
(
"8/4P3/8/8/8/8/8/8 w - - 10 23",
"e8=N",
"4N3/8/8/8/8/8/8/8 b - - 0 23",
),
(
"8/8/8/8/8/8/2p5/8 b - - 10 23",
"c1=B",
"8/8/8/8/8/8/8/2b5 w - - 0 24",
),
(
"7k/4P3/8/8/8/8/8/8 w - - 10 23",
"e8=Q+",
"4Q2k/8/8/8/8/8/8/8 b - - 0 23",
),
(
"8/8/8/8/4Q3/8/8/8 w - - 10 23",
"Qe8",
"4Q3/8/8/8/8/8/8/8 b - - 11 23",
),
(
"4q3/8/8/8/4Q3/8/8/8 w - - 10 23",
"Qxe8",
"4Q3/8/8/8/8/8/8/8 b - - 0 23",
),
(
"8/8/8/8/4r3/8/8/8 b - - 10 23",
"Rh4",
"8/8/8/8/7r/8/8/8 w - - 11 24",
),
(
"8/8/8/8/Pr3/8/8/8 b - - 10 23",
"Rxa4",
"8/8/8/8/r7/8/8/8 w - - 0 24",
),
(
"8/8/5p2/8/4N3/8/8/8 w - - 10 23",
"Nxf6",
"8/8/5N2/8/8/8/8/8 b - - 0 23",
),
(
"8/8/5p2/8/4k3/8/8/8 b - - 10 23",
"Kd5",
"8/8/5p2/3k4/8/8/8/8 w - - 11 24",
),
(
"8/8/8/8/1N3N2/8/8/8 w - - 10 23",
"Nbd5",
"8/8/8/3N4/5N2/8/8/8 b - - 11 23",
),
(
"8/8/8/8/1N3N2/8/8/8 w - - 10 23",
"Nfd5",
"8/8/8/3N4/1N6/8/8/8 b - - 11 23",
),
(
"8/8/8/8/1R6/1p6/8/1R6 w - - 10 23",
"R1xb3",
"8/8/8/8/1R6/1R6/8/8 b - - 0 23",
),
(
"8/8/8/8/1R6/1p6/8/1R6 w - - 10 23",
"R4xb3",
"8/8/8/8/8/1R6/8/1R6 b - - 0 23",
),
(
"r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQ1RK1 w - - 2 6",
"Re1",
"r1bqk2r/1pppbppp/p1n2n2/4p3/B3P3/5N2/PPPP1PPP/RNBQR1K1 b - - 3 6",
),
(
"Q7/8/2p5/8/8/8/8/7Q w - - 10 23",
"Qf3",
"Q7/8/2p5/8/8/5Q2/8/8 b - - 11 23",
),
(
"r2q1rk1/1b1nbppp/2pp1n2/1p2p3/3PP3/1BN2N1P/PP3PP1/R1BQR1K1 w - - 0 14",
"Bg5",
"r2q1rk1/1b1nbppp/2pp1n2/1p2p1B1/3PP3/1BN2N1P/PP3PP1/R2QR1K1 b - - 1 14",
),
(
"r2q1rk1/1b1nbpp1/3p3p/2p1P3/1p2n2B/1B3N1P/PP3PP1/RN1QR1K1 w - - 0 18",
"Bxe7",
"r2q1rk1/1b1nBpp1/3p3p/2p1P3/1p2n3/1B3N1P/PP3PP1/RN1QR1K1 b - - 0 18",
),
(
"8/8/8/8/1b6/2N5/8/4K1N1 w - - 0 10",
"Ne2",
"8/8/8/8/1b6/2N5/4N3/4K3 b - - 1 10",
),
(
"8/8/8/3p4/4P3/8/8/8 w - - 10 23",
"exd5",
"8/8/8/3P4/8/8/8/8 b - - 0 23",
),
(
"8/8/8/3p4/4P3/8/8/8 b - - 10 23",
"dxe4",
"8/8/8/8/4p3/8/8/8 w - - 0 24",
),
(
"8/8/8/8/3pP3/8/8/8 b - e3 10 23",
"dxe3",
"8/8/8/8/8/4p3/8/8 w - - 0 24",
),
(
"8/8/8/3pP3/8/8/8/8 w - d6 10 23",
"exd6",
"8/8/3P4/8/8/8/8/8 b - - 0 23",
),
(
"3n4/4P3/8/8/8/8/8/8 w - - 10 23",
"exd8=B",
"3B4/8/8/8/8/8/8/8 b - - 0 23",
),
];
#[test]
fn test_manages_moves() {
let matcher = Matches::new();
for (source, mv, target) in DEFNS.iter() {
let board = Board::from_fen(source).unwrap();
match matcher.new_move(mv).unwrap().make_move(&board) {
Ok(end_board) => assert_eq!(Board::from_fen(target).unwrap(), end_board),
Err(e) => {
println!("Error {}", e);
assert!(false);
}
}
}
}
}