From 7ae68f0e4ca5e3bee513357e9c2166fad0fffc8a Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Thu, 9 Oct 2025 23:43:35 -0700 Subject: [PATCH] The basic idea of "Move the tiles instead of rotating the shapes" It is very buggy, but you get the just if what I'm going for. I'll fix it up tomorrow. --- src/bin/tetris/main.rs | 326 ++++++++++++++++++++++++++++++----------- 1 file changed, 240 insertions(+), 86 deletions(-) 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