Add matrix datastructure
This commit is contained in:
parent
58956c1983
commit
908fa77d8a
|
@ -1 +1,5 @@
|
|||
#![no_std]
|
||||
|
||||
pub mod matrix;
|
||||
|
||||
pub use matrix::Mat;
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
//! 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(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use core::ops::RangeInclusive;
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[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::new(&[
|
||||
// xx..xxxx.x y=0
|
||||
0b1011110011 | EMPTY_ROW,
|
||||
// ...x....x. y=1
|
||||
0b0100001000 | EMPTY_ROW,
|
||||
]);
|
||||
|
||||
const M2: &Mat = Mat::new(&[
|
||||
// xxxx.xxxxx y=0
|
||||
0b1111101111 | EMPTY_ROW,
|
||||
// x.......xx y=1
|
||||
0b1100000001 | EMPTY_ROW,
|
||||
// .........x y=2
|
||||
0b1000000000 | EMPTY_ROW,
|
||||
// .........x y=3
|
||||
0b1000000000 | EMPTY_ROW,
|
||||
]);
|
||||
|
||||
#[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<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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue