shrimplify: MatBuf is only generic of its length, not buffer type

This commit is contained in:
tali 2023-04-09 14:35:56 -04:00
parent 15d69fe128
commit d564bc25af
2 changed files with 223 additions and 146 deletions

View File

@ -2,6 +2,9 @@
//! uncolored, since color information is generally not used by bots and many operations
//! can be made much more efficient by not tracking it.
use core::cmp::{max, min};
use core::ops::{Range, RangeFull};
/// Number of columns in a matrix.
pub const COLUMNS: i16 = 10;
@ -64,16 +67,15 @@ impl Mat {
true
}
}
}
fn to_array_range(&self, r: core::ops::Range<i16>) -> core::ops::Range<usize> {
let y1 = core::cmp::max(r.end, 0);
let y1 = core::cmp::min(y1 as usize, self.0.len());
let y0 = core::cmp::max(r.start, 0);
let y0 = core::cmp::min(y0 as usize, y1);
y0..y1
impl AsRef<Mat> for Mat {
fn as_ref(&self) -> &Mat {
self
}
}
// `mat[y]` = bit pattern of row `y`
impl core::ops::Index<i16> for Mat {
type Output = u16;
fn index(&self, y: i16) -> &u16 {
@ -85,16 +87,20 @@ impl core::ops::Index<i16> for Mat {
}
}
impl core::ops::Index<core::ops::Range<i16>> for Mat {
// `mat[y0..y1]` = bit patterns of rows in range
impl core::ops::Index<Range<i16>> for Mat {
type Output = [u16];
fn index(&self, r: core::ops::Range<i16>) -> &[u16] {
&self.0[self.to_array_range(r)]
fn index(&self, r: Range<i16>) -> &[u16] {
let y1 = min(max(r.end, 0), self.rows());
let y0 = min(max(r.start, 0), y1);
&self.0[y0 as usize..y1 as usize]
}
}
impl core::ops::Index<core::ops::RangeFull> for Mat {
// `mat[..]` = bit patterns of all rows
impl core::ops::Index<RangeFull> for Mat {
type Output = [u16];
fn index(&self, _: core::ops::RangeFull) -> &[u16] {
fn index(&self, _: RangeFull) -> &[u16] {
&self.0
}
}
@ -144,164 +150,188 @@ pub mod __ascii {
}
}
/// Wrapper struct for using an underlying buffer (such as an array or vec) as a
/// "writable" matrix. This allows operations such as changing if a cell is occupied or
/// not.
/// Matrix bitboard backed by a static u16 array of a given length. This type allows
/// writable operations such as changing if a cell is occupied or not, and growing or
/// shrinking the number of rows as long as it stays within the underlying capacity.
///
/// [`MatBuf`] implements [`Deref`], so it automatically inherits the methods of [`Mat`].
#[derive(Clone, Default)]
pub struct MatBuf<T: AsRef<[u16]> = [u16; 40]> {
buffer: T,
rows: usize,
#[derive(Clone)]
pub struct MatBuf<const LEN: usize = 40> {
rows: i16,
buf: [u16; LEN],
}
impl<const N: usize> MatBuf<[u16; N]> {
/// Returns a new empty [`MatBuf`] backed by a fixed-size array.
impl MatBuf {
/// Returns a new empty [`MatBuf`] that is able to grow up to 40 rows.
pub fn new() -> Self {
Self::default()
}
}
impl<const LEN: usize> MatBuf<LEN> {
/// Returns a read-only view of this matrix.
#[inline]
pub fn as_mat(&self) -> &Mat {
Mat::new(&self.buf[0..self.rows as usize])
}
/// Modifies the cells in this matrix to be identical to those in `mat`.
///
/// Panics if the buffer space cannot fit the rows of `mat`.
#[inline]
pub fn copy_from(&mut self, mat: &Mat) {
let data = &mat[..];
if data.len() > LEN {
panic!("matrix cannot fit in available buffer space");
}
self.rows = mat.rows();
self.buf[..data.len()].copy_from_slice(data);
}
/// Resets the matrix so it is empty.
#[inline]
pub fn clear(&mut self) {
self.copy_from(Mat::EMPTY)
}
/// Modifies the cells in row `y` to have additional cells occupied according to the
/// bit pattern in `mask`.
///
/// Panics if the buffer space cannot fit the desired row.
#[inline]
pub fn fill_row(&mut self, y: i16, mask: u16) {
if y >= 0 {
self[y] |= mask;
}
}
/// Fills in the cell at the given (x,y) coordinate.
///
/// Panics if the buffer space cannot fit the desired cell.
pub fn set(&mut self, x: i16, y: i16) {
self.fill_row(y, if (0..COLUMNS).contains(&x) { 1 << x } else { 0 })
}
/// Removes any rows that are completely filled, shifting rows above down. Returns the
/// range of rows that were cleared.
pub fn clear_lines(&mut self) -> Range<i16> {
// TODO: this could be made faster if given the following assumptions:
// - provide lowest y that may contain filled rows
// - assume that filled rows must all be adjacent
let mut dst = 0usize;
let mut rng = self.rows..self.rows;
for y in 0..self.rows {
let i = y as usize;
if self.buf[i] == FULL_ROW || self.buf[i] == EMPTY_ROW {
rng.start = min(rng.start, y);
rng.end = y + 1;
} else {
self.buf[dst] = self.buf[i];
dst += 1;
}
}
self.rows = dst as i16;
rng
}
}
impl<const LEN: usize> Default for MatBuf<LEN> {
fn default() -> Self {
Self {
buffer: [0u16; N],
buf: [0; LEN],
rows: 0,
}
}
}
impl<T> MatBuf<T>
where
T: AsRef<[u16]>,
{
/// Returns the underyling buffer.
pub fn into_inner(self) -> T {
self.buffer
impl<const LEN: usize> core::ops::IndexMut<i16> for MatBuf<LEN> {
fn index_mut(&mut self, y: i16) -> &mut u16 {
if y < 0 || y as usize >= LEN {
panic!("row does not fit in available buffer space");
}
if y >= self.rows {
self.buf[self.rows as usize..(y + 1) as usize].fill(EMPTY_ROW);
self.rows = y + 1;
}
&mut self.buf[y as usize]
}
}
/// Returns a read-only view of this matrix.
impl<const LEN: usize> core::ops::IndexMut<Range<i16>> for MatBuf<LEN> {
fn index_mut(&mut self, r: Range<i16>) -> &mut [u16] {
// FIXME: should this expand the buffer like `IndexMut<i16>` does?
let y1 = min(max(r.end, 0), self.rows);
let y0 = min(max(r.start, 0), y1);
&mut self.buf[y0 as usize..y1 as usize]
}
}
impl<const LEN: usize> core::ops::IndexMut<RangeFull> for MatBuf<LEN> {
fn index_mut(&mut self, _: RangeFull) -> &mut [u16] {
&mut self.buf
}
}
// boilerplate impl's that all defer to `self.as_mat()`
impl<const LEN: usize> AsRef<Mat> for MatBuf<LEN> {
#[inline]
pub fn as_mat(&self) -> &Mat {
Mat::new(&self.buffer.as_ref()[..self.rows])
}
/// Resets the matrix so it is empty.
pub fn clear(&mut self) {
self.rows = 0;
fn as_ref(&self) -> &Mat {
self.as_mat()
}
}
impl<T> MatBuf<T>
where
T: AsRef<[u16]> + AsMut<[u16]>,
{
/// Modifies the cells in this matrix to be identical to those in `mat`.
///
/// Panics if the buffer space cannot fit the rows of `mat`.
pub fn copy_from(&mut self, mat: &Mat) {
let mat_data = &mat[..];
let buf_data = self.buffer.as_mut();
if mat_data.len() > buf_data.len() {
panic!("matrix cannot fit in available buffer space");
}
self.rows = mat_data.len();
buf_data[..self.rows].copy_from_slice(mat_data);
}
pub fn fill_row(&mut self, y: i16, mask: u16) {
if y < 0 {
// OOB coordinates are considered already set
return;
}
let y = y as usize;
let buf_data = self.buffer.as_mut();
while y >= self.rows {
*buf_data
.get_mut(self.rows)
.expect("y should be within available buffer space") = EMPTY_ROW;
self.rows += 1;
}
buf_data[y] |= mask;
}
/// Fills in the cell at the given (x,y) coordinate. Adds new rows to the top of the
/// matrix if necessary.
///
/// Panics if the buffer space cannot fit the new rows.
#[inline]
pub fn set(&mut self, x: i16, y: i16) {
if (0..COLUMNS).contains(&x) {
self.fill_row(y, 1 << x);
}
}
/// Removes any rows that are completely filled, shifting rows above down. Returns a
/// new view of the buffer that only includes the remaining rows.
pub fn clear_lines(&mut self) {
let data = self.buffer.as_mut();
let mut dst_y = 0;
for y in 0..self.rows {
if data[y] != FULL_ROW && data[y] != EMPTY_ROW {
data[dst_y] = data[y];
dst_y += 1;
}
}
self.rows = dst_y;
}
}
impl<T> core::ops::IndexMut<core::ops::RangeFull> for MatBuf<T>
where
T: AsRef<[u16]> + AsMut<[u16]>,
{
fn index_mut(&mut self, _: core::ops::RangeFull) -> &mut [u16] {
&mut self.buffer.as_mut()[..self.rows]
}
}
// All boilerplate below
impl<T: AsRef<[u16]>> core::ops::Deref for MatBuf<T> {
impl<const LEN: usize> core::ops::Deref for MatBuf<LEN> {
type Target = Mat;
fn deref(&self) -> &Mat {
self.as_mat()
}
}
impl<T: AsRef<[u16]>> core::fmt::Debug for MatBuf<T> {
impl<const LEN: usize, Rhs: AsRef<Mat>> core::cmp::PartialEq<Rhs> for MatBuf<LEN> {
fn eq(&self, other: &Rhs) -> bool {
*self.as_mat() == *other.as_ref()
}
}
impl<const LEN: usize> core::cmp::Eq for MatBuf<LEN> {}
impl<const LEN: usize> core::fmt::Debug for MatBuf<LEN> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.as_mat().fmt(f)
}
}
impl<T: AsRef<[u16]>> core::cmp::Eq for MatBuf<T> {}
impl<T: AsRef<[u16]>> core::cmp::PartialEq<Mat> for MatBuf<T> {
fn eq(&self, other: &Mat) -> bool {
self.as_mat() == other
impl<const LEN: usize> core::hash::Hash for MatBuf<LEN> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_mat().hash(state);
}
}
impl<T: AsRef<[u16]>> core::cmp::PartialEq<&Mat> for MatBuf<T> {
fn eq(&self, other: &&Mat) -> bool {
self.as_mat() == *other
impl<const LEN: usize> core::ops::Index<i16> for MatBuf<LEN> {
type Output = u16;
fn index(&self, y: i16) -> &u16 {
&self.as_mat()[y]
}
}
impl<T: AsRef<[u16]>, U: AsRef<[u16]>> core::cmp::PartialEq<MatBuf<U>> for MatBuf<T> {
fn eq(&self, other: &MatBuf<U>) -> bool {
self.as_mat() == other.as_mat()
}
}
impl<T: AsRef<[u16]>> core::ops::Index<core::ops::RangeFull> for MatBuf<T> {
impl<const LEN: usize> core::ops::Index<Range<i16>> for MatBuf<LEN> {
type Output = [u16];
fn index(&self, _: core::ops::RangeFull) -> &[u16] {
&self.as_mat()[..]
}
}
impl<T: AsRef<[u16]> + AsMut<[u16]>> core::ops::IndexMut<core::ops::Range<i16>> for MatBuf<T> {
fn index_mut(&mut self, r: core::ops::Range<i16>) -> &mut [u16] {
let ar = self.to_array_range(r);
&mut self.buffer.as_mut()[ar]
}
}
impl<T: AsRef<[u16]>> core::ops::Index<core::ops::Range<i16>> for MatBuf<T> {
type Output = [u16];
fn index(&self, r: core::ops::Range<i16>) -> &[u16] {
fn index(&self, r: Range<i16>) -> &[u16] {
&self.as_mat()[r]
}
}
impl<const LEN: usize> core::ops::Index<RangeFull> for MatBuf<LEN> {
type Output = [u16];
fn index(&self, _: RangeFull) -> &[u16] {
&self.as_mat()[..]
}
}
// TODO(?): MatVec, which is resizable
// TODO(?): test_row(), fill_row(), clear_lines() made into traits
#[cfg(test)]
mod test {
use super::*;
@ -400,9 +430,26 @@ mod test {
}
}
#[test]
fn test_mat_buf_size() {
macro_rules! mat_buf_size {
($n:literal) => {
core::mem::size_of::<MatBuf<$n>>()
};
}
assert_eq!(mat_buf_size!(0), core::mem::size_of::<[u16; 1]>());
assert_eq!(mat_buf_size!(1), core::mem::size_of::<[u16; 2]>());
assert_eq!(mat_buf_size!(10), core::mem::size_of::<[u16; 11]>());
assert_eq!(mat_buf_size!(40), core::mem::size_of::<[u16; 41]>());
assert_eq!(
core::mem::size_of::<MatBuf>(),
core::mem::size_of::<[u16; 41]>()
);
}
#[test]
fn test_mat_buf_copy_from() {
let mut buf: MatBuf = MatBuf::new();
let mut buf = MatBuf::new();
assert_eq!(buf, Mat::EMPTY);
assert_eq!(buf.rows(), 0);
let mat = mat! {
@ -416,7 +463,7 @@ mod test {
}
#[test]
fn test_clear_lines() {
fn test_clear_lines_1() {
let mat0 = mat! {
".........."; // clear
".........."; // clear
@ -431,18 +478,51 @@ mod test {
".x.xxxxxxx";
"x.xxxxxxxx";
};
let mut buf: MatBuf<[u16; 7]> = MatBuf::new();
let mut buf: MatBuf<7> = MatBuf::default();
assert_eq!(buf.rows(), 0);
buf.copy_from(mat0);
assert_eq!(buf.rows(), 7);
buf.clear_lines();
assert_eq!(buf.clear_lines(), 1..7);
assert_eq!(buf, mat1);
assert_eq!(buf.rows(), 3);
}
#[test]
fn test_clear_lines_2() {
let mut buf = MatBuf::new();
buf.copy_from(mat! {
".x.xxxxxxx";
"xxxxxxxxxx"; // clear
"xxxxxxxxxx"; // clear
"x.xxxxxxxx";
});
let tgt = mat! {
".x.xxxxxxx";
"x.xxxxxxxx";
};
assert_eq!(buf.clear_lines(), 1..3);
assert_eq!(buf, tgt);
assert_eq!(buf.clear_lines(), 2..2);
assert_eq!(buf, tgt);
}
#[test]
fn test_clear_lines_3() {
let mut buf = MatBuf::new();
buf.copy_from(mat! {
"xxxxxxxxxx"; // clear
"xxxxxxxxxx"; // clear
"xxxxxxxxxx"; // clear
});
let tgt = Mat::EMPTY;
assert_eq!(buf.clear_lines(), 0..3);
assert_eq!(buf, tgt);
assert_eq!(buf.clear_lines(), 0..0);
}
#[test]
fn test_set() {
let mut buf: MatBuf<[u16; 4]> = MatBuf::new();
let mut buf: MatBuf<4> = MatBuf::default();
buf.set(0, 0); // a
buf.set(9, 3); // b
buf.set(1, 1); // c
@ -464,7 +544,7 @@ mod test {
#[test]
fn test_fill() {
let mut buf: MatBuf<[u16; 5]> = MatBuf::new();
let mut buf: MatBuf<5> = MatBuf::default();
buf.fill_row(1, 0b110111); // a
let mat = mat! {
"aaa.aa....";
@ -485,14 +565,14 @@ mod test {
#[test]
#[should_panic]
fn test_set_oob() {
let mut buf: MatBuf<[u16; 4]> = MatBuf::new();
let mut buf: MatBuf<4> = MatBuf::default();
buf.set(0, 4);
}
#[test]
#[should_panic]
fn test_fill_oob() {
let mut buf: MatBuf<[u16; 4]> = MatBuf::new();
let mut buf: MatBuf<4> = MatBuf::default();
buf.fill_row(4, 0b1001);
}
}

View File

@ -152,10 +152,7 @@ impl<'c> Cells<'c> {
/// Fills in all of the cells onto the given matrix. Panics if the piece is out of
/// bounds.
pub fn fill<T>(&self, mat: &mut MatBuf<T>)
where
T: AsRef<[u16]> + AsMut<[u16]>,
{
pub fn fill<const LEN: usize>(&self, mat: &mut MatBuf<LEN>) {
assert!(
self.x >= 0 && self.x + self.w <= mat.cols() && self.y >= 0,
"fill() cells oob"