Skip to main content

chess/game/
options.rs

1use super::{self as game, traversal::Choose, tree::Node, *};
2
3/// An error from mutating the options in a game tree.
4#[derive(Debug, thiserror::Error)]
5pub enum Error {
6    #[error(transparent)]
7    Insert(#[from] Insert),
8    #[error(transparent)]
9    Remove(#[from] Remove),
10}
11
12/// An error from inserting an option.
13#[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/// An error from removing an option.
24#[derive(Debug, thiserror::Error)]
25pub enum Remove {
26    #[error("position slot {0} may not be removed")]
27    Unremovable(PositionSlot),
28}
29
30/// A result of mutating the options in a game tree.
31pub type Result<T, E = Error> = core::result::Result<T, E>;
32
33/// Options Mutation API
34impl<'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    // the natural same implementation as in into_option_or_push does
46    // not work here due to borrow checker limitations - even though
47    // it is "obviously" correct.
48    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
330/// Options Mutation API.
331impl 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}