624 lines
18 KiB
Rust
624 lines
18 KiB
Rust
//! Data structures and operations on tetrix matrices. The matrix types in this module are
|
|
//! 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;
|
|
|
|
/// Bitboard representation of a full row.
|
|
pub const FULL_ROW: u16 = u16::MAX;
|
|
|
|
/// Bitboard representation of an empty row.
|
|
pub const EMPTY_ROW: u16 = !((1u16 << COLUMNS) - 1);
|
|
|
|
/// Represents a "slice" to a tetris matrix bitboard.
|
|
///
|
|
/// Bitboards are represented rowwise, with each row represented by a u16 holding bits
|
|
/// describing if a cell is occupied (1) or not (0). The least significant bit describes
|
|
/// the leftmost column, and the most significant bits are all set, even past the logical
|
|
/// end of the matrix (columns 11+).
|
|
///
|
|
/// Logically, a matrix is considered infinite in every direction. Cells below the bottom
|
|
/// or beyond the left/right sides are all considered occupied. Cells above the physical
|
|
/// top of the matrix are considered unoccupied.
|
|
#[derive(Eq, PartialEq, Hash)]
|
|
#[repr(transparent)]
|
|
pub struct Mat([u16]);
|
|
|
|
impl Mat {
|
|
/// Constructs a new matrix from a slice of rowwise bitboard data.
|
|
pub const fn new(data: &[u16]) -> &Self {
|
|
if data.len() >= i16::MAX as usize {
|
|
panic!("matrix height overflows i16");
|
|
}
|
|
unsafe { core::mem::transmute(data) }
|
|
}
|
|
|
|
pub const EMPTY: &'static Self = Self::new(&[]);
|
|
|
|
/// Returns the number of columns in the matrix. This always returns `COLUMNS`.
|
|
#[inline]
|
|
pub const fn cols(&self) -> i16 {
|
|
COLUMNS
|
|
}
|
|
|
|
/// Returns the number of rows in the matrix.
|
|
#[inline]
|
|
pub const fn rows(&self) -> i16 {
|
|
// XXX(iitalics): this is guarunteed not to wrap, since we check len in `new`.
|
|
self.0.len() as i16
|
|
}
|
|
|
|
/// Returns true if the cells in row `y` selected by `mask` match the pattern `test`.
|
|
#[inline]
|
|
pub fn test_row(&self, y: i16, mask: u16, test: u16) -> bool {
|
|
(self[y] & mask) == test
|
|
}
|
|
|
|
/// Returns true if the cell at `(x,y)` is occupied.
|
|
#[inline]
|
|
pub fn get(&self, x: i16, y: i16) -> bool {
|
|
if (0..COLUMNS).contains(&x) {
|
|
self.test_row(y, 1 << x, 1 << x)
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
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 {
|
|
if y < 0 {
|
|
&FULL_ROW
|
|
} else {
|
|
self.0.get(y as usize).unwrap_or(&EMPTY_ROW)
|
|
}
|
|
}
|
|
}
|
|
|
|
// `mat[y0..y1]` = bit patterns of rows in range
|
|
impl core::ops::Index<Range<i16>> for Mat {
|
|
type Output = [u16];
|
|
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]
|
|
}
|
|
}
|
|
|
|
// `mat[..]` = bit patterns of all rows
|
|
impl core::ops::Index<RangeFull> for Mat {
|
|
type Output = [u16];
|
|
fn index(&self, _: RangeFull) -> &[u16] {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for Mat {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
write!(f, "mat!{{")?;
|
|
let mut sep = "";
|
|
for y in (0..self.rows()).rev() {
|
|
write!(f, "{sep}\"")?;
|
|
for x in 0..self.cols() {
|
|
let occ = self.get(x, y);
|
|
f.write_str(if occ { "x" } else { "." })?;
|
|
}
|
|
write!(f, "\"")?;
|
|
sep = ";";
|
|
}
|
|
write!(f, "}}")
|
|
}
|
|
}
|
|
|
|
#[cfg(any(feature = "ascii", test))]
|
|
#[doc(hidden)]
|
|
pub mod __ascii {
|
|
use super::*;
|
|
|
|
pub const fn parse<const N: usize>(strs: [&str; N]) -> [u16; N] {
|
|
let mut data = [EMPTY_ROW; N];
|
|
let mut i = 0;
|
|
while i < N {
|
|
let row = strs[i].as_bytes();
|
|
if row.len() != COLUMNS as usize {
|
|
panic!("wrong number of columns in ascii row");
|
|
}
|
|
let y = N - i - 1;
|
|
let mut x = 0i16;
|
|
while x < COLUMNS {
|
|
match row[x as usize] {
|
|
b'.' | b'_' | b' ' => {}
|
|
_ => data[y] |= 1 << x,
|
|
}
|
|
x += 1;
|
|
}
|
|
i += 1;
|
|
}
|
|
data
|
|
}
|
|
}
|
|
|
|
/// 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)]
|
|
pub struct MatBuf<const LEN: usize = 40> {
|
|
rows: i16,
|
|
buf: [u16; LEN],
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
pub fn shift_up(&mut self) {
|
|
if self.rows as usize == LEN {
|
|
panic!("not enough available buffer space to shift up");
|
|
}
|
|
self.buf.copy_within(0..self.rows as usize, 1);
|
|
self.buf[0] = EMPTY_ROW;
|
|
self.rows += 1;
|
|
}
|
|
}
|
|
|
|
impl<const LEN: usize> Default for MatBuf<LEN> {
|
|
fn default() -> Self {
|
|
Self {
|
|
buf: [0; LEN],
|
|
rows: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
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]
|
|
}
|
|
}
|
|
|
|
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[..self.rows as usize]
|
|
}
|
|
}
|
|
|
|
// boilerplate impl's that all defer to `self.as_mat()`
|
|
|
|
impl<const LEN: usize> AsRef<Mat> for MatBuf<LEN> {
|
|
#[inline]
|
|
fn as_ref(&self) -> &Mat {
|
|
self.as_mat()
|
|
}
|
|
}
|
|
|
|
impl<const LEN: usize> core::ops::Deref for MatBuf<LEN> {
|
|
type Target = Mat;
|
|
fn deref(&self) -> &Mat {
|
|
self.as_mat()
|
|
}
|
|
}
|
|
|
|
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<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<const LEN: usize> core::ops::Index<i16> for MatBuf<LEN> {
|
|
type Output = u16;
|
|
fn index(&self, y: i16) -> &u16 {
|
|
&self.as_mat()[y]
|
|
}
|
|
}
|
|
|
|
impl<const LEN: usize> core::ops::Index<Range<i16>> for MatBuf<LEN> {
|
|
type Output = [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::*;
|
|
use crate::mat;
|
|
|
|
use alloc::vec::Vec;
|
|
use core::ops::RangeInclusive;
|
|
|
|
#[test]
|
|
fn test_bit_constants() {
|
|
for i in 0..16 {
|
|
let m = 1u16 << i;
|
|
let e = EMPTY_ROW & m != 0;
|
|
let f = FULL_ROW & m != 0;
|
|
assert!(f, "full, i={i}");
|
|
assert_eq!(e, i >= 10, "empty, i={i}");
|
|
}
|
|
}
|
|
|
|
const M1: &Mat = mat! {
|
|
"...x....x.";
|
|
"xx..xxxx.x";
|
|
};
|
|
|
|
const M2: &Mat = mat! {
|
|
".........x";
|
|
".........x";
|
|
"x.......xx";
|
|
"xxxx.xxxxx";
|
|
};
|
|
|
|
#[test]
|
|
fn test_dims() {
|
|
assert_eq!(Mat::EMPTY.rows(), 0);
|
|
assert_eq!(Mat::EMPTY.cols(), 10);
|
|
assert_eq!(M1.rows(), 2);
|
|
assert_eq!(M1.cols(), 10);
|
|
assert_eq!(M2.rows(), 4);
|
|
assert_eq!(M2.cols(), 10);
|
|
}
|
|
|
|
#[test]
|
|
fn test_index() {
|
|
assert_eq!(Mat::EMPTY[0], EMPTY_ROW);
|
|
assert_eq!(Mat::EMPTY[1], EMPTY_ROW);
|
|
assert_eq!(Mat::EMPTY[-1], FULL_ROW);
|
|
//
|
|
let m1_0 = 0b1011110011 | EMPTY_ROW;
|
|
let m1_1 = 0b0100001000 | EMPTY_ROW;
|
|
assert_eq!(M1[0], m1_0, "{:b}", M1[0]);
|
|
assert_eq!(M1[1], m1_1, "{:b}", M1[1]);
|
|
assert_eq!(M1[2], EMPTY_ROW);
|
|
assert_eq!(M1[-1], FULL_ROW);
|
|
assert_eq!(M1[0..2], [m1_0, m1_1]);
|
|
//
|
|
let m2_0 = 0b1111101111 | EMPTY_ROW;
|
|
let m2_1 = 0b1100000001 | EMPTY_ROW;
|
|
let m2_2 = 0b1000000000 | EMPTY_ROW;
|
|
let m2_3 = 0b1000000000 | EMPTY_ROW;
|
|
assert_eq!(M2[0..4], [m2_0, m2_1, m2_2, m2_3]);
|
|
assert_eq!(M2[0..1], [m2_0]);
|
|
assert_eq!(M2[2..4], [m2_2, m2_3]);
|
|
assert_eq!(M2[2..5], M2[2..4]);
|
|
assert_eq!(M2[-1..3], M2[0..3]);
|
|
}
|
|
|
|
fn occ(m: &Mat, y: i16, xs: RangeInclusive<i16>) -> Vec<bool> {
|
|
xs.map(|x| m.get(x, y)).collect()
|
|
}
|
|
|
|
#[test]
|
|
#[allow(clippy::just_underscores_and_digits)]
|
|
fn test_occupied() {
|
|
// get row data as bools
|
|
let __ = false;
|
|
let xx = true;
|
|
assert_eq!(
|
|
occ(M1, 0, -1..=10),
|
|
[xx, xx, xx, __, __, xx, xx, xx, xx, __, xx, xx]
|
|
);
|
|
assert_eq!(occ(M1, 1, 0..=9), [__, __, __, xx, __, __, __, __, xx, __]);
|
|
assert_eq!(occ(M2, 1, 0..=9), [xx, __, __, __, __, __, __, __, xx, xx],);
|
|
// test oob circumstances
|
|
for x in -16..=16 {
|
|
let oob = !(0..COLUMNS).contains(&x);
|
|
assert_eq!(M1.get(x, 2), oob, "M1,x={x},y=2");
|
|
assert_eq!(M1.get(x, 3), oob, "M1,x={x},y=3");
|
|
assert_eq!(M2.get(x, 4), oob, "M2,x={x},y=4");
|
|
assert_eq!(M2.get(x, 5), oob, "M2,x={x},y=5");
|
|
assert_eq!(M1.get(x, 16), oob, "M1,x={x},y=16");
|
|
assert_eq!(M2.get(x, 17), oob, "M2,x={x},y=17");
|
|
for y in -4..0 {
|
|
assert!(M1.get(x, y), "M1,x={x},y={y}");
|
|
assert!(M2.get(x, y), "M2,x={x},y={y}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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::new();
|
|
assert_eq!(buf, Mat::EMPTY);
|
|
assert_eq!(buf.rows(), 0);
|
|
let mat = mat! {
|
|
"xxx.......";
|
|
"xx........";
|
|
"x.........";
|
|
};
|
|
buf.copy_from(mat);
|
|
assert_eq!(buf, mat);
|
|
assert_eq!(buf.rows(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clear_lines_1() {
|
|
let mat0 = mat! {
|
|
".........."; // clear
|
|
".........."; // clear
|
|
"x.........";
|
|
".........."; // clear
|
|
".x.xxxxxxx";
|
|
"xxxxxxxxxx"; // clear
|
|
"x.xxxxxxxx";
|
|
};
|
|
let mat1 = mat! {
|
|
"x.........";
|
|
".x.xxxxxxx";
|
|
"x.xxxxxxxx";
|
|
};
|
|
let mut buf: MatBuf<7> = MatBuf::default();
|
|
assert_eq!(buf.rows(), 0);
|
|
buf.copy_from(mat0);
|
|
assert_eq!(buf.rows(), 7);
|
|
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<4> = MatBuf::default();
|
|
buf.set(0, 0); // a
|
|
buf.set(9, 3); // b
|
|
buf.set(1, 1); // c
|
|
buf.set(2, 1); // d
|
|
buf.set(3, 1); // e
|
|
assert!(buf.get(0, 0));
|
|
assert!(buf.get(9, 3));
|
|
assert!(buf.get(1, 1));
|
|
assert!(buf.get(2, 1));
|
|
assert!(buf.get(3, 1));
|
|
let mat = mat! {
|
|
".........b";
|
|
"..........";
|
|
".cde......";
|
|
"a.........";
|
|
};
|
|
assert_eq!(buf, mat);
|
|
}
|
|
|
|
#[test]
|
|
fn test_fill() {
|
|
let mut buf: MatBuf<5> = MatBuf::default();
|
|
buf.fill_row(1, 0b110111); // a
|
|
let mat = mat! {
|
|
"aaa.aa....";
|
|
"..........";
|
|
};
|
|
assert_eq!(buf, mat);
|
|
buf.fill_row(3, 0b1000000000); // b
|
|
buf.fill_row(0, u16::MAX); // c
|
|
let mat = mat! {
|
|
".........b";
|
|
"..........";
|
|
"aaa.aa....";
|
|
"cccccccccc";
|
|
};
|
|
assert_eq!(buf, mat);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_set_oob() {
|
|
let mut buf: MatBuf<4> = MatBuf::default();
|
|
buf.set(0, 4);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_fill_oob() {
|
|
let mut buf: MatBuf<4> = MatBuf::default();
|
|
buf.fill_row(4, 0b1001);
|
|
}
|
|
|
|
#[test]
|
|
fn test_shift_up() {
|
|
let mat0 = mat! {
|
|
".x........";
|
|
"xxxxx.xxxx";
|
|
"x.xxxxxxxx";
|
|
".xxxxxxxxx";
|
|
};
|
|
let mat1 = mat! {
|
|
".x........";
|
|
"xxxxx.xxxx";
|
|
"x.xxxxxxxx";
|
|
".xxxxxxxxx";
|
|
"..........";
|
|
};
|
|
let mat2 = mat! {
|
|
".x........";
|
|
"xxxxx.xxxx";
|
|
"x.xxxxxxxx";
|
|
".xxxxxxxxx";
|
|
"..........";
|
|
"..........";
|
|
};
|
|
let mut buf = MatBuf::new();
|
|
buf.copy_from(mat0);
|
|
buf.shift_up();
|
|
assert_eq!(buf, mat1);
|
|
buf.shift_up();
|
|
assert_eq!(buf, mat2);
|
|
assert_eq!(buf.clear_lines(), 0..2);
|
|
assert_eq!(buf, mat0);
|
|
buf.shift_up();
|
|
assert_eq!(buf, mat1);
|
|
assert_eq!(buf.clear_lines(), 0..1);
|
|
}
|
|
}
|