From 728e36171bebb5dc81b8dc994ca95d100c0bebde Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Sat, 25 Oct 2025 22:00:49 -0700 Subject: [PATCH] Adding score and next piece preview --- src/bin/tetris/main.rs | 195 +++++++++++++++++++++++++++++++++-------- 1 file changed, 157 insertions(+), 38 deletions(-) diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index 224ffb0..4713670 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -8,7 +8,10 @@ use itertools::Itertools; mod test; // TODO: Space key: skip to end -// TODO: When line is "full" (has 10 children) clear line and add to score +// TODO: When piece is near wall and rotates, move it over if it fits +// TODO: Make falling based on a timer resource ticking +// This allows us to tune the falling rate over time +// TODO: Preview next batch of pieces that will drop fn main() { App::new() @@ -19,35 +22,54 @@ fn main() { ..default() }) .init_state::() + .init_resource::() + .init_resource::() .add_systems(Startup, (init_world, init_debug_ui, init_ui)) + // Input and basic systems + .add_systems( + Update, ( + kb_input.run_if(on_event::), + toggle_state_visibility::.run_if(state_changed::), + ) + ) .add_systems( Update, ( - kb_input.run_if(on_event::), + update_next_shapes.run_if(resource_changed::.or(resource_added::)), + add_piece.run_if(not(any_with_component::)).after(update_next_shapes), + update_shape_blocks + .run_if(any_component_added::.or(any_component_changed::)), + falling .run_if(in_state(GameState::Falling)) .run_if(clock_cycle(1.0)), - update_shape_blocks - .run_if(any_component_added::.or(any_component_changed::)), - sync_singleton_to_ui::.run_if(any_component_changed::), - sync_singleton_to_ui::.run_if(any_component_changed::), update_position.run_if(any_component_changed::), - add_piece.run_if(not(any_with_component::)), + + deactivate_shape.run_if(any_component_removed::), + + // Clearing lines systems clear_line.run_if(any_component_changed::), - adjust_block_lines.run_if(any_component_changed::), - toggle_state_visibility::.run_if(state_changed::), + adjust_block_lines.run_if(any_component_changed::).after(clear_line), ), ) + // UI systems + .add_systems(Update, + ( + sync_resource_to_ui::.run_if(resource_changed::), + sync_resource_to_ui::.run_if(resource_changed::), + sync_singleton_to_ui::.run_if(any_component_changed::), + sync_singleton_to_ui::.run_if(any_component_changed::), + ) + ) .add_systems(Update, draw_grid) - .add_observer(deactive_shape) .run(); } const SCALE: f32 = 30.0; // Declare the size of the play area -const X_MAX: u32 = 10; -const Y_MAX: u32 = 20; +const X_MAX: usize = 10; +const Y_MAX: usize = 20; // The blocks making up this shape #[derive(Component)] @@ -87,8 +109,8 @@ struct Block; #[derive(Component, Debug, Clone, Copy, PartialEq)] #[require(Transform, Visibility)] struct GridPosition { - x: u32, - y: u32, + x: usize, + y: usize, } impl GridPosition { @@ -104,8 +126,8 @@ impl GridPosition { Err(GameError::OutOfBoundsDown) } else { Ok(GridPosition { - x: x as u32, - y: y as u32, + x: x as usize, + y: y as usize, }) } } @@ -149,8 +171,8 @@ impl From<&GridPosition> for Vec3 { } } -impl From<(u32, u32)> for GridPosition { - fn from((x, y): (u32, u32)) -> GridPosition { +impl From<(usize, usize)> for GridPosition { + fn from((x, y): (usize, usize)) -> GridPosition { GridPosition { x, y } } } @@ -225,6 +247,32 @@ struct Visuals { mesh: Handle, } +#[derive(Resource, Debug, Default)] +struct Score(usize); + +impl Display for Score { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// ShapesBuffer resource stores non-active shapes +#[derive(Resource, Debug, Default)] +struct ShapesBuffer { + /// Next stores a vector of 2N shapes that will come up in play + next: VecDeque, +} + +impl Display for ShapesBuffer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(shape) = self.next.front() { + write!(f, "{shape}") + } else { + write!(f, "ERR") + } + } +} + fn init_world( mut commands: Commands, mut meshes: ResMut>, @@ -238,12 +286,52 @@ fn init_world( mesh: meshes.add(Rectangle::new(SCALE, SCALE)), }); - (0..20).for_each(|i| { + (0..Y_MAX).for_each(|i| { + info!("Spawning line {i}"); commands.spawn((Line(i), LineBlocks::default())); }); } fn init_ui(mut commands: Commands) { + commands + .spawn(( + Node { + align_self: AlignSelf::Center, + justify_self: JustifySelf::End, + flex_direction: FlexDirection::Column, + ..default() + }, + BackgroundColor(BLACK.into()), + )) + .with_children(|parent| { + parent.spawn(( + Node { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + ..default() + }, + )).with_children(|parent|{ + parent.spawn(Text::new("Next:")); + parent.spawn(( + Text::new("???"), + SyncResource::::default(), + )); + }); + parent.spawn(( + Node { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + ..default() + }, + )).with_children(|parent|{ + parent.spawn(Text::new("Score:")); + parent.spawn(( + Text::new("???"), + SyncResource::::default(), + )); + }); + }); + commands .spawn(( Node { @@ -549,7 +637,7 @@ fn draw_grid(mut gizmos: Gizmos) { gizmos .grid_2d( Isometry2d::IDENTITY, - UVec2::new(X_MAX, Y_MAX), + UVec2::new(X_MAX as u32, Y_MAX as u32), Vec2::new(SCALE, SCALE), GREEN, ) @@ -577,13 +665,12 @@ fn clock_cycle(n: f32) -> impl FnMut(Res