From 92ee8c759899e2b20d30815465f59e34b24ec380 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Tue, 16 Dec 2025 20:47:59 -0800 Subject: [PATCH] Rotation exists, but needs polish --- justfile | 3 + tetris/src/blocks.rs | 98 ++++++++++++++++++++++++++++++--- tetris/src/main.rs | 2 + tetris/src/test/shape_layout.rs | 42 ++++++++++++++ 4 files changed, 137 insertions(+), 8 deletions(-) diff --git a/justfile b/justfile index a65e767..e86ae87 100644 --- a/justfile +++ b/justfile @@ -3,6 +3,9 @@ VERSION := `git rev-parse --short HEAD` check: cargo check -p tetris +clippy: + cargo clippy -p tetris + run: cargo run -p tetris diff --git a/tetris/src/blocks.rs b/tetris/src/blocks.rs index 12e4279..e4c13d1 100644 --- a/tetris/src/blocks.rs +++ b/tetris/src/blocks.rs @@ -42,10 +42,11 @@ impl Plugin for BlocksPlugin { Update, ( updated_shape_asset.run_if(on_message::>), - update_grid_position.run_if( + update_grid_position_transform.run_if( any_component_added:: .or(any_component_changed::), ), + propogate_orientation.run_if(any_component_changed::), propogate_grid_position.run_if(any_component_changed::), handle_kb_input.run_if(on_message::), handle_movement.run_if(on_message::), @@ -84,6 +85,8 @@ impl ShapeAsset { ( GridPosition { x: 5, y: 5 }, // TEMP + self.layout.clone(), + Orientation::Up, related!(ShapeBlocks[ (self.mesh.clone(), self.material.clone(), a), (self.mesh.clone(), self.material.clone(), b), @@ -164,12 +167,13 @@ impl From<(isize, isize)> for RelativePosition { } /// Layout for a given shape -#[derive(Debug, Deserialize)] +#[derive(Debug, Component, Deserialize, PartialEq, Clone)] pub(crate) struct ShapeLayout(pub Vec>); impl ShapeLayout { pub(crate) fn positions(&self) -> [RelativePosition; 4] { let mut c: Vec = Vec::with_capacity(4); + 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 { @@ -200,6 +204,44 @@ impl ShapeLayout { [c[0], c[1], c[2], c[3]] } + + pub(crate) fn rotated(&self, o: Orientation) -> Self { + info!("Rotation: {:?} {:?}", o, self); + // Now we have the base layout, rotate it based on orientation + match o { + Orientation::Up => { + // The identity + self.clone() + } + Orientation::Right => { + // The main algorithm + let cols = self.0[0].len(); + let rows = self.0.len(); + + 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]; + } + } + + ShapeLayout(rotated) + } + Orientation::Down => { + // Implemented as repeated turns right + self.rotated(Orientation::Right) + .rotated(Orientation::Right) + } + Orientation::Left => { + // Implemented as repeated turns right + self.rotated(Orientation::Right) + .rotated(Orientation::Right) + .rotated(Orientation::Right) + } + } + } } #[derive(Default)] @@ -342,7 +384,7 @@ fn add_relative_position( } /// Populate Transform when GridPosition is added to an entity -fn update_grid_position( +fn update_grid_position_transform( mut query: Query< (&GridPosition, &mut Transform), Or<(Added, Changed)>, @@ -354,6 +396,7 @@ fn update_grid_position( }); } +/// When grid position is updated on a shape, apply that to blocks fn propogate_grid_position( parent: Query<(&GridPosition, &ShapeBlocks), Changed>, mut children: Query<(&mut GridPosition, &RelativePosition), Without>, @@ -366,6 +409,21 @@ fn propogate_grid_position( }); } +/// When a shape's orientation changes, the blocks need to move +fn propogate_orientation( + parent: Query<(&GridPosition, &Orientation, &ShapeLayout, &ShapeBlocks), Changed>, + mut children: Query<&mut GridPosition, Without>, +) { + parent.iter().for_each(|(parent_gp, parent_o, sl, sbs)| { + let new_layout = sl.rotated(*parent_o).positions(); + + sbs.iter().zip(new_layout).for_each(|(e, rp)| { + let mut gp = children.get_mut(e).unwrap(); + *gp = parent_gp.clone() + rp; + }); + }); +} + /// Movement message used to propose/plan a move #[derive(Debug, Message)] enum Movement { @@ -405,31 +463,55 @@ fn handle_kb_input(mut input: MessageReader, mut moves: MessageWr ); } +#[derive(Component, Default, Debug, Clone, Copy)] +pub(crate) enum Orientation { + #[default] + Up, + Right, + Down, + Left, +} + +impl Orientation { + fn rotated(&self) -> Orientation { + match self { + Orientation::Up => Orientation::Right, + Orientation::Right => Orientation::Down, + Orientation::Down => Orientation::Left, + Orientation::Left => Orientation::Up, + } + } +} + fn handle_movement( mut moves: MessageReader, - mut query: Query<&mut GridPosition, With>, + mut grid_positions: Query<&mut GridPosition, With>, + mut orientations: Query<&mut Orientation, With>, ) { moves.read().for_each(|m| match m { Movement::Left => { - query.iter_mut().for_each(|mut gp| { + grid_positions.iter_mut().for_each(|mut gp| { info!("moving shape left"); *gp -= RelativePosition::new(1, 0); }); } Movement::Right => { - query.iter_mut().for_each(|mut gp| { + grid_positions.iter_mut().for_each(|mut gp| { info!("moving shape right"); *gp += RelativePosition::new(1, 0); }); } Movement::Down => { - query.iter_mut().for_each(|mut gp| { + grid_positions.iter_mut().for_each(|mut gp| { info!("moving shape down"); *gp -= RelativePosition::new(0, 1); }); } Movement::Rotate => { - todo!() + orientations.iter_mut().for_each(|mut o| { + info!("rotating shape"); + *o = o.rotated(); + }); } }) } diff --git a/tetris/src/main.rs b/tetris/src/main.rs index 5b17969..0d788cd 100644 --- a/tetris/src/main.rs +++ b/tetris/src/main.rs @@ -1,6 +1,8 @@ mod blocks; mod debug; mod fighter; + +#[cfg(test)] mod test; use engine::*; diff --git a/tetris/src/test/shape_layout.rs b/tetris/src/test/shape_layout.rs index 51de15f..910109c 100644 --- a/tetris/src/test/shape_layout.rs +++ b/tetris/src/test/shape_layout.rs @@ -49,3 +49,45 @@ fn test_shape_layout_03() { debug_assert_eq!(expected, actual); } + +#[test] +fn test_rotation_01() { + let actual = ShapeLayout(vec![vec![1], vec![1], vec![1], vec![1]]).rotated(Orientation::Left); + + let expected = ShapeLayout(vec![vec![1, 1, 1, 1]]); + + debug_assert_eq!(expected, actual); +} + +#[test] +fn test_rotation_02() { + let base = ShapeLayout(vec![vec![0, 1, 0], vec![1, 1, 1]]); + + let expected_up = base.clone(); + let actual_up = base.rotated(Orientation::Up); + + let expected_down = ShapeLayout(vec![vec![1, 1, 1], vec![0, 1, 0]]); + let actual_down = base.rotated(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_left = ShapeLayout(vec![vec![0, 1], vec![1, 1], vec![0, 1]]); + let actual_left = base.rotated(Orientation::Left); + + debug_assert_eq!(expected_up, actual_up); + debug_assert_eq!(expected_down, actual_down); + debug_assert_eq!(expected_right, actual_right); + debug_assert_eq!(expected_left, actual_left); +} + +#[test] +fn test_rotation_03() { + let base = ShapeLayout(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]]); + + debug_assert_eq!(expected_right, actual_right); +} +