saving my place

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

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

Loading…
Cancel
Save