//! 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. /// 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(&[]); const fn data(&self) -> &[u16] { unsafe { core::mem::transmute(self) } } /// Returns the number of columns in the matrix. This always returns `COLUMNS`. 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.data().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 { let bits = match usize::try_from(y) { Ok(i) => self.data().get(i).copied().unwrap_or(EMPTY_ROW), Err(_) => FULL_ROW, }; (bits & 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 } } } #[cfg(any(feature = "ascii", test))] #[doc(hidden)] pub mod __ascii { use super::*; pub const fn parse(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 } } #[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); } fn occ(m: &Mat, y: i16, xs: RangeInclusive) -> Vec { 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}"); } } } }