Starting on shape-is-matrix implementation

main
Elijah Voigt 1 week ago
parent 7ef5a73704
commit 0d1a680625

@ -0,0 +1,45 @@
# Design
## Tetris
Matrix Multiplication for rotating pieces.
Each piece (shape) contains a Mat4 containing a representation of it's shape.
For example:
```
0 1 0 0
0 1 0 0
0 1 0 0
0 1 0 0
```
This is the classic `line` piece.
And here it is on it's `up` side
```
0 0 0 0
1 1 1 1
0 0 0 0
0 0 0 0
```
And here is the `t` piece
```
0 1 0
1 1 1
0 0 0
```
A matrix multiplication is applied to this Mat6 to achieve a piece rotation.
When that matrix is updated, the 4 blocks parented to the shape are moved to reflect this new shape.
This matrix also allows us to do checks to see if any of the blocks in the shape would intersect with another piece on the board.
We can also check if a piece would go out of bounds during a move or rotation.
We can use this to "plan -> validate -> commit" changes based on user input.
Question: How the fuck do matrix multiplications work??

@ -1,6 +1,8 @@
// Bevy basically forces "complex types" with Querys // Bevy basically forces "complex types" with Querys
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use bevy::render::render_resource::encase::matrix::AsMutMatrixParts;
use games::*; use games::*;
// *TODO: Detect when piece is going to go out of bounds and restirct parent from moving there // *TODO: Detect when piece is going to go out of bounds and restirct parent from moving there
@ -24,7 +26,7 @@ fn main() {
falling falling
.run_if(in_state(Falling::On)) .run_if(in_state(Falling::On))
.run_if(clock_cycle(1.0)), .run_if(clock_cycle(1.0)),
set_piece.run_if( set_shape.run_if(
any_component_added::<Shape> any_component_added::<Shape>
.or(any_component_changed::<Shape>) .or(any_component_changed::<Shape>)
.or(any_component_added::<Orientation>) .or(any_component_added::<Orientation>)
@ -49,33 +51,6 @@ const SCALE: f32 = 30.0;
const X_MAX: u32 = 10; const X_MAX: u32 = 10;
const Y_MAX: u32 = 20; const Y_MAX: u32 = 20;
/// A shape, e.g., the long piece
#[derive(Component, Debug, Default)]
enum Shape {
#[default]
O,
T,
L,
J,
S,
Z,
I,
}
impl Display for Shape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Shape::O => write!(f, "O"),
Shape::T => write!(f, "T"),
Shape::L => write!(f, "L"),
Shape::J => write!(f, "J"),
Shape::S => write!(f, "S"),
Shape::Z => write!(f, "Z"),
Shape::I => write!(f, "I"),
}
}
}
// The blocks making up this shape // The blocks making up this shape
#[derive(Component)] #[derive(Component)]
#[relationship_target(relationship = ShapeBlock)] #[relationship_target(relationship = ShapeBlock)]
@ -111,7 +86,7 @@ struct Line(u8);
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct Block; struct Block;
#[derive(Component, Event, Debug, Clone, Copy)] #[derive(Component, Event, Debug, Clone, Copy, PartialEq)]
#[require(GridPosition)] #[require(GridPosition)]
struct RelativePosition { struct RelativePosition {
x: i8, x: i8,
@ -363,146 +338,258 @@ fn init_debug_ui(mut commands: Commands) {
}); });
} }
#[rustfmt::skip] #[derive(Component, Debug)]
fn block_positions(s: &Shape, o: &Orientation) -> [RelativePosition;4] { enum Shape {
match s { M4(Mat4),
Shape::O => [ M3(Mat3),
(0,1).into(),(1,1).into(), }
(0,0).into(),(1,0).into()
], impl Default for Shape {
Shape::T => match o { fn default() -> Self {
Orientation::Up => [ Self::new_t()
(0,1).into(), }
(-1,0).into(),(0,0).into(),(1,0).into(), }
],
Orientation::Down => [ impl Display for Shape {
(-1,0).into(),(0,0).into(),(1,0).into(), fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(0,-1).into(), write!(f, "{}", self.as_ascii())
], }
Orientation::Right => [ }
(0,1).into(),
(0,0).into(), (1,0).into(), impl Shape {
(0,-1).into(), fn from_mat4(input: Mat4) -> Self {
], Self::M4(input)
Orientation::Left => [ }
(0,1).into(),
(-1,0).into(),(0,0).into(), fn from_mat3(input: Mat3) -> Self {
(0,-1).into(), Self::M3(input)
] }
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.],
]))
}
fn new_t() -> Self {
Self::from_mat3(Mat3::from_cols_array_2d(&[
[0.,1.,0.],
[1.,1.,1.],
[0.,0.,0.],
]))
}
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.],
]))
}
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.],
]))
}
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.],
]))
}
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.],
]))
}
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.],
]))
}
// 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)
}
}
}
fn rotate(&mut self) {
*self = self.rotated();
}
// TODO: return impl Iterator<Item = &RelativePosition>
fn relative_coordinates(&self) -> Vec<RelativePosition> {
todo!()
}
fn computed_coordinates(&self, center: &GridPosition) -> Vec<GridPosition> {
todo!()
}
fn as_ascii(&self) -> String {
let mut output = String::default();
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();
}
}, },
Shape::L => match o { Self::M3(this) => {
Orientation::Up => [ for i in 0..3 {
(0,1).into(), let col = this.col(i).to_array();
(0,0).into(), output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str();
(0,-1).into(),(1,-1).into(), }
],
Orientation::Down => [
(-1,1).into(),(0,1).into(),
(0,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(-1,-1).into(),
],
Orientation::Left => [
(1,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into(),
],
}, },
Shape::J => match o { };
Orientation::Up => [
(0,1).into(), output
(0,0).into(), }
(-1,-1).into(),(0,-1).into(), }
],
Orientation::Down => [ #[cfg(test)]
(0,1).into(),(1,1).into(), mod test {
use super::*;
#[test]
fn test_shape_t() {
let mut shape = Shape::new_t();
let expected_up = "010\n\
111\n\
000\n";
let expected_right = "010\n\
011\n\
010\n";
let expected_down = "000\n\
111\n\
010\n";
let expected_left = "010\n\
110\n\
010\n";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
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";
assert_eq!(shape.as_ascii(), expected_up);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_right);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_down);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_left);
shape.rotate();
assert_eq!(shape.as_ascii(), expected_up);
}
#[test]
fn test_relative_coordinates() {
let shape = Shape::new_t();
let expected = vec![
(-1, 0).into(),
(0, 0).into(), (0, 0).into(),
(0,-1).into(), (1, 0).into(),
],
Orientation::Left => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(1,-1).into(),
],
Orientation::Right => [
(-1,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into()
],
},
Shape::S => match o {
Orientation::Up => [
(0,0).into(),(1,0).into(),
(-1,-1).into(),(0,-1).into(),
],
Orientation::Down => [
(0,1).into(),(1,1).into(),
(-1,0).into(),(0,0).into(),
],
Orientation::Right => [
(-1,1).into(),
(-1,0).into(),(0,0).into(),
(0,-1).into(),
],
Orientation::Left => [
(0,1).into(),
(0,0).into(),(1,0).into(),
(1,-1).into(),
],
},
Shape::Z => match o {
Orientation::Up => [
(-1,0).into(),(0,0).into(),
(0,-1).into(),(1,-1).into(),
],
Orientation::Down => [
(-1,1).into(),(0,1).into(),
(0,0).into(),(1,0).into(),
],
Orientation::Left => [
(1,1).into(),
(0,0).into(),(1,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(0, 1).into(), (0, 1).into(),
(-1,0).into(),(0,0).into(), ];
(-1,-1).into(),
], assert_eq!(shape.relative_coordinates(), expected);
},
// TODO: This does not match tetris!
Shape::I => match o {
Orientation::Up => [
(0,2).into(),
(0,1).into(),
(0,0).into(),
(0,-1).into(),
],
Orientation::Down => [
(-1,2).into(),
(-1,1).into(),
(-1,0).into(),
(-1,-1).into(),
],
Orientation::Left => [
(-2,0).into(),(-1,0).into(),(0,0).into(),(1,0).into(),
],
Orientation::Right => [
(-2,1).into(),(-1,1).into(),(0,1).into(),(1,1).into(),
]
} }
#[test]
fn test_computed_coordinates() {
let shape = Shape::new_t();
let center = GridPosition { x: 5, y: 5 };
let expected = vec![
(4, 5).into(),
(5, 5).into(),
(6, 5).into(),
(5, 6).into(),
];
assert_eq!(shape.computed_coordinates(&center), expected);
} }
} }
fn set_piece( fn set_shape(
query: Query< query: Query<
(Entity, &Shape, &Orientation), (Entity, &Shape, &Orientation),
Or<( Or<(Added<Shape>, Changed<Shape>)>,
Added<Shape>,
Changed<Shape>,
Added<Orientation>,
Changed<Orientation>,
)>,
>, >,
mut blocks: Query<&mut RelativePosition, With<ShapeBlock>>, mut blocks: Query<&mut RelativePosition, With<ShapeBlock>>,
mut commands: Commands, mut commands: Commands,
@ -513,7 +600,9 @@ fn set_piece(
let mesh = visuals.mesh.clone(); let mesh = visuals.mesh.clone();
let mat = visuals.material.clone(); let mat = visuals.material.clone();
let positions = block_positions(s, o); let positions: [RelativePosition;4] = todo!();
// todo: map positions to coordinates
if blocks.is_empty() { if blocks.is_empty() {
commands commands
@ -607,13 +696,13 @@ fn kb_input(
Falling::On => Falling::Off, Falling::On => Falling::Off,
Falling::Off => Falling::On, Falling::Off => Falling::On,
}), }),
KeyCode::Digit1 => *s = Shape::T, KeyCode::Digit1 => *s = Shape::new_t(),
KeyCode::Digit2 => *s = Shape::O, KeyCode::Digit2 => *s = Shape::new_o(),
KeyCode::Digit3 => *s = Shape::L, KeyCode::Digit3 => *s = Shape::new_l(),
KeyCode::Digit4 => *s = Shape::J, KeyCode::Digit4 => *s = Shape::new_j(),
KeyCode::Digit5 => *s = Shape::S, KeyCode::Digit5 => *s = Shape::new_s(),
KeyCode::Digit6 => *s = Shape::Z, KeyCode::Digit6 => *s = Shape::new_z(),
KeyCode::Digit7 => *s = Shape::I, KeyCode::Digit7 => *s = Shape::new_i(),
_ => (), _ => (),
} }
}); });
@ -657,7 +746,7 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
fn add_piece(mut commands: Commands) { fn add_piece(mut commands: Commands) {
// TODO: Choose a different piece // TODO: Choose a different piece
commands commands
.spawn((Orientation::default(), GridPosition::default(), Shape::T)) .spawn((Orientation::default(), GridPosition::default(), Shape::default()))
.observe(movement) .observe(movement)
.observe(rotation); .observe(rotation);
} }
@ -731,9 +820,11 @@ fn movement(
fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) { fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) {
let mut o = q.get_mut(trigger.target()).unwrap(); let mut o = q.get_mut(trigger.target()).unwrap();
// If children would go out of bounds to the left // If children would go out of bounds going left, move slightly to the right
// If children would not go out of bounds to the right // If that would cause collision, deactive piece
// If children would not go out of bounds to the down // If children would go out of bounds going right, move slightly to the left
// If that would cause collision, deactive piece
// If children would not go out of bounds going down
*o = trigger.event().clone(); *o = trigger.event().clone();
} }
@ -752,3 +843,5 @@ fn deactive_shape(
.remove_related::<ShapeBlock>(v.as_slice()) .remove_related::<ShapeBlock>(v.as_slice())
.despawn(); .despawn();
} }
// TODO: When Orientation changed, perform matrix multiplication or something(?)

Loading…
Cancel
Save