From 6726326ec8a51835ae664ac07ececc12a0c9c0cc Mon Sep 17 00:00:00 2001 From: tali Date: Wed, 14 Dec 2022 12:28:22 -0500 Subject: [PATCH] Add input::rotate(), Kicks trait --- mino/src/input.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/mino/src/input.rs b/mino/src/input.rs index d9eac83..cf0a3f2 100644 --- a/mino/src/input.rs +++ b/mino/src/input.rs @@ -48,6 +48,21 @@ impl core::ops::AddAssign 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, 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, mat: &Mat, dir: Spin) -> Option +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) { + 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 + } }