use regex::Regex;
use board::*;
use files::*;
use ranks::*;
pub fn parse_fen(s: &str) -> Result<(Board, u8, u16), IllegalBoard> {
let fileds: Vec<_> = s.split_whitespace().collect();
if fileds.len() == 6 {
let pieces = try!(parse_fen_piece_placement(fileds[0]));
let to_move = try!(parse_fen_active_color(fileds[1]));
let castling_rights = try!(parse_fen_castling_rights(fileds[2]));
let enpassant_file = if let Some(x) = try!(parse_fen_enpassant_square(fileds[3])) {
match to_move {
WHITE if Board::rank(x) == RANK_6 => Board::file(x),
BLACK if Board::rank(x) == RANK_3 => Board::file(x),
_ => return Err(IllegalBoard),
}
} else {
8
};
let halfmove_clock = try!(fileds[4].parse::<u8>().map_err(|_| IllegalBoard));
let fullmove_number = try!(fileds[5].parse::<u16>().map_err(|_| IllegalBoard));
if let 1...9000 = fullmove_number {
return Ok((Board {
occupied: pieces.color[WHITE] | pieces.color[BLACK],
pieces: pieces,
to_move: to_move,
castling_rights: castling_rights,
enpassant_file: enpassant_file,
},
halfmove_clock,
fullmove_number));
}
}
Err(IllegalBoard)
}
pub fn parse_square(s: &str) -> Result<Square, IllegalBoard> {
lazy_static! {
static ref RE: Regex = Regex::new(r"^[a-h][1-8]$").unwrap();
}
if RE.is_match(s) {
let mut chars = s.chars();
let file = (chars.next().unwrap().to_digit(18).unwrap() - 10) as usize;
let rank = (chars.next().unwrap().to_digit(9).unwrap() - 1) as usize;
Ok(Board::square(file, rank))
} else {
Err(IllegalBoard)
}
}
fn parse_fen_piece_placement(s: &str) -> Result<PiecesPlacement, IllegalBoard> {
enum Token {
Piece(Color, PieceType),
EmptySquares(u32),
Separator,
}
let mut file = FILE_A;
let mut rank = RANK_8;
let mut pieces = PiecesPlacement {
piece_type: [0u64; 6],
color: [0u64; 2],
};
for c in s.chars() {
let token = match c {
'K' => Token::Piece(WHITE, KING),
'Q' => Token::Piece(WHITE, QUEEN),
'R' => Token::Piece(WHITE, ROOK),
'B' => Token::Piece(WHITE, BISHOP),
'N' => Token::Piece(WHITE, KNIGHT),
'P' => Token::Piece(WHITE, PAWN),
'k' => Token::Piece(BLACK, KING),
'q' => Token::Piece(BLACK, QUEEN),
'r' => Token::Piece(BLACK, ROOK),
'b' => Token::Piece(BLACK, BISHOP),
'n' => Token::Piece(BLACK, KNIGHT),
'p' => Token::Piece(BLACK, PAWN),
n @ '1'...'8' => Token::EmptySquares(n.to_digit(9).unwrap()),
'/' => Token::Separator,
_ => return Err(IllegalBoard),
};
match token {
Token::Piece(color, piece_type) => {
if file > 7 {
return Err(IllegalBoard);
}
let mask = 1 << Board::square(file, rank);
pieces.piece_type[piece_type] |= mask;
pieces.color[color] |= mask;
file += 1;
}
Token::EmptySquares(n) => {
file += n as usize;
if file > 8 {
return Err(IllegalBoard);
}
}
Token::Separator => {
if file == 8 && rank > 0 {
file = 0;
rank -= 1;
} else {
return Err(IllegalBoard);
}
}
}
}
if file != 8 || rank != 0 {
return Err(IllegalBoard);
}
Ok(pieces)
}
fn parse_fen_active_color(s: &str) -> Result<Color, IllegalBoard> {
match s {
"w" => Ok(WHITE),
"b" => Ok(BLACK),
_ => Err(IllegalBoard),
}
}
fn parse_fen_castling_rights(s: &str) -> Result<CastlingRights, IllegalBoard> {
let mut rights = CastlingRights::new(0);
if s != "-" {
for c in s.chars() {
let (color, side) = match c {
'K' => (WHITE, KINGSIDE),
'Q' => (WHITE, QUEENSIDE),
'k' => (BLACK, KINGSIDE),
'q' => (BLACK, QUEENSIDE),
_ => return Err(IllegalBoard),
};
if !rights.grant(color, side) {
return Err(IllegalBoard);
}
}
}
Ok(rights)
}
fn parse_fen_enpassant_square(s: &str) -> Result<Option<Square>, IllegalBoard> {
if s == "-" {
Ok(None)
} else {
parse_square(s).map(|x| Some(x))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_fen_string() {
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").is_ok());
assert!(parse_fen("rnbqkbnr/pppppppp/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("nbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr1/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBN b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR/ b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNRR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPP01PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPP91PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPP*1PPP/RNBQKBNR b KQkq e3 0 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 * 1").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 *").is_err());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b - e3 0 1").is_ok());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1").is_ok());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b Kkq e3 0 1").is_ok());
assert!(parse_fen("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b kq - 0 1").is_ok());
assert!(parse_fen("k7/8/8/8/8/8/8/7K w - - 0 1").is_ok());
assert!(parse_fen("k7/pppppppp/8/8/8/8/PPPPPPPP/7K w - - 0 1").is_ok());
assert!(parse_fen("k7/8/8/8/7P/8/8/7K w - h3 0 1").is_err());
assert!(parse_fen("k7/8/8/7P/8/8/8/7K b - h4 0 1").is_err());
assert!(parse_fen("8/8/8/6k1/7P/8/8/6RK b - h3 0 1").is_ok());
assert!(parse_fen("8/8/8/6k1/7P/8/8/7K b - h3 0 0").is_err());
}
}