|
|
|
|
@ -1,5 +1,12 @@
|
|
|
|
|
// Bevy basically forces "complex types" with Querys
|
|
|
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
|
|
|
|
|
|
use games::*;
|
|
|
|
|
|
|
|
|
|
// TODO: Detect when piece is going to go out of bounds and restirct parent from moving there
|
|
|
|
|
// TODO: When shape touches the rest of the pieces, re-parent to line entity
|
|
|
|
|
// TODO: When line is "full" (has 10 children) clear line and add to score
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
App::new()
|
|
|
|
|
.add_plugins(BaseGamePlugin {
|
|
|
|
|
@ -13,11 +20,21 @@ fn main() {
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
(
|
|
|
|
|
kb_movement.run_if(on_event::<KeyboardInput>),
|
|
|
|
|
kb_input.run_if(on_event::<KeyboardInput>),
|
|
|
|
|
update_position,
|
|
|
|
|
update_orientation,
|
|
|
|
|
toggle_falling.run_if(on_event::<KeyboardInput>),
|
|
|
|
|
falling.run_if(in_state(Falling::On)).run_if(clock_cycle(1.0))
|
|
|
|
|
falling
|
|
|
|
|
.run_if(in_state(Falling::On))
|
|
|
|
|
.run_if(clock_cycle(1.0)),
|
|
|
|
|
set_piece
|
|
|
|
|
.run_if(any_component_added::<Shape>
|
|
|
|
|
.or(any_component_changed::<Shape>)
|
|
|
|
|
.or(any_component_added::<Orientation>)
|
|
|
|
|
.or(any_component_changed::<Orientation>)
|
|
|
|
|
),
|
|
|
|
|
set_relative_piece_positions.run_if(
|
|
|
|
|
any_component_added::<RelativePosition>
|
|
|
|
|
.or(any_component_changed::<RelativePosition>),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, draw_grid)
|
|
|
|
|
@ -26,6 +43,34 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
const SCALE: f32 = 30.0;
|
|
|
|
|
|
|
|
|
|
/// A shape, e.g., the long piece
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
enum Shape {
|
|
|
|
|
O,
|
|
|
|
|
T,
|
|
|
|
|
L,
|
|
|
|
|
J,
|
|
|
|
|
S,
|
|
|
|
|
Z,
|
|
|
|
|
I,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A part of a piece, i.e., a single square of a piece
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct ShapePiece;
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct RelativePosition {
|
|
|
|
|
x: i8,
|
|
|
|
|
y: i8,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<(i8, i8)> for RelativePosition {
|
|
|
|
|
fn from((x, y): (i8, i8)) -> RelativePosition {
|
|
|
|
|
RelativePosition { x, y }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
#[require(Transform, Visibility)]
|
|
|
|
|
struct GridPosition {
|
|
|
|
|
@ -36,39 +81,44 @@ struct GridPosition {
|
|
|
|
|
impl GridPosition {
|
|
|
|
|
fn move_up(&self) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
y: if self.y + 1 < 20 { self.y.saturating_add(1) } else { self.y },
|
|
|
|
|
x: self.x
|
|
|
|
|
y: if self.y + 1 < 20 {
|
|
|
|
|
self.y.saturating_add(1)
|
|
|
|
|
} else {
|
|
|
|
|
self.y
|
|
|
|
|
},
|
|
|
|
|
x: self.x,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_down(&mut self) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
y: self.y.saturating_sub(1),
|
|
|
|
|
x: self.x
|
|
|
|
|
x: self.x,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_left(&mut self) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: self.x.saturating_sub(1),
|
|
|
|
|
y: self.y
|
|
|
|
|
y: self.y,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_right(&mut self) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: if self.x + 1 < 10 { self.x.saturating_add(1) } else { self.x },
|
|
|
|
|
y: self.y
|
|
|
|
|
x: if self.x + 1 < 10 {
|
|
|
|
|
self.x.saturating_add(1)
|
|
|
|
|
} else {
|
|
|
|
|
self.x
|
|
|
|
|
},
|
|
|
|
|
y: self.y,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for GridPosition {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
GridPosition {
|
|
|
|
|
x: 5,
|
|
|
|
|
y: 20,
|
|
|
|
|
}
|
|
|
|
|
GridPosition { x: 5, y: 20 }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -77,6 +127,7 @@ impl From<&GridPosition> for Vec3 {
|
|
|
|
|
// Grid Positions start in the bottom left of the area
|
|
|
|
|
// So (0, 0) is the bottom left, (0, 9) is the bottom right, etc
|
|
|
|
|
|
|
|
|
|
// TODO: Custom offset allowing pieces like O and I to have correct center
|
|
|
|
|
let x_0 = -SCALE * 5.0 + (0.5 * SCALE);
|
|
|
|
|
let x = x_0 + ((*x as f32) * SCALE);
|
|
|
|
|
|
|
|
|
|
@ -138,23 +189,17 @@ impl Orientation {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&Orientation> for Quat {
|
|
|
|
|
fn from(other: &Orientation) -> Quat {
|
|
|
|
|
let z = match other {
|
|
|
|
|
Orientation::Up => 0.0,
|
|
|
|
|
Orientation::Left => -PI * 0.5,
|
|
|
|
|
Orientation::Down => -PI,
|
|
|
|
|
Orientation::Right => -PI * 1.5,
|
|
|
|
|
};
|
|
|
|
|
Quat::from_rotation_z(z)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component)]
|
|
|
|
|
enum Falling {
|
|
|
|
|
#[default]
|
|
|
|
|
On,
|
|
|
|
|
Off
|
|
|
|
|
Off,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Debug)]
|
|
|
|
|
struct Visuals {
|
|
|
|
|
material: Handle<ColorMaterial>,
|
|
|
|
|
mesh: Handle<Mesh>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init_pieces(
|
|
|
|
|
@ -162,33 +207,149 @@ fn init_pieces(
|
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
) {
|
|
|
|
|
commands
|
|
|
|
|
.spawn((Orientation::default(), GridPosition::default()))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
let mat = materials.add(ColorMaterial {
|
|
|
|
|
commands.insert_resource(Visuals {
|
|
|
|
|
material: materials.add(ColorMaterial {
|
|
|
|
|
color: WHITE.into(),
|
|
|
|
|
..default()
|
|
|
|
|
}),
|
|
|
|
|
mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
|
|
|
|
|
});
|
|
|
|
|
let mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Mesh2d(mesh.clone()),
|
|
|
|
|
MeshMaterial2d(mat.clone()),
|
|
|
|
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Mesh2d(mesh.clone()),
|
|
|
|
|
MeshMaterial2d(mat.clone()),
|
|
|
|
|
Transform::from_xyz(SCALE, 0.0, 0.0),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Mesh2d(mesh.clone()),
|
|
|
|
|
MeshMaterial2d(mat.clone()),
|
|
|
|
|
Transform::from_xyz(0.0, SCALE, 0.0),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Mesh2d(mesh.clone()),
|
|
|
|
|
MeshMaterial2d(mat.clone()),
|
|
|
|
|
Transform::from_xyz(-SCALE, 0.0, 0.0),
|
|
|
|
|
|
|
|
|
|
commands.spawn((Orientation::default(), GridPosition::default(), Shape::T));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_piece(
|
|
|
|
|
query: Query<(Entity, &Shape, &Orientation), Or<(Added<Shape>, Changed<Shape>, Added<Orientation>, Changed<Orientation>)>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
visuals: Res<Visuals>,
|
|
|
|
|
) {
|
|
|
|
|
query.iter().for_each(|(e, s, o)| {
|
|
|
|
|
info!("{e:?} {s:?} {o:?}");
|
|
|
|
|
commands
|
|
|
|
|
.entity(e)
|
|
|
|
|
.despawn_related::<Children>()
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
let mesh = visuals.mesh.clone();
|
|
|
|
|
let mat = visuals.material.clone();
|
|
|
|
|
|
|
|
|
|
#[rustfmt::skip]
|
|
|
|
|
let piece_positions: [RelativePosition;4] = match s {
|
|
|
|
|
Shape::O => [
|
|
|
|
|
(0,1).into(),(1,1).into(),
|
|
|
|
|
(0,0).into(),(1,0).into()
|
|
|
|
|
],
|
|
|
|
|
Shape::T => match o {
|
|
|
|
|
Orientation::Up => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(-1,0).into(),(0,0).into(),(1,0).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
(-1,0).into(),(0,0).into(),(1,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(0,0).into(), (1,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
Shape::L => match o {
|
|
|
|
|
Orientation::Up => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
(0,-1).into(),(1,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
(-1,1).into(),(0,1).into(),
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
(-1,0).into(),(0,0).into(),(0,1).into(),
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
(1,1).into(),
|
|
|
|
|
(-1,0).into(),(0,0).into(),(0,1).into(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
Shape::J => match o {
|
|
|
|
|
Orientation::Up => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
(-1,-1).into(),(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
(0,1).into(),(1,1).into(),
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
(-1,0).into(),(0,0).into(),(1,0).into(),
|
|
|
|
|
(1,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
(-1,0).into(),(0,0).into(),(1,0).into()
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
Shape::S => match o {
|
|
|
|
|
Orientation::Up | Orientation::Down => [
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
(-1,-1).into(),(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left | Orientation::Right => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
Shape::Z => match o {
|
|
|
|
|
Orientation::Up | Orientation::Down => [
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
(0,-1).into(),(1,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left | Orientation::Right => [
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
(1,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
Shape::I => match o {
|
|
|
|
|
Orientation::Up | Orientation::Down => [
|
|
|
|
|
(0,2).into(),
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
],
|
|
|
|
|
Orientation::Left | Orientation::Right => todo!()
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
piece_positions.into_iter().for_each(|rp| {
|
|
|
|
|
parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_relative_piece_positions(
|
|
|
|
|
query: Query<
|
|
|
|
|
(Entity, &RelativePosition),
|
|
|
|
|
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
|
|
|
|
|
>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
query.iter().for_each(|(e, rp)| {
|
|
|
|
|
commands.entity(e).insert(Transform::from_xyz(
|
|
|
|
|
(rp.x as f32) * SCALE,
|
|
|
|
|
(rp.y as f32) * SCALE,
|
|
|
|
|
0.0,
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -200,14 +361,12 @@ fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<Gri
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn update_orientation(mut query: Query<(&Orientation, &mut Transform), Changed<Orientation>>) {
|
|
|
|
|
query.iter_mut().for_each(|(o, mut t)| {
|
|
|
|
|
t.rotation = o.into();
|
|
|
|
|
debug!("Setting orientation to {:?}", o);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn kb_movement(mut events: EventReader<KeyboardInput>, mut query: Query<(&mut GridPosition, &mut Orientation)>) {
|
|
|
|
|
fn kb_input(
|
|
|
|
|
mut events: EventReader<KeyboardInput>,
|
|
|
|
|
mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>,
|
|
|
|
|
curr: Res<State<Falling>>,
|
|
|
|
|
mut next: ResMut<NextState<Falling>>,
|
|
|
|
|
) {
|
|
|
|
|
events.read().for_each(
|
|
|
|
|
|KeyboardInput {
|
|
|
|
|
key_code, state, ..
|
|
|
|
|
@ -215,14 +374,26 @@ fn kb_movement(mut events: EventReader<KeyboardInput>, mut query: Query<(&mut Gr
|
|
|
|
|
if let ButtonState::Pressed = state {
|
|
|
|
|
// TODO: Restict movement based on size/orientation of piece
|
|
|
|
|
// Check if children would be outside play area...
|
|
|
|
|
query.iter_mut().for_each(|(mut gp, mut o)| {
|
|
|
|
|
query.iter_mut().for_each(|(mut gp, mut o, mut s)| {
|
|
|
|
|
match key_code {
|
|
|
|
|
KeyCode::ArrowUp => *gp = gp.move_up(),
|
|
|
|
|
// Up arrow should rotate if in falling mode
|
|
|
|
|
// Only move up if in falling::off mode
|
|
|
|
|
KeyCode::ArrowUp => *o = o.next(),
|
|
|
|
|
KeyCode::ArrowDown => *gp = gp.move_down(),
|
|
|
|
|
KeyCode::ArrowLeft => *gp = gp.move_left(),
|
|
|
|
|
KeyCode::ArrowRight => *gp = gp.move_right(),
|
|
|
|
|
KeyCode::Enter => *o = o.next(),
|
|
|
|
|
_ => ()
|
|
|
|
|
KeyCode::Space => next.set(match curr.get() {
|
|
|
|
|
Falling::On => Falling::Off,
|
|
|
|
|
Falling::Off => Falling::On,
|
|
|
|
|
}),
|
|
|
|
|
KeyCode::Digit1 => *s = Shape::T,
|
|
|
|
|
KeyCode::Digit2 => *s = Shape::O,
|
|
|
|
|
KeyCode::Digit3 => *s = Shape::L,
|
|
|
|
|
KeyCode::Digit4 => *s = Shape::J,
|
|
|
|
|
KeyCode::Digit5 => *s = Shape::S,
|
|
|
|
|
KeyCode::Digit6 => *s = Shape::Z,
|
|
|
|
|
KeyCode::Digit7 => *s = Shape::I,
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -241,24 +412,7 @@ fn draw_grid(mut gizmos: Gizmos) {
|
|
|
|
|
.outer_edges();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn toggle_falling(
|
|
|
|
|
mut events: EventReader<KeyboardInput>,
|
|
|
|
|
curr: Res<State<Falling>>,
|
|
|
|
|
mut next: ResMut<NextState<Falling>>,
|
|
|
|
|
) {
|
|
|
|
|
events.read().for_each(|KeyboardInput { state, key_code, .. }| {
|
|
|
|
|
if let ButtonState::Pressed = state && *key_code == KeyCode::Space {
|
|
|
|
|
next.set(match curr.get() {
|
|
|
|
|
Falling::On => Falling::Off,
|
|
|
|
|
Falling::Off => Falling::On,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn falling(
|
|
|
|
|
mut query: Query<&mut GridPosition>
|
|
|
|
|
) {
|
|
|
|
|
fn falling(mut query: Query<&mut GridPosition, With<Shape>>) {
|
|
|
|
|
query.iter_mut().for_each(|mut gp| {
|
|
|
|
|
let next = gp.move_down();
|
|
|
|
|
if next != *gp {
|
|
|
|
|
|