Compare commits

...

9 Commits

Author SHA1 Message Date
Elijah Voigt 4c35daac3e Tests pass so we are... almost cooking 6 days ago
Elijah Voigt 0d1a680625 Starting on shape-is-matrix implementation 1 week ago
Elijah Voigt 7ef5a73704 not perfect, but gooder movement 1 week ago
Elijah Voigt c4394f9728 Moving in the right direction for movement checking 2 weeks ago
Elijah Voigt c21636629e Initial clear line code, untested 2 weeks ago
Elijah Voigt f0d41f15ee Potential logic fix 2 weeks ago
Elijah Voigt f6aae11f8c Hit detection not working, but not horribly broken either... somewhere in between. 2 weeks ago
Elijah Voigt 2cab218e5c Tighten up tetrino grid movement 2 weeks ago
Elijah Voigt b51884ac69 Moved away from childof relationship!
Instead of using ChildOf, which has Transform implications, we created
our own relationship:

    ShapeBlocks(Vec<Entity>) <-> BlockOf { parent: Entity }

This relationship just informs how to place blocks with a
RelativePosition near their GridPosition parents without having to deal
with the Transform of the parent.
2 weeks ago

@ -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??

@ -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,72 +16,99 @@ fn main() {
..default() ..default()
}) })
.init_state::<Falling>() .init_state::<Falling>()
.add_systems(Startup, (init_pieces, init_debug_ui)) .add_systems(Startup, (init_world, 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_piece set_shape.run_if(
.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>)
.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;
/// A shape, e.g., the long piece // Declare the size of the play area
#[derive(Component, Debug, Default)] const X_MAX: u32 = 10;
enum Shape { const Y_MAX: u32 = 20;
#[default]
O,
T,
L,
J,
S,
Z,
I,
}
impl Display for Shape { // The blocks making up this shape
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[derive(Component)]
match self { #[relationship_target(relationship = ShapeBlock)]
Shape::O => write!(f, "O"), struct ShapeBlocks(Vec<Entity>);
Shape::T => write!(f, "T"),
Shape::L => write!(f, "L"), /// A part of a piece, i.e., a single square of a piece
Shape::J => write!(f, "J"), #[derive(Component, Debug)]
Shape::S => write!(f, "S"), #[relationship(relationship_target = ShapeBlocks)]
Shape::Z => write!(f, "Z"), struct ShapeBlock {
Shape::I => write!(f, "I"), #[relationship]
} shape: Entity,
}
} }
// The blocks making up this shape
#[derive(Component, Default)]
#[relationship_target(relationship = LineBlock)]
struct LineBlocks(Vec<Entity>);
/// A part of a piece, i.e., a single square of a piece /// A part of a piece, i.e., a single square of a piece
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct ShapePiece; #[require(Transform, Visibility)]
#[relationship(relationship_target = LineBlocks)]
struct LineBlock {
#[relationship]
line: Entity,
}
// A line holds up to 10 blocks before being cleared
#[derive(Component, Debug)]
struct Line(u8);
// 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 }
@ -91,14 +118,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: usize, x: u32,
y: usize, y: u32,
} }
impl GridPosition { impl GridPosition {
fn move_up(&self) -> Self { fn move_up(&self) -> Self {
Self { Self {
y: if self.y + 1 < 20 { y: if self.y + 1 < Y_MAX {
self.y.saturating_add(1) self.y.saturating_add(1)
} else { } else {
self.y self.y
@ -123,7 +150,7 @@ impl GridPosition {
fn move_right(&mut self) -> Self { fn move_right(&mut self) -> Self {
Self { Self {
x: if self.x + 1 < 10 { x: if self.x + 1 < X_MAX {
self.x.saturating_add(1) self.x.saturating_add(1)
} else { } else {
self.x self.x
@ -131,11 +158,48 @@ 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: 20 } GridPosition { x: 5, y: Y_MAX }
} }
} }
@ -154,8 +218,8 @@ impl From<&GridPosition> for Vec3 {
} }
} }
impl From<(usize, usize)> for GridPosition { impl From<(u32, u32)> for GridPosition {
fn from((x, y): (usize, usize)) -> GridPosition { fn from((x, y): (u32, u32)) -> GridPosition {
GridPosition { x, y } GridPosition { x, y }
} }
} }
@ -177,7 +241,7 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition {
} }
} }
#[derive(Component, Default, Debug)] #[derive(Component, Default, Event, Clone, Debug)]
enum Orientation { enum Orientation {
#[default] #[default]
Up, Up,
@ -230,7 +294,7 @@ struct Visuals {
mesh: Handle<Mesh>, mesh: Handle<Mesh>,
} }
fn init_pieces( fn init_world(
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>>,
@ -243,205 +307,392 @@ fn init_pieces(
mesh: meshes.add(Rectangle::new(SCALE, SCALE)), mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
}); });
commands.spawn((Orientation::default(), GridPosition::default(), Shape::T)); (0..20).for_each(|i| {
commands.spawn((Line(i), LineBlocks::default()));
});
} }
fn init_debug_ui( fn init_debug_ui(mut commands: Commands) {
mut commands: Commands, commands
) { .spawn((
commands.spawn((Node { 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()
),
],
)); ));
}); });
} }
fn set_piece( #[derive(Component, Debug)]
query: Query<(Entity, &Shape, &Orientation), Or<(Added<Shape>, Changed<Shape>, Added<Orientation>, Changed<Orientation>)>>, enum Shape {
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!("{e:?} {s:?} {o:?}"); debug!("Setting piece: {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!();
#[rustfmt::skip] // todo: map positions to coordinates
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(),
]
}
};
piece_positions.into_iter().for_each(|rp| { if blocks.is_empty() {
parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp)); commands
.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 set_relative_piece_positions( fn update_position(
query: Query< mut changed: Query<
(Entity, &RelativePosition), (Entity, &GridPosition, &mut Transform),
Or<(Added<RelativePosition>, Changed<RelativePosition>)>, Or<(Added<GridPosition>, Changed<GridPosition>)>,
>, >,
mut commands: Commands,
) { ) {
query.iter().for_each(|(e, rp)| { changed.iter_mut().for_each(|(e, gp, mut t)| {
commands.entity(e).insert(Transform::from_xyz( let v3: Vec3 = gp.into();
(rp.x as f32) * SCALE, debug!(
(rp.y as f32) * SCALE, "Updating {e} with grid position {:?} to coordinates {:?}",
0.0, gp, v3
)); );
debug_assert!(gp.x < X_MAX, "block x > x_max");
t.translation = gp.into();
}); });
} }
fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) { fn update_relative_position(
query.iter_mut().for_each(|(gp, mut t)| { shape: Single<&GridPosition, With<ShapeBlocks>>,
t.translation = gp.into(); mut query: Query<
debug!("Updating position {:?}", t.translation); (Entity, &mut GridPosition, &RelativePosition),
(
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<(&mut GridPosition, &mut Orientation, &mut Shape)>, mut query: Query<(Entity, &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 {
@ -450,25 +701,33 @@ 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(|(mut gp, mut o, mut s)| { query.iter_mut().for_each(|(e, 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 => *o = o.prev(), KeyCode::ArrowUp => {
KeyCode::ArrowDown => *gp = gp.move_down(), commands.entity(e).trigger(o.prev());
KeyCode::ArrowLeft => *gp = gp.move_left(), }
KeyCode::ArrowRight => *gp = gp.move_right(), KeyCode::ArrowDown => {
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::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(),
_ => (), _ => (),
} }
}); });
@ -481,25 +740,22 @@ fn draw_grid(mut gizmos: Gizmos) {
gizmos gizmos
.grid_2d( .grid_2d(
Isometry2d::IDENTITY, Isometry2d::IDENTITY,
UVec2::new(10, 20), UVec2::new(X_MAX, Y_MAX),
Vec2::new(SCALE, SCALE), Vec2::new(SCALE, SCALE),
GREEN, GREEN,
) )
.outer_edges(); .outer_edges();
} }
fn falling(mut query: Query<&mut GridPosition, With<Shape>>) { fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
query.iter_mut().for_each(|mut gp| { shape.iter_mut().for_each(|e| {
let next = gp.move_down(); info!("Making {:?} fall", e);
if next != *gp { commands.entity(e).trigger(RelativePosition::down());
*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();
@ -511,3 +767,106 @@ 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