use std::fmt;
use std::str::FromStr;
use std::error::Error;
use square::Square;
use types::{Move, Role};
use position::{IllegalMove, Position};
#[derive(Debug, Eq, PartialEq)]
pub struct InvalidUci;
impl fmt::Display for InvalidUci {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
"invalid uci".fmt(f)
}
}
impl Error for InvalidUci {
fn description(&self) -> &str {
"invalid uci"
}
}
impl From<()> for InvalidUci {
fn from(_: ()) -> InvalidUci {
InvalidUci
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Uci {
Normal {
from: Square,
to: Square,
promotion: Option<Role>,
},
Put { role: Role, to: Square },
Null,
}
impl FromStr for Uci {
type Err = InvalidUci;
fn from_str(uci: &str) -> Result<Uci, InvalidUci> {
Uci::from_ascii(uci.as_bytes())
}
}
impl fmt::Display for Uci {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Uci::Normal { from, to, promotion: None } =>
write!(f, "{}{}", from, to),
Uci::Normal { from, to, promotion: Some(promotion) } =>
write!(f, "{}{}{}", from, to, promotion.char()),
Uci::Put { to, role } =>
write!(f, "{}@{}", role.upper_char(), to),
Uci::Null =>
write!(f, "0000")
}
}
}
impl Uci {
pub fn from_ascii(uci: &[u8]) -> Result<Uci, InvalidUci> {
if uci.len() != 4 && uci.len() != 5 {
return Err(InvalidUci);
}
if uci == b"0000" {
return Ok(Uci::Null);
}
let to = Square::from_ascii(&uci[2..4]).map_err(|_| ())?;
if uci[1] == b'@' {
Ok(Uci::Put { role: Role::from_char(uci[0] as char).ok_or(())?, to })
} else {
let from = Square::from_ascii(&uci[0..2]).map_err(|_| ())?;
if uci.len() == 5 {
Ok(Uci::Normal {
from,
to,
promotion: Some(Role::from_char(uci[4] as char).ok_or(())?)
})
} else {
Ok(Uci::Normal { from, to, promotion: None })
}
}
}
pub fn from_move<P: Position>(pos: &P, m: &Move) -> Uci {
match *m {
Move::Castle { king, rook } if !pos.castles().is_chess960() => {
Uci::Normal {
from: king,
to: if king < rook {
pos.turn().fold(Square::G1, Square::G8)
} else {
pos.turn().fold(Square::C1, Square::C8)
},
promotion: None,
}
}
_ => Uci::from_chess960(m)
}
}
pub fn from_chess960(m: &Move) -> Uci {
match *m {
Move::Normal { from, to, promotion, .. } =>
Uci::Normal { from, to, promotion },
Move::EnPassant { from, to, .. } =>
Uci::Normal { from, to, promotion: None },
Move::Castle { king, rook } =>
Uci::Normal { from: king, to: rook, promotion: None }, Move::Put { role, to } =>
Uci::Put { role, to },
}
}
pub fn to_move<P: Position>(&self, pos: &P) -> Result<Move, IllegalMove> {
let candidate = match *self {
Uci::Normal { from, to, promotion } => {
let role = pos.board().role_at(from).ok_or(IllegalMove)?;
if promotion.is_some() && role != Role::Pawn {
return Err(IllegalMove)
}
if role == Role::King && pos.castling_rights().contains(to) {
Move::Castle { king: from, rook: to }
} else if role == Role::King &&
from == pos.turn().fold(Square::E1, Square::E8) &&
to.rank() == pos.turn().fold(0, 7) &&
from.distance(to) == 2 {
if from.file() < to.file() {
Move::Castle { king: from, rook: pos.turn().fold(Square::H1, Square::H8) }
} else {
Move::Castle { king: from, rook: pos.turn().fold(Square::A1, Square::A8) }
}
} else if role == Role::Pawn &&
from.file() != to.file() &&
!pos.board().occupied().contains(to) {
Move::EnPassant { from, to }
} else {
Move::Normal { role, from, capture: pos.board().role_at(to), to, promotion }
}
},
Uci::Put { role, to } => Move::Put { role, to },
Uci::Null => return Err(IllegalMove)
};
if pos.is_legal(&candidate) {
Ok(candidate)
} else {
Err(IllegalMove)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use position::Chess;
#[test]
pub fn test_uci_to_en_passant() {
let mut pos = Chess::default();
let e4 = "e2e4".parse::<Uci>().expect("e4").to_move(&pos).expect("legal");
pos.play_unchecked(&e4);
let nc6 = "b8c6".parse::<Uci>().expect("Nc6").to_move(&pos).expect("legal");
pos.play_unchecked(&nc6);
let e5 = "e4e5".parse::<Uci>().expect("e5").to_move(&pos).expect("legal");
pos.play_unchecked(&e5);
let d5 = "d7d5".parse::<Uci>().expect("d5").to_move(&pos).expect("legal");
pos.play_unchecked(&d5);
let exd5 = "e5d6".parse::<Uci>().expect("exd6").to_move(&pos).expect("legal en passant");
assert!(exd5.is_en_passant());
}
}