Add Shape trait, Piece, Cells types

This commit is contained in:
tali 2022-12-13 16:53:26 -05:00
parent fa6aac86f6
commit 2c4afe54a0
2 changed files with 238 additions and 0 deletions

View File

@ -2,6 +2,7 @@
pub mod location;
pub mod matrix;
pub mod piece;
pub use location::Loc;
pub use matrix::Mat;

237
mino/src/piece.rs Normal file
View File

@ -0,0 +1,237 @@
//! Data structures for representing pieces and shapes.
use crate::location::{Loc, Rot, Spin};
use crate::matrix::Mat;
use core::ops::Range;
/// Interface for representations of shapes in the abstract, not tied to any particular
/// position or orientation.
pub trait Shape<'c> {
/// Returns the cell data for this shape when given a specific rotation state.
fn cells(&self, rot: Rot) -> Cells<'c>;
}
/// Data structure representing a set of cells located somewhere on the board. Logically
/// this is like a set of (x,y) coordinates, but the implementation uses rowwise bit
/// patterns similar to [`Mat`] for more efficient [intersection
/// tests](Cells::intersects).
///
/// The cells in this representation cannot be rotated; rotations are achieved by
/// modifying the rotation state of a [`Loc`] and then determining the cells of the result
/// using [`Piece::cells`].
#[derive(Copy, Clone, Eq)]
pub struct Cells<'c> {
data: *const u16,
x: i16,
y: i16,
h: i16,
w: i16,
_slice: core::marker::PhantomData<&'c [u16]>,
}
impl PartialEq for Cells<'_> {
fn eq(&self, rhs: &Self) -> bool {
// XXX(iitalics): identical 'data' should imply identical width; width is only
// stored to make bounds tests faster but could theoretically be derived from the
// bits in each row
debug_assert!(self.data != rhs.data || self.w == rhs.w);
(self.data, self.x, self.y, self.h) == (rhs.data, rhs.x, rhs.y, rhs.h)
}
}
impl<'c> Cells<'c> {
/// Constructs cells from the initial extents and rowwise bit representations of the
/// occupied cells.
#[inline]
pub fn new(xs: Range<i16>, ys: Range<i16>, data: &'c [u16]) -> Self {
assert!(!xs.is_empty());
assert!(!ys.is_empty());
assert_eq!(ys.len(), data.len());
Self {
x: xs.start,
y: ys.start,
w: xs.end - xs.start,
h: ys.end - ys.start,
data: data.as_ptr(),
_slice: core::marker::PhantomData,
}
}
/// Returns a pair of ranges, `(xs, ys)`, which indicates the furthest extents that
/// may contain cells. For example, a north T piece at the origin would return
/// `(-1..=1, 0..=1)` since it contains leftmost cell (-1,0), rightmost cell (0,1),
/// and uppermost cell (0,1).
#[inline]
pub fn extents(&self) -> (Range<i16>, Range<i16>) {
(self.x..self.x + self.w, self.y..self.y + self.h)
}
#[inline]
fn data(&self) -> &'c [u16] {
let ptr = self.data;
let len = self.h as usize;
unsafe { core::slice::from_raw_parts(ptr, len) }
}
/// Returns true if any of the cells intersect with the given matrix.
pub fn intersects(&self, mat: &Mat) -> bool {
if self.x < 0 || self.x + self.w > mat.cols() {
return true;
}
for (dy, &row) in self.data().iter().enumerate() {
let y = self.y + dy as i16;
let mask = row << self.x; // should never overflow
if !mat.test_row(y, mask, 0) {
return true;
}
}
false
}
/// Translates the cells by the given amount in both directions.
#[inline]
pub fn translate(&mut self, dx: i16, dy: i16) {
self.x = self.x.checked_add(dx).expect("overflow/underflow");
self.y = self.y.checked_add(dy).expect("overflow/underflow");
}
#[cfg(test)]
pub(crate) fn coords(&self) -> impl Iterator<Item = (i16, i16)> + 'c {
let data = self.data();
let (xs, ys) = self.extents();
ys.enumerate()
.flat_map(move |(i, y)| xs.clone().enumerate().map(move |(j, x)| (i, j, (x, y))))
.filter_map(move |(i, j, pos)| {
if data[i] & (1 << j) != 0 {
Some(pos)
} else {
None
}
})
}
}
/// Represents the current state of a piece.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Piece<S> {
/// The shape of the piece.
pub shape: S,
/// The location of the piece.
pub loc: Loc,
}
impl<'c, S: Shape<'c>> Piece<S> {
/// Returns the cells occupied by this piece.
pub fn cells(&self) -> Cells<'c> {
let mut cells = self.shape.cells(self.loc.r);
cells.translate(self.loc.x, self.loc.y);
cells
}
/// Translates this piece by the given amount in both directions.
pub fn translate(&mut self, dx: i16, dy: i16) {
self.loc.x = self.loc.x.checked_add(dx).expect("overflow/underflow");
self.loc.y = self.loc.y.checked_add(dy).expect("overflow/underflow");
}
/// Rotates this piece in the given direction.
pub fn rotate(&mut self, dr: Spin) {
self.loc.r += dr;
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::mat;
use core::ops::RangeInclusive;
extern crate alloc;
use alloc::vec::Vec;
// .X.
// .XX origin at (1,1)
// ...
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
struct Tri;
impl Shape<'static> for Tri {
fn cells(&self, rot: Rot) -> Cells<'static> {
match rot {
Rot::N => Cells::new(0..2, 0..2, &[0b11, 0b01]),
Rot::E => Cells::new(0..2, -1..1, &[0b01, 0b11]),
Rot::S => Cells::new(-1..1, -1..1, &[0b10, 0b11]),
Rot::W => Cells::new(-1..1, 0..2, &[0b11, 0b10]),
}
}
}
fn coords(x: i16, y: i16, r: Rot) -> Vec<(i16, i16)> {
let piece = Piece {
shape: Tri,
loc: Loc { x, y, r },
};
let mut coords: Vec<_> = piece.cells().coords().collect();
coords.sort();
coords
}
#[test]
fn test_coords() {
assert_eq!(coords(5, 8, Rot::N), [(5, 8), (5, 9), (6, 8)]);
assert_eq!(coords(5, 8, Rot::E), [(5, 7), (5, 8), (6, 8),]);
assert_eq!(coords(5, 8, Rot::S), [(4, 8), (5, 7), (5, 8),]);
assert_eq!(coords(5, 8, Rot::W), [(4, 8), (5, 8), (5, 9),]);
}
const MAT: &Mat = mat! {
".........x";
"..x.....xx";
"xxx.xxx.xx";
};
fn isects(xs: RangeInclusive<i16>, y: i16, r: Rot) -> Vec<bool> {
xs.map(|x| {
let piece = Piece {
shape: Tri,
loc: Loc { x, y, r },
};
piece.cells().intersects(MAT)
})
.collect()
}
#[test]
#[allow(clippy::just_underscores_and_digits)]
fn test_intersects() {
use Rot::*;
let __ = false;
let xx = true;
// N.
// NN
assert_eq!(isects(0..=8, 0, N), [xx; 9]);
assert_eq!(isects(0..=8, 1, N), [__, xx, xx, __, __, __, __, xx, xx]);
assert_eq!(isects(0..=8, 2, N), [__, __, __, __, __, __, __, __, xx]);
assert_eq!(isects(0..=8, 3, N), [__; 9]);
// EE
// E.
assert_eq!(isects(0..=8, 0, E), [xx; 9]);
assert_eq!(isects(0..=8, 1, E), [xx, xx, xx, __, xx, xx, xx, xx, xx]);
assert_eq!(isects(0..=8, 2, E), [__, __, xx, __, __, __, __, __, xx]);
assert_eq!(isects(0..=8, 3, E), [__; 9]);
// SS
// .S
assert_eq!(isects(1..=9, 0, S), [xx; 9]);
assert_eq!(isects(1..=9, 1, S), [xx, xx, xx, xx, xx, xx, __, xx, xx]);
assert_eq!(isects(1..=9, 2, S), [__, xx, __, __, __, __, __, xx, xx]);
assert_eq!(isects(1..=9, 3, S), [__, __, __, __, __, __, __, __, xx]);
// .W
// WW
assert_eq!(isects(1..=9, 0, W), [xx; 9]);
assert_eq!(isects(1..=9, 1, W), [__, xx, xx, __, __, __, __, xx, xx]);
assert_eq!(isects(1..=9, 2, W), [__, __, __, __, __, __, __, __, xx]);
assert_eq!(isects(1..=9, 3, W), [__; 9]);
}
}