Skip to main content

ai_connect4/
mv.rs

1use std::fmt;
2use std::str::FromStr;
3
4/// One of Connect Four's seven columns, indexed left to right.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub struct Column(u8);
7
8impl Column {
9    pub const COUNT: usize = 7;
10
11    /// Creates a column from its zero-based left-to-right index.
12    ///
13    /// # Panics
14    ///
15    /// Panics when `index` is greater than six.
16    pub const fn new(index: u8) -> Self {
17        assert!(index < Self::COUNT as u8, "column index must be 0..7");
18        Self(index)
19    }
20
21    pub const fn index(self) -> usize {
22        self.0 as usize
23    }
24
25    pub const fn mirrored(self) -> Self {
26        Self::new(Self::COUNT as u8 - 1 - self.0)
27    }
28
29    pub fn all() -> impl Iterator<Item = Self> {
30        (0..Self::COUNT as u8).map(Self)
31    }
32}
33
34impl fmt::Display for Column {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "{}", char::from(b'a' + self.0))
37    }
38}
39
40impl FromStr for Column {
41    type Err = &'static str;
42
43    fn from_str(value: &str) -> Result<Self, Self::Err> {
44        let bytes = value.as_bytes();
45        if bytes.len() != 1 {
46            return Err("column must be a..g or 1..7");
47        }
48        match bytes[0] {
49            b'a'..=b'g' => Ok(Self::new(bytes[0] - b'a')),
50            b'A'..=b'G' => Ok(Self::new(bytes[0] - b'A')),
51            b'1'..=b'7' => Ok(Self::new(bytes[0] - b'1')),
52            _ => Err("column must be a..g or 1..7"),
53        }
54    }
55}
56
57/// A move drops one disc into a column.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct Move(Column);
60
61impl Move {
62    pub const fn new(column: Column) -> Self {
63        Self(column)
64    }
65
66    pub const fn column(self) -> Column {
67        self.0
68    }
69
70    pub const fn mirrored(self) -> Self {
71        Self(self.0.mirrored())
72    }
73}
74
75impl fmt::Display for Move {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        self.0.fmt(f)
78    }
79}
80
81impl FromStr for Move {
82    type Err = &'static str;
83
84    fn from_str(value: &str) -> Result<Self, Self::Err> {
85        value.parse().map(Self)
86    }
87}
88
89/// A physical board cell. Rank zero is the bottom row.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
91pub struct Cell {
92    column: Column,
93    row: u8,
94}
95
96impl Cell {
97    pub const ROWS: usize = 6;
98
99    /// Creates a physical cell.
100    ///
101    /// # Panics
102    ///
103    /// Panics when `row` is greater than five.
104    pub const fn new(column: Column, row: u8) -> Self {
105        assert!(row < Self::ROWS as u8, "row index must be 0..6");
106        Self { column, row }
107    }
108
109    pub const fn column(self) -> Column {
110        self.column
111    }
112
113    pub const fn row(self) -> usize {
114        self.row as usize
115    }
116
117    pub const fn bit_index(self) -> usize {
118        self.column.index() * 7 + self.row()
119    }
120
121    pub const fn mirrored(self) -> Self {
122        Self::new(self.column.mirrored(), self.row)
123    }
124}
125
126impl fmt::Display for Cell {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "{}{}", self.column, self.row + 1)
129    }
130}
131
132/// At most seven moves exist, so search never needs a heap-backed move list.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct MoveList {
135    moves: [Move; Column::COUNT],
136    len: u8,
137}
138
139impl Default for MoveList {
140    fn default() -> Self {
141        Self {
142            moves: [Move::new(Column::new(0)); Column::COUNT],
143            len: 0,
144        }
145    }
146}
147
148impl MoveList {
149    pub const fn len(self) -> usize {
150        self.len as usize
151    }
152
153    pub const fn is_empty(self) -> bool {
154        self.len == 0
155    }
156
157    pub fn as_slice(&self) -> &[Move] {
158        &self.moves[..self.len()]
159    }
160
161    pub(crate) fn push(&mut self, mv: Move) {
162        debug_assert!(self.len() < Column::COUNT);
163        self.moves[self.len()] = mv;
164        self.len += 1;
165    }
166}
167
168impl IntoIterator for MoveList {
169    type Item = Move;
170    type IntoIter = std::iter::Take<std::array::IntoIter<Move, { Column::COUNT }>>;
171
172    fn into_iter(self) -> Self::IntoIter {
173        self.moves.into_iter().take(self.len())
174    }
175}