1use std::fmt::{Debug, Display, Formatter, Write};
2
3use itertools::Itertools;
4
5use crate::board::Player;
6use crate::games::ataxx::{AtaxxBoard, Move};
7use crate::util::bitboard::BitBoard8;
8use crate::util::coord::Coord8;
9
10pub fn coord_to_uai(coord: Coord8) -> String {
11 let x = coord.x();
12 let y = coord.y();
13 format!("{}{}", (b'a' + x) as char, y + 1)
14}
15
16pub fn coord_from_uai(s: &str) -> Coord8 {
17 assert_eq!(s.len(), 2);
18 let x = s.as_bytes()[0] - b'a';
19 let y = (s.as_bytes()[1] - b'0') - 1;
20 Coord8::from_xy(x, y)
21}
22
23impl Debug for Move {
24 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{}", self.to_uai())
26 }
27}
28
29impl Display for Move {
30 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
31 write!(f, "{}", self.to_uai())
32 }
33}
34
35#[derive(Debug, Eq, PartialEq)]
36pub struct InvalidUaiMove(String);
37
38impl Move {
39 pub fn to_uai(self) -> String {
40 match self {
41 Move::Pass => "0000".to_string(),
42 Move::Copy { to } => coord_to_uai(to),
43 Move::Jump { from, to } => format!("{}{}", coord_to_uai(from), coord_to_uai(to)),
44 }
45 }
46
47 pub fn from_uai(s: &str) -> Result<Move, InvalidUaiMove> {
48 match s {
49 "0000" => Ok(Move::Pass),
50 _ if s.len() == 2 => Ok(Move::Copy { to: coord_from_uai(s) }),
51 _ if s.len() == 4 => Ok(Move::Jump {
52 from: coord_from_uai(&s[..2]),
53 to: coord_from_uai(&s[2..]),
54 }),
55 _ => Err(InvalidUaiMove(s.to_owned())),
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
61pub struct InvalidAtaxxFen {
62 pub fen: String,
63 pub reason: &'static str,
64}
65
66impl AtaxxBoard {
67 pub fn from_fen(fen: &str) -> Result<AtaxxBoard, InvalidAtaxxFen> {
68 let err = |reason| InvalidAtaxxFen {
69 fen: fen.into(),
70 reason,
71 };
72
73 let blocks = fen.split(' ').collect_vec();
74 let [board_str, next_str, half_str, full_str] = match &*blocks {
75 &[a, b, c, d] => [a, b, c, d],
76 _ => return Err(err("Not all 4 components present")),
77 };
78
79 let mut board = if board_str == "/" {
81 AtaxxBoard::empty(0)
82 } else {
83 let rows = board_str.split('/').collect_vec();
84
85 let size = rows.len();
86 if size > AtaxxBoard::MAX_SIZE as usize {
87 return Err(err("More rows than maximum board size"));
88 }
89
90 let mut board = AtaxxBoard::empty(size as u8);
91 for (y, &line) in rows.iter().rev().enumerate().rev() {
92 let mut x = 0;
93
94 for c in line.chars() {
95 if x >= size {
96 return Err(err("Too many columns for size"));
97 }
98
99 let tile = BitBoard8::coord(Coord8::from_xy(x as u8, y as u8));
100
101 match c {
102 'x' => board.tiles_a |= tile,
103 'o' => board.tiles_b |= tile,
104 '-' => board.gaps |= tile,
105 d if d.is_ascii_digit() => {
106 x += d.to_digit(10).unwrap() as usize;
107 continue;
108 }
109 _ => return Err(err("Invalid character in board")),
110 }
111
112 x += 1;
113 }
114 }
115
116 board
117 };
118
119 board.next_player = match next_str {
121 "x" => Player::A,
122 "o" => Player::B,
123 _ => return Err(err("Invalid next player")),
124 };
125
126 board.moves_since_last_copy = half_str.parse::<u8>().map_err(|_| err("Invalid half counter"))?;
127 let _ = full_str.parse::<u32>().map_err(|_| err("Invalid full counter"))?;
128
129 board.update_outcome();
130 board.assert_valid();
131
132 Ok(board)
133 }
134
135 pub fn to_fen(&self) -> String {
136 let mut s = String::new();
137
138 for y in (0..self.size).rev() {
139 if y != self.size - 1 {
140 write!(&mut s, "/").unwrap();
141 }
142
143 let mut empty_count = 0;
144
145 for x in 0..self.size {
146 let coord = Coord8::from_xy(x, y);
147
148 if self.free_tiles().has(coord) {
149 empty_count += 1;
150 } else {
151 if empty_count != 0 {
152 write!(&mut s, "{}", empty_count).unwrap();
153 empty_count = 0;
154 }
155
156 match self.tile(coord) {
157 Some(player) => {
158 write!(&mut s, "{}", player_symbol(player)).unwrap();
159 }
160 None => {
161 assert!(self.gaps.has(coord));
162 write!(&mut s, "-").unwrap();
163 }
164 }
165 }
166 }
167
168 if empty_count != 0 {
169 write!(&mut s, "{}", empty_count).unwrap();
170 }
171 }
172
173 write!(
174 &mut s,
175 " {} {} 1",
176 player_symbol(self.next_player),
177 self.moves_since_last_copy
178 )
179 .unwrap();
180
181 s
182 }
183}
184
185impl Debug for AtaxxBoard {
186 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
187 write!(f, "AtaxxBoard(\"{}\")", self.to_fen())
188 }
189}
190
191impl Display for AtaxxBoard {
192 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
193 writeln!(f, "FEN: {}", self.to_fen())?;
194
195 for y in (0..self.size).rev() {
196 write!(f, "{} ", y + 1)?;
197
198 for x in 0..self.size {
199 let coord = Coord8::from_xy(x, y);
200 let tuple = (self.gaps.has(coord), self.tile(coord));
201 let c = match tuple {
202 (true, None) => '-',
203 (false, None) => '.',
204 (false, Some(player)) => player_symbol(player),
205 (true, Some(_)) => unreachable!("Tile with block cannot have player"),
206 };
207
208 write!(f, "{}", c)?;
209 }
210
211 if y == 3 {
212 write!(
213 f,
214 " {} {}",
215 player_symbol(self.next_player),
216 self.moves_since_last_copy
217 )?;
218 }
219 writeln!(f)?;
220 }
221 write!(f, " ")?;
222 for x in 0..self.size {
223 write!(f, "{}", (b'a' + x) as char)?;
224 }
225 writeln!(f)?;
226
227 Ok(())
228 }
229}
230
231fn player_symbol(player: Player) -> char {
232 match player {
233 Player::A => 'x',
234 Player::B => 'o',
235 }
236}