1use std::str::FromStr;
49
50use crate::bitboard::Bitboard;
51use crate::board::Board;
52use crate::movegen::castling::CastlingRights;
53use crate::piece::Color;
54use crate::piece::Piece;
55use crate::piece::PieceType;
56use crate::square::Square;
57use anyhow::anyhow;
58use itertools::Itertools;
59
60impl Board {
61 pub fn to_fen(&self) -> String {
63 let ranks = self.piece_list.into_iter().chunks(8);
64 let ranks = ranks.into_iter().collect_vec();
65 let mut rank_strs: Vec<String> = Vec::new();
66
67 for rank in ranks.into_iter().rev() {
68 let mut elements: Vec<String> = Vec::new();
69 let piece_runs = rank.into_iter().group_by(|p| p.is_some());
70
71 for run in &piece_runs {
72 match run {
73 (true, pieces) => {
74 for piece in pieces {
75 elements.push(piece.unwrap().to_string())
76 }
77 }
78 (false, gaps) => elements.push(gaps.count().to_string()),
79 }
80 }
81
82 rank_strs.push(elements.join(""));
83 }
84
85 let pieces = rank_strs.into_iter().join("/");
86 let next_player = self.current.to_string();
87 let castling = self.castling_rights.to_string();
88 let en_passant = self
89 .en_passant
90 .map(|sq| sq.to_string())
91 .unwrap_or(String::from("-"));
92 let half_moves = self.half_moves;
93 let full_moves = self.full_moves;
94
95 format!("{pieces} {next_player} {castling} {en_passant} {half_moves} {full_moves}")
96 }
97
98 pub fn from_fen(fen: &str) -> anyhow::Result<Board> {
100 let mut parts = fen.split(' ');
101
102 let piece_string = parts.next().ok_or(anyhow!("Invalid FEN string"))?;
103
104 let mut piece_bbs = [Bitboard::EMPTY; PieceType::COUNT];
107 let mut occupied_squares = [Bitboard::EMPTY; Color::COUNT];
108 let mut piece_list = [None; Square::COUNT];
109 let mut square_idx: usize = 0;
110
111 for rank in piece_string.split('/').rev() {
114 for c in rank.chars() {
115 let c = c.to_string();
116
117 if let Ok(gap) = usize::from_str(&c) {
118 square_idx += gap;
119 } else if let Ok(piece) = Piece::from_str(&c) {
120 let square = Square::from(square_idx);
121 let bb = Bitboard::from(square);
122
123 piece_list[square_idx] = Some(piece);
124 piece_bbs[piece.piece_type()] |= bb;
125 occupied_squares[piece.color()] |= bb;
126
127 square_idx += 1;
128 }
129 }
130 }
131
132 let current: Color =
135 parts.next().ok_or(anyhow!("Invalid FEN string"))?.parse()?;
136
137 let castling_rights: CastlingRights =
138 parts.next().ok_or(anyhow!("Invalid FEN string"))?.parse()?;
139
140 let en_passant: Option<Square> = parts
141 .next()
142 .ok_or(anyhow!("Invalid FEN string"))?
143 .parse()
144 .ok();
145
146 let half_moves =
147 parts.next().ok_or(anyhow!("Invalid FEN string"))?.parse()?;
148
149 let full_moves =
150 parts.next().ok_or(anyhow!("Invalid FEN string"))?.parse()?;
151
152 let board = Board::new(
153 piece_list,
154 piece_bbs,
155 occupied_squares,
156 current,
157 castling_rights,
158 en_passant,
159 half_moves,
160 full_moves,
161 );
162
163 Ok(board)
164 }
165}
166
167#[test]
174fn test_to_fen() {
175 let initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
176 let board = Board::from_str(initial_fen).unwrap();
177 let fen = board.to_fen();
178 assert_eq!(initial_fen, fen);
179}