Compare commits

..

No commits in common. '4c35daac3e9663c6dd2a11bcd7137276c051971c' and 'fb535106cae403ec72b94b46f029b52f7cfe6a08' have entirely different histories.

@ -1,45 +0,0 @@
# 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??

@ -3,7 +3,7 @@
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
// TODO: When shape touches the rest of the pieces, re-parent to line entity // TODO: When shape touches the rest of the pieces, re-parent to line entity
// TODO: When line is "full" (has 10 children) clear line and add to score // TODO: When line is "full" (has 10 children) clear line and add to score
@ -16,99 +16,72 @@ fn main() {
..default() ..default()
}) })
.init_state::<Falling>() .init_state::<Falling>()
.add_systems(Startup, (init_world, init_debug_ui)) .add_systems(Startup, (init_pieces, init_debug_ui))
.add_systems( .add_systems(
Update, Update,
( (
kb_input.run_if(on_event::<KeyboardInput>), kb_input.run_if(on_event::<KeyboardInput>),
update_position,
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_shape.run_if( set_piece
any_component_added::<Shape> .run_if(any_component_added::<Shape>
.or(any_component_changed::<Shape>) .or(any_component_changed::<Shape>)
.or(any_component_added::<Orientation>) .or(any_component_added::<Orientation>)
.or(any_component_changed::<Orientation>), .or(any_component_changed::<Orientation>)
),
set_relative_piece_positions.run_if(
any_component_added::<RelativePosition>
.or(any_component_changed::<RelativePosition>),
), ),
update_relative_position,
update_position.after(update_relative_position),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>), sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>), sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
add_piece.run_if(not(any_with_component::<Shape>)),
clear_line.run_if(any_component_changed::<LineBlocks>),
), ),
) )
.add_systems(Update, draw_grid) .add_systems(Update, draw_grid)
.add_observer(deactive_shape)
.run(); .run();
} }
const SCALE: f32 = 30.0; const SCALE: f32 = 30.0;
// Declare the size of the play area /// A shape, e.g., the long piece
const X_MAX: u32 = 10; #[derive(Component, Debug, Default)]
const Y_MAX: u32 = 20; enum Shape {
#[default]
// The blocks making up this shape O,
#[derive(Component)] T,
#[relationship_target(relationship = ShapeBlock)] L,
struct ShapeBlocks(Vec<Entity>); J,
S,
/// A part of a piece, i.e., a single square of a piece Z,
#[derive(Component, Debug)] I,
#[relationship(relationship_target = ShapeBlocks)]
struct ShapeBlock {
#[relationship]
shape: Entity,
} }
// The blocks making up this shape impl Display for Shape {
#[derive(Component, Default)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[relationship_target(relationship = LineBlock)] match self {
struct LineBlocks(Vec<Entity>); Shape::O => write!(f, "O"),
Shape::T => write!(f, "T"),
/// A part of a piece, i.e., a single square of a piece Shape::L => write!(f, "L"),
#[derive(Component, Debug)] Shape::J => write!(f, "J"),
#[require(Transform, Visibility)] Shape::S => write!(f, "S"),
#[relationship(relationship_target = LineBlocks)] Shape::Z => write!(f, "Z"),
struct LineBlock { Shape::I => write!(f, "I"),
#[relationship] }
line: Entity, }
} }
// A line holds up to 10 blocks before being cleared /// A part of a piece, i.e., a single square of a piece
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct Line(u8); struct ShapePiece;
// Just marks a block either of a shape or line
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct Block;
#[derive(Component, Event, Debug, Clone, Copy, PartialEq)]
#[require(GridPosition)]
struct RelativePosition { struct RelativePosition {
x: i8, x: i8,
y: i8, y: i8,
} }
impl RelativePosition {
fn up() -> Self {
RelativePosition { x: 0, y: 1 }
}
fn down() -> Self {
RelativePosition { x: 0, y: -1 }
}
fn left() -> Self {
RelativePosition { x: -1, y: 0 }
}
fn right() -> Self {
RelativePosition { x: 1, y: 0 }
}
}
impl From<(i8, i8)> for RelativePosition { impl From<(i8, i8)> for RelativePosition {
fn from((x, y): (i8, i8)) -> RelativePosition { fn from((x, y): (i8, i8)) -> RelativePosition {
RelativePosition { x, y } RelativePosition { x, y }
@ -118,14 +91,14 @@ impl From<(i8, i8)> for RelativePosition {
#[derive(Component, Debug, Clone, Copy, PartialEq)] #[derive(Component, Debug, Clone, Copy, PartialEq)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]
struct GridPosition { struct GridPosition {
x: u32, x: usize,
y: u32, y: usize,
} }
impl GridPosition { impl GridPosition {
fn move_up(&self) -> Self { fn move_up(&self) -> Self {
Self { Self {
y: if self.y + 1 < Y_MAX { y: if self.y + 1 < 20 {
self.y.saturating_add(1) self.y.saturating_add(1)
} else { } else {
self.y self.y
@ -150,7 +123,7 @@ impl GridPosition {
fn move_right(&mut self) -> Self { fn move_right(&mut self) -> Self {
Self { Self {
x: if self.x + 1 < X_MAX { x: if self.x + 1 < 10 {
self.x.saturating_add(1) self.x.saturating_add(1)
} else { } else {
self.x self.x
@ -158,48 +131,11 @@ impl GridPosition {
y: self.y, y: self.y,
} }
} }
fn is_colliding_with(&self, other: &Self) -> bool {
self.x == other.x && self.y.saturating_sub(1) == other.y
}
fn add_relative(
&self,
RelativePosition { x: x1, y: y1 }: &RelativePosition,
) -> Result<Self, GameError> {
let x = self
.x
.checked_add_signed(*x1 as i32)
.ok_or(GameError::OutOfBoundsLeft)?;
let y = self
.y
.checked_add_signed(*y1 as i32)
.ok_or(GameError::OutOfBoundsDown)?;
if x >= X_MAX {
// TODO: y > Y_MAX?
Err(GameError::OutOfBoundsRight)
} else {
debug!("Moving to {x},{y}");
Ok(GridPosition { x, y })
}
}
}
#[derive(Error, Debug, PartialEq)]
enum GameError {
#[error("Coordinates are out of bounds: Left")]
OutOfBoundsLeft,
#[error("Coordinates are out of bounds: Right")]
OutOfBoundsRight,
#[error("Coordinates are out of bounds: Down")]
OutOfBoundsDown,
#[error("Coordiante collision")]
Collision,
} }
impl Default for GridPosition { impl Default for GridPosition {
fn default() -> Self { fn default() -> Self {
GridPosition { x: 5, y: Y_MAX } GridPosition { x: 5, y: 20 }
} }
} }
@ -218,8 +154,8 @@ impl From<&GridPosition> for Vec3 {
} }
} }
impl From<(u32, u32)> for GridPosition { impl From<(usize, usize)> for GridPosition {
fn from((x, y): (u32, u32)) -> GridPosition { fn from((x, y): (usize, usize)) -> GridPosition {
GridPosition { x, y } GridPosition { x, y }
} }
} }
@ -241,7 +177,7 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition {
} }
} }
#[derive(Component, Default, Event, Clone, Debug)] #[derive(Component, Default, Debug)]
enum Orientation { enum Orientation {
#[default] #[default]
Up, Up,
@ -294,7 +230,7 @@ struct Visuals {
mesh: Handle<Mesh>, mesh: Handle<Mesh>,
} }
fn init_world( fn init_pieces(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>, mut materials: ResMut<Assets<ColorMaterial>>,
@ -307,392 +243,205 @@ fn init_world(
mesh: meshes.add(Rectangle::new(SCALE, SCALE)), mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
}); });
(0..20).for_each(|i| { commands.spawn((Orientation::default(), GridPosition::default(), Shape::T));
commands.spawn((Line(i), LineBlocks::default()));
});
} }
fn init_debug_ui(mut commands: Commands) { fn init_debug_ui(
commands mut commands: Commands,
.spawn(( ) {
Node { commands.spawn((Node {
top: Val::Px(0.0), top: Val::Px(0.0),
left: Val::Px(0.0), left: Val::Px(0.0),
..default() ..default()
}, }, DebuggingState::On)).with_children(|parent| {
DebuggingState::On,
))
.with_children(|parent| {
parent.spawn(( parent.spawn((
Node::default(), Node::default(),
children![ children![
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()), (Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
( (Text::new("ORIENTATION"), SyncSingleton::<Orientation>::default()),
Text::new("ORIENTATION"), ]
SyncSingleton::<Orientation>::default()
),
],
)); ));
}); });
} }
#[derive(Component, Debug)] fn set_piece(
enum Shape { query: Query<(Entity, &Shape, &Orientation), Or<(Added<Shape>, Changed<Shape>, Added<Orientation>, Changed<Orientation>)>>,
M4(Mat4),
M3(Mat3),
}
impl Default for Shape {
fn default() -> Self {
Self::new_t()
}
}
impl Display for Shape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_ascii())
}
}
impl Shape {
fn from_mat4(input: Mat4) -> Self {
Self::M4(input)
}
fn from_mat3(input: Mat3) -> Self {
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) -> impl Iterator<Item = RelativePosition> {
let mut v: Vec<RelativePosition> = 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(RelativePosition { 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(RelativePosition { x, y });
}
}
}
}
};
v.into_iter()
}
fn computed_coordinates(&self, center: &GridPosition) -> impl Iterator<Item = Result<GridPosition, GameError>> {
self.relative_coordinates().map(|rp| center.add_relative(&rp))
}
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();
}
},
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();
}
},
};
output
}
}
#[cfg(test)]
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<RelativePosition> = vec![
(0, 1).into(),
(-1, 0).into(),
(0, 0).into(),
(1, 0).into(),
];
let actual: Vec<RelativePosition> = shape.relative_coordinates().collect();
assert_eq!(actual, expected);
}
#[test]
fn test_computed_coordinates() {
let shape = Shape::new_t();
let center = GridPosition { x: 5, y: 5 };
let expected: Vec<Result<GridPosition, GameError>> = vec![
Ok((5, 6).into()),
Ok((4, 5).into()),
Ok((5, 5).into()),
Ok((6, 5).into()),
];
let actual: Vec<Result<GridPosition, GameError>> = shape.computed_coordinates(&center).collect();
assert_eq!(actual, expected);
}
}
fn set_shape(
query: Query<
(Entity, &Shape, &Orientation),
Or<(Added<Shape>, Changed<Shape>)>,
>,
mut blocks: Query<&mut RelativePosition, With<ShapeBlock>>,
mut commands: Commands, mut commands: Commands,
visuals: Res<Visuals>, visuals: Res<Visuals>,
) { ) {
query.iter().for_each(|(e, s, o)| { query.iter().for_each(|(e, s, o)| {
debug!("Setting piece: {e:?} {s:?} {o:?}"); debug!("{e:?} {s:?} {o:?}");
commands
.entity(e)
.despawn_related::<Children>()
.with_children(|parent| {
let mesh = visuals.mesh.clone(); let mesh = visuals.mesh.clone();
let mat = visuals.material.clone(); let mat = visuals.material.clone();
let positions: [RelativePosition;4] = todo!();
// todo: map positions to coordinates #[rustfmt::skip]
let piece_positions: [RelativePosition;4] = match s {
Shape::O => [
(0,1).into(),(1,1).into(),
(0,0).into(),(1,0).into()
],
Shape::T => match o {
Orientation::Up => [
(0,1).into(),
(-1,0).into(),(0,0).into(),(1,0).into(),
],
Orientation::Down => [
(-1,0).into(),(0,0).into(),(1,0).into(),
(0,-1).into(),
],
Orientation::Right => [
(0,1).into(),
(0,0).into(), (1,0).into(),
(0,-1).into(),
],
Orientation::Left => [
(0,1).into(),
(-1,0).into(),(0,0).into(),
(0,-1).into(),
]
},
Shape::L => match o {
Orientation::Up => [
(0,1).into(),
(0,0).into(),
(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(),
(0,0).into(),
(-1,-1).into(),(0,-1).into(),
],
Orientation::Down => [
(0,1).into(),(1,1).into(),
(0,0).into(),
(0,-1).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(),
(-1,0).into(),(0,0).into(),
(-1,-1).into(),
],
},
// 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(),
]
}
};
if blocks.is_empty() { piece_positions.into_iter().for_each(|rp| {
commands parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp));
.entity(e)
.with_related_entities::<ShapeBlock>(|parent| {
positions.into_iter().for_each(|rp| {
parent
.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp, Block))
.observe(movement);
});
}); });
} else {
let mut p = positions.into_iter();
blocks.iter_mut().for_each(|mut rp| {
*rp = p.next().unwrap();
}); });
}
}); });
} }
fn update_position( fn set_relative_piece_positions(
mut changed: Query< query: Query<
(Entity, &GridPosition, &mut Transform), (Entity, &RelativePosition),
Or<(Added<GridPosition>, Changed<GridPosition>)>, Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
>, >,
mut commands: Commands,
) { ) {
changed.iter_mut().for_each(|(e, gp, mut t)| { query.iter().for_each(|(e, rp)| {
let v3: Vec3 = gp.into(); commands.entity(e).insert(Transform::from_xyz(
debug!( (rp.x as f32) * SCALE,
"Updating {e} with grid position {:?} to coordinates {:?}", (rp.y as f32) * SCALE,
gp, v3 0.0,
); ));
debug_assert!(gp.x < X_MAX, "block x > x_max");
t.translation = gp.into();
}); });
} }
fn update_relative_position( fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) {
shape: Single<&GridPosition, With<ShapeBlocks>>, query.iter_mut().for_each(|(gp, mut t)| {
mut query: Query< t.translation = gp.into();
(Entity, &mut GridPosition, &RelativePosition), debug!("Updating position {:?}", t.translation);
(
Without<ShapeBlocks>,
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
),
>,
) {
query.iter_mut().for_each(|(e, mut gp, rp)| {
debug!(
"Updating {e} grid position to {:?} + {:?} = {:?}",
gp,
rp,
gp.add_relative(rp).unwrap()
);
*gp = (*shape).add_relative(rp).unwrap();
}); });
} }
fn kb_input( fn kb_input(
mut events: EventReader<KeyboardInput>, mut events: EventReader<KeyboardInput>,
mut query: Query<(Entity, &Orientation, &mut Shape)>, mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>,
curr: Res<State<Falling>>, curr: Res<State<Falling>>,
mut next: ResMut<NextState<Falling>>, mut next: ResMut<NextState<Falling>>,
mut commands: Commands,
) { ) {
events.read().for_each( events.read().for_each(
|KeyboardInput { |KeyboardInput {
@ -701,33 +450,25 @@ fn kb_input(
if let ButtonState::Pressed = state { if let ButtonState::Pressed = state {
// TODO: Restict movement based on size/orientation of piece // TODO: Restict movement based on size/orientation of piece
// Check if children would be outside play area... // Check if children would be outside play area...
query.iter_mut().for_each(|(e, o, mut s)| { query.iter_mut().for_each(|(mut gp, mut o, mut s)| {
match key_code { match key_code {
// Up arrow should rotate if in falling mode // Up arrow should rotate if in falling mode
// Only move up if in falling::off mode // Only move up if in falling::off mode
KeyCode::ArrowUp => { KeyCode::ArrowUp => *o = o.prev(),
commands.entity(e).trigger(o.prev()); KeyCode::ArrowDown => *gp = gp.move_down(),
} KeyCode::ArrowLeft => *gp = gp.move_left(),
KeyCode::ArrowDown => { KeyCode::ArrowRight => *gp = gp.move_right(),
commands.entity(e).trigger(RelativePosition::down());
}
KeyCode::ArrowLeft => {
commands.entity(e).trigger(RelativePosition::left());
}
KeyCode::ArrowRight => {
commands.entity(e).trigger(RelativePosition::right());
}
KeyCode::Space => next.set(match curr.get() { KeyCode::Space => next.set(match curr.get() {
Falling::On => Falling::Off, Falling::On => Falling::Off,
Falling::Off => Falling::On, Falling::Off => Falling::On,
}), }),
KeyCode::Digit1 => *s = Shape::new_t(), KeyCode::Digit1 => *s = Shape::T,
KeyCode::Digit2 => *s = Shape::new_o(), KeyCode::Digit2 => *s = Shape::O,
KeyCode::Digit3 => *s = Shape::new_l(), KeyCode::Digit3 => *s = Shape::L,
KeyCode::Digit4 => *s = Shape::new_j(), KeyCode::Digit4 => *s = Shape::J,
KeyCode::Digit5 => *s = Shape::new_s(), KeyCode::Digit5 => *s = Shape::S,
KeyCode::Digit6 => *s = Shape::new_z(), KeyCode::Digit6 => *s = Shape::Z,
KeyCode::Digit7 => *s = Shape::new_i(), KeyCode::Digit7 => *s = Shape::I,
_ => (), _ => (),
} }
}); });
@ -740,22 +481,25 @@ fn draw_grid(mut gizmos: Gizmos) {
gizmos gizmos
.grid_2d( .grid_2d(
Isometry2d::IDENTITY, Isometry2d::IDENTITY,
UVec2::new(X_MAX, Y_MAX), UVec2::new(10, 20),
Vec2::new(SCALE, SCALE), Vec2::new(SCALE, SCALE),
GREEN, GREEN,
) )
.outer_edges(); .outer_edges();
} }
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) { fn falling(mut query: Query<&mut GridPosition, With<Shape>>) {
shape.iter_mut().for_each(|e| { query.iter_mut().for_each(|mut gp| {
info!("Making {:?} fall", e); let next = gp.move_down();
commands.entity(e).trigger(RelativePosition::down()); if next != *gp {
*gp = next;
} else {
// Remove the falling component from this entity
}
}); });
} }
// Run condition that returns `true` every `n` seconds // Run condition that returns `true` every `n` seconds
// TODO: Update a resource with the current tick
fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool { fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
move |t: Res<Time>, mut buf: Local<f32>| -> bool { move |t: Res<Time>, mut buf: Local<f32>| -> bool {
*buf += t.delta_secs(); *buf += t.delta_secs();
@ -767,106 +511,3 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
} }
} }
} }
fn add_piece(mut commands: Commands) {
// TODO: Choose a different piece
commands
.spawn((Orientation::default(), GridPosition::default(), Shape::default()))
.observe(movement)
.observe(rotation);
}
/// When a line reaches 10 blocks, clear it
fn clear_line(
lines: Query<(Entity, &LineBlocks), (With<LineBlocks>, Changed<LineBlocks>)>,
mut commands: Commands,
) {
lines.iter().for_each(|(e, lb)| {
if lb.0.len() == 10 {
commands.entity(e).despawn_related::<LineBlocks>();
// TODO: re-parent all blocks above this to the next line down
// TODO: Parent blocks to lines for movement
}
});
}
fn movement(
trigger: Trigger<RelativePosition>,
mut grid_positions: Query<&mut GridPosition, Or<(With<RelativePosition>, With<Shape>)>>,
parent: Query<&ShapeBlocks>,
inactive: Query<
&GridPosition,
(
Without<RelativePosition>,
Without<Shape>,
),
>,
mut commands: Commands,
) {
// Do a bunch of checks if this move is valid
for block in parent.iter_descendants(trigger.target()) {
let block_gp = grid_positions.get(block).unwrap();
match block_gp.add_relative(trigger.event()) {
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => {
// Hit the left/right wall, just ignore this move
return;
}
Err(GameError::OutOfBoundsDown) => {
commands.entity(trigger.target()).remove::<Shape>();
return;
}
Err(GameError::Collision) => {
// Do nothing. add_relative does not return this variant
}
Ok(new_block_gp) => {
let collision = inactive
.iter()
.any(|inactive_block_gp| new_block_gp == *inactive_block_gp);
if collision {
commands.entity(trigger.target()).remove::<Shape>();
}
}
}
}
// Move shape itself
if let Ok(mut shape_gp) = grid_positions.get_mut(trigger.target()) {
*shape_gp = shape_gp.add_relative(trigger.event()).unwrap();
}
// Move the blocks of this shape
parent.iter_descendants(trigger.target()).for_each(|block| {
info!("Propogating movement {:?} to child {:?}", trigger.event(), block);
commands.entity(block).trigger(*trigger.event());
});
}
fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) {
let mut o = q.get_mut(trigger.target()).unwrap();
// If children would go out of bounds going left, move slightly to the right
// If that would cause collision, deactive piece
// 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();
}
// TODO: Just despawn?
fn deactive_shape(
trigger: Trigger<OnRemove, Shape>,
parent: Query<&ShapeBlocks>,
mut commands: Commands,
) {
let v: Vec<Entity> = parent.iter_descendants(trigger.target()).collect();
parent.iter_descendants(trigger.target()).for_each(|block| {
commands.entity(block).remove::<RelativePosition>();
});
commands
.entity(trigger.target())
.remove_related::<ShapeBlock>(v.as_slice())
.despawn();
}
// TODO: When Orientation changed, perform matrix multiplication or something(?)

Loading…
Cancel
Save