diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index d7711af..554612f 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -115,13 +115,43 @@ struct Line(u8); #[derive(Component, Debug)] struct Block; -#[derive(Component, Debug)] +#[derive(Component, Event, Debug)] #[require(GridPosition)] struct RelativePosition { x: 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 { fn from((x, y): (i8, i8)) -> RelativePosition { RelativePosition { x, y } @@ -176,15 +206,29 @@ impl GridPosition { self.x == other.x && self.y.saturating_sub(1) == other.y } - fn add_offset(&self, RelativePosition { x: x1, y: y1 }: &RelativePosition) -> Self { - debug!("{} + {}, {} + {}", self.x, *x1 as i32, self.y, *y1 as i32); - GridPosition { - x: self.x.checked_add_signed(*x1 as i32).unwrap(), - y: self.y.checked_add_signed(*y1 as i32).unwrap(), + fn add_offset(&self, RelativePosition { x: x1, y: y1 }: &RelativePosition) -> Result { + 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 { + Ok(GridPosition { x, y }) } } } +#[derive(Error, Debug)] +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 { fn default() -> Self { GridPosition { x: 5, y: Y_MAX } @@ -229,7 +273,7 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition { } } -#[derive(Component, Default, Debug)] +#[derive(Component, Default, Event, Clone, Debug)] enum Orientation { #[default] Up, @@ -503,9 +547,10 @@ fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed, - mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>, + mut query: Query<(Entity, &Orientation, &mut Shape)>, curr: Res>, mut next: ResMut>, + mut commands: Commands, ) { events.read().for_each( |KeyboardInput { @@ -514,14 +559,22 @@ fn kb_input( if let ButtonState::Pressed = state { // TODO: Restict movement based on size/orientation of piece // 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 { // Up arrow should rotate if in falling mode // Only move up if in falling::off mode - KeyCode::ArrowUp => *o = o.prev(), - KeyCode::ArrowDown => *gp = gp.move_down(), - KeyCode::ArrowLeft => *gp = gp.move_left(), - KeyCode::ArrowRight => *gp = gp.move_right(), + KeyCode::ArrowUp => { + commands.entity(e).trigger(o.prev()); + } + 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() { Falling::On => Falling::Off, Falling::Off => Falling::On, @@ -552,14 +605,9 @@ fn draw_grid(mut gizmos: Gizmos) { .outer_edges(); } -fn falling(mut shape: Query<&mut GridPosition, With>) { - shape.iter_mut().for_each(|mut gp| { - let next = gp.move_down(); - if next != *gp { - *gp = next; - } else { - // TODO: What to do here?? - } +fn falling(mut shape: Query>, mut commands: Commands) { + shape.iter_mut().for_each(|e| { + commands.entity(e).trigger(RelativePosition::down()); }); } @@ -576,7 +624,7 @@ fn shape_block_movement( ) { shape.iter().for_each(|shape_gp| { blocks.iter_mut().for_each(|(mut block_gp, block_rp)| { - *block_gp = shape_gp.add_offset(block_rp); + *block_gp = shape_gp.add_offset(block_rp).expect("Cannot move there..."); }); }); } @@ -646,7 +694,7 @@ fn check_collision( fn add_piece(mut commands: Commands) { // TODO: Choose a different piece - commands.spawn((Orientation::default(), GridPosition::default(), Shape::T)); + commands.spawn((Orientation::default(), GridPosition::default(), Shape::T)).observe(movement).observe(rotation); } /// When a line reaches 10 blocks, clear it @@ -659,7 +707,57 @@ fn clear_line( commands.entity(e).despawn_related::(); // TODO: re-parent all blocks above this to the next line down // TODO: Parent blocks to lines for movement - } }); } + +fn movement( + trigger: Trigger, + mut shapes: Query<&mut GridPosition, With>, + mut active: Query<&mut GridPosition, (With, Without)>, + inactive: Query<&GridPosition, (Without, Without)>, + blocks: Query<&ShapeBlocks>, + mut commands: Commands, +) { + // Find any errors + for block in blocks.iter_descendants(trigger.target()) { + // Get the child's grid position + let gp = active.get(block).unwrap(); + + match gp.add_offset(trigger.event()) { + // If this would go out of bounds to the left or right, prevent this move + Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => { + return + } + // If this would go out of bounds down, or cause a collision, remove the active + // component from the piece + Err(GameError::OutOfBoundsDown) | Err(GameError::Collision) => { + // remove active component + commands.entity(trigger.target()).remove::(); + return + } + Ok(new_gp) => { + // If any children collide with the new position, remove the active component + if inactive.iter().any(|other_gp| new_gp == *other_gp) { + commands.entity(trigger.target()).remove::(); + return + } + } + } + } + + // If the above checks went ok, we can move + let mut gp = shapes.get_mut(trigger.target()).unwrap(); + *gp = gp.add_offset(trigger.event()).unwrap(); +} + +fn rotation( + trigger: Trigger, + mut q: Query<(&mut Orientation, &mut GridPosition)>, +) { + let (mut o, mut _gp) = q.get_mut(trigger.target()).unwrap(); + // If children would go out of bounds to the left + // If children would not go out of bounds to the right + // If children would not go out of bounds to the down + *o = trigger.event().clone(); +}