tetromino_impl/game/
matrix.rs

1// Copyright (C) 2023-2024 Daniel Mueller <deso@posteo.net>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use std::ops::Index;
5use std::ops::IndexMut;
6
7use crate::Point;
8
9
10/// A 2D matrix.
11#[derive(Clone, Debug, PartialEq)]
12pub(crate) struct Matrix<T> {
13  /// The actual matrix.
14  matrix: Box<[T]>,
15  /// The width of the matrix.
16  width: i16,
17  /// The height of the matrix.
18  height: i16,
19}
20
21impl<T> Matrix<T> {
22  /// # Panics
23  /// This constructor panics if either dimension is 0.
24  pub(crate) fn new(width: i16, height: i16) -> Self
25  where
26    T: Default,
27  {
28    assert!(width > 0);
29    assert!(height > 0);
30
31    let mut matrix = Vec::new();
32    let () = matrix.resize_with((width * height) as usize, T::default);
33
34    Self {
35      width,
36      height,
37      matrix: matrix.into_boxed_slice(),
38    }
39  }
40
41  /// Remove the given line, moving all elements above one line down.
42  pub(super) fn remove_line(&mut self, line: i16)
43  where
44    T: Copy + Default,
45  {
46    let src_index = self.calculate_index((0, line + 1));
47    let dst_index = self.calculate_index((0, line));
48    let src_range = src_index..;
49    let () = self.matrix.copy_within(src_range, dst_index);
50
51    // Now clear the very top line, as everything was copied one line
52    // down.
53    let src_index = self.calculate_index((0, self.height - 1));
54    let src_range = src_index..src_index + self.width as usize;
55    let () = self.matrix.get_mut(src_range).unwrap().fill(T::default());
56  }
57
58  /// Clear the matrix, removing all elements from it.
59  #[inline]
60  pub(super) fn clear(&mut self)
61  where
62    T: Clone + Default,
63  {
64    let () = self.matrix.fill(T::default());
65  }
66
67  /// Create an iterator over all elements, along with their positions.
68  pub(crate) fn iter(&self) -> impl Iterator<Item = (&T, Point<i16>)> {
69    let width = self.width as usize;
70
71    self.matrix.iter().enumerate().map(move |(i, t)| {
72      let x = i % width;
73      let y = i / width;
74      (t, Point::new(x as i16, y as i16))
75    })
76  }
77
78  /// Create a mutable iterator over all elements, along with their
79  /// positions.
80  pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (&mut T, Point<i16>)> {
81    let width = self.width as usize;
82
83    self.matrix.iter_mut().enumerate().map(move |(i, t)| {
84      let x = i % width;
85      let y = i / width;
86      (t, Point::new(x as i16, y as i16))
87    })
88  }
89
90  /// Create an iterator over all elements in the given line.
91  pub(crate) fn iter_line(&self, line: i16) -> impl Iterator<Item = &T> {
92    let index = self.calculate_index((0, line));
93    self.matrix[index..index + self.width as usize].iter()
94  }
95
96  /// Convert the `Matrix` into one with a different `T`, using the
97  /// provided function, `f`, to to convert individual items.
98  pub(crate) fn to_other<U, F>(&self, f: F) -> Matrix<U>
99  where
100    F: Fn(&T) -> U,
101  {
102    Matrix {
103      width: self.width,
104      height: self.height,
105      matrix: self.matrix.iter().map(f).collect(),
106    }
107  }
108
109  #[inline]
110  pub(crate) fn width(&self) -> i16 {
111    self.width
112  }
113
114  #[inline]
115  pub(crate) fn height(&self) -> i16 {
116    self.height
117  }
118
119  fn calculate_index(&self, (x, y): (i16, i16)) -> usize {
120    (x + y * self.width) as _
121  }
122}
123
124impl<T> Index<(i16, i16)> for Matrix<T> {
125  type Output = T;
126
127  fn index(&self, index: (i16, i16)) -> &Self::Output {
128    let index = self.calculate_index(index);
129    &self.matrix[index]
130  }
131}
132
133impl<T> Index<Point<i16>> for Matrix<T> {
134  type Output = T;
135
136  fn index(&self, index: Point<i16>) -> &Self::Output {
137    self.index((index.x, index.y))
138  }
139}
140
141impl<T> IndexMut<(i16, i16)> for Matrix<T> {
142  fn index_mut(&mut self, index: (i16, i16)) -> &mut Self::Output {
143    let index = self.calculate_index(index);
144    &mut self.matrix[index]
145  }
146}
147
148impl<T> IndexMut<Point<i16>> for Matrix<T> {
149  fn index_mut(&mut self, index: Point<i16>) -> &mut Self::Output {
150    self.index_mut((index.x, index.y))
151  }
152}
153
154
155#[cfg(test)]
156mod tests {
157  use super::*;
158
159
160  /// Check that indexing into a `Matrix` object works as it should.
161  #[test]
162  fn index_access() {
163    let mut matrix = Matrix::<Option<usize>>::new(3, 6);
164    assert_eq!(matrix.width(), 3);
165    assert_eq!(matrix.height(), 6);
166
167    // Set lower-left corner element without using our custom `Index`
168    // impl.
169    matrix.matrix[0] = Some(42);
170    assert_eq!(matrix[(0, 0)], Some(42));
171
172    // Set lower-right corner element.
173    matrix.matrix[2] = Some(43);
174    assert_eq!(matrix[(2, 0)], Some(43));
175
176    // Set upper-left corner element.
177    matrix.matrix[15] = Some(44);
178    assert_eq!(matrix[(0, 5)], Some(44));
179
180    // Set upper-right corner element.
181    matrix.matrix[17] = Some(45);
182    assert_eq!(matrix[(2, 5)], Some(45));
183  }
184
185  /// Make sure that we can remove a line from the matrix.
186  #[test]
187  fn line_removal() {
188    let mut matrix = Matrix::<Option<usize>>::new(2, 4);
189    let mut x = 0;
190    let () = matrix.matrix.fill_with(|| {
191      x += 1;
192      Some(x)
193    });
194
195    // |7,8|
196    // |5,6|
197    // |3,4|
198    // |1,2|
199    // -----
200    #[rustfmt::skip]
201    let expected = [
202      Some(1), Some(2),
203      Some(3), Some(4),
204      Some(5), Some(6),
205      Some(7), Some(8),
206    ];
207    assert_eq!(&*matrix.matrix, expected.as_slice());
208
209    let () = matrix.remove_line(0);
210    // |   |
211    // |7,8|
212    // |5,6|
213    // |3,4|
214    // -----
215    #[rustfmt::skip]
216    let expected = [
217      Some(3), Some(4),
218      Some(5), Some(6),
219      Some(7), Some(8),
220      None, None,
221    ];
222    assert_eq!(&*matrix.matrix, expected.as_slice());
223  }
224}