From 62d24bec0ee4a4e20dc61da09891c42c89aed017 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Tue, 16 Dec 2025 21:28:42 -0800 Subject: [PATCH] Rotation works! --- tetris/src/blocks.rs | 108 ++++++++++++++++++++++---------- tetris/src/test/shape_layout.rs | 52 +++++++++------ 2 files changed, 106 insertions(+), 54 deletions(-) diff --git a/tetris/src/blocks.rs b/tetris/src/blocks.rs index e4c13d1..e4725b9 100644 --- a/tetris/src/blocks.rs +++ b/tetris/src/blocks.rs @@ -65,7 +65,7 @@ const Y_MAX: usize = 20; /// Stores shape data in an asset file, likely toml #[derive(Asset, TypePath, Debug, Deserialize)] struct ShapeAsset { - layout: ShapeLayout, + layout: Vec>, #[serde(default = "default_tint")] tint: String, #[serde(skip)] @@ -81,11 +81,12 @@ fn default_tint() -> String { impl ShapeAsset { fn as_bundle(&self) -> impl Bundle { // Get shape block's relative positions - let [a, b, c, d] = self.layout.positions(); + let layout = ShapeLayout::new(self.layout.clone()); + let [a, b, c, d] = layout.positions(Orientation::Up); ( GridPosition { x: 5, y: 5 }, // TEMP - self.layout.clone(), + layout.clone(), Orientation::Up, related!(ShapeBlocks[ (self.mesh.clone(), self.material.clone(), a), @@ -168,30 +169,58 @@ impl From<(isize, isize)> for RelativePosition { /// Layout for a given shape #[derive(Debug, Component, Deserialize, PartialEq, Clone)] -pub(crate) struct ShapeLayout(pub Vec>); +pub(crate) struct ShapeLayout { + pub matrix: Vec>, + #[serde(skip)] + symmetrical: bool, +} impl ShapeLayout { - pub(crate) fn positions(&self) -> [RelativePosition; 4] { + pub(crate) fn new(matrix: Vec>) -> Self { + let mut s = Self { + matrix, + symmetrical: true, + }; + + // Check if symmetrical + s.symmetrical = s.matrix == s.rotated_matrix(Orientation::Down); + + s + } + + pub(crate) fn positions(&self, o: Orientation) -> [RelativePosition; 4] { let mut c: Vec = Vec::with_capacity(4); + let layout = self.rotated_matrix(o); + let center: (isize, isize) = { // This could be less lines of code but it would be confusing af - let y = if self.0.len() % 2 == 1 { - self.0.len() / 2 + let y = if layout.len() % 2 == 1 { + layout.len() / 2 } else { - self.0.len() / 2 - 1 + layout.len() / 2 - 1 }; - let x = if self.0[0].len() % 2 == 1 { - self.0[0].len() / 2 + let x = if layout[0].len() % 2 == 1 { + layout[0].len() / 2 } else { - self.0[0].len() / 2 - 1 + layout[0].len() / 2 - 1 + }; + + let (x_off, y_off) = match self.symmetrical { + true => (0, 0), + false => match o { + Orientation::Up => (0, 0), + Orientation::Right => (0, 0), + Orientation::Down => (0, 1), + Orientation::Left => (1, 0), + }, }; - (x as isize, y as isize) + (x_off + x as isize, y_off + y as isize) }; - for (y, nested) in self.0.iter().rev().enumerate() { + for (y, nested) in layout.iter().rev().enumerate() { for (x, val) in nested.iter().enumerate() { if *val == 1 { c.push(RelativePosition { @@ -205,40 +234,52 @@ impl ShapeLayout { [c[0], c[1], c[2], c[3]] } - pub(crate) fn rotated(&self, o: Orientation) -> Self { - info!("Rotation: {:?} {:?}", o, self); + pub(crate) fn rotated_matrix(&self, o: Orientation) -> Vec> { // Now we have the base layout, rotate it based on orientation match o { Orientation::Up => { // The identity - self.clone() + self.matrix.clone() } Orientation::Right => { // The main algorithm - let cols = self.0[0].len(); - let rows = self.0.len(); + let cols = self.matrix[0].len(); + let rows = self.matrix.len(); - let mut rotated = vec![vec![0;rows];cols]; + let mut rotated = vec![vec![0; rows]; cols]; // (i, j) -> (j,rows-1-i) for i in 0..rows { for j in 0..cols { - rotated[j][rows - 1 - i] = self.0[i][j]; + rotated[j][rows - 1 - i] = self.matrix[i][j]; } } - ShapeLayout(rotated) + rotated } Orientation::Down => { - // Implemented as repeated turns right - self.rotated(Orientation::Right) - .rotated(Orientation::Right) + // HACK: Implemented as repeated turns right + // TODO: Just hard-code this + ShapeLayout { + matrix: self.rotated_matrix(Orientation::Right), + symmetrical: false + }.rotated_matrix(Orientation::Right) } Orientation::Left => { - // Implemented as repeated turns right - self.rotated(Orientation::Right) - .rotated(Orientation::Right) - .rotated(Orientation::Right) + // The main algorithm + let cols = self.matrix[0].len(); + let rows = self.matrix.len(); + + let mut rotated = vec![vec![0; rows]; cols]; + + // (i, j) -> (cols-1-j,i) + for i in 0..rows { + for j in 0..cols { + rotated[cols - 1 - j][i] = self.matrix[i][j]; + } + } + + rotated } } } @@ -391,7 +432,6 @@ fn update_grid_position_transform( >, ) { query.iter_mut().for_each(|(gp, mut t)| { - info!("Updating grid position to {:?}", gp); t.translation = gp.into(); }); } @@ -415,7 +455,7 @@ fn propogate_orientation( mut children: Query<&mut GridPosition, Without>, ) { parent.iter().for_each(|(parent_gp, parent_o, sl, sbs)| { - let new_layout = sl.rotated(*parent_o).positions(); + let new_layout = sl.positions(*parent_o); sbs.iter().zip(new_layout).for_each(|(e, rp)| { let mut gp = children.get_mut(e).unwrap(); @@ -491,25 +531,25 @@ fn handle_movement( moves.read().for_each(|m| match m { Movement::Left => { grid_positions.iter_mut().for_each(|mut gp| { - info!("moving shape left"); + debug!("moving shape left"); *gp -= RelativePosition::new(1, 0); }); } Movement::Right => { grid_positions.iter_mut().for_each(|mut gp| { - info!("moving shape right"); + debug!("moving shape right"); *gp += RelativePosition::new(1, 0); }); } Movement::Down => { grid_positions.iter_mut().for_each(|mut gp| { - info!("moving shape down"); + debug!("moving shape down"); *gp -= RelativePosition::new(0, 1); }); } Movement::Rotate => { orientations.iter_mut().for_each(|mut o| { - info!("rotating shape"); + debug!("rotating shape"); *o = o.rotated(); }); } diff --git a/tetris/src/test/shape_layout.rs b/tetris/src/test/shape_layout.rs index 910109c..e95debd 100644 --- a/tetris/src/test/shape_layout.rs +++ b/tetris/src/test/shape_layout.rs @@ -2,7 +2,7 @@ use super::*; #[test] fn test_shape_layout_01a() { - let actual = ShapeLayout(vec![vec![0, 1, 0], vec![1, 1, 1]]).positions(); + let actual = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]).positions(Orientation::Up); let expected: [RelativePosition; 4] = [(-1, 0).into(), (0, 0).into(), (1, 0).into(), (0, 1).into()]; @@ -12,7 +12,8 @@ fn test_shape_layout_01a() { #[test] fn test_shape_layout_01b() { - let actual = ShapeLayout(vec![vec![1, 0], vec![1, 1], vec![1, 0]]).positions(); + let actual = + ShapeLayout::new(vec![vec![1, 0], vec![1, 1], vec![1, 0]]).positions(Orientation::Up); let expected: [RelativePosition; 4] = [(0, -1).into(), (0, 0).into(), (1, 0).into(), (0, 1).into()]; @@ -20,9 +21,20 @@ fn test_shape_layout_01b() { debug_assert_eq!(expected, actual); } +#[test] +fn test_shape_layout_01c() { + let actual = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]).positions(Orientation::Down); + + let expected: [RelativePosition; 4] = + [(0, -1).into(), (-1, 0).into(), (0, 0).into(), (1, 0).into(),]; + + debug_assert_eq!(expected, actual); +} + #[test] fn test_shape_layout_02a() { - let actual = ShapeLayout(vec![vec![1], vec![1], vec![1], vec![1]]).positions(); + let actual = + ShapeLayout::new(vec![vec![1], vec![1], vec![1], vec![1]]).positions(Orientation::Up); let expected: [RelativePosition; 4] = [(0, -1).into(), (0, 0).into(), (0, 1).into(), (0, 2).into()]; @@ -32,7 +44,7 @@ fn test_shape_layout_02a() { #[test] fn test_shape_layout_02b() { - let actual = ShapeLayout(vec![vec![1, 1, 1, 1]]).positions(); + let actual = ShapeLayout::new(vec![vec![1, 1, 1, 1]]).positions(Orientation::Up); let expected: [RelativePosition; 4] = [(-1, 0).into(), (0, 0).into(), (1, 0).into(), (2, 0).into()]; @@ -42,7 +54,7 @@ fn test_shape_layout_02b() { #[test] fn test_shape_layout_03() { - let actual = ShapeLayout(vec![vec![1, 1, 0], vec![0, 1, 1]]).positions(); + let actual = ShapeLayout::new(vec![vec![1, 1, 0], vec![0, 1, 1]]).positions(Orientation::Up); let expected: [RelativePosition; 4] = [(0, 0).into(), (1, 0).into(), (-1, 1).into(), (0, 1).into()]; @@ -52,28 +64,29 @@ fn test_shape_layout_03() { #[test] fn test_rotation_01() { - let actual = ShapeLayout(vec![vec![1], vec![1], vec![1], vec![1]]).rotated(Orientation::Left); + let actual = + ShapeLayout::new(vec![vec![1], vec![1], vec![1], vec![1]]).positions(Orientation::Left); - let expected = ShapeLayout(vec![vec![1, 1, 1, 1]]); + let expected = ShapeLayout::new(vec![vec![1, 1, 1, 1]]).positions(Orientation::Up); debug_assert_eq!(expected, actual); } #[test] fn test_rotation_02() { - let base = ShapeLayout(vec![vec![0, 1, 0], vec![1, 1, 1]]); + let base = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]); - let expected_up = base.clone(); - let actual_up = base.rotated(Orientation::Up); + let expected_up = base.matrix.clone(); + let actual_up = base.rotated_matrix(Orientation::Up); - let expected_down = ShapeLayout(vec![vec![1, 1, 1], vec![0, 1, 0]]); - let actual_down = base.rotated(Orientation::Down); + let expected_down = ShapeLayout::new(vec![vec![1, 1, 1], vec![0, 1, 0]]).matrix; + let actual_down = base.rotated_matrix(Orientation::Down); - let expected_right = ShapeLayout(vec![vec![1, 0], vec![1, 1], vec![1, 0]]); - let actual_right = base.rotated(Orientation::Right); + let expected_right = ShapeLayout::new(vec![vec![1, 0], vec![1, 1], vec![1, 0]]).matrix; + let actual_right = base.rotated_matrix(Orientation::Right); - let expected_left = ShapeLayout(vec![vec![0, 1], vec![1, 1], vec![0, 1]]); - let actual_left = base.rotated(Orientation::Left); + let expected_left = ShapeLayout::new(vec![vec![0, 1], vec![1, 1], vec![0, 1]]).matrix; + let actual_left = base.rotated_matrix(Orientation::Left); debug_assert_eq!(expected_up, actual_up); debug_assert_eq!(expected_down, actual_down); @@ -83,11 +96,10 @@ fn test_rotation_02() { #[test] fn test_rotation_03() { - let base = ShapeLayout(vec![vec![0, 1, 0], vec![1, 1, 1]]); + let base = ShapeLayout::new(vec![vec![0, 1, 0], vec![1, 1, 1]]); - let actual_right = base.rotated(Orientation::Right); - let expected_right = ShapeLayout(vec![vec![1, 0], vec![1, 1], vec![1, 0]]); + let actual_right = base.rotated_matrix(Orientation::Right); + let expected_right = ShapeLayout::new(vec![vec![1, 0], vec![1, 1], vec![1, 0]]).matrix; debug_assert_eq!(expected_right, actual_right); } -