1use std::convert::TryFrom;
10use std::error;
11use std::fmt::{self, Display};
12use std::ops::RangeInclusive;
13use std::str::FromStr;
14
15use crate::boardrepr::{Mailbox, PieceSets};
16use crate::coretypes::{Castling, Color, File, MoveCount, Piece, Rank, Square};
17use crate::position::Position;
18
19#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
20pub enum ParseFenError {
21 IllFormed,
22 Placement,
23 SideToMove,
24 Castling,
25 EnPassant,
26 HalfMoveClock,
27 FullMoveNumber,
28}
29
30impl ParseFenError {
31 pub fn as_str(&self) -> &'static str {
32 use ParseFenError::*;
33 match self {
34 IllFormed => "ill formed",
35 Placement => "placement",
36 SideToMove => "side to move",
37 Castling => "castling",
38 EnPassant => "en passant",
39 HalfMoveClock => "half move clock",
40 FullMoveNumber => "full move number",
41 }
42 }
43}
44
45impl Display for ParseFenError {
46 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47 write!(f, "{}", self.as_str())
48 }
49}
50
51impl error::Error for ParseFenError {}
52
53pub trait Fen: Sized {
55 fn parse_fen(s: &str) -> Result<Self, ParseFenError>;
57
58 fn to_fen(&self) -> String;
60
61 fn parse_halfmove_clock(s: &str) -> Result<MoveCount, ParseFenError> {
63 s.parse::<MoveCount>()
64 .map_err(|_| ParseFenError::HalfMoveClock)
65 }
66
67 fn parse_fullmove_number(s: &str) -> Result<MoveCount, ParseFenError> {
69 let fullmove: MoveCount = s.parse().unwrap_or(0);
70 if fullmove != 0 {
71 Ok(fullmove)
72 } else {
73 Err(ParseFenError::FullMoveNumber)
74 }
75 }
76}
77
78impl Fen for Position {
79 fn parse_fen(s: &str) -> Result<Self, ParseFenError> {
81 if s.split_whitespace().count() != 6 {
83 return Err(ParseFenError::IllFormed);
84 }
85 let fen_parts: Vec<&str> = s.split_whitespace().collect();
86
87 let pieces: PieceSets = FenComponent::try_from_fen_str(fen_parts[0])?;
89 let player: Color = FenComponent::try_from_fen_str(fen_parts[1])?;
90 let castling: Castling = FenComponent::try_from_fen_str(fen_parts[2])?;
91 let en_passant: Option<Square> = FenComponent::try_from_fen_str(fen_parts[3])?;
92 let halfmoves: MoveCount = Self::parse_halfmove_clock(fen_parts[4])?;
93 let fullmoves: MoveCount = Self::parse_fullmove_number(fen_parts[5])?;
94
95 Ok(Self {
96 pieces,
97 player,
98 castling,
99 en_passant,
100 halfmoves,
101 fullmoves,
102 })
103 }
104
105 fn to_fen(&self) -> String {
107 format!(
108 "{} {} {} {} {} {}",
109 self.pieces().to_fen_str(),
110 self.player().to_fen_str(),
111 self.castling().to_fen_str(),
112 self.en_passant().to_fen_str(),
113 self.halfmoves(),
114 self.fullmoves()
115 )
116 }
117}
118
119pub trait FenComponent: Sized {
122 type Error;
123 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error>;
124 fn to_fen_str(&self) -> String;
125}
126
127impl FenComponent for Mailbox {
129 type Error = ParseFenError;
130 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error> {
131 const NUMS: RangeInclusive<char> = '1'..='8';
134 const PIECES: [char; 12] = ['R', 'N', 'B', 'Q', 'K', 'P', 'r', 'n', 'b', 'q', 'k', 'p'];
135 const ERR: ParseFenError = ParseFenError::Placement;
136
137 let mut num_ranks = 0u32;
138 let mut squares = Square::iter();
139 let mut board = Mailbox::new();
140
141 for rank_str in s.split('/').rev() {
143 let mut sum_rank = 0;
144 num_ranks += 1;
145
146 for ch in rank_str.chars() {
147 if NUMS.contains(&ch) {
148 let num = ch.to_digit(10).ok_or(ERR)?;
149 squares.nth(num as usize - 1);
150 sum_rank += num;
151 } else if PIECES.contains(&ch) {
152 let piece = Piece::try_from(ch).map_err(|_| ERR)?;
153 let square = squares.next().ok_or(ERR)?;
154 board[square] = Some(piece);
155 sum_rank += 1;
156 } else {
157 return Err(ERR);
158 }
159 }
160 if sum_rank != 8 {
161 return Err(ERR);
162 }
163 }
164
165 (num_ranks == 8)
166 .then(|| board)
167 .ok_or(ParseFenError::Placement)
168 }
169
170 fn to_fen_str(&self) -> String {
171 use File::*;
174 use Rank::*;
175 let mut fen_str = String::new();
176
177 for rank in [R8, R7, R6, R5, R4, R3, R2, R1] {
178 let mut empty_counter = 0u8;
179
180 for file in [A, B, C, D, E, F, G, H] {
181 match self[(file, rank)] {
182 Some(piece) => {
183 if empty_counter != 0 {
184 fen_str.push_str(&empty_counter.to_string());
185 empty_counter = 0;
186 }
187 fen_str.push(piece.into())
188 }
189 None => empty_counter += 1,
190 };
191 }
192
193 if empty_counter != 0 {
194 fen_str.push_str(&empty_counter.to_string());
195 }
196 fen_str.push('/');
197 }
198 fen_str.pop(); fen_str
200 }
201}
202
203impl FenComponent for PieceSets {
205 type Error = ParseFenError;
206 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error> {
207 Mailbox::try_from_fen_str(s).map(|mailbox| Self::from(&mailbox))
208 }
209 fn to_fen_str(&self) -> String {
210 Mailbox::from(self).to_fen_str()
211 }
212}
213
214impl FenComponent for Color {
216 type Error = ParseFenError;
217 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error> {
219 let ch = s.chars().next().ok_or(ParseFenError::SideToMove)?;
220 Color::try_from(ch).map_err(|_| ParseFenError::SideToMove)
221 }
222 fn to_fen_str(&self) -> String {
223 self.to_string()
224 }
225}
226
227impl FenComponent for Castling {
229 type Error = ParseFenError;
230 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error> {
232 Castling::from_str(s).map_err(|_| ParseFenError::Castling)
233 }
234 fn to_fen_str(&self) -> String {
235 self.to_string()
236 }
237}
238
239impl FenComponent for Option<Square> {
241 type Error = ParseFenError;
242 fn try_from_fen_str(s: &str) -> Result<Self, Self::Error> {
244 const RANKS: [char; 2] = ['3', '6'];
245 let mut chars = s.chars();
246 let first = chars.next().ok_or(ParseFenError::EnPassant)?;
247
248 if first == '-' {
249 Ok(None)
250 } else {
251 let second = chars.next().ok_or(ParseFenError::EnPassant)?;
252 Ok(Some(
253 RANKS
254 .contains(&second)
255 .then(|| Square::from_str(s))
256 .ok_or(ParseFenError::EnPassant)?
257 .map_err(|_| ParseFenError::EnPassant)?,
258 ))
259 }
260 }
261 fn to_fen_str(&self) -> String {
262 match self {
263 Some(square) => square.to_string(),
264 None => "-".to_string(),
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn parse_default_fen_string() {
275 const FEN_STR: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
279 let pos = Position::parse_fen(FEN_STR).unwrap();
280 let start_pos = Position::start_position();
281
282 assert_eq!(pos, start_pos);
283 assert_eq!(pos.to_fen(), FEN_STR);
284 assert_eq!(start_pos.to_fen(), FEN_STR);
285 println!("{}", start_pos.to_fen());
286 }
287
288 #[test]
289 fn parse_placement_fen_substrings() {
290 const VALID1: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
292 const VALID2: &str = "rn1qkb1r/ppp2ppp/4pn2/3p4/3P2bP/2N1PN2/PPP2PP1/R1BQKB1R";
293 const VALID3: &str = "r1Q2rk1/p3qppp/np1bpn2/3p4/1PpP2bP/2N1PN2/PBP2PPR/R3KB2";
294 const VALID4: &str = "2r2rk1/p4p2/nR4Pp/3p4/3P2P1/P1p5/2P1KP1R/4b3";
295
296 const INVALID1: &str = "";
297 const INVALID2: &str = "hello world";
298 const INVALID3: &str = "nbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
299 const INVALID4: &str = "nbqkbnr/ pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
300 const INVALID5: &str = " rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
301 const INVALID6: &str = "rnbqkbnr/pppppppp/27/8/8/8/PPPPPPPP/RNBQKBNR";
302
303 assert_eq!(
304 Mailbox::try_from_fen_str(VALID1).unwrap().to_fen_str(),
305 VALID1
306 );
307 assert_eq!(
308 Mailbox::try_from_fen_str(VALID2).unwrap().to_fen_str(),
309 VALID2
310 );
311 assert_eq!(
312 Mailbox::try_from_fen_str(VALID3).unwrap().to_fen_str(),
313 VALID3
314 );
315 assert_eq!(
316 Mailbox::try_from_fen_str(VALID4).unwrap().to_fen_str(),
317 VALID4
318 );
319 assert!(Mailbox::try_from_fen_str(INVALID1).is_err());
320 assert!(Mailbox::try_from_fen_str(INVALID2).is_err());
321 assert!(Mailbox::try_from_fen_str(INVALID3).is_err());
322 assert!(Mailbox::try_from_fen_str(INVALID4).is_err());
323 assert!(Mailbox::try_from_fen_str(INVALID5).is_err());
324 assert!(Mailbox::try_from_fen_str(INVALID6).is_err());
325 }
326
327 #[test]
328 fn parse_castling_fen_substring() {
329 const VALID1: &str = "-";
330 const VALID2: &str = "Q";
331 const VALID3: &str = "K";
332 const VALID4: &str = "q";
333 const VALID5: &str = "k";
334 const VALID6: &str = "KQkq";
335
336 const INVALID1: &str = "";
337 const INVALID2: &str = "a";
338 const INVALID3: &str = " KQkq";
339
340 assert_eq!(
341 Castling::try_from_fen_str(VALID1).unwrap().to_fen_str(),
342 VALID1
343 );
344 assert_eq!(
345 Castling::try_from_fen_str(VALID2).unwrap().to_fen_str(),
346 VALID2
347 );
348 assert_eq!(
349 Castling::try_from_fen_str(VALID3).unwrap().to_fen_str(),
350 VALID3
351 );
352 assert_eq!(
353 Castling::try_from_fen_str(VALID4).unwrap().to_fen_str(),
354 VALID4
355 );
356 assert_eq!(
357 Castling::try_from_fen_str(VALID5).unwrap().to_fen_str(),
358 VALID5
359 );
360 assert_eq!(
361 Castling::try_from_fen_str(VALID6).unwrap().to_fen_str(),
362 VALID6
363 );
364 assert!(Castling::try_from_fen_str(INVALID1).is_err());
365 assert!(Castling::try_from_fen_str(INVALID2).is_err());
366 assert!(Castling::try_from_fen_str(INVALID3).is_err());
367 }
368}