1use anyhow::Result;
2use std::fmt::{self, Display, Formatter};
3use std::str::FromStr;
4
5use super::constants::{BOARD_HEIGHT, BOARD_WIDTH};
6use super::game_state::GameState;
7use super::piece_board::PieceBoard;
8use super::{Piece, Square};
9
10impl Display for GameState {
11 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
12 let move_number = self.get_move_number();
13 let curr_player = if self.is_p1_turn_to_move() { "g" } else { "s" };
14 let piece_board = &self.get_piece_board();
15 writeln!(f, "{}{}", move_number, curr_player)?;
16
17 writeln!(f, " +-----------------+")?;
18
19 for row_idx in 0..BOARD_HEIGHT {
20 write!(f, "{}|", BOARD_HEIGHT - row_idx)?;
21 for col_idx in 0..BOARD_WIDTH {
22 let idx = (row_idx * BOARD_WIDTH + col_idx) as u8;
23 let square = Square::from_index(idx);
24 let letter = if let Some(piece) = piece_board.get_piece_type_at_square(&square) {
25 let is_p1_piece = is_p1_piece(square.as_bit_board(), piece_board);
26 convert_piece_to_letter(&piece, is_p1_piece)
27 } else if idx == 18 || idx == 21 || idx == 42 || idx == 45 {
28 "x".to_string()
29 } else {
30 " ".to_string()
31 };
32
33 write!(f, " {}", letter)?;
34 }
35 writeln!(f, " |")?;
36 }
37
38 writeln!(f, " +-----------------+")?;
39 writeln!(f, " a b c d e f g h")?;
40
41 Ok(())
42 }
43}
44
45impl FromStr for GameState {
46 type Err = anyhow::Error;
47
48 fn from_str(s: &str) -> Result<Self> {
49 let compact_regex = regex::Regex::new(r"^\s*(?:(\d+))?([gswb])\s*\[([^\]]+)\]").unwrap();
53 if let Some(caps) = compact_regex.captures(s) {
54 let move_number: usize = caps.get(1).map_or(1, |m| m.as_str().parse().unwrap_or(1));
55 let player = caps.get(2).unwrap().as_str();
56 let p1_turn_to_move = player != "s" && player != "b";
57 let pieces_str = caps.get(3).unwrap().as_str();
58
59 let mut p1_pieces = 0u64;
60 let mut elephants = 0u64;
61 let mut camels = 0u64;
62 let mut horses = 0u64;
63 let mut dogs = 0u64;
64 let mut cats = 0u64;
65 let mut rabbits = 0u64;
66
67 for (i, charr) in pieces_str.chars().enumerate().take(BOARD_WIDTH * BOARD_HEIGHT) {
68 if let Some((piece, is_p1)) = convert_char_to_piece(charr) {
69 let row_idx = i / BOARD_WIDTH;
70 let col_idx = i % BOARD_WIDTH;
71 let idx = (row_idx * BOARD_WIDTH + col_idx) as u8;
72 let square = Square::from_index(idx);
73 let square_bit = square.as_bit_board();
74
75 match piece {
76 Piece::Elephant => elephants |= square_bit,
77 Piece::Camel => camels |= square_bit,
78 Piece::Horse => horses |= square_bit,
79 Piece::Dog => dogs |= square_bit,
80 Piece::Cat => cats |= square_bit,
81 Piece::Rabbit => rabbits |= square_bit,
82 }
83
84 if is_p1 {
85 p1_pieces |= square_bit;
86 }
87 }
88 }
89
90 let piece_board = PieceBoard::new(p1_pieces, elephants, camels, horses, dogs, cats, rabbits);
91 return Ok(GameState::new(p1_turn_to_move, move_number, piece_board));
92 }
93
94 let lines: Vec<_> = s.split('|').enumerate().filter(|(i, _)| i % 2 == 1).map(|(_, s)| s).collect();
97 let regex = regex::Regex::new(r"^\s*(\d+)([gswb])").unwrap();
98
99 let (move_number, p1_turn_to_move) = regex.captures(s.split('|').next().unwrap()).map_or((2, true), |c| {
100 (c.get(1).unwrap().as_str().parse().unwrap(), c.get(2).unwrap().as_str() != "s" && c.get(2).unwrap().as_str() != "b")
101 });
102
103 let mut p1_pieces = 0;
104 let mut elephants = 0;
105 let mut camels = 0;
106 let mut horses = 0;
107 let mut dogs = 0;
108 let mut cats = 0;
109 let mut rabbits = 0;
110
111 for (row_idx, line) in lines.iter().enumerate() {
112 for (col_idx, charr) in line.chars().enumerate().filter(|(i, _)| i % 2 == 1).map(|(_, s)| s).enumerate() {
113 let idx = (row_idx * BOARD_WIDTH + col_idx) as u8;
114 let square = Square::from_index(idx);
115 if let Some((piece, is_p1)) = convert_char_to_piece(charr) {
116 let square_bit = square.as_bit_board();
117
118 match piece {
119 Piece::Elephant => elephants |= square_bit,
120 Piece::Camel => camels |= square_bit,
121 Piece::Horse => horses |= square_bit,
122 Piece::Dog => dogs |= square_bit,
123 Piece::Cat => cats |= square_bit,
124 Piece::Rabbit => rabbits |= square_bit,
125 }
126
127 if is_p1 {
128 p1_pieces |= square_bit;
129 }
130 }
131 }
132 }
133
134 let piece_board = PieceBoard::new(p1_pieces, elephants, camels, horses, dogs, cats, rabbits);
135
136 Ok(GameState::new(p1_turn_to_move, move_number, piece_board))
137 }
138}
139
140fn convert_char_to_piece(c: char) -> Option<(Piece, bool)> {
141 let is_p1 = c.is_uppercase();
142
143 let piece = match c {
144 'E' | 'e' => Some(Piece::Elephant),
145 'M' | 'm' => Some(Piece::Camel),
146 'H' | 'h' => Some(Piece::Horse),
147 'D' | 'd' => Some(Piece::Dog),
148 'C' | 'c' => Some(Piece::Cat),
149 'R' | 'r' => Some(Piece::Rabbit),
150 _ => None,
151 };
152
153 piece.map(|p| (p, is_p1))
154}
155
156fn is_p1_piece(square_bit: u64, piece_board: &PieceBoard) -> bool {
157 let p1_piece_mask = piece_board.get_player_piece_mask(true);
158 (square_bit & p1_piece_mask) != 0
159}
160
161pub fn convert_piece_to_letter(piece: &Piece, is_p1: bool) -> String {
162 let letter = match piece {
163 Piece::Elephant => "E",
164 Piece::Camel => "M",
165 Piece::Horse => "H",
166 Piece::Dog => "D",
167 Piece::Cat => "C",
168 Piece::Rabbit => "R",
169 };
170
171 if is_p1 { letter.to_string() } else { letter.to_lowercase() }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::super::{Piece, take_actions};
177 use super::*;
178
179 #[must_use]
180 fn place_gold_pieces(game_state: GameState) -> GameState {
181 take_actions!(game_state => e1, d1, a1, h1, c1, f1, b1, g1)
182 }
183
184 #[must_use]
185 fn place_silver_pieces(game_state: GameState) -> GameState {
186 take_actions!(game_state => e8, d8, a8, h8, c8, f8, b8, g8)
187 }
188
189 fn initial_play_state() -> GameState {
190 let game_state = GameState::initial();
191 let game_state = place_gold_pieces(game_state);
192 place_silver_pieces(game_state)
193 }
194
195 #[test]
196 fn test_gamestate_fromstr() {
197 let game_state: GameState = "
198 +-----------------+
199 8| r r r r |
200 7| m h e c |
201 6| r x r r x r |
202 5| h d c d |
203 4| E H M |
204 3| R x R R H R |
205 2| D C C D |
206 1| R R R R |
207 +-----------------+
208 a b c d e f g h"
209 .parse()
210 .unwrap();
211
212 let piece_board = game_state.get_piece_board();
213 assert_eq!(
214 piece_board.get_bits_for_piece(Piece::Rabbit, true),
215 0b__01011010__00000000__01011010__00000000__00000000__00000000__00000000__00000000
216 );
217 assert_eq!(
218 piece_board.get_bits_for_piece(Piece::Cat, true),
219 0b__00000000__00100100__00000000__00000000__00000000__00000000__00000000__00000000
220 );
221 assert_eq!(
222 piece_board.get_bits_for_piece(Piece::Dog, true),
223 0b__00000000__10000001__00000000__00000000__00000000__00000000__00000000__00000000
224 );
225 assert_eq!(
226 piece_board.get_bits_for_piece(Piece::Horse, true),
227 0b__00000000__00000000__00100000__00000100__00000000__00000000__00000000__00000000
228 );
229 assert_eq!(
230 piece_board.get_bits_for_piece(Piece::Camel, true),
231 0b__00000000__00000000__00000000__10000000__00000000__00000000__00000000__00000000
232 );
233 assert_eq!(
234 piece_board.get_bits_for_piece(Piece::Elephant, true),
235 0b__00000000__00000000__00000000__00000001__00000000__00000000__00000000__00000000
236 );
237
238 assert_eq!(
239 piece_board.get_bits_for_piece(Piece::Rabbit, false),
240 0b__00000000__00000000__00000000__00000000__00000000__01011010__00000000__01011010
241 );
242 assert_eq!(
243 piece_board.get_bits_for_piece(Piece::Cat, false),
244 0b__00000000__00000000__00000000__00000000__00100000__00000000__10000000__00000000
245 );
246 assert_eq!(
247 piece_board.get_bits_for_piece(Piece::Dog, false),
248 0b__00000000__00000000__00000000__00000000__10000100__00000000__00000000__00000000
249 );
250 assert_eq!(
251 piece_board.get_bits_for_piece(Piece::Horse, false),
252 0b__00000000__00000000__00000000__00000000__00000001__00000000__00000100__00000000
253 );
254 assert_eq!(
255 piece_board.get_bits_for_piece(Piece::Camel, false),
256 0b__00000000__00000000__00000000__00000000__00000000__00000000__00000001__00000000
257 );
258 assert_eq!(
259 piece_board.get_bits_for_piece(Piece::Elephant, false),
260 0b__00000000__00000000__00000000__00000000__00000000__00000000__00100000__00000000
261 );
262 }
263
264 #[test]
265 fn test_gamestate_fromstr_move_number_default() {
266 let game_state: GameState = "
267 +-----------------+
268 8| r r r r |
269 7| m h e c |
270 6| r x r r x r |
271 5| h d c d |
272 4| E H M |
273 3| R x R R H R |
274 2| D C C D |
275 1| R R R R |
276 +-----------------+
277 a b c d e f g h"
278 .parse()
279 .unwrap();
280
281 assert_eq!(game_state.get_move_number(), 2);
282 assert!(game_state.is_p1_turn_to_move());
283 }
284
285 #[test]
286 fn test_gamestate_fromstr_move_number() {
287 let game_state: GameState = "
288 5s
289 +-----------------+
290 8| r r r r |
291 7| m h e c |
292 6| r x r r x r |
293 5| h d c d |
294 4| E H M |
295 3| R x R R H R |
296 2| D C C D |
297 1| R R R R |
298 +-----------------+
299 a b c d e f g h"
300 .parse()
301 .unwrap();
302
303 assert_eq!(game_state.get_move_number(), 5);
304 assert!(!game_state.is_p1_turn_to_move());
305 }
306
307 #[test]
308 fn test_gamestate_fromstr_player() {
309 let game_state: GameState = "
310 176b
311 +-----------------+
312 8| r r r r |
313 7| m h e c |
314 6| r x r r x r |
315 5| h d c d |
316 4| E H M |
317 3| R x R R H R |
318 2| D C C D |
319 1| R R R R |
320 +-----------------+
321 a b c d e f g h"
322 .parse()
323 .unwrap();
324
325 assert_eq!(game_state.get_move_number(), 176);
326 assert!(!game_state.is_p1_turn_to_move());
327 }
328
329 #[test]
330 fn test_gamestate_fromstr_player_w() {
331 let game_state: GameState = "
332 13w
333 +-----------------+
334 8| r r r r |
335 7| m h e c |
336 6| r x r r x r |
337 5| h d c d |
338 4| E H M |
339 3| R x R R H R |
340 2| D C C D |
341 1| R R R R |
342 +-----------------+
343 a b c d e f g h"
344 .parse()
345 .unwrap();
346
347 assert_eq!(game_state.get_move_number(), 13);
348 assert!(game_state.is_p1_turn_to_move());
349 }
350
351 #[test]
352 fn test_gamestate_to_str() {
353 let expected_output = "2g
354 +-----------------+
3558| h c d m e d c h |
3567| r r r r r r r r |
3576| x x |
3585| |
3594| |
3603| x x |
3612| R R R R R R R R |
3621| H C D M E D C H |
363 +-----------------+
364 a b c d e f g h
365";
366
367 assert_eq!(format!("{}", initial_play_state()), expected_output);
368 }
369
370 #[test]
371 fn test_gamestate_from_str_and_to_str() {
372 let orig_str = "14g
373 +-----------------+
3748| r r r r |
3757| m h e c |
3766| r x r r x r |
3775| h d c d |
3784| E H M |
3793| R x R R x R |
3802| D C C D |
3811| R R R R |
382 +-----------------+
383 a b c d e f g h
384";
385
386 let game_state: GameState = orig_str.parse().unwrap();
387 let new_str = format!("{}", game_state);
388
389 assert_eq!(new_str, orig_str);
390 }
391
392 #[test]
393 fn test_gamestate_fromstr_compact_initial() {
394 let expected_output = "2g
397 +-----------------+
3988| h c d m e d c h |
3997| r r r r r r r r |
4006| x x |
4015| |
4024| |
4033| x x |
4042| R R R R R R R R |
4051| H C D M E D C H |
406 +-----------------+
407 a b c d e f g h
408";
409
410 let game_state: GameState = "2g [hcdmedchrrrrrrrr RRRRRRRRHCDMEDCH]"
411 .parse()
412 .unwrap();
413 assert_eq!(format!("{}", game_state), expected_output);
414 }
415
416 #[test]
417 fn test_gamestate_fromstr_compact_no_move_number() {
418 let game_state: GameState = "g [rredchrrrhrdcmrr MHHEDDCRRRRRRRRC]"
420 .parse()
421 .unwrap();
422 assert_eq!(game_state.get_move_number(), 1);
423 assert!(game_state.is_p1_turn_to_move());
424 let piece_board = game_state.get_piece_board();
426 assert_ne!(piece_board.get_bits_for_piece(Piece::Elephant, true), 0);
428 assert_ne!(piece_board.get_bits_for_piece(Piece::Elephant, false), 0);
430 }
431
432 #[test]
433 fn test_gamestate_fromstr_compact_silver_to_move() {
434 let game_state: GameState = "5s [hcdmedchrrrrrrrr RRRRRRRRHCDMEDCH]"
435 .parse()
436 .unwrap();
437 assert_eq!(game_state.get_move_number(), 5);
438 assert!(!game_state.is_p1_turn_to_move());
439 }
440}