Skip to main content

chess/
game.rs

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/// A game of chess, including variations and annotations.
46#[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    /// State before any options are played.
53    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    /// The move played.
64    play: Move,
65    short: Short,
66    /// State after playing this move, before any options are played.
67    state: State,
68    /// Options to play after this position.
69    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    // Responsible for validating the move, calculating derived state, assigning
169    // a slot, and storing the play. It does not attach the play to the options of the node yet.
170    fn create_play(&mut self, node: Node, play: Move) -> Result<Slot, Error> {
171        // avoid move generation
172        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        // legality check (legal moves are already computed)
182        if !state.legal().contains(&play) {
183            return Err(Error::Illegal);
184        }
185
186        // apply
187        let position = state.position().apply_unchecked(play);
188
189        // compute legal moves for the new position
190        let legal = position.legal_moves();
191
192        // update derived state
193        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
497/// Read-only iterator over the options of a `Node`.
498pub struct OptionsRef<'g> {
499    game: &'g Game,
500    node: Node,
501    options: &'g [Slot],
502}
503
504// Here and elsewhere, a #[derive(Clone, Copy)] won't work due to
505// derive macro limitations - OptionsRef is in fact Copy
506impl Copy for OptionsRef<'_> {}
507
508impl Clone for OptionsRef<'_> {
509    fn clone(&self) -> Self {
510        *self
511    }
512}
513
514// The Options Traversal API
515impl<'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    /// Combines [`Self::first`] and [`Self::after_first`].
569    ///
570    /// When this contains all options after a move, the first option is the
571    /// mainline move, and the remaining options are variations from the same
572    /// position.
573    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
627// Forwarding to OptionsRef or "conversion of manipulation to traversal API"
628// - explicit for now
629// - can bring back helpful ones once the APIs settle
630impl OptionsMut<'_> {
631    pub fn as_ref(&self) -> OptionsRef<'_> {
632        self.game.options_ref(self.node)
633    }
634
635    // pub fn is_empty(&self) -> bool {
636    //     self.as_ref().is_empty()
637    // }
638
639    // pub fn len(&self) -> usize {
640    //     self.as_ref().len()
641    // }
642
643    // pub fn legal(&self) -> &[Move] {
644    //     self.as_ref().state().legal()
645    // }
646}
647
648// The Options Manipulation API
649impl<'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    // DANGEROUS, don't want such a thing.
782    // For reads, can do self.as_ref().iter() and friends
783    // pub fn for_each_mut(&mut self, mut f: impl FnMut(&mut Play)) {
784    //     let ids = self.ids().to_vec();
785
786    //     for slot in slots {
787    //         f(self.game.slots.get_mut(&slot).expect("option slot must reference an existing play"));
788    //     }
789    // }
790}