From 50c802fdab3f727406ead2df924a6d252d3ed433 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Sun, 9 Nov 2025 21:56:38 -0800 Subject: [PATCH] Refactor: change how shapes are represented. --- src/bin/tetris/main.rs | 241 +++++++++++++++++++---------------------- src/bin/tetris/test.rs | 174 +++++++++++++++++++++++++---- 2 files changed, 263 insertions(+), 152 deletions(-) diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index 50af46a..779ccb8 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -264,7 +264,7 @@ struct ShapeStore(Option); impl Display for ShapeStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.0 { + match &self.0 { Some(inner) => write!(f, "{}", inner.as_ascii()), None => write!(f, "---"), } @@ -572,10 +572,9 @@ fn init_debug_ui(mut commands: Commands) { }); } -#[derive(Component, Debug, Clone, Copy)] -enum Shape { - M4(Mat4), - M3(Mat3), +#[derive(Component, Debug, Clone)] +struct Shape { + layout: ShapeBlockLayout, } impl Default for Shape { @@ -586,176 +585,160 @@ impl Default for Shape { impl Display for Shape { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_ascii()) + write!(f, "{}", self.layout.as_ascii()) } } -impl Shape { - fn from_mat4(input: Mat4) -> Self { - Self::M4(input) - } - - fn from_mat3(input: Mat3) -> Self { - Self::M3(input) +impl From>> for Shape { + fn from(inner: Vec>) -> Shape { + Shape { + layout: ShapeBlockLayout { inner }, + } } +} +impl Shape { fn new_o() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 0., 0.], - [0., 1., 1., 0.], - [0., 1., 1., 0.], - [0., 0., 0., 0.], - ])) + vec![vec![1, 1], vec![1, 1]].into() } fn new_t() -> Self { - Self::from_mat3(Mat3::from_cols_array_2d(&[ - [0., 1., 0.], - [1., 1., 1.], - [0., 0., 0.], - ])) + vec![ + vec![0, 1, 0], + vec![1, 1, 1], + vec![0, 0, 0] + ].into() } fn new_l() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 0., 0.], - [0., 1., 0., 0.], - [0., 1., 0., 0.], - [0., 1., 1., 0.], - ])) + vec![vec![1, 0], vec![1, 0], vec![1, 1]].into() } fn new_j() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 0., 0.], - [0., 0., 1., 0.], - [0., 0., 1., 0.], - [0., 1., 1., 0.], - ])) + vec![vec![0, 1], vec![0, 1], vec![1, 1]].into() } fn new_s() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 0., 0.], - [0., 1., 1., 0.], - [1., 1., 0., 0.], - [0., 0., 0., 0.], - ])) + vec![vec![0, 1, 1], vec![1, 1, 0]].into() } fn new_z() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 0., 0.], - [1., 1., 0., 0.], - [0., 1., 1., 0.], - [0., 0., 0., 0.], - ])) + vec![vec![1, 1, 0], vec![0, 1, 1]].into() } fn new_i() -> Self { - Self::from_mat4(Mat4::from_cols_array_2d(&[ - [0., 0., 1., 0.], - [0., 0., 1., 0.], - [0., 0., 1., 0.], - [0., 0., 1., 0.], - ])) + vec![vec![1], vec![1], vec![1], vec![1]].into() } // Rotates 90 degrees to the right // https://stackoverflow.com/a/8664879 fn rotated(&self) -> Self { - match self { - Self::M4(inner) => { - let mut new_self = inner.transpose(); - for i in 0..4 { - let col = new_self.col_mut(i); - *col = Vec4::new(col[3], col[2], col[1], col[0]); - } - Self::M4(new_self) - } - Self::M3(inner) => { - let mut new_self = inner.transpose(); - for i in 0..3 { - let col = new_self.col_mut(i); - *col = Vec3::new(col[2], col[1], col[0]); - } - Self::M3(new_self) - } + Self { + layout: self.layout.rotated(), } } + fn reposition( + (x_offset, y_offset): (isize, isize), + center: &GridPosition, + ) -> Result { + center.with_offset(x_offset, y_offset) + } + fn coordinates( &self, center: &GridPosition, ) -> impl Iterator> { - let mut v: Vec> = Vec::new(); - match self { - Self::M4(inner) => { - for (i, y) in (-1..3).rev().enumerate() { - let c = inner.col(i); - for (j, x) in (-1..3).enumerate() { - if c[j] == 1.0 { - v.push(center.with_offset(x, y)); - } - } - } - } - Self::M3(inner) => { - for (i, y) in (-1..2).rev().enumerate() { - let c = inner.col(i); - for (j, x) in (-1..2).enumerate() { - if c[j] == 1.0 { - v.push(center.with_offset(x, y)); - } - } - } - } - }; - v.into_iter() + self.layout + .coordinates() + .map(|(x, y)| Shape::reposition((x, y), center)) } fn as_ascii(&self) -> String { - let mut output = String::default(); + self.layout.as_ascii() + } - match self { - Self::M4(this) => { - for i in 0..4 { - let col = this.col(i).to_array(); - output += format!("{}{}{}{}\n", col[0], col[1], col[2], col[3]).as_str(); - } - } - Self::M3(this) => { - for i in 0..3 { - let col = this.col(i).to_array(); - output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str(); - } + fn height(&self) -> usize { + self.layout.height() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ShapeBlockLayout { + inner: Vec>, +} + +impl ShapeBlockLayout { + fn rotated(&self) -> Self { + let mut inner = vec![]; + for _ in 0..self.inner[0].len() { + inner.push(vec![]); + } + for y in self.inner.iter() { + for (j, x) in y.iter().enumerate() { + inner[j].insert(0, *x); } + } + ShapeBlockLayout { inner } + } + + fn center(&self) -> (usize, usize) { + let mid_x = match self.inner[0].len() % 2 { + 0 => (self.inner[0].len() - 1) / 2, + 1 => self.inner[0].len() / 2, + + _ => panic!("That's not how mod works!"), + }; + + let mid_y = match self.inner.len() % 2 { + // If we have an even number of elements + 0 => self.inner.len().div_ceil(2), + // If we have an odd number of elements + 1 => self.inner.len() / 2, + + _ => panic!("That's not how mod works!"), }; - output + (mid_x, mid_y) } - fn height(&self) -> usize { - let mut x = 0; + fn coordinates(&self) -> impl Iterator { + let (mid_x, mid_y) = self.center(); + // Loop over outer vec (i) + self.inner + .iter() + .rev() + .enumerate() + .flat_map(move |(i, ys)| { + ys.iter().enumerate().filter_map(move |(j, val)| { + // this_x = j - mid_x + let x = j as isize - mid_x as isize; + // this_y = i - mid_y + let y = i as isize - mid_y as isize; + + (*val > 0).then_some((x, y)) + }) + }) + } - match self { - Self::M4(this) => { - for i in 0..4 { - if this.col(i).to_array().contains(&1.0) { - x += 1 - } - } - } - Self::M3(this) => { - for i in 0..3 { - if this.col(i).to_array().contains(&1.0) { - x += 1 - } + // TODO: Make fnctional with chaining + fn as_ascii(&self) -> String { + let mut s = String::new(); + for y in self.inner.iter() { + for x in y { + match x { + 0 => s.push('0'), + 1 => s.push('1'), + _ => panic!("aur nau"), } } - }; + s.push('\n'); + } + s + } - x + fn height(&self) -> usize { + self.inner.len() } } @@ -1056,7 +1039,7 @@ fn movement( MovementDirection::Down | MovementDirection::Left | MovementDirection::Right - | MovementDirection::Skip => *this_shape, + | MovementDirection::Skip => this_shape.clone(), MovementDirection::Rotate => this_shape.rotated(), }; debug!( @@ -1105,7 +1088,7 @@ fn movement( // Update shape/rotation let mut s = shape.get_mut(event.entity).unwrap(); - *s = new_shape; + *s = new_shape.clone(); } } } @@ -1125,7 +1108,7 @@ fn swap( None => { // Copy current shape into store // De-activate entity - store.0 = Some(*shapes.get(event.entity).unwrap()); + store.0 = Some(shapes.get(event.entity).unwrap().clone()); commands .entity(event.entity) .despawn_related::() diff --git a/src/bin/tetris/test.rs b/src/bin/tetris/test.rs index f2116b3..9b60d6c 100644 --- a/src/bin/tetris/test.rs +++ b/src/bin/tetris/test.rs @@ -35,25 +35,19 @@ fn test_shape_t() { fn test_shape_i() { let mut shape = Shape::new_i(); - let expected_up = "0010\n\ - 0010\n\ - 0010\n\ - 0010\n"; - - let expected_right = "0000\n\ - 0000\n\ - 1111\n\ - 0000\n"; - - let expected_down = "0100\n\ - 0100\n\ - 0100\n\ - 0100\n"; - - let expected_left = "0000\n\ - 1111\n\ - 0000\n\ - 0000\n"; + let expected_up = "1\n\ + 1\n\ + 1\n\ + 1\n"; + + let expected_right = "1111\n"; + + let expected_down = "1\n\ + 1\n\ + 1\n\ + 1\n"; + + let expected_left = "1111\n"; assert_eq!(shape.as_ascii(), expected_up); shape = shape.rotated(); @@ -72,21 +66,155 @@ fn test_coordinates() { let center = GridPosition { x: 5, y: 5 }; - let expected: Vec> = vec![ - Ok((5, 6).into()), + let actual: Vec> = shape.coordinates(¢er).collect(); + + let expected: Vec> = vec![ Ok((4, 5).into()), Ok((5, 5).into()), Ok((6, 5).into()), + Ok((5, 6).into()), ]; - let actual: Vec> = shape.coordinates(¢er).collect(); + assert_eq!(shape.layout.center(), (1, 1)); assert_eq!(actual, expected); } #[test] fn test_height() { - assert_eq!(Shape::new_t().height(), 2); + // todo: should be 2 for t piece + assert_eq!(Shape::new_t().height(), 3); assert_eq!(Shape::new_i().height(), 4); assert_eq!(Shape::new_l().height(), 3); } + +#[test] +fn test_shape_block_layout_rotation() { + { + let actual = ShapeBlockLayout { + inner: vec![vec![0, 0, 1]], + } + .rotated(); + let expected = ShapeBlockLayout { + inner: vec![vec![0], vec![0], vec![1]], + }; + assert_eq!(expected, actual); + } + + { + let actual = ShapeBlockLayout { + inner: vec![vec![1, 2, 3], vec![4, 5, 6]], + } + .rotated(); + let expected = ShapeBlockLayout { + inner: vec![vec![4, 1], vec![5, 2], vec![6, 3]], + }; + assert_eq!(expected, actual); + } + + { + let actual = ShapeBlockLayout { + inner: vec![vec![1, 2, 3], vec![4, 5, 6]], + } + .rotated(); + let expected = ShapeBlockLayout { + inner: vec![vec![4, 1], vec![5, 2], vec![6, 3]], + }; + assert_eq!(expected, actual); + } + + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![1, 2, 3, 4], + vec![5, 6, 7, 8], + vec![9, 10, 11, 12], + vec![13, 14, 15, 16], + ], + } + .rotated(); + let expected = ShapeBlockLayout { + inner: vec![ + vec![13, 9, 5, 1], + vec![14, 10, 6, 2], + vec![15, 11, 7, 3], + vec![16, 12, 8, 4], + ], + }; + assert_eq!(expected, actual); + } +} + +#[test] +fn test_shape_block_center() { + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![0], + vec![0], + vec![0], + ] + }.center(); + + let expected = (0, 1); + + assert_eq!(actual, expected); + } + + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![0], + vec![0], + vec![0], + vec![0], + ] + }.center(); + + let expected = (0, 2); + + assert_eq!(actual, expected); + } + + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![0, 0], + vec![0, 0], + ] + }.center(); + + let expected = (0, 1); + + assert_eq!(actual, expected); + } + + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![0, 0, 0], + vec![0, 0, 0], + vec![0, 0, 0], + ] + }.center(); + + let expected = (1, 1); + + assert_eq!(actual, expected); + } + + { + let actual = ShapeBlockLayout { + inner: vec![ + vec![0, 0, 0, 0], + vec![0, 0, 0, 0], + vec![0, 0, 0, 0], + vec![0, 0, 0, 0], + ] + }.center(); + + let expected = (1, 2); + + assert_eq!(actual, expected); + } +}