fast_tak/
tps.rs

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        // Figure out how many reserves each player has left.
42        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}