1use super::*;
2
3#[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}