saving my place

main
Elijah Voigt 2 weeks ago
parent 6e7ac3ce4e
commit 570b4916bc

@ -28,6 +28,7 @@ fn main() {
.init_resource::<Score>()
.init_resource::<OutputImages>()
.init_resource::<LevelGoal>()
.add_message::<Movement>()
.add_systems(
Startup,
(
@ -68,6 +69,7 @@ fn main() {
falling
.run_if(in_state(GameState::Play))
.run_if(on_timer(Duration::from_secs(1))),
movement.run_if(on_message::<Movement>),
update_position.run_if(any_component_changed::<GridPosition>),
update_shape_blocks
.run_if(any_component_changed::<ShapeLayout>.or(any_component_changed::<GridPosition>))
@ -80,14 +82,13 @@ fn main() {
adjust_block_lines
.run_if(any_component_changed::<Line>)
.after(clear_line),
assert_grid_position_uniqueness.run_if(any_component_changed::<GridPosition>),
sync_health
.run_if(any_component_changed::<Health>.or(any_component_added::<Health>)),
damage_on_place_shape.run_if(any_component_removed::<Shape>),
damage_on_clear_line.run_if(any_component_removed::<LineBlock>),
damage_over_time.run_if(on_timer(Duration::from_secs(5))),
check_level_goal.run_if(resource_changed::<Score>),
check_level_fail.run_if(any_component_removed::<ShapeBlock>),
assert_grid_position_uniqueness.run_if(any_component_changed::<GridPosition>),
toggle_art.run_if(any_component_added::<Art>),
),
)
@ -182,7 +183,7 @@ impl Display for GridPosition {
}
}
#[derive(Error, Debug, PartialEq)]
#[derive(Error, Debug, PartialEq, Clone, Copy)]
enum OutOfBoundsError {
#[error("Coordinates are out of bounds: Left")]
Left,
@ -990,9 +991,7 @@ fn on_add_shape_layout(
.entity(e)
.with_related_entities::<ShapeBlock>(|parent| {
sl.coordinates_at(center).for_each(|gp| {
parent
.spawn((mesh.clone(), MeshMaterial2d(mat.clone()), art.clone(), gp.unwrap(), Block, TETRIS))
.observe(movement);
parent.spawn((mesh.clone(), MeshMaterial2d(mat.clone()), art.clone(), gp.unwrap(), Block, TETRIS));
});
});
}
@ -1002,6 +1001,7 @@ fn kb_input(
mut query: Query<Entity, With<Shape>>, // Single?
curr: Res<State<GameState>>,
mut next: ResMut<NextState<GameState>>,
mut messages: MessageWriter<Movement>,
mut commands: Commands,
) {
events.read().for_each(
@ -1009,43 +1009,43 @@ fn kb_input(
key_code, state, ..
}| {
if let ButtonState::Pressed = state {
query.iter_mut().for_each(|e| {
query.iter_mut().for_each(|entity| {
match curr.get() {
GameState::Play => match key_code {
// Up arrow should rotate if in falling mode
// Only move up if in falling::off mode
KeyCode::ArrowUp => {
commands.entity(e).trigger(|entity| Movement {
messages.write(Movement {
entity,
direction: MovementDirection::Rotate,
});
}
KeyCode::ArrowDown => {
commands.entity(e).trigger(|entity| Movement {
messages.write(Movement {
entity,
direction: MovementDirection::Down,
});
}
KeyCode::ArrowLeft => {
commands.entity(e).trigger(|entity| Movement {
messages.write(Movement {
entity,
direction: MovementDirection::Left,
});
}
KeyCode::ArrowRight => {
commands.entity(e).trigger(|entity| Movement {
messages.write(Movement {
entity,
direction: MovementDirection::Right,
});
}
KeyCode::Enter => {
commands.entity(e).trigger(|entity| Movement {
messages.write(Movement {
entity,
direction: MovementDirection::Skip,
});
}
KeyCode::Space => {
commands.entity(e).trigger(|entity| Swap { entity });
commands.entity(entity).trigger(|entity| Swap { entity });
}
KeyCode::Escape => next.set(GameState::Pause),
_ => (),
@ -1072,10 +1072,10 @@ fn kb_input(
);
}
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
shape.iter_mut().for_each(|e| {
debug!("Making {:?} fall", e);
commands.entity(e).trigger(|entity| Movement {
fn falling(mut shape: Query<Entity, With<Shape>>, mut messages: MessageWriter<Movement>) {
shape.iter_mut().for_each(|entity| {
debug!("Making {:?} fall", entity);
messages.write(Movement {
entity,
direction: MovementDirection::Down,
});
@ -1095,7 +1095,6 @@ fn add_piece(mut commands: Commands, mut shapes: ResMut<ShapesBuffer>) {
shape,
TETRIS,
))
.observe(movement)
.observe(swap);
}
@ -1205,7 +1204,7 @@ enum MovementDirection {
// TODO: When out of bounds left/right, try to move piece away from wall
fn movement(
event: On<Movement>,
mut messages: MessageReader<Movement>,
mut grid_positions: Query<
&mut GridPosition,
Or<(With<ShapeBlock>, With<ShapeBlocks>, Without<LineBlock>)>,
@ -1213,80 +1212,97 @@ fn movement(
mut shape_layouts: Query<&mut ShapeLayout>,
inactive: Query<&GridPosition, (Without<ShapeBlock>, Without<ShapeBlocks>, With<LineBlock>)>,
mut commands: Commands,
mut next_state: ResMut<NextState<GameState>>,
) {
if let (Ok(this_shape_layout), Ok(center)) = (
shape_layouts.get_mut(event.entity),
grid_positions.get(event.entity),
) {
let new_positions = match event.event().direction {
MovementDirection::Down => vec![center.with_offset(0, -1)],
MovementDirection::Left => vec![center.with_offset(-1, 0)],
MovementDirection::Right => vec![center.with_offset(1, 0)],
MovementDirection::Rotate => vec![Ok(*center)],
MovementDirection::Skip => (1..=center.y)
.map(|i| center.with_offset(0, -(i as isize)))
.collect(),
};
let new_shape_layout = match event.event().direction {
MovementDirection::Down
| MovementDirection::Left
| MovementDirection::Right
| MovementDirection::Skip => this_shape_layout.clone(),
MovementDirection::Rotate => this_shape_layout.rotated(),
};
debug!(
"Proposed change: {:?}\n{}",
new_positions,
new_shape_layout.as_ascii()
);
for position in new_positions {
match position {
Err(OutOfBoundsError::Left) | Err(OutOfBoundsError::Right) => (), // Do nothing
Err(OutOfBoundsError::Down) => {
commands.entity(event.entity).remove::<Shape>();
}
Ok(new_center) => {
let new_blocks = new_shape_layout.coordinates_at(&new_center);
for block_gp in new_blocks {
match block_gp {
Err(OutOfBoundsError::Left) | Err(OutOfBoundsError::Right) => {
return;
} // Do nothing
Err(OutOfBoundsError::Down) => {
commands.entity(event.entity).remove::<Shape>();
return;
}
Ok(gp) => {
for other_gp in inactive.iter() {
// If there would be a collision between blocks
if gp == *other_gp {
// And we are moving down
if event.event().direction == MovementDirection::Down {
// De-activate this piece
commands.entity(event.entity).remove::<Shape>();
}
// Regardless, cancel the move
return;
messages.read().for_each(|message| {
if let (Ok(this_shape_layout), Ok(center)) = (
shape_layouts.get_mut(message.entity),
grid_positions.get(message.entity),
) {
let new_positions = match message.direction {
MovementDirection::Down => vec![center.with_offset(0, -1)],
MovementDirection::Left => vec![center.with_offset(-1, 0)],
MovementDirection::Right => vec![center.with_offset(1, 0)],
MovementDirection::Rotate => vec![Ok(*center)],
MovementDirection::Skip => (1..=center.y)
.map(|i| center.with_offset(0, -(i as isize)))
.collect(),
};
let new_shape_layout = match message.direction {
MovementDirection::Down
| MovementDirection::Left
| MovementDirection::Right
| MovementDirection::Skip => this_shape_layout.clone(),
MovementDirection::Rotate => this_shape_layout.rotated(),
};
debug!(
"Proposed change: {:?}\n{}",
new_positions,
new_shape_layout.as_ascii()
);
// For each of the proposed positions
for position in new_positions {
if let Ok(new_center) = position {
let new_blocks: Vec<Result<GridPosition, OutOfBoundsError>> = new_shape_layout
.coordinates_at(&new_center)
.collect();
// If the move would cause one block to be out of bounds,
// Not a valid move so return.
if new_blocks.contains(&Err(OutOfBoundsError::Left))
|| new_blocks.contains(&Err(OutOfBoundsError::Right)) {
return
}
// If would be out of bounds at the bottom of play
// Deactivate shape as we have hit the floor
if new_blocks.contains(&Err(OutOfBoundsError::Down)) {
commands.entity(message.entity).remove::<Shape>();
return;
}
// If all positions are within bounds, check for collisions
if new_blocks.iter().all(|b| b.is_ok()) {
for block_gp in new_blocks.iter().map(|b| b.unwrap()) {
for other_gp in inactive.iter() {
// If there would be a collision between blocks
if block_gp == *other_gp {
// Check if this pieces is out of bounds at the top
let out_of_bounds_top = block_gp.y >= Y_MAX;
// If so, transition to the game over state
if out_of_bounds_top {
next_state.set(GameState::GameOver);
}
// We are moving down
if [
MovementDirection::Down,
MovementDirection::Skip,
].contains(&message.direction) {
// De-activate this piece
commands.entity(message.entity).remove::<Shape>();
}
// Regardless, cancel the move
return;
}
}
}
}
debug!("Checks passed for {position:?}, committing change");
// Update center
let mut gp = grid_positions.get_mut(event.entity).unwrap();
let mut gp = grid_positions.get_mut(message.entity).unwrap();
*gp = new_center;
// Update shape/rotation
let mut sl = shape_layouts.get_mut(event.entity).unwrap();
let mut sl = shape_layouts.get_mut(message.entity).unwrap();
*sl = new_shape_layout.clone();
}
}
} else {
warn!("Oned movement on non-shape entity");
}
} else {
warn!("Oned movement on non-shape entity");
}
});
}
fn swap(
@ -1359,16 +1375,14 @@ fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
fn assert_grid_position_uniqueness(
grid_positions: Query<(Entity, &GridPosition, Option<&LineBlock>, Option<&ShapeBlock>), With<Block>>,
mut next: ResMut<NextState<GameState>>,
) {
grid_positions.iter_combinations().for_each(|[(e1, a, lb1, sb1), (e2, b, lb2, sb2)]| {
if a == b {
error!("Two entities are in the same grid position: {a:?} <-> {b:?}!");
error!("\tE1: {:?} (Parent Block: {:?}| Parent Line: {:?})!", e1, lb1, sb1);
error!("\tE2: {:?} (Parent Block: {:?}| Parent Line: {:?})!", e2, lb2, sb2);
next.set(GameState::Pause);
error!("Entities {e1:?} and {e2:?} @ GP {a:?}/{b:?}!");
error!("\t{:?}: (Block: {:?}| Line: {:?})!", e1, sb1, lb1);
error!("\t{:?}: (Block: {:?}| Line: {:?})!", e2, sb2, lb2);
}
// assert_ne!(a, b, "Two entities are in the same grid position!");
assert_ne!(a, b, "Two entities are in the same grid position!");
});
}
@ -1470,19 +1484,6 @@ fn check_level_goal(
}
}
fn check_level_fail(
query: Query<&GridPosition, (With<Block>, Without<ShapeBlock>, Without<LineBlock>)>,
mut next: ResMut<NextState<GameState>>,
) {
// Check all blocks that are unassigned
query.iter().for_each(|gp| {
// If any block is above the top line, the game is over
if gp.y >= Y_MAX {
next.set(GameState::GameOver);
}
});
}
fn toggle_art(
state: Res<State<DebuggingState>>,
// TODO: Correctness: Query for added or changed <Art> entities

Loading…
Cancel
Save