diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index 3ff35f3..2fa93fd 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -1,5 +1,12 @@ +// Bevy basically forces "complex types" with Querys +#![allow(clippy::type_complexity)] + use games::*; +// 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 line is "full" (has 10 children) clear line and add to score + fn main() { App::new() .add_plugins(BaseGamePlugin { @@ -13,11 +20,21 @@ fn main() { .add_systems( Update, ( - kb_movement.run_if(on_event::), + kb_input.run_if(on_event::), update_position, - update_orientation, - toggle_falling.run_if(on_event::), - falling.run_if(in_state(Falling::On)).run_if(clock_cycle(1.0)) + falling + .run_if(in_state(Falling::On)) + .run_if(clock_cycle(1.0)), + set_piece + .run_if(any_component_added:: + .or(any_component_changed::) + .or(any_component_added::) + .or(any_component_changed::) + ), + set_relative_piece_positions.run_if( + any_component_added:: + .or(any_component_changed::), + ), ), ) .add_systems(Update, draw_grid) @@ -26,6 +43,34 @@ fn main() { const SCALE: f32 = 30.0; +/// A shape, e.g., the long piece +#[derive(Component, Debug)] +enum Shape { + O, + T, + L, + J, + S, + Z, + I, +} + +/// A part of a piece, i.e., a single square of a piece +#[derive(Component, Debug)] +struct ShapePiece; + +#[derive(Component, Debug)] +struct RelativePosition { + x: i8, + y: i8, +} + +impl From<(i8, i8)> for RelativePosition { + fn from((x, y): (i8, i8)) -> RelativePosition { + RelativePosition { x, y } + } +} + #[derive(Component, Debug, Clone, Copy, PartialEq)] #[require(Transform, Visibility)] struct GridPosition { @@ -36,39 +81,44 @@ struct GridPosition { impl GridPosition { fn move_up(&self) -> Self { Self { - y: if self.y + 1 < 20 { self.y.saturating_add(1) } else { self.y }, - x: self.x + y: if self.y + 1 < 20 { + self.y.saturating_add(1) + } else { + self.y + }, + x: self.x, } } fn move_down(&mut self) -> Self { Self { y: self.y.saturating_sub(1), - x: self.x + x: self.x, } } fn move_left(&mut self) -> Self { Self { x: self.x.saturating_sub(1), - y: self.y + y: self.y, } } fn move_right(&mut self) -> Self { Self { - x: if self.x + 1 < 10 { self.x.saturating_add(1) } else { self.x }, - y: self.y + x: if self.x + 1 < 10 { + self.x.saturating_add(1) + } else { + self.x + }, + y: self.y, } } } impl Default for GridPosition { fn default() -> Self { - GridPosition { - x: 5, - y: 20, - } + GridPosition { x: 5, y: 20 } } } @@ -77,6 +127,7 @@ impl From<&GridPosition> for Vec3 { // Grid Positions start in the bottom left of the area // So (0, 0) is the bottom left, (0, 9) is the bottom right, etc + // TODO: Custom offset allowing pieces like O and I to have correct center let x_0 = -SCALE * 5.0 + (0.5 * SCALE); let x = x_0 + ((*x as f32) * SCALE); @@ -138,23 +189,17 @@ impl Orientation { } } -impl From<&Orientation> for Quat { - fn from(other: &Orientation) -> Quat { - let z = match other { - Orientation::Up => 0.0, - Orientation::Left => -PI * 0.5, - Orientation::Down => -PI, - Orientation::Right => -PI * 1.5, - }; - Quat::from_rotation_z(z) - } -} - #[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)] enum Falling { #[default] On, - Off + Off, +} + +#[derive(Resource, Debug)] +struct Visuals { + material: Handle, + mesh: Handle, } fn init_pieces( @@ -162,35 +207,151 @@ fn init_pieces( mut meshes: ResMut>, mut materials: ResMut>, ) { - commands - .spawn((Orientation::default(), GridPosition::default())) - .with_children(|parent| { - let mat = materials.add(ColorMaterial { - color: WHITE.into(), - ..default() + commands.insert_resource(Visuals { + material: materials.add(ColorMaterial { + color: WHITE.into(), + ..default() + }), + mesh: meshes.add(Rectangle::new(SCALE, SCALE)), + }); + + commands.spawn((Orientation::default(), GridPosition::default(), Shape::T)); +} + +fn set_piece( + query: Query<(Entity, &Shape, &Orientation), Or<(Added, Changed, Added, Changed)>>, + mut commands: Commands, + visuals: Res, +) { + query.iter().for_each(|(e, s, o)| { + info!("{e:?} {s:?} {o:?}"); + commands + .entity(e) + .despawn_related::() + .with_children(|parent| { + let mesh = visuals.mesh.clone(); + let mat = visuals.material.clone(); + + #[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(),(0,1).into(), + (-1,-1).into(), + ], + Orientation::Left => [ + (1,1).into(), + (-1,0).into(),(0,0).into(),(0,1).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 | Orientation::Down => [ + (0,0).into(),(1,0).into(), + (-1,-1).into(),(0,-1).into(), + ], + Orientation::Left | Orientation::Right => [ + (0,1).into(), + (-1,0).into(),(0,0).into(), + (-1,-1).into(), + ] + }, + Shape::Z => match o { + Orientation::Up | Orientation::Down => [ + (-1,0).into(),(0,0).into(), + (0,-1).into(),(1,-1).into(), + ], + Orientation::Left | Orientation::Right => [ + (0,1).into(), + (0,0).into(),(1,0).into(), + (1,-1).into(), + ], + }, + Shape::I => match o { + Orientation::Up | Orientation::Down => [ + (0,2).into(), + (0,1).into(), + (0,0).into(), + (0,-1).into(), + ], + Orientation::Left | Orientation::Right => todo!() + } + }; + + piece_positions.into_iter().for_each(|rp| { + parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp)); }); - let mesh = meshes.add(Rectangle::new(SCALE, SCALE)); - parent.spawn(( - Mesh2d(mesh.clone()), - MeshMaterial2d(mat.clone()), - Transform::from_xyz(0.0, 0.0, 0.0), - )); - parent.spawn(( - Mesh2d(mesh.clone()), - MeshMaterial2d(mat.clone()), - Transform::from_xyz(SCALE, 0.0, 0.0), - )); - parent.spawn(( - Mesh2d(mesh.clone()), - MeshMaterial2d(mat.clone()), - Transform::from_xyz(0.0, SCALE, 0.0), - )); - parent.spawn(( - Mesh2d(mesh.clone()), - MeshMaterial2d(mat.clone()), - Transform::from_xyz(-SCALE, 0.0, 0.0), - )); }); + }); +} + +fn set_relative_piece_positions( + query: Query< + (Entity, &RelativePosition), + Or<(Added, Changed)>, + >, + mut commands: Commands, +) { + query.iter().for_each(|(e, rp)| { + commands.entity(e).insert(Transform::from_xyz( + (rp.x as f32) * SCALE, + (rp.y as f32) * SCALE, + 0.0, + )); + }); } fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed>) { @@ -200,14 +361,12 @@ fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed>) { - query.iter_mut().for_each(|(o, mut t)| { - t.rotation = o.into(); - debug!("Setting orientation to {:?}", o); - }); -} - -fn kb_movement(mut events: EventReader, mut query: Query<(&mut GridPosition, &mut Orientation)>) { +fn kb_input( + mut events: EventReader, + mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>, + curr: Res>, + mut next: ResMut>, +) { events.read().for_each( |KeyboardInput { key_code, state, .. @@ -215,14 +374,26 @@ fn kb_movement(mut events: EventReader, mut query: Query<(&mut Gr 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)| { + query.iter_mut().for_each(|(mut gp, mut o, mut s)| { match key_code { - KeyCode::ArrowUp => *gp = gp.move_up(), + // Up arrow should rotate if in falling mode + // Only move up if in falling::off mode + KeyCode::ArrowUp => *o = o.next(), KeyCode::ArrowDown => *gp = gp.move_down(), KeyCode::ArrowLeft => *gp = gp.move_left(), KeyCode::ArrowRight => *gp = gp.move_right(), - KeyCode::Enter => *o = o.next(), - _ => () + KeyCode::Space => next.set(match curr.get() { + Falling::On => Falling::Off, + Falling::Off => Falling::On, + }), + KeyCode::Digit1 => *s = Shape::T, + KeyCode::Digit2 => *s = Shape::O, + KeyCode::Digit3 => *s = Shape::L, + KeyCode::Digit4 => *s = Shape::J, + KeyCode::Digit5 => *s = Shape::S, + KeyCode::Digit6 => *s = Shape::Z, + KeyCode::Digit7 => *s = Shape::I, + _ => (), } }); } @@ -241,25 +412,8 @@ fn draw_grid(mut gizmos: Gizmos) { .outer_edges(); } -fn toggle_falling( - mut events: EventReader, - curr: Res>, - mut next: ResMut>, -) { - events.read().for_each(|KeyboardInput { state, key_code, .. }| { - if let ButtonState::Pressed = state && *key_code == KeyCode::Space { - next.set(match curr.get() { - Falling::On => Falling::Off, - Falling::Off => Falling::On, - }); - } - }) -} - -fn falling( - mut query: Query<&mut GridPosition> -) { - query.iter_mut ().for_each(|mut gp| { +fn falling(mut query: Query<&mut GridPosition, With>) { + query.iter_mut().for_each(|mut gp| { let next = gp.move_down(); if next != *gp { *gp = next; @@ -270,7 +424,7 @@ fn falling( } // Run condition that returns `true` every `n` seconds -fn clock_cycle(n: f32) -> impl FnMut (Res