Skip to main content

chess/game/
cursor.rs

1use core::{
2    borrow::{Borrow, BorrowMut},
3    ops::Deref,
4};
5
6use super::{options, traversal::Choose, *};
7
8// Note: We have three types of cursor, owning Game, &mut Game, &Game,
9// named Cursor, CursorMut, CursorRef.
10//
11// Implementations on G: Borrow<Game> apply to Cursor and CursorRef
12// Implementations on G: BorrowMut<Game> apply to Cursor and CursorMut
13
14/// Access API.
15impl Cursor<Game> {
16    pub const fn new(game: Game) -> Self {
17        Self { game, slot: START, preserve: Set::new() }
18    }
19
20    pub fn into_game(self) -> Game {
21        self.game
22    }
23}
24
25/// Access API.
26impl<G> Cursor<G> {
27    pub const fn slot(&self) -> PositionSlot {
28        self.slot
29    }
30}
31
32/// Access API.
33impl<G: Borrow<Game>> Cursor<G> {
34    pub fn game(&self) -> &Game {
35        self.game.borrow()
36    }
37
38    pub fn position(&self) -> PositionRef<'_> {
39        PositionRef { game: self.game(), slot: self.slot }
40    }
41
42    pub fn play(&self) -> start::Result<MoveRef<'_>> {
43        self.position().play()
44    }
45
46    pub fn at(&self, slot: PositionSlot) -> traversal::Result<CursorRef<'_>> {
47        let game = self.game();
48        game.position(slot)?;
49        Ok(Cursor { game, slot, preserve: self.preserve.clone() })
50    }
51}
52
53impl<G: Borrow<Game>> Deref for Cursor<G> {
54    type Target = Game;
55
56    fn deref(&self) -> &Game {
57        self.game()
58    }
59}
60
61/// Access API.
62impl<G: BorrowMut<Game>> Cursor<G> {
63    pub fn game_mut(&mut self) -> &mut Game {
64        self.game.borrow_mut()
65    }
66
67    pub fn set_orientation(&mut self, orientation: Player) {
68        self.game.borrow_mut().set_orientation(orientation);
69    }
70
71    pub fn set_outcome(&mut self, outcome: Outcome) {
72        self.game.borrow_mut().set_outcome(outcome);
73    }
74
75    pub fn update_roster(&mut self, update: impl FnOnce(&mut Roster)) {
76        self.game.borrow_mut().update_roster(update);
77    }
78
79    pub fn update_tags(&mut self, update: impl FnOnce(&mut Vec<Tag>)) {
80        self.game.borrow_mut().update_tags(update);
81    }
82
83    // "reborrowing" clone
84    pub fn cursor_mut(&mut self) -> CursorMut<'_> {
85        let slot = self.slot;
86        let preserve = self.protected().unremovable;
87        Cursor { game: self.game.borrow_mut(), slot, preserve }
88    }
89
90    pub fn position_mut(&mut self) -> PositionMut<'_> {
91        let slot = self.slot;
92        let protected = self.protected();
93        PositionMut { game: self.game.borrow_mut(), slot, protected }
94    }
95
96    pub fn play_mut(&mut self) -> start::Result<MoveMut<'_>> {
97        self.position_mut().into_play()
98    }
99
100    pub fn at_mut(&mut self, slot: PositionSlot) -> traversal::Result<CursorMut<'_>> {
101        if !self.game.borrow().tree.contains(slot) {
102            return Err(traversal::Error::Position(slot));
103        }
104
105        let preserve = self.protected().unremovable;
106        Ok(Cursor { game: self.game.borrow_mut(), slot, preserve })
107    }
108
109    fn protected(&self) -> Protected {
110        let mut unremovable = self.preserve.clone();
111        unremovable.insert(self.slot);
112        Protected { unremovable }
113    }
114}
115
116/// Access API.
117impl<'g> CursorRef<'g> {
118    pub fn into_game(self) -> &'g Game {
119        self.game
120    }
121
122    pub fn into_position(self) -> PositionRef<'g> {
123        PositionRef { game: self.game, slot: self.slot }
124    }
125
126    pub fn into_play(self) -> start::Result<MoveRef<'g>> {
127        self.into_position().play()
128    }
129}
130
131/// Access API.
132impl<'g> CursorMut<'g> {
133    pub fn into_position_mut(self) -> PositionMut<'g> {
134        let slot = self.slot;
135        let mut unremovable = self.preserve;
136        unremovable.insert(slot);
137        PositionMut { game: self.game, slot, protected: Protected { unremovable } }
138    }
139
140    pub fn into_play_mut(self) -> start::Result<MoveMut<'g>> {
141        self.into_position_mut().into_play()
142    }
143}
144
145/// Traversal API.
146impl<G: Borrow<Game>> Cursor<G> {
147    pub fn set(&mut self, slot: PositionSlot) -> traversal::Result<bool> {
148        if !self.game.borrow().tree.contains(slot) {
149            return Err(traversal::Error::Position(slot));
150        }
151
152        let changed = self.slot != slot;
153        self.slot = slot;
154        Ok(changed)
155    }
156
157    pub fn prev(&mut self) -> start::Result<PositionSlot> {
158        let slot = self.position().play()?.previous().slot();
159        self.slot = slot;
160        Ok(slot)
161    }
162
163    pub fn mainline_end(&mut self) -> PositionSlot {
164        while self.main().is_ok() {}
165        self.slot
166    }
167
168    pub fn main(&mut self) -> traversal::Result<PositionSlot> {
169        self.option(0)
170    }
171
172    pub fn option(&mut self, option: impl Choose) -> traversal::Result<PositionSlot> {
173        let slot = self.position().option(option)?.position().slot();
174        self.slot = slot;
175        Ok(slot)
176    }
177}
178
179/// Options Mutation API
180impl<G: BorrowMut<Game>> Cursor<G> {
181    pub fn option_mut_or_push(&mut self, play: chess::Move) -> options::Result<PositionSlot> {
182        let slot = self.position_mut().option_mut_or_push(play)?.slot().into();
183        self.slot = slot;
184        Ok(slot)
185    }
186
187    pub fn option_mut_or_insert_before(
188        &mut self,
189        option: impl Choose,
190        play: chess::Move,
191    ) -> Result<PositionSlot> {
192        let slot = self.position_mut().option_mut_or_insert_before(option, play)?.slot().into();
193        self.slot = slot;
194        Ok(slot)
195    }
196
197    pub fn option_mut_or_insert_after(
198        &mut self,
199        option: impl Choose,
200        play: chess::Move,
201    ) -> Result<PositionSlot> {
202        let slot = self.position_mut().option_mut_or_insert_after(option, play)?.slot().into();
203        self.slot = slot;
204        Ok(slot)
205    }
206
207    /// Unlike `self.position_mut().into_remove_play()`, this first retreats the cursor so its
208    /// former position is no longer protected by the cursor itself.
209    pub fn remove_play(&mut self) -> Result<()> {
210        let play = self.play()?;
211        let removed = self.slot;
212        let slot = play.slot();
213        self.slot = play.previous().slot();
214
215        if let Err(error) = self.position_mut().remove(slot) {
216            self.slot = removed;
217            return Err(error);
218        }
219
220        Ok(())
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use crate::{Role::Pawn, Square::*};
227
228    use super::*;
229
230    #[test]
231    fn cursor_traversal() {
232        let mut cursor = Cursor::new(Game::new(chess::Position::start()));
233        let e4 = chess::Move::normal(Pawn, E2, E4);
234        let e5 = chess::Move::normal(Pawn, E7, E5);
235
236        assert_eq!(cursor.slot(), START);
237        assert!(cursor.prev().is_err());
238        assert!(cursor.main().is_err());
239
240        cursor.option_mut_or_push(e4).unwrap();
241        let after_e4 = cursor.slot();
242        cursor.option_mut_or_push(e5).unwrap();
243        let after_e5 = cursor.slot();
244
245        assert_eq!(cursor.prev().unwrap(), after_e4);
246        assert_eq!(cursor.main().unwrap(), after_e5);
247        assert!(!cursor.set(after_e5).unwrap());
248        assert_eq!(**cursor.play().unwrap(), e5);
249    }
250
251    #[test]
252    fn cursor_removal_protection() {
253        let mut game = Game::new(chess::Position::start());
254        let e4 = game.start_mut().into_push(chess::Move::normal(Pawn, E2, E4)).unwrap().slot();
255        let e5 = game
256            .play_mut(e4)
257            .unwrap()
258            .into_position()
259            .into_push(chess::Move::normal(Pawn, E7, E5))
260            .unwrap()
261            .slot();
262        let d4 = game.start_mut().into_push(chess::Move::normal(Pawn, D2, D4)).unwrap().slot();
263
264        let mut cursor = Cursor::new(game);
265        assert!(cursor.set(e5.into()).unwrap());
266
267        let mut root = cursor.at_mut(START).unwrap();
268        let mut start = root.position_mut();
269        assert!(matches!(
270            start.remove(e4),
271            Err(Error::Options(options::Error::Remove(options::Remove::Unremovable(slot))))
272                if slot == e5.into()
273        ));
274        assert!(start.remove(d4).is_ok());
275
276        assert_eq!(cursor.slot(), e5.into());
277        assert_eq!(cursor.position().slot(), e5.into());
278        assert!(cursor.game().play(e4).is_ok());
279        assert!(cursor.game().play(e5).is_ok());
280        assert!(cursor.game().play(d4).is_err());
281    }
282
283    #[test]
284    fn cursor_remove_play() {
285        let mut cursor = Cursor::new(Game::new(chess::Position::start()));
286        let e4 = cursor.option_mut_or_push(chess::Move::normal(Pawn, E2, E4)).unwrap();
287        let e5 = cursor.option_mut_or_push(chess::Move::normal(Pawn, E7, E5)).unwrap();
288
289        assert!(cursor.position_mut().into_remove_play().is_err());
290        assert_eq!(cursor.slot(), e5);
291        assert!(cursor.remove_play().is_ok());
292        assert_eq!(cursor.slot(), e4);
293        assert!(cursor.game().position(e5).is_err());
294        assert!(cursor.remove_play().is_ok());
295        assert_eq!(cursor.slot(), START);
296        assert!(matches!(cursor.remove_play(), Err(Error::Start(start::Error))));
297    }
298}