334 lines
10 KiB
Rust
334 lines
10 KiB
Rust
//! Data structures for representing pieces and shapes.
|
|
|
|
use crate::matrix::Mat;
|
|
|
|
use core::ops::Range;
|
|
|
|
/// Represents a location for a piece, including its orientation.
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct Loc {
|
|
/// Horizontal coordinate.
|
|
pub x: i16,
|
|
/// Vertical coordinate.
|
|
pub y: i16,
|
|
/// Rotation state.
|
|
pub r: Rot,
|
|
}
|
|
|
|
impl From<(i16, i16, Rot)> for Loc {
|
|
#[inline]
|
|
fn from((x, y, r): (i16, i16, Rot)) -> Self {
|
|
Loc { x, y, r }
|
|
}
|
|
}
|
|
|
|
impl From<(i16, i16)> for Loc {
|
|
#[inline]
|
|
fn from((x, y): (i16, i16)) -> Self {
|
|
(x, y, Rot::default()).into()
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for Loc {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
(self.x, self.y, self.r).fmt(f)
|
|
}
|
|
}
|
|
|
|
/// Represents a rotation state for a piece. The initial state is "north" (`N`), and there
|
|
/// are 4 total orientations to represent each 90 degree turn possible.
|
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default, Hash)]
|
|
#[repr(i8)]
|
|
pub enum Rot {
|
|
/// North; the initial state.
|
|
#[default]
|
|
N = 0,
|
|
/// East; clockwise ("cw") from north.
|
|
E = 1,
|
|
/// South; two rotations in any direction from north.
|
|
S = 2,
|
|
/// West; counterclockwise ("ccw") from north.
|
|
W = 3,
|
|
}
|
|
|
|
impl From<i8> for Rot {
|
|
#[inline]
|
|
fn from(v: i8) -> Self {
|
|
unsafe { core::mem::transmute(v & 3) }
|
|
}
|
|
}
|
|
|
|
/// 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<'_> {
|
|
#[inline]
|
|
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 core::hash::Hash for Cells<'_> {
|
|
#[inline]
|
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
|
(self.x, self.y, self.data()).hash(state)
|
|
}
|
|
}
|
|
|
|
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(&self, dx: i16, dy: i16) -> Self {
|
|
let mut copy = *self;
|
|
copy.x = self.x.checked_add(dx).expect("overflow/underflow");
|
|
copy.y = self.y.checked_add(dy).expect("overflow/underflow");
|
|
copy
|
|
}
|
|
|
|
#[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
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Debug for Cells<'_> {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
let data = self.data();
|
|
let (xs, ys) = self.extents();
|
|
write!(f, "Cells{{x0={},y0={},data=[", xs.start, ys.start)?;
|
|
for (i, &row) in data.iter().enumerate() {
|
|
if i > 0 {
|
|
write!(f, ",")?;
|
|
}
|
|
write!(f, "\"")?;
|
|
for j in 0..xs.len() {
|
|
let occ = row & (1 << j) != 0;
|
|
write!(f, "{}", if occ { 'x' } else { '.' })?;
|
|
}
|
|
write!(f, "\"")?;
|
|
}
|
|
write!(f, "]}}")?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Represents the current state of a piece.
|
|
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
|
pub struct Piece<T> {
|
|
/// The piece type (i.e. its shape).
|
|
pub ty: T,
|
|
/// The location of the piece.
|
|
pub loc: Loc,
|
|
}
|
|
|
|
impl<'c, T: Shape<'c>> Piece<T> {
|
|
/// Returns the cells occupied by this piece.
|
|
pub fn cells(&self) -> Cells<'c> {
|
|
self.ty.cells(self.loc.r).translate(self.loc.x, self.loc.y)
|
|
}
|
|
}
|
|
|
|
/// Interface for piece types that have a initial spawn location.
|
|
pub trait Spawn {
|
|
/// Returns the (x,y) coordinates where the piece should spawn.
|
|
fn spawn_pos(&self) -> (i16, i16);
|
|
}
|
|
|
|
impl<T: Spawn> Piece<T> {
|
|
pub fn spawn(ty: T) -> Self {
|
|
let loc = ty.spawn_pos().into();
|
|
Self { ty, loc }
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test {
|
|
use super::*;
|
|
use crate::mat;
|
|
|
|
use alloc::vec::Vec;
|
|
use core::ops::RangeInclusive;
|
|
|
|
#[test]
|
|
fn test_angle_from_i8() {
|
|
let rots = core::iter::repeat([Rot::N, Rot::E, Rot::S, Rot::W]).flatten();
|
|
let ints = 0i8..=127;
|
|
for (i, r) in ints.zip(rots) {
|
|
assert_eq!(r, i.into(), "fwd,i={i}");
|
|
}
|
|
let rots = core::iter::repeat([Rot::W, Rot::S, Rot::E, Rot::N]).flatten();
|
|
let ints = (-128i8..=-1).rev(); // -1,-2,...,-128
|
|
for (i, r) in ints.zip(rots) {
|
|
assert_eq!(r, i.into(), "bwd,i={i}");
|
|
}
|
|
}
|
|
|
|
// .X.
|
|
// .XX origin at (1,1)
|
|
// ...
|
|
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
|
pub 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 {
|
|
ty: 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 {
|
|
ty: 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]);
|
|
}
|
|
}
|