Add input::rotate(), Kicks trait

This commit is contained in:
tali 2022-12-14 12:28:22 -05:00
parent c5f7d9d622
commit 6726326ec8
1 changed files with 83 additions and 0 deletions

View File

@ -48,6 +48,21 @@ impl core::ops::AddAssign<Spin> for Rot {
}
}
/// Interface for shapes that have a table of "kicks" to perform when checking if a
/// rotation is valid.
pub trait Kicks<'k> {
/// Returns the list of offsets to test when rotating from `r0` to `r1`. Offsets are
/// tested from left to right. If `r0` == `r1`, then this function is permitted to
/// return any list (incl. empty list) since that condition will never occur in
/// practice.
///
/// Note: `(0,0)` is not tested by default so it must be explicitly included if an
/// immobile test should be performed. In this implementation of SRS, the O piece
/// moves around an origin so its center must be corrected by a singular "kick" for
/// each rotation, thus the O piece never tests `(0,0)` during a rotation.
fn kicks(&self, r0: Rot, r1: Rot) -> &'k [(i16, i16)];
}
/// Drops `piece` until it hits `mat`. Returns true if the piece fell at least one block.
pub fn drop<'c, S>(piece: &mut Piece<S>, mat: &Mat) -> bool
where
@ -91,6 +106,32 @@ where
false
}
/// Rotates a piece, performing kicks to find a final location. Returns `Some(idx)` if
/// successful, where `idx` is the index into the kick table performed. Index `0` means it
/// was not kicked. Returns `None` if the rotation failed.
pub fn rotate<'c, 'k, S>(piece: &mut Piece<S>, mat: &Mat, dir: Spin) -> Option<usize>
where
S: Shape<'c> + Kicks<'k>,
{
let r0 = piece.loc.r;
let r1 = piece.loc.r + dir;
let kicks = piece.shape.kicks(r0, r1);
piece.loc.r = r1;
let cells = piece.cells();
for (i, &(dx, dy)) in kicks.iter().enumerate() {
if !cells.translate(dx, dy).intersects(mat) {
piece.loc.x += dx;
piece.loc.y += dy;
return Some(i);
}
}
piece.loc.r = r0;
None
}
#[cfg(test)]
mod test {
use super::*;
@ -179,4 +220,46 @@ mod test {
assert_eq!(shift(3, 1, Rot::E, Left), 3);
assert_eq!(shift(3, 1, Rot::E, Right), 3);
}
impl Kicks<'static> for Tri {
fn kicks(&self, r0: Rot, r1: Rot) -> &'static [(i16, i16)] {
match (r0, r1) {
(Rot::N, Rot::E) => &[(0, 0), (0, 1)],
(Rot::N, Rot::W) => &[(0, 0), (1, 0)],
(Rot::E, Rot::S) => &[(0, 0), (1, 0)],
(Rot::E, Rot::N) => &[(0, 0), (0, -1)],
(Rot::S, Rot::W) => &[(0, 0), (0, -1)],
(Rot::S, Rot::E) => &[(0, 0), (-1, 0)],
(Rot::W, Rot::N) => &[(0, 0), (-1, 0)],
(Rot::W, Rot::S) => &[(0, 0), (0, 1)],
(_, _) => &[(0, 0)], // flip or non rotation
}
}
}
fn rotate(x: i16, y: i16, r: Rot, dir: Spin) -> (i16, i16, Rot, Option<usize>) {
let loc = Loc { x, y, r };
let mut piece = Piece { shape: Tri, loc };
let result = super::rotate(&mut piece, MAT, dir);
assert_eq!(result.is_some(), piece.loc != loc, "{:?},{:?}", piece, loc);
(piece.loc.x, piece.loc.y, piece.loc.r, result)
}
#[test]
fn test_rotate() {
use Rot::*;
use Spin::*;
assert_eq!(rotate(3, 2, N, Cw), (3, 2, E, Some(0)));
assert_eq!(rotate(3, 2, E, Cw), (3, 2, S, Some(0)));
assert_eq!(rotate(3, 2, S, Cw), (3, 2, W, Some(0)));
assert_eq!(rotate(3, 2, W, Cw), (3, 2, N, Some(0)));
assert_eq!(rotate(3, 2, N, Ccw), (3, 2, W, Some(0)));
assert_eq!(rotate(3, 2, W, Ccw), (3, 2, S, Some(0)));
assert_eq!(rotate(3, 2, S, Ccw), (3, 2, E, Some(0)));
assert_eq!(rotate(3, 2, E, Ccw), (3, 2, N, Some(0)));
assert_eq!(rotate(7, 1, S, Cw), (7, 1, W, Some(0)));
assert_eq!(rotate(7, 1, S, Ccw), (7, 1, S, None)); // locked in
assert_eq!(rotate(0, 1, N, Ccw), (1, 1, W, Some(1))); // kicks right
assert_eq!(rotate(0, 1, N, Cw), (0, 2, E, Some(1))); // kicks up
}
}