Adding score and next piece preview

main
Elijah Voigt 1 day ago
parent c3335e9263
commit 728e36171b

@ -8,7 +8,10 @@ use itertools::Itertools;
mod test; mod test;
// TODO: Space key: skip to end // 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() { fn main() {
App::new() App::new()
@ -19,35 +22,54 @@ fn main() {
..default() ..default()
}) })
.init_state::<GameState>() .init_state::<GameState>()
.init_resource::<ShapesBuffer>()
.init_resource::<Score>()
.add_systems(Startup, (init_world, init_debug_ui, init_ui)) .add_systems(Startup, (init_world, init_debug_ui, init_ui))
// Input and basic systems
.add_systems(
Update, (
kb_input.run_if(on_event::<KeyboardInput>),
toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>),
)
)
.add_systems( .add_systems(
Update, Update,
( (
kb_input.run_if(on_event::<KeyboardInput>), update_next_shapes.run_if(resource_changed::<ShapesBuffer>.or(resource_added::<ShapesBuffer>)),
add_piece.run_if(not(any_with_component::<Shape>)).after(update_next_shapes),
update_shape_blocks
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)),
falling falling
.run_if(in_state(GameState::Falling)) .run_if(in_state(GameState::Falling))
.run_if(clock_cycle(1.0)), .run_if(clock_cycle(1.0)),
update_shape_blocks
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
update_position.run_if(any_component_changed::<GridPosition>), update_position.run_if(any_component_changed::<GridPosition>),
add_piece.run_if(not(any_with_component::<Shape>)),
deactivate_shape.run_if(any_component_removed::<Shape>),
// Clearing lines systems
clear_line.run_if(any_component_changed::<LineBlocks>), clear_line.run_if(any_component_changed::<LineBlocks>),
adjust_block_lines.run_if(any_component_changed::<Line>), adjust_block_lines.run_if(any_component_changed::<Line>).after(clear_line),
toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>),
), ),
) )
// UI systems
.add_systems(Update,
(
sync_resource_to_ui::<ShapesBuffer>.run_if(resource_changed::<ShapesBuffer>),
sync_resource_to_ui::<Score>.run_if(resource_changed::<Score>),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
)
)
.add_systems(Update, draw_grid) .add_systems(Update, draw_grid)
.add_observer(deactive_shape)
.run(); .run();
} }
const SCALE: f32 = 30.0; const SCALE: f32 = 30.0;
// Declare the size of the play area // Declare the size of the play area
const X_MAX: u32 = 10; const X_MAX: usize = 10;
const Y_MAX: u32 = 20; const Y_MAX: usize = 20;
// The blocks making up this shape // The blocks making up this shape
#[derive(Component)] #[derive(Component)]
@ -87,8 +109,8 @@ struct Block;
#[derive(Component, Debug, Clone, Copy, PartialEq)] #[derive(Component, Debug, Clone, Copy, PartialEq)]
#[require(Transform, Visibility)] #[require(Transform, Visibility)]
struct GridPosition { struct GridPosition {
x: u32, x: usize,
y: u32, y: usize,
} }
impl GridPosition { impl GridPosition {
@ -104,8 +126,8 @@ impl GridPosition {
Err(GameError::OutOfBoundsDown) Err(GameError::OutOfBoundsDown)
} else { } else {
Ok(GridPosition { Ok(GridPosition {
x: x as u32, x: x as usize,
y: y as u32, y: y as usize,
}) })
} }
} }
@ -149,8 +171,8 @@ impl From<&GridPosition> for Vec3 {
} }
} }
impl From<(u32, u32)> for GridPosition { impl From<(usize, usize)> for GridPosition {
fn from((x, y): (u32, u32)) -> GridPosition { fn from((x, y): (usize, usize)) -> GridPosition {
GridPosition { x, y } GridPosition { x, y }
} }
} }
@ -225,6 +247,32 @@ struct Visuals {
mesh: Handle<Mesh>, mesh: Handle<Mesh>,
} }
#[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<Shape>,
}
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( fn init_world(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
@ -238,12 +286,52 @@ fn init_world(
mesh: meshes.add(Rectangle::new(SCALE, SCALE)), 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())); commands.spawn((Line(i), LineBlocks::default()));
}); });
} }
fn init_ui(mut commands: Commands) { 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::<ShapesBuffer>::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::<Score>::default(),
));
});
});
commands commands
.spawn(( .spawn((
Node { Node {
@ -549,7 +637,7 @@ fn draw_grid(mut gizmos: Gizmos) {
gizmos gizmos
.grid_2d( .grid_2d(
Isometry2d::IDENTITY, Isometry2d::IDENTITY,
UVec2::new(X_MAX, Y_MAX), UVec2::new(X_MAX as u32, Y_MAX as u32),
Vec2::new(SCALE, SCALE), Vec2::new(SCALE, SCALE),
GREEN, GREEN,
) )
@ -577,13 +665,12 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
} }
} }
fn add_piece(mut commands: Commands) { fn add_piece(mut commands: Commands, mut shapes: ResMut<ShapesBuffer>) {
// TODO: Choose a different piece
commands commands
.spawn(( .spawn((
Orientation::default(), Orientation::default(),
GridPosition::default(), GridPosition::default(),
Shape::default(), shapes.next.pop_front().unwrap(),
)) ))
.observe(movement); .observe(movement);
} }
@ -592,6 +679,7 @@ fn add_piece(mut commands: Commands) {
fn clear_line( fn clear_line(
changed_lines: Query<Entity, Changed<LineBlocks>>, changed_lines: Query<Entity, Changed<LineBlocks>>,
mut lines: Query<(Entity, &LineBlocks, &mut Line)>, mut lines: Query<(Entity, &LineBlocks, &mut Line)>,
mut score: ResMut<Score>,
mut commands: Commands, mut commands: Commands,
) { ) {
let cleared_lines: Vec<usize> = changed_lines let cleared_lines: Vec<usize> = changed_lines
@ -600,6 +688,8 @@ fn clear_line(
.filter_map(|(e, lb, Line(i))| { .filter_map(|(e, lb, Line(i))| {
if lb.0.len() == 10 { if lb.0.len() == 10 {
commands.entity(e).despawn_related::<LineBlocks>(); commands.entity(e).despawn_related::<LineBlocks>();
score.0 += 1;
info!("New score: {:?}", score.0);
Some(*i) Some(*i)
} else { } else {
None None
@ -612,11 +702,11 @@ fn clear_line(
for (idx, cleared_line_number) in cleared_lines.into_iter().sorted().enumerate() { for (idx, cleared_line_number) in cleared_lines.into_iter().sorted().enumerate() {
info!("Processing line {cleared_line_number} ({idx})"); info!("Processing line {cleared_line_number} ({idx})");
let cleared_line_number = cleared_line_number - idx; let cleared_line_number = cleared_line_number - idx;
lines.iter_mut().for_each(|(_, _, mut l)| { lines.iter_mut().sorted_by(|(_, _, i), (_, _, j)| i.0.cmp(&j.0)).for_each(|(_, _, mut l)| {
let dest = if l.0 > cleared_line_number { let dest = if l.0 > cleared_line_number {
l.0 - 1 l.0 - 1
} else if l.0 == cleared_line_number { } else if l.0 == cleared_line_number {
(Y_MAX - (idx + 1) as u32) as usize Y_MAX - 1
} else { } else {
l.0 l.0
}; };
@ -631,10 +721,21 @@ fn adjust_block_lines(
parent: Query<&LineBlocks>, parent: Query<&LineBlocks>,
mut blocks: Query<&mut GridPosition>, mut blocks: Query<&mut GridPosition>,
) { ) {
#[cfg(debug_assertions)]
{
// Check that all line numbers are present
let expected_line_numbers = 0..Y_MAX;
let actual_line_numbers = query.iter().map(|(_, Line(i))| i).sorted();
query.iter().map(|(_, Line(i))| i).sorted().for_each(|i| info!("Line #: {i}"));
std::iter::zip(expected_line_numbers, actual_line_numbers).for_each(|(a, b)| {
debug_assert_eq!(a as usize, *b);
});
}
query.iter().for_each(|(e, Line(i))| { query.iter().for_each(|(e, Line(i))| {
parent.iter_descendants(e).for_each(|block| { parent.iter_descendants(e).for_each(|block| {
if let Ok(mut gp) = blocks.get_mut(block) { if let Ok(mut gp) = blocks.get_mut(block) {
gp.y = *i as u32; gp.y = *i;
} }
}); });
}); });
@ -719,23 +820,41 @@ fn movement(
} }
// TODO: Just despawn? // TODO: Just despawn?
fn deactive_shape( fn deactivate_shape(
trigger: Trigger<OnRemove, Shape>, mut events: RemovedComponents<Shape>,
grid_positions: Query<&GridPosition>, grid_positions: Query<&GridPosition>,
parent: Query<&ShapeBlocks>, parent: Query<&ShapeBlocks>,
lines: Query<(Entity, &Line), With<LineBlocks>>, lines: Query<(Entity, &Line), With<LineBlocks>>,
mut commands: Commands, mut commands: Commands,
) { ) {
parent.iter_descendants(trigger.target()).for_each(|block| { events.read().for_each(|target| {
parent.iter_descendants(target).for_each(|block| {
let GridPosition { y, .. } = grid_positions.get(block).unwrap(); let GridPosition { y, .. } = grid_positions.get(block).unwrap();
let parent_line = lines let parent_line = lines
.iter() .iter()
.find_map(|(e, Line(i))| (*y == *i as u32).then_some(e)) .find_map(|(e, Line(i))| (*y == *i).then_some(e))
.unwrap(); .unwrap(); // TODO: This crashed once kinda late in a game... why?
commands commands
.entity(parent_line) .entity(parent_line)
.add_one_related::<LineBlock>(block); .add_one_related::<LineBlock>(block);
}); });
commands.entity(trigger.target()).despawn(); commands.entity(target).despawn();
});
} }
fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
// If the buffer contains less than n+1 shapes (where n is the number of possible shapes)
// Ideally we have between 1n and 2n shapes in the `next` buffer
while buffer.next.len() < 8 {
// TODO: Shuffle these!
buffer.next.extend([
Shape::new_o(),
Shape::new_t(),
Shape::new_l(),
Shape::new_j(),
Shape::new_s(),
Shape::new_z(),
Shape::new_i(),
]);
}
}

Loading…
Cancel
Save