1use core::{
2 fmt,
3 ops::{Deref, DerefMut},
4};
5
6use crate::{
7 Move, Position,
8 formats::san::Check,
9 square::{File, Rank},
10};
11
12pub mod cursor;
13pub use cursor::Cursor;
14#[cfg(feature = "serde")]
15pub mod storage;
16pub mod tree;
17pub use tree::{Node, Slot, Tree};
18
19pub type Duplicate = usize;
20pub type Result<T, E = Error> = core::result::Result<T, E>;
21
22#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
23pub enum Mode {
24 #[default]
25 Chess,
26 Freestyle,
27}
28
29impl Mode {
30 pub const fn is_freestyle(self) -> bool {
31 matches!(self, Self::Freestyle)
32 }
33}
34
35#[derive(Debug, thiserror::Error)]
36pub enum Error {
37 #[error("move already exists at index {0}")]
38 Duplicate(Duplicate),
39 #[error("illegal move")]
40 Illegal,
41 #[error("index {index} out of bounds for length {len}")]
42 OutOfBounds { index: usize, len: usize },
43}
44
45#[derive(Clone, PartialEq)]
47pub struct Game {
48 pub roster: Roster,
49 pub tags: Vec<Tag>,
50 pub intro: Option<Text>,
51 pub outcome: Outcome,
52 start: State,
54 tree: Tree,
55 mode: Mode,
56}
57
58#[derive(Clone, PartialEq)]
59pub struct Play {
60 slot: Slot,
61 previous: Node,
62 pub meta: Meta,
63 play: Move,
65 short: Short,
66 state: State,
68 options: Vec<Slot>,
70}
71
72#[derive(Clone, PartialEq)]
73pub struct State {
74 position: Position,
75 legal: Vec<Move>,
76 check: Option<Check>,
77}
78
79impl Game {
80 pub fn start_options(&self) -> OptionsRef<'_> {
81 self.options_ref(Node::Start)
82 }
83
84 pub fn play(&self, slot: Slot) -> Option<PlayRef<'_>> {
85 self.tree.contains(slot).then_some(PlayRef { game: self, slot })
86 }
87
88 pub fn play_mut(&mut self, slot: Slot) -> Option<PlayMut<'_>> {
89 self.tree.contains(slot).then_some(PlayMut { game: self, slot })
90 }
91
92 fn contains(&self, node: Node) -> bool {
93 match node {
94 Node::Start => true,
95 Node::Play(slot) => self.tree.contains(slot),
96 }
97 }
98
99 fn options_ref(&self, node: Node) -> OptionsRef<'_> {
100 let options = self.tree.options(node);
101 OptionsRef { game: self, node, options }
102 }
103
104 fn state(&self, node: Node) -> &State {
105 match node {
106 Node::Start => &self.start,
107 Node::Play(slot) => &self.tree.play(slot).expect("slot exists").state,
108 }
109 }
110
111 fn delete_slot(&mut self, slot: Slot) {
112 self.tree.remove(slot);
113 }
114}
115
116impl Game {
117 pub fn start(&self) -> Position {
118 self.start.position()
119 }
120
121 pub const fn mode(&self) -> Mode {
122 self.mode
123 }
124
125 fn position(&self, node: Node) -> Position {
126 self.state(node).position()
127 }
128}
129
130impl Game {
131 pub fn new(position: Position, mode: Mode) -> Self {
132 let legal = position.legal_moves();
133 Self {
134 roster: Default::default(),
135 tags: Default::default(),
136 intro: None,
137 outcome: Default::default(),
138 start: State { position, legal, check: None },
139 tree: Tree::new(),
140 mode,
141 }
142 }
143
144 pub fn chess(position: Position) -> Option<Self> {
145 position.castles().chess_compatible().then(|| Self::new(position, Mode::Chess))
146 }
147
148 pub fn freestyle(position: Position) -> Self {
149 Self::new(position, Mode::Freestyle)
150 }
151
152 pub fn start_options_mut(&mut self) -> OptionsMut<'_> {
153 OptionsMut { game: self, node: Node::Start }
154 }
155
156 pub fn cursor(self) -> Cursor {
157 Cursor::new(self)
158 }
159
160 pub fn options(&self, node: Node) -> Option<OptionsRef<'_>> {
161 if self.contains(node) { Some(self.options_ref(node)) } else { None }
162 }
163
164 pub fn options_mut(&mut self, node: Node) -> Option<OptionsMut<'_>> {
165 if self.contains(node) { Some(OptionsMut { game: self, node }) } else { None }
166 }
167
168 fn create_play(&mut self, node: Node, play: Move) -> Result<Slot, Error> {
171 if let Some(index) = self.options_ref(node).index(play) {
173 return Err(Error::Duplicate(index));
174 }
175
176 let state = self.state(node);
177 if let Some(Check::Checkmate) = state.check() {
178 return Err(Error::Illegal);
179 }
180
181 if !state.legal().contains(&play) {
183 return Err(Error::Illegal);
184 }
185
186 let position = state.position().apply_unchecked(play);
188
189 let legal = position.legal_moves();
191
192 let check = if position.is_check() {
194 Some(if legal.is_empty() { Check::Checkmate } else { Check::Check })
195 } else {
196 None
197 };
198 let short = Short::new(state.legal(), play);
199
200 let slot = self.tree.insert(|slot| Play {
201 slot,
202 previous: node,
203 meta: Default::default(),
204 state: State { position, legal, check },
205 play,
206 short,
207 options: Default::default(),
208 });
209
210 Ok(slot)
211 }
212}
213
214impl From<Position> for Game {
215 fn from(position: Position) -> Self {
216 let mode =
217 if position.castles().chess_compatible() { Mode::Chess } else { Mode::Freestyle };
218 Self::new(position, mode)
219 }
220}
221
222impl Play {
223 pub fn slot(&self) -> Slot {
224 self.slot
225 }
226
227 pub fn previous(&self) -> Node {
228 self.previous
229 }
230
231 pub fn play(&self) -> Move {
232 self.play
233 }
234
235 pub fn short(&self) -> Short {
236 self.short
237 }
238
239 pub fn check(&self) -> Option<Check> {
240 self.state.check()
241 }
242
243 pub fn legal(&self) -> &[Move] {
244 self.state.legal()
245 }
246}
247
248impl Play {
249 pub fn position(&self) -> Position {
250 self.state.position()
251 }
252}
253
254impl State {
255 #[inline]
256 pub fn legal(&self) -> &[Move] {
257 &self.legal
258 }
259
260 #[inline]
261 pub fn check(&self) -> Option<Check> {
262 self.check
263 }
264}
265
266impl State {
267 #[inline]
268 pub fn position(&self) -> Position {
269 self.position
270 }
271}
272
273#[derive(Clone, Debug, Default, PartialEq)]
274#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
275pub struct Meta {
276 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
277 pub intro: Option<Text>,
278 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
279 pub comment: Option<Text>,
280 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
281 pub outro: Option<Text>,
282 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
283 pub nags: Vec<Nag>,
284 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
285 pub commands: Vec<Command>,
286}
287
288impl Meta {
289 pub fn is_empty(&self) -> bool {
290 self.intro.is_none()
291 && self.comment.is_none()
292 && self.outro.is_none()
293 && self.nags.is_empty()
294 && self.commands.is_empty()
295 }
296}
297
298#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
299pub struct Short {
300 pub file: Option<File>,
301 pub rank: Option<Rank>,
302}
303
304impl Short {
305 pub fn new(legal: &[Move], play: Move) -> Self {
306 let mut short = Short::default();
307
308 if play.role == crate::Role::Pawn || play.is_castle() {
309 return short;
310 }
311
312 let different_move = |other: &Move| *other != play;
313 let same_role_and_to = |other: &Move| (other.role, other.to) == (play.role, play.to);
314 let ambiguity = legal
315 .iter()
316 .copied()
317 .filter(different_move)
318 .filter(same_role_and_to)
319 .fold(Ambiguity::default(), |ambiguity, other| ambiguity.consider(play, other));
320
321 if ambiguity.file_resolves() {
322 short.file = Some(play.from.file());
323 } else if ambiguity.rank_resolves() {
324 short.rank = Some(play.from.rank());
325 } else if ambiguity.exists {
326 short.file = Some(play.from.file());
327 short.rank = Some(play.from.rank());
328 }
329
330 short
331 }
332}
333
334#[derive(Default)]
335struct Ambiguity {
336 exists: bool,
337 file: bool,
338 rank: bool,
339}
340
341impl Ambiguity {
342 fn consider(mut self, play: Move, other: Move) -> Self {
343 self.exists = true;
344 self.file |= other.from.file() == play.from.file();
345 self.rank |= other.from.rank() == play.from.rank();
346 self
347 }
348
349 fn file_resolves(&self) -> bool {
350 self.exists && !self.file
351 }
352
353 fn rank_resolves(&self) -> bool {
354 self.exists && !self.rank
355 }
356}
357
358#[derive(Clone, Debug, Default, PartialEq, Eq)]
359#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
360pub struct Roster {
361 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
362 pub event: Option<Text>,
363 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
364 pub site: Option<Text>,
365 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
366 pub date: Option<Text>,
367 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
368 pub round: Option<Text>,
369 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
370 pub white: Option<Text>,
371 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
372 pub black: Option<Text>,
373}
374
375#[derive(Clone, Debug, PartialEq, Eq)]
376#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
377pub struct Tag {
378 pub key: Text,
379 pub value: String,
380}
381
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
383#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
384pub enum Outcome {
385 White,
386 Black,
387 Draw,
388 #[default]
389 Unknown,
390}
391
392#[derive(Clone, Debug, PartialEq, Eq)]
393#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
394#[cfg_attr(feature = "serde", serde(transparent))]
395pub struct Text(String);
396
397impl Text {
398 pub fn new(text: impl AsRef<str>) -> Option<Self> {
399 let text = text.as_ref().trim();
400 (!text.is_empty()).then(|| Self(text.to_string()))
401 }
402
403 pub fn merge(&mut self, text: &Text) {
404 self.0.push_str("\n\n");
405 self.0.push_str(&text.0);
406 }
407}
408
409impl AsRef<str> for Text {
410 fn as_ref(&self) -> &str {
411 &self.0
412 }
413}
414
415impl fmt::Display for Text {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 self.0.fmt(f)
418 }
419}
420
421impl Deref for Text {
422 type Target = str;
423
424 fn deref(&self) -> &Self::Target {
425 &self.0
426 }
427}
428
429#[derive(Clone, Debug, PartialEq, Eq)]
430#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
431pub struct Command {
432 pub command: Text,
433 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Vec::is_empty"))]
434 pub parameters: Vec<String>,
435}
436
437#[derive(Clone, Debug, PartialEq, Eq)]
438#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
439#[cfg_attr(feature = "serde", serde(untagged))]
440pub enum Nag {
441 Numeric(u32),
442 Symbol(String),
443}
444
445pub struct PlayRef<'g> {
446 game: &'g Game,
447 slot: Slot,
448}
449
450impl Deref for PlayRef<'_> {
451 type Target = Play;
452
453 fn deref(&self) -> &Play {
454 self.game.tree.play(self.slot).expect("valid play")
455 }
456}
457
458impl<'g> PlayRef<'g> {
459 pub fn options(&self) -> OptionsRef<'g> {
460 self.game.options_ref(Node::Play(self.slot))
461 }
462}
463
464pub struct PlayMut<'g> {
465 game: &'g mut Game,
466 slot: Slot,
467}
468
469impl Deref for PlayMut<'_> {
470 type Target = Play;
471
472 fn deref(&self) -> &Play {
473 self.game.tree.play(self.slot).expect("valid play")
474 }
475}
476
477impl DerefMut for PlayMut<'_> {
478 fn deref_mut(&mut self) -> &mut Play {
479 self.game.tree.play_mut(self.slot).expect("valid play")
480 }
481}
482
483impl<'g> PlayMut<'g> {
484 pub fn options(&self) -> OptionsRef<'_> {
485 self.game.options_ref(Node::Play(self.slot))
486 }
487
488 pub fn options_mut(&mut self) -> OptionsMut<'_> {
489 OptionsMut { game: self.game, node: Node::Play(self.slot) }
490 }
491
492 pub fn into_options_mut(self) -> OptionsMut<'g> {
493 OptionsMut { game: self.game, node: Node::Play(self.slot) }
494 }
495}
496
497pub struct OptionsRef<'g> {
499 game: &'g Game,
500 node: Node,
501 options: &'g [Slot],
502}
503
504impl Copy for OptionsRef<'_> {}
507
508impl Clone for OptionsRef<'_> {
509 fn clone(&self) -> Self {
510 *self
511 }
512}
513
514impl<'g> OptionsRef<'g> {
516 fn index(&self, play: Move) -> Option<usize> {
517 self.iter().position(|option| option.play == play)
518 }
519
520 pub fn get(&self, play: Move) -> Option<PlayRef<'g>> {
521 self.get_index(self.index(play)?)
522 }
523
524 pub fn contains(&self, play: Move) -> bool {
525 self.index(play).is_some()
526 }
527
528 pub fn position(&self) -> Position {
529 self.game.position(self.node)
530 }
531 pub fn get_index(&self, index: usize) -> Option<PlayRef<'g>> {
532 let slot = self.options.get(index)?;
533 Some(PlayRef { game: self.game, slot: *slot })
534 }
535
536 pub fn is_empty(&self) -> bool {
537 self.options.is_empty()
538 }
539
540 pub fn len(&self) -> usize {
541 self.options.len()
542 }
543
544 pub const fn mode(&self) -> Mode {
545 self.game.mode()
546 }
547
548 pub fn state(&self) -> &State {
549 self.game.state(self.node)
550 }
551
552 pub fn legal(&self) -> &[Move] {
553 self.state().legal()
554 }
555
556 pub fn first(&self) -> Option<PlayRef<'g>> {
557 self.get_index(0)
558 }
559
560 pub fn after_first(&self) -> OptionsRef<'g> {
561 OptionsRef {
562 game: self.game,
563 node: self.node,
564 options: self.options.get(1..).unwrap_or_default(),
565 }
566 }
567
568 pub fn split_first(&self) -> Option<(PlayRef<'g>, OptionsRef<'g>)> {
574 let (slot, rest) = self.options.split_first()?;
575 Some((
576 PlayRef { game: self.game, slot: *slot },
577 OptionsRef { game: self.game, node: self.node, options: rest },
578 ))
579 }
580
581 pub fn iter(self) -> OptionsIter<'g> {
582 self.into_iter()
583 }
584}
585
586impl<'g> IntoIterator for OptionsRef<'g> {
587 type Item = PlayRef<'g>;
588 type IntoIter = OptionsIter<'g>;
589
590 fn into_iter(self) -> Self::IntoIter {
591 OptionsIter { game: self.game, options: self.options, index: 0 }
592 }
593}
594
595pub struct OptionsIter<'g> {
596 game: &'g Game,
597 options: &'g [Slot],
598 index: usize,
599}
600
601impl<'g> Iterator for OptionsIter<'g> {
602 type Item = PlayRef<'g>;
603
604 fn next(&mut self) -> Option<Self::Item> {
605 let slot = self.options.get(self.index)?;
606 self.index += 1;
607 Some(PlayRef { game: self.game, slot: *slot })
608 }
609
610 fn size_hint(&self) -> (usize, Option<usize>) {
611 let len = self.len();
612 (len, Some(len))
613 }
614}
615
616impl ExactSizeIterator for OptionsIter<'_> {
617 fn len(&self) -> usize {
618 self.options.len() - self.index
619 }
620}
621
622pub struct OptionsMut<'g> {
623 game: &'g mut Game,
624 node: Node,
625}
626
627impl OptionsMut<'_> {
631 pub fn as_ref(&self) -> OptionsRef<'_> {
632 self.game.options_ref(self.node)
633 }
634
635 }
647
648impl<'g> OptionsMut<'g> {
650 pub fn push(&mut self, play: Move) -> Result<PlayMut<'_>, Error> {
651 let slot = self.game.create_play(self.node, play)?;
652 self.game.tree.options_mut(self.node).push(slot);
653 Ok(PlayMut { game: self.game, slot })
654 }
655
656 pub fn into_push(self, play: Move) -> Result<PlayMut<'g>, Error> {
657 let slot = self.game.create_play(self.node, play)?;
658 self.game.tree.options_mut(self.node).push(slot);
659 Ok(PlayMut { game: self.game, slot })
660 }
661
662 pub fn insert(&mut self, index: usize, play: Move) -> Result<PlayMut<'_>, Error> {
663 let len = self.as_ref().len();
664 if index > len {
665 return Err(Error::OutOfBounds { index, len });
666 }
667
668 let slot = self.game.create_play(self.node, play)?;
669 self.game.tree.options_mut(self.node).insert(index, slot);
670 Ok(PlayMut { game: self.game, slot })
671 }
672
673 pub fn into_insert(self, index: usize, play: Move) -> Result<PlayMut<'g>, Error> {
674 let len = self.as_ref().len();
675 if index > len {
676 return Err(Error::OutOfBounds { index, len });
677 }
678
679 let slot = self.game.create_play(self.node, play)?;
680 self.game.tree.options_mut(self.node).insert(index, slot);
681 Ok(PlayMut { game: self.game, slot })
682 }
683
684 pub fn into_get(self, play: Move) -> Option<PlayMut<'g>> {
685 let index = self.as_ref().index(play)?;
686 self.into_get_index(index)
687 }
688
689 pub fn into_get_index(self, index: usize) -> Option<PlayMut<'g>> {
690 let slot = self.game.tree.options(self.node).get(index).copied()?;
691 Some(PlayMut { game: self.game, slot })
692 }
693
694 #[must_use]
695 pub fn remove(&mut self, play: Move) -> bool {
696 let Some(index) = self.as_ref().index(play) else {
697 return false;
698 };
699 self.remove_index(index)
700 }
701
702 fn remove_index(&mut self, index: usize) -> bool {
703 let Some(slot) = self.game.tree.options_mut(self.node).get(index).copied() else {
704 return false;
705 };
706 self.game.tree.options_mut(self.node).remove(index);
707 self.game.delete_slot(slot);
708 true
709 }
710
711 #[must_use]
712 pub fn swap(&mut self, a: Move, b: Move) -> bool {
713 let Some(a) = self.as_ref().index(a) else {
714 return false;
715 };
716 let Some(b) = self.as_ref().index(b) else {
717 return false;
718 };
719 self.swap_index(a, b)
720 }
721
722 fn swap_index(&mut self, a: usize, b: usize) -> bool {
723 let len = self.as_ref().len();
724 if a >= len || b >= len {
725 false
726 } else {
727 self.game.tree.options_mut(self.node).swap(a, b);
728 true
729 }
730 }
731
732 #[must_use]
733 pub fn raise(&mut self, play: Move) -> bool {
734 let Some(index) = self.as_ref().index(play) else {
735 return false;
736 };
737 self.raise_index(index)
738 }
739
740 fn raise_index(&mut self, index: usize) -> bool {
741 if index >= self.as_ref().len() {
742 false
743 } else {
744 index == 0 || self.swap_index(index - 1, index)
745 }
746 }
747
748 #[must_use]
749 pub fn lower(&mut self, play: Move) -> bool {
750 let Some(index) = self.as_ref().index(play) else {
751 return false;
752 };
753 self.lower_index(index)
754 }
755
756 fn lower_index(&mut self, index: usize) -> bool {
757 let len = self.as_ref().len();
758 if index >= len { false } else { index + 1 == len || self.swap_index(index, index + 1) }
759 }
760
761 #[must_use]
762 pub fn promote(&mut self, play: Move) -> bool {
763 let Some(index) = self.as_ref().index(play) else {
764 return false;
765 };
766 self.promote_index(index)
767 }
768
769 fn promote_index(&mut self, index: usize) -> bool {
770 let len = self.as_ref().len();
771 if index >= len {
772 return false;
773 }
774
775 let options = self.game.tree.options_mut(self.node);
776 let option = options.remove(index);
777 options.insert(0, option);
778 true
779 }
780
781 }