1use std::num::NonZeroUsize;
2
3use takparse::{Color, ExtendedSquare, Piece, Stack as TpsStack, Tps};
4
5use crate::{reserves::Reserves, Game};
6
7impl<const N: usize, const HALF_KOMI: i8> From<Game<N, HALF_KOMI>> for Tps {
8 fn from(game: Game<N, HALF_KOMI>) -> Self {
9 let mut board: Vec<_> = game
10 .board
11 .iter()
12 .map(|row| {
13 row.map(|stack| match stack.top() {
14 None => ExtendedSquare::EmptySquares(1),
15 Some((piece, _color)) => {
16 ExtendedSquare::Stack(TpsStack::new(piece, stack.colors()))
17 }
18 })
19 .collect()
20 })
21 .collect();
22 board.reverse();
23
24 unsafe {
25 Self::new_unchecked(
26 board,
27 game.to_move,
28 NonZeroUsize::new(1 + usize::from(game.ply) / 2).unwrap_unchecked(),
29 )
30 }
31 }
32}
33
34impl<const N: usize, const HALF_KOMI: i8> From<Tps> for Game<N, HALF_KOMI>
35where
36 Reserves<N>: Default,
37{
38 fn from(tps: Tps) -> Self {
39 let board = tps.board().collect();
40
41 let Reserves {
43 stones: mut white_stones,
44 caps: mut white_caps,
45 } = Reserves::<N>::default();
46 let Reserves {
47 stones: mut black_stones,
48 caps: mut black_caps,
49 } = Reserves::<N>::default();
50
51 for stack in tps.board().flatten() {
52 if stack.top() == Piece::Cap {
53 match stack.colors().last() {
54 Some(Color::White) => {
55 white_stones += 1;
56 white_caps -= 1;
57 }
58 Some(Color::Black) => {
59 black_stones += 1;
60 black_caps -= 1;
61 }
62 None => {}
63 }
64 }
65 for color in stack.colors() {
66 match color {
67 Color::White => white_stones -= 1,
68 Color::Black => black_stones -= 1,
69 }
70 }
71 }
72
73 Self {
74 board,
75 to_move: tps.color(),
76 ply: tps.ply().try_into().unwrap_or_default(),
77 white_reserves: Reserves {
78 stones: white_stones,
79 caps: white_caps,
80 },
81 black_reserves: Reserves {
82 stones: black_stones,
83 caps: black_caps,
84 },
85 ..Default::default()
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use takparse::Tps;
93
94 use crate::{reserves::Reserves, Game, GameResult, PlayError};
95
96 #[test]
97 fn complicated_board() {
98 let game = Game::<6, 0>::from_ptn_moves(&[
99 "e1", "f2", "Sb5", "Cd6", "d3", "d4", "Sc1", "c3", "Ca6", "f6", "b1", "Sb4", "b3",
100 "b2", "d5", "e1>", "d3>", "b2<", "Se2", "f4", "f2-", "c3-", "e4", "Sa5", "c3", "c5",
101 "b5>", "a2-", "Sb5", "e6", "2c5-11", "d6>", "d5<", "b2", "b3-", "b3", "e3+", "e6>",
102 "a4", "Sf5", "d6", "e6-", "f1+", "d4<", "d3", "d4", "b2>", "e3", "2e4+11", "a1>",
103 "2c3>11", "Sc6", "d3-", "e4", "d5", "a2", "d5-", "a2+", "2c2+11", "c2", "d1", "c3>",
104 "3c4-", "2d3-11", "Sa2", "c4", "2d2<11", "Sd2", "d3", "b3-", "f2+", "b3", "a1", "e4+",
105 "d5", "2e5<11", "2d4>", "2b2>", "d5-", "d2+", "e4+", "d2", "c3<", "c3<", "e2<", "c2+",
106 "c2<", "e2", "d5>", "c3<", "b2>", "d5", "d4>", "d5+", "c2<", "d5", "b2-", "d5>", "c2+",
107 "b3>", "2d2<", "d2", "3c2+21", "d4", "e4<", "d5", "c2",
108 ]);
109
110 let tps: Tps = game.into();
111 assert_eq!(
112 tps.to_string(),
113 "1C,x,2S,12,1,22C/2S,1S,12,2,2112,2S/1,2S,21S,21,2,2/2,212,21222,12S,21S,1/1S,2,1,2,2,\
114 x/1,121,1S,12,x,2 2 54"
115 );
116 }
117
118 fn tps_consistency<const N: usize>(seed: usize) -> Result<(), PlayError>
119 where
120 Reserves<N>: Default,
121 {
122 let mut game = Game::<N, 0>::default();
123 let mut moves = Vec::new();
124 while game.result() == GameResult::Ongoing {
125 moves.clear();
126 game.possible_moves(&mut moves);
127 let my_move = moves[seed % moves.len()];
128
129 println!("{my_move}");
130 game.play(my_move)?;
131
132 let tps: Tps = game.clone().into();
133 println!("{tps}");
134 let tps_game: Game<N, 0> = tps.into();
135
136 assert_eq!(game.board, tps_game.board, "board does not equal");
137 assert_eq!(game.to_move, tps_game.to_move, "to_move does not equal");
138 assert_eq!(game.ply, tps_game.ply, "ply does not equal");
139 assert_eq!(
140 game.white_reserves, tps_game.white_reserves,
141 "white reserves do not equal"
142 );
143 assert_eq!(
144 game.black_reserves, tps_game.black_reserves,
145 "black reserves do not equal"
146 );
147 }
148 Ok(())
149 }
150
151 macro_rules! tps_consistency_seeded {
152 [$($name:ident $seed:literal),*] => {
153 $(
154 #[test]
155 fn $name() {
156 tps_consistency::<3>($seed).unwrap();
157 tps_consistency::<4>($seed).unwrap();
158 tps_consistency::<5>($seed).unwrap();
159 tps_consistency::<6>($seed).unwrap();
160 tps_consistency::<7>($seed).unwrap();
161 tps_consistency::<8>($seed).unwrap();
162 }
163 )*
164 };
165 }
166
167 tps_consistency_seeded![
168 tps_consistency_5915587277 5_915_587_277,
169 tps_consistency_1500450271 1_500_450_271,
170 tps_consistency_3267000013 3_267_000_013,
171 tps_consistency_5754853343 5_754_853_343,
172 tps_consistency_4093082899 4_093_082_899,
173 tps_consistency_9576890767 9_576_890_767,
174 tps_consistency_3628273133 3_628_273_133,
175 tps_consistency_2860486313 2_860_486_313,
176 tps_consistency_5463458053 5_463_458_053,
177 tps_consistency_3367900313 3_367_900_313
178 ];
179}