fast_tak/
stack.rs

1use takparse::{Color, Piece};
2
3use crate::{
4    colors::Colors,
5    error::{StackError, TakeError},
6};
7
8#[derive(Clone, Copy, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
9pub struct Stack {
10    piece: Piece,
11    colors: Colors,
12}
13
14impl Stack {
15    /// Create a new stack with a single piece.
16    #[must_use]
17    pub const fn new(piece: Piece, color: Color) -> Self {
18        Self {
19            piece,
20            colors: Colors::of_one(color),
21        }
22    }
23
24    /// Create a new stack with the given colors.
25    ///
26    /// # Panics
27    ///
28    /// Panics if the colors are empty.
29    #[must_use]
30    pub fn exact(piece: Piece, colors: Colors) -> Self {
31        assert!(!colors.is_empty());
32        Self { piece, colors }
33    }
34
35    /// Check if anything is in this stack. If false, it means the square is
36    /// empty.
37    #[must_use]
38    pub const fn is_empty(&self) -> bool {
39        self.colors.is_empty()
40    }
41
42    /// Get the size of the stack.
43    #[must_use]
44    pub const fn size(&self) -> u32 {
45        self.colors.len()
46    }
47
48    /// Get the top piece and color of this stack.
49    #[must_use]
50    pub fn top(&self) -> Option<(Piece, Color)> {
51        self.colors.top().map(|color| (self.piece, color))
52    }
53
54    #[must_use]
55    pub const fn colors(&self) -> Colors {
56        self.colors
57    }
58
59    /// Check if this stack contributes to roads for the given color.
60    #[must_use]
61    pub fn road(&self, color: Color) -> bool {
62        matches!(self.top(), Some((Piece::Flat | Piece::Cap, c)) if c == color)
63    }
64
65    /// Try to put a piece on top of this stack.
66    ///
67    /// # Errors
68    ///
69    /// You cannot stack on top of capstones or walls with the exception
70    /// that capstones can flatten walls.
71    pub fn stack(&mut self, piece: Piece, color: Color) -> Result<(), StackError> {
72        // Only allow stacking on top of flats, or flattening walls.
73        match self.piece {
74            Piece::Flat => Ok(()),
75            Piece::Wall => {
76                if matches!(piece, Piece::Cap) {
77                    Ok(())
78                } else {
79                    Err(StackError::Wall)
80                }
81            }
82            Piece::Cap => Err(StackError::Cap),
83        }?;
84
85        self.piece = piece;
86        self.colors.push(color);
87        Ok(())
88    }
89
90    /// Try taking the top `amount` pieces from this tile.
91    ///
92    /// # Errors
93    ///
94    /// Trying to take 0, more than the carry limit, or more than the stack size
95    /// will result in an error.
96    ///
97    /// # Panics
98    ///
99    /// Should not panic because we check the amount beforehand.
100    pub fn take<const N: usize>(&mut self, amount: u32) -> Result<(Piece, Colors), TakeError> {
101        if amount == 0 {
102            return Err(TakeError::Zero);
103        } else if amount as usize > N {
104            return Err(TakeError::CarryLimit);
105        } else if amount > self.size() {
106            return Err(TakeError::StackSize);
107        }
108
109        let piece = self.piece;
110        self.piece = Piece::Flat;
111        Ok((
112            piece,
113            self.colors
114                .take(amount)
115                .expect("The amount should be small enough since we checked that above."),
116        ))
117    }
118}