|
|
|
|
@ -39,15 +39,11 @@ fn main() {
|
|
|
|
|
init_battler,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
OnEnter(DebuggingState::On), toggle_art
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
OnExit(DebuggingState::On), toggle_art
|
|
|
|
|
)
|
|
|
|
|
.add_systems(OnEnter(DebuggingState::On), toggle_art)
|
|
|
|
|
.add_systems(OnExit(DebuggingState::On), toggle_art)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
game_state_machine.run_if(state_changed::<GameState>)
|
|
|
|
|
game_state_machine.run_if(state_changed::<GameState>),
|
|
|
|
|
)
|
|
|
|
|
// Input and basic systems
|
|
|
|
|
.add_systems(
|
|
|
|
|
@ -70,12 +66,17 @@ fn main() {
|
|
|
|
|
.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_line_position.run_if(any_component_changed::<Line>),
|
|
|
|
|
update_block_position.run_if(any_component_changed::<GridPosition>),
|
|
|
|
|
update_shape_blocks
|
|
|
|
|
.run_if(any_component_changed::<ShapeLayout>.or(any_component_changed::<GridPosition>))
|
|
|
|
|
.after(update_position),
|
|
|
|
|
.run_if(
|
|
|
|
|
any_component_changed::<ShapeLayout>
|
|
|
|
|
.or(any_component_changed::<GridPosition>),
|
|
|
|
|
)
|
|
|
|
|
.after(update_block_position),
|
|
|
|
|
deactivate_shape
|
|
|
|
|
.run_if(any_component_removed::<Shape>)
|
|
|
|
|
.after(game_state_machine)
|
|
|
|
|
.after(update_shape_blocks),
|
|
|
|
|
// Clearing lines systems
|
|
|
|
|
clear_line.run_if(any_component_changed::<LineBlocks>),
|
|
|
|
|
@ -146,6 +147,14 @@ struct LineBlock {
|
|
|
|
|
#[derive(Component, Debug, Clone, Copy)]
|
|
|
|
|
struct Line(usize);
|
|
|
|
|
|
|
|
|
|
impl From<&Line> for Vec3 {
|
|
|
|
|
fn from(Line(y): &Line) -> Vec3 {
|
|
|
|
|
let y_0 = -SCALE * 10.0 + (0.5 * SCALE);
|
|
|
|
|
let y = y_0 + ((*y as f32) * SCALE);
|
|
|
|
|
Vec3::new(0.0, y, 0.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Just marks a block either of a shape or line
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct Block;
|
|
|
|
|
@ -277,9 +286,9 @@ fn game_state_machine(
|
|
|
|
|
|
|
|
|
|
// Set goal based on current goal
|
|
|
|
|
next.set(GameState::Play);
|
|
|
|
|
},
|
|
|
|
|
GameState::Play => (), // handled by user input
|
|
|
|
|
GameState::Pause => (), // handled by user input
|
|
|
|
|
}
|
|
|
|
|
GameState::Play => (), // handled by user input
|
|
|
|
|
GameState::Pause => (), // handled by user input
|
|
|
|
|
GameState::LevelComplete => (), // hanled by user input
|
|
|
|
|
GameState::NextLevel => {
|
|
|
|
|
// Increase goal
|
|
|
|
|
@ -298,7 +307,7 @@ fn game_state_machine(
|
|
|
|
|
// Progress to play
|
|
|
|
|
next.set(GameState::Play)
|
|
|
|
|
}
|
|
|
|
|
GameState::GameOver => (),// handled by user input
|
|
|
|
|
GameState::GameOver => (), // handled by user input
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -316,7 +325,7 @@ impl Default for LevelGoal {
|
|
|
|
|
impl Display for LevelGoal {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
match self {
|
|
|
|
|
LevelGoal::Score(n) => write!(f, "{n}")
|
|
|
|
|
LevelGoal::Score(n) => write!(f, "{n}"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -337,7 +346,7 @@ struct Visuals {
|
|
|
|
|
/// Stores the art assets that may be toggled on and off for development
|
|
|
|
|
#[derive(Component, Debug, Clone)]
|
|
|
|
|
struct Art {
|
|
|
|
|
texture: Handle<Image>
|
|
|
|
|
texture: Handle<Image>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Debug, Default)]
|
|
|
|
|
@ -446,7 +455,23 @@ fn init_tetris(
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Spawn line for holding blocks
|
|
|
|
|
commands.spawn((Line(y), LineBlocks::default(), TETRIS));
|
|
|
|
|
commands
|
|
|
|
|
.spawn((
|
|
|
|
|
Line(y),
|
|
|
|
|
LineBlocks::default(),
|
|
|
|
|
TETRIS,
|
|
|
|
|
Transform::default(),
|
|
|
|
|
Visibility::default(),
|
|
|
|
|
))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
// Spawn a line number next to the line so we can track it
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Text2d(format!("{}", y)),
|
|
|
|
|
TETRIS,
|
|
|
|
|
Transform::from_xyz(0.0, 0.0, 99.0),
|
|
|
|
|
Visibility::default(),
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -646,41 +671,50 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, images: Res
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::Pause,
|
|
|
|
|
children![(Text::new("paused"), TextColor(WHITE.into()), GameState::Pause)],
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::LevelComplete,
|
|
|
|
|
children![(Text::new("you did it!"), TextColor(WHITE.into()), GameState::LevelComplete)],
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::GameOver,
|
|
|
|
|
children![(Text::new("aww, you suck. i'm sorry."), TextColor(WHITE.into()), GameState::GameOver)],
|
|
|
|
|
));
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::Pause,
|
|
|
|
|
children![(
|
|
|
|
|
Text::new("paused"),
|
|
|
|
|
TextColor(WHITE.into()),
|
|
|
|
|
GameState::Pause
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::LevelComplete,
|
|
|
|
|
children![(
|
|
|
|
|
Text::new("you did it!"),
|
|
|
|
|
TextColor(WHITE.into()),
|
|
|
|
|
GameState::LevelComplete
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Node {
|
|
|
|
|
align_self: AlignSelf::Center,
|
|
|
|
|
justify_self: JustifySelf::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
ZIndex(1),
|
|
|
|
|
GameState::GameOver,
|
|
|
|
|
children![(
|
|
|
|
|
Text::new("aww, you suck. i'm sorry."),
|
|
|
|
|
TextColor(WHITE.into()),
|
|
|
|
|
GameState::GameOver
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.spawn((
|
|
|
|
|
@ -815,11 +849,7 @@ impl ShapeLayout {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_t() -> Self {
|
|
|
|
|
vec![
|
|
|
|
|
vec![0, 1, 0],
|
|
|
|
|
vec![1, 1, 1],
|
|
|
|
|
vec![0, 0, 0]
|
|
|
|
|
].into()
|
|
|
|
|
vec![vec![0, 1, 0], vec![1, 1, 1], vec![0, 0, 0]].into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn new_l() -> Self {
|
|
|
|
|
@ -923,7 +953,7 @@ impl ShapeLayout {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_position(
|
|
|
|
|
fn update_block_position(
|
|
|
|
|
mut changed: Query<
|
|
|
|
|
(Entity, &GridPosition, &mut Transform),
|
|
|
|
|
Or<(Added<GridPosition>, Changed<GridPosition>)>,
|
|
|
|
|
@ -941,13 +971,25 @@ fn update_position(
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_line_position(
|
|
|
|
|
mut changed: Query<(Entity, &Line, &mut Transform), Or<(Added<Line>, Changed<Line>)>>,
|
|
|
|
|
) {
|
|
|
|
|
changed.iter_mut().for_each(|(e, gp, mut t)| {
|
|
|
|
|
let v3: Vec3 = gp.into();
|
|
|
|
|
debug!(
|
|
|
|
|
"Updating {e} with grid position {:?} to coordinates {:?}",
|
|
|
|
|
gp, v3
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
t.translation.x = v3.x;
|
|
|
|
|
t.translation.y = v3.y;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_shape_blocks(
|
|
|
|
|
query: Query<
|
|
|
|
|
(Entity, &ShapeLayout, &GridPosition),
|
|
|
|
|
Or<(
|
|
|
|
|
Changed<ShapeLayout>,
|
|
|
|
|
Changed<GridPosition>,
|
|
|
|
|
)>,
|
|
|
|
|
Or<(Changed<ShapeLayout>, Changed<GridPosition>)>,
|
|
|
|
|
>,
|
|
|
|
|
mut blocks: Query<&mut GridPosition, (With<ShapeBlock>, Without<ShapeLayout>)>,
|
|
|
|
|
) {
|
|
|
|
|
@ -957,14 +999,12 @@ fn update_shape_blocks(
|
|
|
|
|
debug_assert!(!blocks.is_empty());
|
|
|
|
|
|
|
|
|
|
let mut p = sl.coordinates_at(center);
|
|
|
|
|
blocks.iter_mut().for_each(|mut gp| {
|
|
|
|
|
match p.next() {
|
|
|
|
|
Some(next) => match next {
|
|
|
|
|
Ok(next_gp) => *gp = next_gp,
|
|
|
|
|
Err(_) => warn!("Next coordinate was an Err?"),
|
|
|
|
|
},
|
|
|
|
|
None => warn!("Next coordinate was a None?"),
|
|
|
|
|
}
|
|
|
|
|
blocks.iter_mut().for_each(|mut gp| match p.next() {
|
|
|
|
|
Some(next) => match next {
|
|
|
|
|
Ok(next_gp) => *gp = next_gp,
|
|
|
|
|
Err(_) => warn!("Next coordinate was an Err?"),
|
|
|
|
|
},
|
|
|
|
|
None => warn!("Next coordinate was a None?"),
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -991,7 +1031,14 @@ 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));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
mesh.clone(),
|
|
|
|
|
MeshMaterial2d(mat.clone()),
|
|
|
|
|
art.clone(),
|
|
|
|
|
gp.unwrap(),
|
|
|
|
|
Block,
|
|
|
|
|
TETRIS,
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -1052,17 +1099,17 @@ fn kb_input(
|
|
|
|
|
},
|
|
|
|
|
GameState::Pause => match key_code {
|
|
|
|
|
KeyCode::Escape | KeyCode::Enter => next.set(GameState::Play),
|
|
|
|
|
_ => ()
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
},
|
|
|
|
|
GameState::GameOver => match key_code {
|
|
|
|
|
KeyCode::Escape => todo!("Quit the game"),
|
|
|
|
|
KeyCode::Enter => next.set(GameState::NewGame),
|
|
|
|
|
_ => ()
|
|
|
|
|
_ => (),
|
|
|
|
|
},
|
|
|
|
|
GameState::LevelComplete => match key_code {
|
|
|
|
|
KeyCode::Escape => todo!("Quit the game"),
|
|
|
|
|
KeyCode::Enter => next.set(GameState::NextLevel),
|
|
|
|
|
_ => ()
|
|
|
|
|
_ => (),
|
|
|
|
|
},
|
|
|
|
|
GameState::NewGame | GameState::NextLevel => (),
|
|
|
|
|
}
|
|
|
|
|
@ -1243,15 +1290,15 @@ fn movement(
|
|
|
|
|
// 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();
|
|
|
|
|
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
|
|
|
|
|
|| new_blocks.contains(&Err(OutOfBoundsError::Right))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If would be out of bounds at the bottom of play
|
|
|
|
|
@ -1274,10 +1321,9 @@ fn movement(
|
|
|
|
|
next_state.set(GameState::GameOver);
|
|
|
|
|
}
|
|
|
|
|
// We are moving down
|
|
|
|
|
if [
|
|
|
|
|
MovementDirection::Down,
|
|
|
|
|
MovementDirection::Skip,
|
|
|
|
|
].contains(&message.direction) {
|
|
|
|
|
if [MovementDirection::Down, MovementDirection::Skip]
|
|
|
|
|
.contains(&message.direction)
|
|
|
|
|
{
|
|
|
|
|
// De-activate this piece
|
|
|
|
|
commands.entity(message.entity).remove::<Shape>();
|
|
|
|
|
}
|
|
|
|
|
@ -1374,16 +1420,26 @@ fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>,
|
|
|
|
|
>,
|
|
|
|
|
) {
|
|
|
|
|
grid_positions.iter_combinations().for_each(|[(e1, a, lb1, sb1), (e2, b, lb2, sb2)]| {
|
|
|
|
|
if a == b {
|
|
|
|
|
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!");
|
|
|
|
|
});
|
|
|
|
|
grid_positions
|
|
|
|
|
.iter_combinations()
|
|
|
|
|
.for_each(|[(e1, a, lb1, sb1), (e2, b, lb2, sb2)]| {
|
|
|
|
|
if a == b {
|
|
|
|
|
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!");
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sync_health(
|
|
|
|
|
|