ruci/gui/
position.rs

1extern crate alloc;
2
3use alloc::borrow::Cow;
4use alloc::vec::Vec;
5use core::fmt::{Display, Formatter};
6use alloc::string::{String};
7use shakmaty::fen::Fen;
8use shakmaty::uci::UciMove;
9use crate::{parsing, uci_moves, MessageParseErrorKind, OptionReplaceIf};
10use crate::dev_macros::{from_str_parts, impl_message, message_from_impl};
11use crate::errors::MessageParseError;
12use crate::gui::pointers::{PositionParameterPointer};
13use super::{pointers, traits};
14
15#[allow(clippy::module_name_repetitions)]
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18/// Changes the position to analyze.
19///
20/// Returns an error when parsing if neither `startpos` nor `fen` parameters are present.
21/// If both are present, the first one takes precedence (because Stockfish and Dragon do it like that).
22/// 
23/// <https://backscattering.de/chess/uci/#gui-position>
24pub enum Position<'a> {
25    StartPos {
26        moves: Cow<'a, [UciMove]>,
27    },
28    Fen {
29        fen: Cow<'a, Fen>,
30        moves: Cow<'a, [UciMove]>,
31    }
32}
33
34impl_message!(Position<'_>);
35message_from_impl!(gui Position<'a>);
36from_str_parts!(impl Position<'_> for parts -> Result {
37    let mut startpos = false;
38    let mut fen = None::<Fen>;
39    let mut moves = Vec::new();
40    let parameter_fn = |parameter, _, value: &str, parts| { match parameter {
41        PositionParameterPointer::Fen => if !startpos { fen.replace_if(value.parse().ok()); },
42        PositionParameterPointer::Moves => {
43            let parsed = uci_moves::from_str(value);
44
45            if !parsed.is_empty() {
46                moves = parsed;
47            }
48        },
49        PositionParameterPointer::StartPos => if fen.is_none() {
50            startpos = true;
51        },
52    } Some(parts) };
53
54    let mut value = String::with_capacity(200);
55    parsing::apply_parameters(parts, &mut value, parameter_fn);
56
57    if let Some(fen) = fen {
58        Ok(Self::Fen { fen: Cow::Owned(fen), moves: Cow::Owned(moves) })
59    } else if startpos {
60        Ok(Self::StartPos { moves: Cow::Owned(moves) })
61    } else {
62        Err(MessageParseError {
63            expected: "startpos",
64            kind: MessageParseErrorKind::MissingParameters
65        })
66    }
67});
68
69impl Display for Position<'_> {
70    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
71        f.write_str("position")?;
72
73        match self {
74            Self::StartPos { moves } => {
75                f.write_str(" startpos")?;
76
77                if !moves.is_empty() {
78                    f.write_str(" moves ")?;
79                    uci_moves::fmt(moves, f)?;
80                }
81            }
82            Self::Fen { fen, moves } => {
83                write!(f, " fen {fen}")?;
84
85                if !moves.is_empty() {
86                    f.write_str(" moves ")?;
87                    uci_moves::fmt(moves, f)?;
88                }
89            }
90        }
91
92        Ok(())
93    }
94}
95
96#[cfg(test)]
97#[allow(clippy::unwrap_used)]
98mod tests {
99    use alloc::borrow::Cow;
100    use shakmaty::uci::UciMove;
101    use alloc::string::ToString;
102    use crate::gui::Position;
103    use shakmaty::fen::Fen;
104    use crate::dev_macros::{assert_from_str_message, assert_message_to_from_str, assert_message_to_str};
105    use crate::errors::MessageParseError;
106    use crate::MessageParseErrorKind;
107
108    #[test]
109    fn to_from_str_start_pos() {
110        let moves = [UciMove::from_ascii(b"d2d4").unwrap(), UciMove::from_ascii(b"d7d5").unwrap()];
111        let repr = Position::StartPos {
112            moves: Cow::Borrowed(&moves),
113        };
114
115        assert_message_to_from_str!(
116            gui 
117            repr,
118            "position startpos moves d2d4 d7d5"
119        );
120
121        let moves = [UciMove::from_ascii(b"d2d4").unwrap(), UciMove::from_ascii(b"d7d5").unwrap()];
122        let m = Position::StartPos {
123            moves: Cow::Borrowed(&moves),
124        };
125
126        assert_from_str_message!(
127            gui
128            "position    startpos fen rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5 moves \t d2d4 d7d5",
129            Ok(m.clone())
130        );
131        assert_message_to_str!(
132            gui
133            m,
134            "position startpos moves d2d4 d7d5"
135        );
136    }
137
138    #[test]
139    fn to_from_str_fen() {
140        let fen = Fen::from_ascii(b"rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5").unwrap();
141        let moves = [UciMove::from_ascii(b"b1c3").unwrap()];
142        let m = Position::Fen {
143            fen: Cow::Borrowed(&fen),
144            moves: Cow::Borrowed(&moves),
145        };
146        
147        assert_message_to_from_str!(
148            gui 
149            m,
150            "position fen rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5 moves b1c3"
151        );
152
153        let fen = Fen::from_ascii(b"rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5").unwrap();
154        let moves = [UciMove::from_ascii(b"b1c3").unwrap()];
155        let m = Position::Fen {
156            fen: Cow::Borrowed(&fen),
157            moves: Cow::Borrowed(&moves),
158        };
159
160        assert_from_str_message!(
161            gui
162            "position fen   rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5 startpos \t moves b1c3",
163            Ok(m.clone())
164        );
165        assert_message_to_str!(
166            gui
167            m,
168            "position fen rnbqk2r/ppppp1bp/5np1/5p2/2PP4/6P1/PP2PPBP/RNBQK1NR w KQkq - 1 5 moves b1c3"
169        );
170    }
171    
172    #[test]
173    fn invalid_tail() {
174        let moves = [UciMove::from_ascii(b"d2d4").unwrap()];
175        
176        let m = Position::StartPos {
177            moves: Cow::Borrowed(&moves),
178        };
179
180        assert_from_str_message!(
181            gui
182            "position startpos moves d2d4 this ain't a move buddy pal",
183            Ok(m.clone())
184        );
185        assert_message_to_str!(
186            gui
187            m,
188            "position startpos moves d2d4"
189        );
190    }
191
192    #[test]
193    fn parse_error() {
194        assert_from_str_message!(
195            gui 
196            "position moves e2e4",
197            Err::<Position, MessageParseError>(MessageParseError {
198                expected: "startpos",
199                kind: MessageParseErrorKind::MissingParameters
200            })
201        );
202    }
203}