diff --git a/mino-code-gen/src/lib.rs b/mino-code-gen/src/lib.rs index 48e0f8f..d89bf56 100644 --- a/mino-code-gen/src/lib.rs +++ b/mino-code-gen/src/lib.rs @@ -18,6 +18,7 @@ pub fn compile_ruleset(in_path: impl AsRef, out_path: impl AsRef) -> #[derive(serde::Deserialize)] struct RulesData { shapes: BTreeMap>, + kicks: BTreeMap; 4]>, } /// Output data (Rust code) @@ -27,6 +28,9 @@ struct RulesCode { extents: Extents>, cells_bits: Vec, cells_indices: Vec, + kicks_tests: Vec<(i16, i16)>, + kicks_counts: Vec, + kicks_indices: Vec, } struct Extents { @@ -45,6 +49,7 @@ fn compile(rules: &RulesData) -> RulesCode { .collect(); let len_per_shape_rot = shape_indices.len() * 4; + let len_per_shape_kicks = shape_indices.len() * 4 * 4; let mut extents = Extents { x0: vec![i16::MAX; len_per_shape_rot], @@ -56,6 +61,18 @@ fn compile(rules: &RulesData) -> RulesCode { let mut cells_bits = vec![]; let mut cells_indices = vec![usize::MAX; len_per_shape_rot]; + let mut shapes_kicks: HashMap; 4]> = HashMap::new(); + for (shapes_string, kicks) in rules.kicks.iter() { + for ch in shapes_string.chars() { + let shape = std::iter::once(ch).collect::(); + shapes_kicks.insert(shape, kicks); + } + } + + let mut kicks_tests = vec![]; + let mut kicks_counts = vec![0; len_per_shape_kicks]; + let mut kicks_indices = vec![usize::MAX; len_per_shape_kicks]; + for (shape, &i0) in shape_indices.iter() { let coords = rules.shapes.get(shape).unwrap(); for r in 0u8..4 { @@ -67,6 +84,16 @@ fn compile(rules: &RulesData) -> RulesCode { extents.y1[i] = exts.y1; cells_indices[i] = find_or_insert(&mut cells_bits, &data); } + + let kicks = *shapes_kicks.get(shape).unwrap(); + for r0 in 0u8..4 { + for r1 in 0u8..4 { + let i = i0 * 16 + r0 as usize * 4 + r1 as usize; + let tests = process_kicks(kicks, r0, r1); + kicks_counts[i] = tests.len(); + kicks_indices[i] = find_or_insert(&mut kicks_tests, &tests); + } + } } RulesCode { @@ -74,6 +101,9 @@ fn compile(rules: &RulesData) -> RulesCode { extents, cells_bits, cells_indices, + kicks_tests, + kicks_counts, + kicks_indices, } } @@ -109,6 +139,20 @@ fn rotate(x: i16, y: i16, r: u8) -> (i16, i16) { xy } +fn process_kicks(kicks: &[Vec<(i16, i16)>; 4], r0: u8, r1: u8) -> Vec<(i16, i16)> { + if r0 == r1 { + return vec![]; + } + + let kicks0 = &kicks[r0 as usize]; + let kicks1 = &kicks[r1 as usize]; + kicks0 + .iter() + .zip(kicks1.iter()) + .map(|(&(x0, y0), &(x1, y1))| (x0 - x1, y0 - y1)) + .collect() +} + /// Finds the first occurence of `needle` in `haystack`, or appends a suffix of it (up to /// the entire length), returning the index within `haystack` that now contains `needle`. fn find_or_insert(haystack: &mut Vec, needle: &[T]) -> usize { @@ -150,6 +194,18 @@ impl RulesCode { let len = self.cells_indices.len(); writeln!(out, "pub const CELLS_INDEX: [usize; {len}] = {arr:?};")?; + let arr = &self.kicks_tests; + let len = self.kicks_tests.len(); + writeln!(out, "pub const KICKS_TESTS: [(i16, i16); {len}] = {arr:?};")?; + + let arr = &self.kicks_counts; + let len = self.kicks_counts.len(); + writeln!(out, "pub const KICKS_COUNT: [usize; {len}] = {arr:?};")?; + + let arr = &self.kicks_indices; + let len = self.kicks_indices.len(); + writeln!(out, "pub const KICKS_INDEX: [usize; {len}] = {arr:?};")?; + Ok(()) } } diff --git a/mino/src/srs.rs b/mino/src/srs.rs index b73eaee..1e6b1c1 100644 --- a/mino/src/srs.rs +++ b/mino/src/srs.rs @@ -1,5 +1,6 @@ //! Implementation of SRS shapes and movement quirks. +use crate::input::Kicks as KicksTrait; use crate::piece::{Cells, Rot, Shape as ShapeTrait}; mod code_gen { @@ -29,12 +30,23 @@ impl ShapeTrait<'static> for Shape { } } +impl KicksTrait<'static> for Shape { + fn kicks(&self, r0: Rot, r1: Rot) -> &'static [(i16, i16)] { + let i = (*self as usize) * 16 + (r0 as usize) * 4 + r1 as usize; + let idx = code_gen::KICKS_INDEX[i]; + let len = code_gen::KICKS_COUNT[i]; + &code_gen::KICKS_TESTS[idx..idx + len] + } +} + pub type Piece = crate::piece::Piece; #[cfg(test)] mod test { use super::*; + use crate::matrix::Mat; use crate::piece::Loc; + use crate::{input, mat}; use alloc::vec::Vec; use core::ops::Range; @@ -90,4 +102,74 @@ mod test { test_cells_all_rotations(Shape::T, &[(0, 1), (-1, 0), (0, 0), (1, 0)]); test_cells_all_rotations(Shape::Z, &[(1, 0), (0, 0), (0, 1), (-1, 1)]); } + + #[test] + fn test_srs_o_kicks_in_place() { + use input::Spin::*; + let mut piece = Piece { + shape: Shape::O, + loc: Loc { + x: 6, + y: 6, + r: Rot::N, + }, + }; + assert_eq!(input::rotate(&mut piece, Mat::EMPTY, Cw), Some(0)); + assert_eq!(piece.loc.x, 6); + assert_eq!(piece.loc.y, 7); + assert_eq!(input::rotate(&mut piece, Mat::EMPTY, Cw), Some(0)); + assert_eq!(piece.loc.x, 7); + assert_eq!(piece.loc.y, 7); + assert_eq!(input::rotate(&mut piece, Mat::EMPTY, Cw), Some(0)); + assert_eq!(piece.loc.x, 7); + assert_eq!(piece.loc.y, 6); + assert_eq!(input::rotate(&mut piece, Mat::EMPTY, Cw), Some(0)); + assert_eq!(piece.loc.x, 6); + assert_eq!(piece.loc.y, 6); + } + + #[test] + fn test_srs_t_spin_triple() { + const SETUP: &Mat = mat! { + "......xx.."; + ".......x.."; + "xxxxxx.xxx"; + "xxxxx..xxx"; + "xxxxxx.xxx"; + }; + let mut piece = Piece { + shape: Shape::T, + loc: Loc { + x: 5, + y: 3, + r: Rot::N, + }, + }; + assert_eq!(input::rotate(&mut piece, SETUP, input::Spin::Ccw), Some(4)); + assert_eq!(piece.loc.x, 6); + assert_eq!(piece.loc.y, 1); + assert_eq!(piece.loc.r, Rot::W); + } + + #[test] + fn test_srs_z_spin_triple() { + const SETUP: &Mat = mat! { + "....x....."; + "xxxxxx.xxx"; + "xxxxx..xxx"; + "xxxxx.xxxx"; + }; + let mut piece = Piece { + shape: Shape::Z, + loc: Loc { + x: 5, + y: 3, + r: Rot::N, + }, + }; + assert_eq!(input::rotate(&mut piece, SETUP, input::Spin::Cw), Some(3)); + assert_eq!(piece.loc.x, 5); + assert_eq!(piece.loc.y, 1); + assert_eq!(piece.loc.r, Rot::E); + } }