Skip to main content

chess/game/
cursor.rs

1use super::*;
2
3/// Cursor over the main line of a linear game.
4#[derive(Clone, PartialEq)]
5pub struct Cursor {
6    game: Game,
7    node: Node,
8}
9
10pub struct Mainline<'a> {
11    game: &'a Game,
12    node: Node,
13}
14
15#[derive(Debug, thiserror::Error)]
16pub enum Error {
17    #[error(transparent)]
18    Game(#[from] super::Error),
19    #[error("position already has main play {0:?}")]
20    Nonlinear(Node),
21}
22
23impl Cursor {
24    pub fn new(game: Game) -> Self {
25        Self { game, node: Node::Start }
26    }
27
28    pub fn into_inner(self) -> Game {
29        self.game
30    }
31
32    pub fn game(&self) -> &Game {
33        &self.game
34    }
35
36    pub fn into_game(self) -> Game {
37        self.game
38    }
39
40    pub fn node(&self) -> Node {
41        self.node
42    }
43
44    pub fn state(&self) -> &State {
45        self.game.state(self.node)
46    }
47
48    #[must_use]
49    pub fn set(&mut self, node: Node) -> bool {
50        if !self.game.contains(node) {
51            return false;
52        }
53
54        self.node = node;
55        true
56    }
57
58    pub fn play(&self) -> Option<PlayRef<'_>> {
59        match self.node {
60            Node::Start => None,
61            Node::Play(slot) => self.game.play(slot),
62        }
63    }
64
65    pub fn options(&self) -> OptionsRef<'_> {
66        self.game.options_ref(self.node)
67    }
68
69    pub fn previous(&self) -> Node {
70        match self.node {
71            Node::Start => Node::Start,
72            Node::Play(slot) => self
73                .game
74                .tree
75                .play(slot)
76                .expect("cursor slot must reference an existing play")
77                .previous(),
78        }
79    }
80
81    pub fn next(&self) -> Option<Node> {
82        self.game.tree.options(self.node).first().copied().map(Node::Play)
83    }
84
85    #[must_use]
86    pub fn back(&mut self) -> bool {
87        let previous = self.previous();
88        if self.node == previous {
89            false
90        } else {
91            self.node = previous;
92            true
93        }
94    }
95
96    #[must_use]
97    pub fn forward(&mut self) -> bool {
98        let Some(next) = self.next() else {
99            return false;
100        };
101        self.node = next;
102        true
103    }
104
105    pub fn start(&mut self) {
106        self.node = Node::Start;
107    }
108
109    pub fn end(&mut self) {
110        while self.forward() {}
111    }
112}
113
114impl Cursor {
115    pub fn position(&self) -> Position {
116        self.game.position(self.node)
117    }
118}
119
120impl Cursor {
121    #[must_use]
122    pub fn take_back(&mut self) -> bool {
123        let Node::Play(slot) = self.node else {
124            return false;
125        };
126        let previous = self.previous();
127        let play = self.game.play(slot).expect("cursor slot must exist").play();
128
129        let _ = self.game.options_mut(previous).expect("previous options must exist").remove(play);
130        self.node = previous;
131        true
132    }
133
134    pub fn push(&mut self, play: Move) -> Result<Slot, Error> {
135        if let Some(next) = self.next() {
136            return Err(Error::Nonlinear(next));
137        }
138
139        let slot =
140            self.game.options_mut(self.node).expect("cursor node must exist").push(play)?.slot();
141        self.node = Node::Play(slot);
142        Ok(slot)
143    }
144}
145
146impl<'a> Mainline<'a> {
147    pub fn new(game: &'a Game) -> Self {
148        Self { game, node: Node::Start }
149    }
150}
151
152impl<'a> Iterator for Mainline<'a> {
153    type Item = &'a Play;
154
155    fn next(&mut self) -> Option<Self::Item> {
156        let slot = self.game.tree.options(self.node).first().copied()?;
157        self.node = Node::Play(slot);
158        Some(self.game.tree.play(slot).expect("mainline slot must exist"))
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use crate::{Position, board::Role::*, square::Square::*};
165
166    use super::*;
167
168    #[test]
169    fn walks_main_line() {
170        let mut cursor = Game::chess(Position::start()).unwrap().cursor();
171        let play = cursor.position().legal_moves()[0];
172        let slot = cursor.push(play).unwrap();
173
174        assert_eq!(cursor.node(), Node::Play(slot));
175        assert!(!cursor.forward());
176        assert!(cursor.back());
177        assert_eq!(cursor.node(), Node::Start);
178        assert!(cursor.forward());
179        assert_eq!(cursor.node(), Node::Play(slot));
180
181        cursor.start();
182        assert!(matches!(
183            cursor.push(cursor.position().legal_moves()[0]),
184            Err(Error::Nonlinear(existing)) if existing == Node::Play(slot)
185        ));
186    }
187
188    #[test]
189    fn takes_back_current_play() {
190        let mut cursor = Game::chess(Position::start()).unwrap().cursor();
191        let e4 = cursor.push(crate::Move::normal(Pawn, E2, E4)).unwrap();
192        cursor.push(crate::Move::normal(Pawn, E7, E5)).unwrap();
193
194        assert!(cursor.take_back());
195        assert_eq!(cursor.node(), Node::Play(e4));
196        assert!(cursor.next().is_none());
197
198        assert!(cursor.take_back());
199        assert_eq!(cursor.node(), Node::Start);
200        assert!(cursor.next().is_none());
201        assert!(!cursor.take_back());
202    }
203}