//! 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 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(&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, ys: Range, 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, Range) { (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 + '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 { /// The piece type (i.e. its shape). pub ty: T, /// The location of the piece. pub loc: Loc, } impl<'c, T: Shape<'c>> Piece { /// 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 Piece { 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, y: i16, r: Rot) -> Vec { 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]); } }