Add Shape trait, Piece, Cells types
This commit is contained in:
parent
fa6aac86f6
commit
2c4afe54a0
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod location;
|
||||
pub mod matrix;
|
||||
pub mod piece;
|
||||
|
||||
pub use location::Loc;
|
||||
pub use matrix::Mat;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue