diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index f4de31c..070ed90 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -35,7 +35,6 @@ fn main() { init_cameras, init_ui.after(init_cameras), init_battler, - init_deck, ), ) // Input and basic systems @@ -56,10 +55,10 @@ fn main() { .after(update_next_shapes), falling .run_if(in_state(GameState::Falling)) - .run_if(clock_cycle(1.0)), + .run_if(on_timer(Duration::from_secs(1))), update_position.run_if(any_component_changed::), update_shape_blocks - .run_if(any_component_added::.or(any_component_changed::)) + .run_if(any_component_changed::.or(any_component_changed::)) .after(update_position), deactivate_shape .run_if(any_component_removed::) @@ -74,7 +73,7 @@ fn main() { .run_if(any_component_changed::.or(any_component_added::)), damage_on_place_shape.run_if(any_component_removed::), damage_on_clear_line.run_if(any_component_removed::), - damage_over_time.run_if(clock_cycle(5.0)), + damage_over_time.run_if(on_timer(Duration::from_secs(5))), ), ) // UI systems @@ -88,6 +87,8 @@ fn main() { ), ) .add_observer(deal_damage) + .add_observer(on_add_shape_layout) + .add_observer(on_add_health) .run(); } @@ -227,8 +228,14 @@ enum GameState { #[derive(Resource, Debug)] struct Visuals { - material: Handle, mesh: Handle, + material_o: Handle, + material_t: Handle, + material_l: Handle, + material_j: Handle, + material_s: Handle, + material_z: Handle, + material_i: Handle, } #[derive(Resource, Debug, Default)] @@ -266,7 +273,7 @@ struct ShapeStore(Option); impl Display for ShapeStore { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.0 { - Some(inner) => write!(f, "{}", inner.as_ascii()), + Some(s) => write!(f, "{}", ShapeLayout::from_shape(s).as_ascii()), None => write!(f, "---"), } } @@ -281,17 +288,36 @@ fn init_tetris( mut materials: ResMut>, ) { let mesh = meshes.add(Rectangle::new(SCALE, SCALE)); - let block_material = materials.add(ColorMaterial { - color: WHITE.into(), - ..default() - }); - let grid_material = materials.add(ColorMaterial { - color: BLACK.into(), - ..default() - }); commands.insert_resource(Visuals { - material: block_material.clone(), mesh: mesh.clone(), + material_o: materials.add(ColorMaterial { + color: YELLOW.into(), + ..default() + }), + material_t: materials.add(ColorMaterial { + color: PURPLE.into(), + ..default() + }), + material_l: materials.add(ColorMaterial { + color: ORANGE.into(), + ..default() + }), + material_j: materials.add(ColorMaterial { + color: BLUE.into(), + ..default() + }), + material_s: materials.add(ColorMaterial { + color: LIME.into(), + ..default() + }), + material_z: materials.add(ColorMaterial { + color: RED.into(), + ..default() + }), + material_i: materials.add(ColorMaterial { + color: AQUA.into(), + ..default() + }), }); (0..Y_MAX).for_each(|y| { @@ -299,7 +325,10 @@ fn init_tetris( (0..X_MAX).for_each(|x| { commands.spawn(( Mesh2d(mesh.clone()), - MeshMaterial2d(grid_material.clone()), + MeshMaterial2d(materials.add(ColorMaterial { + color: BLACK.into(), + ..default() + })), GridPosition { x, y }, Transform::from_xyz(0.0, 0.0, -1.0), GridBackground, @@ -312,12 +341,6 @@ fn init_tetris( }); } -fn init_deck( - mut commands: Commands, -) { - todo!() -} - #[derive(Component, Debug)] struct Protagonist; @@ -579,32 +602,63 @@ fn init_debug_ui(mut commands: Commands) { }); } +/// Enum describing a shape option #[derive(Component, Debug, Clone)] -struct Shape { - layout: ShapeBlockLayout, +enum Shape { + O, + T, + L, + J, + S, + Z, + I, } impl Default for Shape { fn default() -> Self { - Self::new_t() + Self::T } } impl Display for Shape { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.layout.as_ascii()) + write!(f, "{self:?}") } } -impl From>> for Shape { - fn from(inner: Vec>) -> Shape { - Shape { - layout: ShapeBlockLayout { inner }, - } +impl Shape { + fn reposition( + (x_offset, y_offset): (isize, isize), + center: &GridPosition, + ) -> Result { + center.with_offset(x_offset, y_offset) } } -impl Shape { +#[derive(Component, Debug, Clone, PartialEq, Eq)] +struct ShapeLayout { + inner: Vec>, +} + +impl From>> for ShapeLayout { + fn from(inner: Vec>) -> Self { + Self { inner } + } +} + +impl ShapeLayout { + fn from_shape(s: &Shape) -> Self { + match s { + Shape::O => Self::new_o(), + Shape::T => Self::new_t(), + Shape::L => Self::new_l(), + Shape::J => Self::new_j(), + Shape::S => Self::new_s(), + Shape::Z => Self::new_z(), + Shape::I => Self::new_i(), + } + } + fn new_o() -> Self { vec![vec![1, 1], vec![1, 1]].into() } @@ -637,45 +691,6 @@ impl Shape { vec![vec![1], vec![1], vec![1], vec![1]].into() } - // Rotates 90 degrees to the right - // https://stackoverflow.com/a/8664879 - fn rotated(&self) -> Self { - Self { - layout: self.layout.rotated(), - } - } - - fn reposition( - (x_offset, y_offset): (isize, isize), - center: &GridPosition, - ) -> Result { - center.with_offset(x_offset, y_offset) - } - - fn coordinates( - &self, - center: &GridPosition, - ) -> impl Iterator> { - self.layout - .coordinates() - .map(|(x, y)| Shape::reposition((x, y), center)) - } - - fn as_ascii(&self) -> String { - self.layout.as_ascii() - } - - fn height(&self) -> usize { - self.layout.height() - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -struct ShapeBlockLayout { - inner: Vec>, -} - -impl ShapeBlockLayout { fn rotated(&self) -> Self { let mut inner = vec![]; for _ in 0..self.inner[0].len() { @@ -686,7 +701,7 @@ impl ShapeBlockLayout { inner[j].insert(0, *x); } } - ShapeBlockLayout { inner } + ShapeLayout { inner } } fn center(&self) -> (usize, usize) { @@ -709,6 +724,14 @@ impl ShapeBlockLayout { (mid_x, mid_y) } + fn coordinates_at( + &self, + center: &GridPosition, + ) -> impl Iterator> { + self.coordinates() + .map(|(x, y)| Shape::reposition((x, y), center)) + } + fn coordinates(&self) -> impl Iterator { let (mid_x, mid_y) = self.center(); // Loop over outer vec (i) @@ -771,45 +794,57 @@ fn update_position( // TODO: Move to event fn update_shape_blocks( query: Query< - (Entity, &Shape, &GridPosition), + (Entity, &ShapeLayout, &GridPosition), Or<( - Added, - Changed, - Added, + Changed, Changed, )>, >, - mut blocks: Query<&mut GridPosition, (With, Without)>, - mut commands: Commands, - visuals: Res, + mut blocks: Query<&mut GridPosition, (With, Without)>, ) { - query.iter().for_each(|(e, s, center)| { - debug!("Setting piece: {e:?} {center:?}\n{}", s.as_ascii()); + query.iter().for_each(|(e, sl, center)| { + debug!("Setting piece: {e:?} {center:?}\n{}", sl.as_ascii()); - if blocks.is_empty() { - let mesh = Mesh2d(visuals.mesh.clone()); - let mat = MeshMaterial2d(visuals.material.clone()); - commands - .entity(e) - .with_related_entities::(|parent| { - s.coordinates(center).for_each(|gp| { - parent - .spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block, TETRIS)) - .observe(movement); - }); - }); - } else { - let mut p = s.coordinates(center); - blocks.iter_mut().for_each(|mut gp| { - *gp = p.next().unwrap().unwrap(); - }); - } + debug_assert!(!blocks.is_empty()); + + let mut p = sl.coordinates_at(center); + blocks.iter_mut().for_each(|mut gp| { + *gp = p.next().unwrap().unwrap(); + }); }); } +fn on_add_shape_layout( + trigger: On, + visuals: Res, + query: Query<(Entity, &Shape, &ShapeLayout, &GridPosition)>, + mut commands: Commands, +) { + let (e, s, sl, center) = query.get(trigger.entity).unwrap(); + let mesh = Mesh2d(visuals.mesh.clone()); + let mat = match s { + Shape::O => &visuals.material_o, + Shape::T => &visuals.material_t, + Shape::L => &visuals.material_l, + Shape::J => &visuals.material_j, + Shape::S => &visuals.material_s, + Shape::Z => &visuals.material_z, + Shape::I => &visuals.material_i, + }; + commands + .entity(e) + .with_related_entities::(|parent| { + sl.coordinates_at(center).for_each(|gp| { + parent + .spawn((mesh.clone(), MeshMaterial2d(mat.clone()), gp.unwrap(), Block, TETRIS)) + .observe(movement); + }); + }); +} + fn kb_input( mut events: MessageReader, - mut query: Query<(Entity, &mut Shape)>, + mut query: Query>, curr: Res>, mut next: ResMut>, mut commands: Commands, @@ -819,7 +854,7 @@ fn kb_input( key_code, state, .. }| { if let ButtonState::Pressed = state { - query.iter_mut().for_each(|(e, mut s)| { + query.iter_mut().for_each(|e| { match key_code { // Up arrow should rotate if in falling mode // Only move up if in falling::off mode @@ -860,13 +895,6 @@ fn kb_input( GameState::Falling => GameState::Pause, GameState::Pause => GameState::Falling, }), - KeyCode::Digit1 => *s = Shape::new_t(), - KeyCode::Digit2 => *s = Shape::new_o(), - KeyCode::Digit3 => *s = Shape::new_l(), - KeyCode::Digit4 => *s = Shape::new_j(), - KeyCode::Digit5 => *s = Shape::new_s(), - KeyCode::Digit6 => *s = Shape::new_z(), - KeyCode::Digit7 => *s = Shape::new_i(), _ => (), } }); @@ -885,29 +913,17 @@ fn falling(mut shape: Query>, mut commands: Commands) { }); } -// Run condition that returns `true` every `n` seconds -// TODO: Update a resource with the current tick -fn clock_cycle(n: f32) -> impl FnMut(Res