1use crate::bitboard::Bitboard;
2use crate::constants::SHAPE_LETTERS;
3
4pub fn bb_to_qfen(bb: &Bitboard) -> String {
9 let mut ranks = [String::new(), String::new(), String::new(), String::new()];
10
11 for (r, rank) in ranks.iter_mut().enumerate() {
12 for c in 0..4 {
13 let pos = r * 4 + c;
14 let mut ch = '.';
15 for color in 0..2u8 {
16 for s in 0..4u8 {
17 if (bb.planes[Bitboard::plane_index(color, s)] >> pos) & 1 == 1 {
18 let letter = SHAPE_LETTERS[s as usize];
19 ch = if color == 0 {
20 letter
21 } else {
22 letter.to_ascii_lowercase()
23 };
24 }
25 }
26 }
27 rank.push(ch);
28 }
29 }
30 ranks.join("/")
31}
32
33pub fn bb_from_qfen(qfen: &str) -> Result<Bitboard, String> {
35 let parts: Vec<&str> = qfen.split('/').collect();
36 if parts.len() != 4 {
37 return Err(format!("QFEN must have 4 ranks, got {}", parts.len()));
38 }
39 let mut planes = [0u16; 8];
40
41 for (r, rank) in parts.iter().enumerate() {
42 let chars: Vec<char> = rank.chars().collect();
43 if chars.len() != 4 {
44 return Err(format!("Rank {} must have 4 chars, got {}", r, chars.len()));
45 }
46 for (c, &ch) in chars.iter().enumerate() {
47 if ch == '.' {
48 continue;
49 }
50 let upper = ch.to_ascii_uppercase();
51 let shape = match upper {
52 'A' => 0u8,
53 'B' => 1,
54 'C' => 2,
55 'D' => 3,
56 _ => return Err(format!("Invalid char '{}' in QFEN", ch)),
57 };
58 let color: u8 = if ch.is_uppercase() { 0 } else { 1 };
59 let pos = r * 4 + c;
60 planes[Bitboard::plane_index(color, shape)] |= 1u16 << pos;
61 }
62 }
63 Ok(Bitboard::new(planes))
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn empty_board_qfen() {
72 let bb = Bitboard::EMPTY;
73 assert_eq!(bb_to_qfen(&bb), "..../..../..../....");
74 }
75
76 #[test]
77 fn roundtrip_qfen() {
78 let qfen = "AbCd/..../..../....";
79 let bb = bb_from_qfen(qfen).unwrap();
80 assert_eq!(bb_to_qfen(&bb), qfen);
81 }
82
83 #[test]
84 fn mixed_position() {
85 let qfen = "A.bC/..../d..B/...a";
86 let bb = bb_from_qfen(qfen).unwrap();
87 assert_eq!(bb_to_qfen(&bb), qfen);
88 }
89
90 #[test]
91 fn invalid_qfen_rank_count() {
92 assert!(bb_from_qfen("..../....").is_err());
93 }
94
95 #[test]
96 fn invalid_qfen_char() {
97 assert!(bb_from_qfen("X.../..../..../....").is_err());
98 }
99}