|
|
|
@ -1,3 +1,4 @@
|
|
|
|
|
|
|
|
#![feature(try_blocks)]
|
|
|
|
// Bevy basically forces "complex types" with Querys
|
|
|
|
// Bevy basically forces "complex types" with Querys
|
|
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
|
|
|
|
|
|
|
|
@ -27,39 +28,44 @@ fn main() {
|
|
|
|
.add_systems(Startup, (init_world, init_debug_ui, init_ui))
|
|
|
|
.add_systems(Startup, (init_world, init_debug_ui, init_ui))
|
|
|
|
// Input and basic systems
|
|
|
|
// Input and basic systems
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update, (
|
|
|
|
Update,
|
|
|
|
|
|
|
|
(
|
|
|
|
kb_input.run_if(on_event::<KeyboardInput>),
|
|
|
|
kb_input.run_if(on_event::<KeyboardInput>),
|
|
|
|
toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>),
|
|
|
|
toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
update_next_shapes.run_if(resource_changed::<ShapesBuffer>.or(resource_added::<ShapesBuffer>)),
|
|
|
|
update_next_shapes
|
|
|
|
add_piece.run_if(not(any_with_component::<Shape>)).after(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
|
|
|
|
update_shape_blocks
|
|
|
|
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)),
|
|
|
|
.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_position.run_if(any_component_changed::<GridPosition>),
|
|
|
|
update_position.run_if(any_component_changed::<GridPosition>),
|
|
|
|
|
|
|
|
|
|
|
|
deactivate_shape.run_if(any_component_removed::<Shape>),
|
|
|
|
deactivate_shape.run_if(any_component_removed::<Shape>),
|
|
|
|
|
|
|
|
check_line_removal,
|
|
|
|
// Clearing lines systems
|
|
|
|
// 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>).after(clear_line),
|
|
|
|
adjust_block_lines
|
|
|
|
|
|
|
|
.run_if(any_component_changed::<Line>)
|
|
|
|
|
|
|
|
.after(clear_line),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
// UI systems
|
|
|
|
// UI systems
|
|
|
|
.add_systems(Update,
|
|
|
|
.add_systems(
|
|
|
|
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
sync_resource_to_ui::<ShapesBuffer>.run_if(resource_changed::<ShapesBuffer>),
|
|
|
|
sync_resource_to_ui::<ShapesBuffer>.run_if(resource_changed::<ShapesBuffer>),
|
|
|
|
sync_resource_to_ui::<Score>.run_if(resource_changed::<Score>),
|
|
|
|
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::<Shape>.run_if(any_component_changed::<Shape>),
|
|
|
|
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
|
|
|
|
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(Update, draw_grid)
|
|
|
|
.add_systems(Update, draw_grid)
|
|
|
|
.run();
|
|
|
|
.run();
|
|
|
|
@ -304,32 +310,26 @@ fn init_ui(mut commands: Commands) {
|
|
|
|
BackgroundColor(BLACK.into()),
|
|
|
|
BackgroundColor(BLACK.into()),
|
|
|
|
))
|
|
|
|
))
|
|
|
|
.with_children(|parent| {
|
|
|
|
.with_children(|parent| {
|
|
|
|
parent.spawn((
|
|
|
|
parent
|
|
|
|
Node {
|
|
|
|
.spawn((Node {
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
align_items: AlignItems::Center,
|
|
|
|
align_items: AlignItems::Center,
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
},))
|
|
|
|
)).with_children(|parent|{
|
|
|
|
.with_children(|parent| {
|
|
|
|
parent.spawn(Text::new("Next:"));
|
|
|
|
parent.spawn(Text::new("Next:"));
|
|
|
|
parent.spawn((
|
|
|
|
parent.spawn((Text::new("???"), SyncResource::<ShapesBuffer>::default()));
|
|
|
|
Text::new("???"),
|
|
|
|
});
|
|
|
|
SyncResource::<ShapesBuffer>::default(),
|
|
|
|
parent
|
|
|
|
));
|
|
|
|
.spawn((Node {
|
|
|
|
});
|
|
|
|
|
|
|
|
parent.spawn((
|
|
|
|
|
|
|
|
Node {
|
|
|
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
align_items: AlignItems::Center,
|
|
|
|
align_items: AlignItems::Center,
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
},))
|
|
|
|
)).with_children(|parent|{
|
|
|
|
.with_children(|parent| {
|
|
|
|
parent.spawn(Text::new("Score:"));
|
|
|
|
parent.spawn(Text::new("Score:"));
|
|
|
|
parent.spawn((
|
|
|
|
parent.spawn((Text::new("???"), SyncResource::<Score>::default()));
|
|
|
|
Text::new("???"),
|
|
|
|
});
|
|
|
|
SyncResource::<Score>::default(),
|
|
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
commands
|
|
|
|
@ -563,7 +563,7 @@ fn update_shape_blocks(
|
|
|
|
visuals: Res<Visuals>,
|
|
|
|
visuals: Res<Visuals>,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
query.iter().for_each(|(e, s, o, center)| {
|
|
|
|
query.iter().for_each(|(e, s, o, center)| {
|
|
|
|
info!("Setting piece: {e:?} {o:?} {center:?}\n{}", s.as_ascii());
|
|
|
|
debug!("Setting piece: {e:?} {o:?} {center:?}\n{}", s.as_ascii());
|
|
|
|
|
|
|
|
|
|
|
|
if blocks.is_empty() {
|
|
|
|
if blocks.is_empty() {
|
|
|
|
let mesh = Mesh2d(visuals.mesh.clone());
|
|
|
|
let mesh = Mesh2d(visuals.mesh.clone());
|
|
|
|
@ -678,16 +678,17 @@ fn add_piece(mut commands: Commands, mut shapes: ResMut<ShapesBuffer>) {
|
|
|
|
/// When a line reaches 10 blocks, clear it
|
|
|
|
/// When a line reaches 10 blocks, clear it
|
|
|
|
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<&mut Line>,
|
|
|
|
|
|
|
|
line_blocks: Query<&LineBlocks>,
|
|
|
|
mut score: ResMut<Score>,
|
|
|
|
mut score: ResMut<Score>,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
let cleared_lines: Vec<usize> = changed_lines
|
|
|
|
let mut cleared_lines: Vec<usize> = changed_lines
|
|
|
|
.iter()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|e| lines.get(e).ok())
|
|
|
|
.filter_map(|e| try { (Some(e)?, line_blocks.get(e).ok()?, lines.get(e).ok()?) } )
|
|
|
|
.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>().insert(LineBlocks::default());
|
|
|
|
score.0 += 1;
|
|
|
|
score.0 += 1;
|
|
|
|
info!("New score: {:?}", score.0);
|
|
|
|
info!("New score: {:?}", score.0);
|
|
|
|
Some(*i)
|
|
|
|
Some(*i)
|
|
|
|
@ -695,24 +696,42 @@ fn clear_line(
|
|
|
|
None
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
.sorted()
|
|
|
|
.collect();
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
info!("Cleared lines: {:?}", cleared_lines);
|
|
|
|
if !cleared_lines.is_empty() {
|
|
|
|
|
|
|
|
info!("Cleared lines: {:?}", cleared_lines);
|
|
|
|
|
|
|
|
|
|
|
|
for (idx, cleared_line_number) in cleared_lines.into_iter().sorted().enumerate() {
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
info!("Processing line {cleared_line_number} ({idx})");
|
|
|
|
{
|
|
|
|
let cleared_line_number = cleared_line_number - idx;
|
|
|
|
debug_assert_eq!(lines.iter().count(), 20, "There should be 20 lines");
|
|
|
|
lines.iter_mut().sorted_by(|(_, _, i), (_, _, j)| i.0.cmp(&j.0)).for_each(|(_, _, mut l)| {
|
|
|
|
// Check that all line numbers are present
|
|
|
|
let dest = if l.0 > cleared_line_number {
|
|
|
|
lines.iter().map(|Line(i)| i).sorted().enumerate().for_each(|(i, line_num)| {
|
|
|
|
l.0 - 1
|
|
|
|
debug_assert_eq!(i, *line_num, "Line numbers should match their sorted index");
|
|
|
|
} else if l.0 == cleared_line_number {
|
|
|
|
});
|
|
|
|
Y_MAX - 1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
|
|
l.0
|
|
|
|
let original_cleared_lines_len = cleared_lines.len();
|
|
|
|
};
|
|
|
|
|
|
|
|
info!("Moving line {:?} to {:?}", l, dest);
|
|
|
|
// Iterate over all lines in reverse sorted order (largest to smallest)
|
|
|
|
l.0 = dest;
|
|
|
|
lines
|
|
|
|
});
|
|
|
|
.iter_mut()
|
|
|
|
|
|
|
|
.sorted_by(|i, j| i.0.cmp(&j.0))
|
|
|
|
|
|
|
|
.rev()
|
|
|
|
|
|
|
|
.for_each(|mut l| {
|
|
|
|
|
|
|
|
// If the current index is in the set of cleared lines, move it to the top
|
|
|
|
|
|
|
|
// Otherwise, move it down by the number of cleared lines
|
|
|
|
|
|
|
|
if cleared_lines.contains(&l.0) {
|
|
|
|
|
|
|
|
// Move to the N-offset line number (top, top-1, etc)
|
|
|
|
|
|
|
|
let offset = original_cleared_lines_len - cleared_lines.len();
|
|
|
|
|
|
|
|
info!("Moving line {:?}->{:?}", l.0, Y_MAX - 1 - offset);
|
|
|
|
|
|
|
|
l.0 = Y_MAX - 1 - offset;
|
|
|
|
|
|
|
|
cleared_lines.pop();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
info!("Moving line {:?}->{:?}", l.0, l.0 - cleared_lines.len());
|
|
|
|
|
|
|
|
l.0 -= cleared_lines.len();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -721,17 +740,6 @@ 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) {
|
|
|
|
@ -767,7 +775,7 @@ fn movement(
|
|
|
|
Movement::Right => (center.with_offset(1, 0), *this_shape),
|
|
|
|
Movement::Right => (center.with_offset(1, 0), *this_shape),
|
|
|
|
Movement::Rotate => (Ok(*center), this_shape.rotated()),
|
|
|
|
Movement::Rotate => (Ok(*center), this_shape.rotated()),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
info!(
|
|
|
|
debug!(
|
|
|
|
"Proposed change: {:?}\n{}",
|
|
|
|
"Proposed change: {:?}\n{}",
|
|
|
|
new_center,
|
|
|
|
new_center,
|
|
|
|
new_shape.as_ascii()
|
|
|
|
new_shape.as_ascii()
|
|
|
|
@ -819,6 +827,14 @@ fn movement(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn check_line_removal(
|
|
|
|
|
|
|
|
mut events: RemovedComponents<Line>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
events.read().for_each(|e| {
|
|
|
|
|
|
|
|
info!("Line entity {:?} removed", e);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Just despawn?
|
|
|
|
// TODO: Just despawn?
|
|
|
|
fn deactivate_shape(
|
|
|
|
fn deactivate_shape(
|
|
|
|
mut events: RemovedComponents<Shape>,
|
|
|
|
mut events: RemovedComponents<Shape>,
|
|
|
|
|