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))]
18pub 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}