1use super::{self as game, traversal::Choose, tree::Node, *};
2
3#[derive(Debug, thiserror::Error)]
5pub enum Error {
6 #[error(transparent)]
7 Insert(#[from] Insert),
8 #[error(transparent)]
9 Remove(#[from] Remove),
10}
11
12#[derive(Debug, thiserror::Error)]
14pub enum Insert {
15 #[error("move already exists at index {0}")]
16 Duplicate(usize),
17 #[error("illegal move")]
18 Illegal,
19 #[error("move slot space exhausted")]
20 Exhausted,
21}
22
23#[derive(Debug, thiserror::Error)]
25pub enum Remove {
26 #[error("position slot {0} may not be removed")]
27 Unremovable(PositionSlot),
28}
29
30pub type Result<T, E = Error> = core::result::Result<T, E>;
32
33impl<'g> PositionMut<'g> {
35 pub fn push(&mut self, play: chess::Move) -> Result<MoveMut<'_>> {
36 let slot = self.insert_at(self.options().len(), play)?;
37 Ok(MoveMut { game: self.game, slot, protected: self.protected.clone() })
38 }
39
40 pub fn into_push(mut self, play: chess::Move) -> Result<MoveMut<'g>> {
41 let slot = self.insert_at(self.options().len(), play)?;
42 Ok(MoveMut { game: self.game, slot, protected: self.protected })
43 }
44
45 pub fn option_mut_or_push(&mut self, play: chess::Move) -> Result<MoveMut<'_>> {
49 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
50 return Ok(MoveMut { game: self.game, slot, protected: self.protected.clone() });
51 }
52 self.push(play)
53 }
54
55 pub fn into_option_or_push(self, play: chess::Move) -> Result<MoveMut<'g>> {
56 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
57 return Ok(MoveMut { game: self.game, slot, protected: self.protected });
58 }
59 self.into_push(play)
60 }
61
62 pub fn insert_before(
63 &mut self,
64 option: impl Choose,
65 play: chess::Move,
66 ) -> game::Result<MoveMut<'_>> {
67 let index = self.as_ref().index(option)?;
68 let slot = self.insert_at(index, play)?;
69 Ok(MoveMut { game: self.game, slot, protected: self.protected.clone() })
70 }
71
72 pub fn into_insert_before(
73 mut self,
74 option: impl Choose,
75 play: chess::Move,
76 ) -> game::Result<MoveMut<'g>> {
77 let index = self.as_ref().index(option)?;
78 let slot = self.insert_at(index, play)?;
79 Ok(MoveMut { game: self.game, slot, protected: self.protected })
80 }
81
82 pub fn option_mut_or_insert_before(
83 &mut self,
84 option: impl Choose,
85 play: chess::Move,
86 ) -> game::Result<MoveMut<'_>> {
87 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
88 return Ok(MoveMut { game: self.game, slot, protected: self.protected.clone() });
89 }
90 self.insert_before(option, play)
91 }
92
93 pub fn into_option_or_insert_before(
94 self,
95 option: impl Choose,
96 play: chess::Move,
97 ) -> game::Result<MoveMut<'g>> {
98 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
99 return Ok(MoveMut { game: self.game, slot, protected: self.protected });
100 }
101 self.into_insert_before(option, play)
102 }
103
104 pub fn insert_after(
105 &mut self,
106 option: impl Choose,
107 play: chess::Move,
108 ) -> game::Result<MoveMut<'_>> {
109 let index = self.as_ref().index(option)? + 1;
110 if index == self.options().len() {
111 Ok(self.push(play)?)
112 } else {
113 self.insert_before(index, play)
114 }
115 }
116
117 pub fn into_insert_after(
118 self,
119 option: impl Choose,
120 play: chess::Move,
121 ) -> game::Result<MoveMut<'g>> {
122 let index = self.as_ref().index(option)? + 1;
123 if index == self.options().len() {
124 Ok(self.into_push(play)?)
125 } else {
126 self.into_insert_before(index, play)
127 }
128 }
129
130 pub fn option_mut_or_insert_after(
131 &mut self,
132 option: impl Choose,
133 play: chess::Move,
134 ) -> game::Result<MoveMut<'_>> {
135 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
136 return Ok(MoveMut { game: self.game, slot, protected: self.protected.clone() });
137 }
138 self.insert_after(option, play)
139 }
140
141 pub fn into_option_or_insert_after(
142 self,
143 option: impl Choose,
144 play: chess::Move,
145 ) -> game::Result<MoveMut<'g>> {
146 if let Ok(slot) = self.as_ref().option(play).map(|option| option.slot()) {
147 return Ok(MoveMut { game: self.game, slot, protected: self.protected });
148 }
149 self.into_insert_after(option, play)
150 }
151
152 pub fn pop(&mut self) -> game::Result<()> {
153 self.remove(self.options().len().saturating_sub(1))
154 }
155
156 pub fn remove(&mut self, option: impl Choose) -> game::Result<()> {
157 let index = self.as_ref().index(option)?;
158 let slot = self.position().options[index];
159 if let Some(slot) = self
160 .protected
161 .unremovable
162 .iter()
163 .copied()
164 .find(|at| self.game.order(*at) >= slot.into())
165 {
166 return Err(Error::from(Remove::Unremovable(slot)).into());
167 }
168
169 let branch = self.game.tracks_changes().then(|| self.game.tree.branch(slot));
170 self.remove_untracked(index)?;
171 if let Some(branch) = branch {
172 self.game.track(change::Patch::Insert { position: self.slot, index, branch });
173 }
174 Ok(())
175 }
176
177 pub fn into_remove_play(self) -> game::Result<PositionMut<'g>> {
178 let play = self.as_ref().play()?;
179 let slot = play.slot();
180 let previous = play.previous().slot();
181 let mut position =
182 PositionMut { game: self.game, slot: previous, protected: self.protected };
183 position.remove(slot)?;
184 Ok(position)
185 }
186
187 pub fn swap(&mut self, a: impl Choose, b: impl Choose) -> traversal::Result<bool> {
188 let position = self.as_ref();
189 let a = position.index(a)?;
190 let b = position.index(b)?;
191 let changed = self.swap_untracked(a, b)?;
192 if self.game.tracks_changes() && changed {
193 self.game.track(change::Patch::Swap { position: self.slot, a, b });
194 }
195 Ok(changed)
196 }
197
198 pub fn raise(&mut self, option: impl Choose) -> traversal::Result<bool> {
199 let index = self.as_ref().index(option)?;
200 if index == 0 { Ok(false) } else { self.drag(index, index - 1) }
201 }
202
203 pub fn lower(&mut self, option: impl Choose) -> traversal::Result<bool> {
204 let index = self.as_ref().index(option)?;
205 if index + 1 == self.options().len() { Ok(false) } else { self.drag(index, index + 1) }
206 }
207
208 pub fn promote(&mut self, option: impl Choose) -> traversal::Result<bool> {
209 let index = self.as_ref().index(option)?;
210 if index == 0 {
211 return Ok(false);
212 }
213
214 self.drag(index, 0)
215 }
216
217 pub fn demote(&mut self, option: impl Choose) -> traversal::Result<bool> {
218 let index = self.as_ref().index(option)?;
219 if index + 1 == self.options().len() {
220 return Ok(false);
221 }
222
223 let last = self.options().len() - 1;
224 self.drag(index, last)
225 }
226
227 fn insert_at(&mut self, index: usize, play: chess::Move) -> Result<MoveSlot> {
228 let slot = self.insert_at_untracked(None, index, play)?;
229 if self.game.tracks_changes() {
230 self.game.track(change::Patch::Remove(slot));
231 }
232 Ok(slot)
233 }
234
235 pub(in crate::game) fn insert_at_untracked(
236 &mut self,
237 slot: Option<MoveSlot>,
238 index: usize,
239 play: chess::Move,
240 ) -> Result<MoveSlot> {
241 let node = {
242 let position = self.as_ref();
243 if let Ok(index) = position.index(play) {
244 return Err(Insert::Duplicate(index).into());
245 }
246 if !position.legal().contains(&play) {
247 return Err(Insert::Illegal.into());
248 }
249
250 let play = Move {
251 previous: self.slot,
252 play,
253 short: Short::new(position.legal(), play),
254 public: MovePublic::default(),
255 };
256 let position = position.position().apply_unchecked(*play);
257 Node { position: Position::new(position), play: Some(play) }
258 };
259
260 let slot = match slot {
261 Some(slot) => {
262 if slot.next().is_none() {
263 return Err(Insert::Exhausted.into());
264 }
265 let inserted = self.game.tree.insert_at(slot, node);
266 assert!(inserted, "move slot unavailable");
267 slot
268 }
269 None => self.game.tree.insert(node).ok_or(Insert::Exhausted)?,
270 };
271 self.position_mut().options.insert(index, slot);
272 Ok(slot)
273 }
274
275 pub(in crate::game) fn remove_untracked(
276 &mut self,
277 option: impl Choose,
278 ) -> traversal::Result<()> {
279 let index = self.as_ref().index(option)?;
280 let slot = self.position().options[index];
281 self.position_mut().options.remove(index);
282 let removed = self.game.tree.remove(slot);
283 debug_assert!(removed);
284 Ok(())
285 }
286
287 pub(in crate::game) fn swap_untracked(
288 &mut self,
289 a: impl Choose,
290 b: impl Choose,
291 ) -> traversal::Result<bool> {
292 let a = self.as_ref().index(a)?;
293 let b = self.as_ref().index(b)?;
294 if a == b {
295 Ok(false)
296 } else {
297 self.position_mut().options.swap(a, b);
298 Ok(true)
299 }
300 }
301
302 pub(in crate::game) fn drag_untracked(
303 &mut self,
304 from: impl Choose,
305 to: impl Choose,
306 ) -> traversal::Result<bool> {
307 let from = self.as_ref().index(from)?;
308 let to = self.as_ref().index(to)?;
309 if from == to {
310 return Ok(false);
311 }
312
313 let play = self.position_mut().options.remove(from);
314 self.position_mut().options.insert(to, play);
315 Ok(true)
316 }
317
318 pub fn drag(&mut self, from: impl Choose, to: impl Choose) -> traversal::Result<bool> {
319 let position = self.as_ref();
320 let from = position.index(from)?;
321 let to = position.index(to)?;
322 let changed = self.drag_untracked(from, to)?;
323 if self.game.tracks_changes() && changed {
324 self.game.track(change::Patch::Drag { position: self.slot, from: to, to: from });
325 }
326 Ok(changed)
327 }
328}
329
330impl MoveMut<'_> {
332 pub fn raise(&mut self) -> bool {
333 let slot = self.slot;
334 self.previous_mut().raise(slot).expect("move exists in previous position")
335 }
336
337 pub fn lower(&mut self) -> bool {
338 let slot = self.slot;
339 self.previous_mut().lower(slot).expect("move exists in previous position")
340 }
341
342 pub fn promote(&mut self) -> bool {
343 let slot = self.slot;
344 self.previous_mut().promote(slot).expect("move exists in previous position")
345 }
346
347 pub fn demote(&mut self) -> bool {
348 let slot = self.slot;
349 self.previous_mut().demote(slot).expect("move exists in previous position")
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use crate::{Role::Pawn, Square::*};
356
357 use super::*;
358
359 #[test]
360 fn option_or_insert() {
361 let mut game = Game::new(chess::Position::start());
362 let e4 = chess::Move::normal(Pawn, E2, E4);
363 let d4 = chess::Move::normal(Pawn, D2, D4);
364 let c4 = chess::Move::normal(Pawn, C2, C4);
365 let f4 = chess::Move::normal(Pawn, F2, F4);
366
367 let e4_slot = game.start_mut().push(e4).unwrap().slot();
368 let d4_slot = game.start_mut().option_mut_or_push(d4).unwrap().slot();
369 let existing = game.start_mut().option_mut_or_insert_after(d4_slot, e4).unwrap().slot();
370 assert_eq!(existing, e4_slot);
371 assert_eq!(
372 game.start().iter().map(|play| play.slot()).collect::<Vec<_>>(),
373 [e4_slot, d4_slot]
374 );
375
376 let c4_slot = game.start_mut().option_mut_or_insert_before(d4_slot, c4).unwrap().slot();
377 let f4_slot = game.start_mut().into_option_or_insert_after(c4_slot, f4).unwrap().slot();
378 assert_eq!(
379 game.start().iter().map(|play| play.slot()).collect::<Vec<_>>(),
380 [e4_slot, c4_slot, f4_slot, d4_slot]
381 );
382
383 let existing = game.start_mut().into_option_or_push(e4).unwrap().slot();
384 assert_eq!(existing, e4_slot);
385 }
386
387 #[test]
388 fn remove_play() {
389 let mut game = Game::new(chess::Position::start());
390 let e4 = game.start_mut().push(chess::Move::normal(Pawn, E2, E4)).unwrap().slot();
391 let e5 = game
392 .play_mut(e4)
393 .unwrap()
394 .into_position()
395 .into_push(chess::Move::normal(Pawn, E7, E5))
396 .unwrap()
397 .slot();
398
399 let previous = game.position_mut(e5.into()).unwrap().into_remove_play().unwrap();
400 assert_eq!(previous.slot(), e4.into());
401 assert!(!previous.as_ref().has_options());
402 assert!(previous.game().play(e5).is_err());
403 }
404}