|
|
|
@ -3,7 +3,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
use games::*;
|
|
|
|
use games::*;
|
|
|
|
|
|
|
|
|
|
|
|
// *TODO: Detect when piece is going to go out of bounds and restirct parent from moving there
|
|
|
|
// 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 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
|
|
|
|
// TODO: When line is "full" (has 10 children) clear line and add to score
|
|
|
|
|
|
|
|
|
|
|
|
@ -16,99 +16,72 @@ fn main() {
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.init_state::<Falling>()
|
|
|
|
.init_state::<Falling>()
|
|
|
|
.add_systems(Startup, (init_world, init_debug_ui))
|
|
|
|
.add_systems(Startup, (init_pieces, init_debug_ui))
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
(
|
|
|
|
(
|
|
|
|
kb_input.run_if(on_event::<KeyboardInput>),
|
|
|
|
kb_input.run_if(on_event::<KeyboardInput>),
|
|
|
|
|
|
|
|
update_position,
|
|
|
|
falling
|
|
|
|
falling
|
|
|
|
.run_if(in_state(Falling::On))
|
|
|
|
.run_if(in_state(Falling::On))
|
|
|
|
.run_if(clock_cycle(1.0)),
|
|
|
|
.run_if(clock_cycle(1.0)),
|
|
|
|
set_shape.run_if(
|
|
|
|
set_piece
|
|
|
|
any_component_added::<Shape>
|
|
|
|
.run_if(any_component_added::<Shape>
|
|
|
|
.or(any_component_changed::<Shape>)
|
|
|
|
.or(any_component_changed::<Shape>)
|
|
|
|
.or(any_component_added::<Orientation>)
|
|
|
|
.or(any_component_added::<Orientation>)
|
|
|
|
.or(any_component_changed::<Orientation>),
|
|
|
|
.or(any_component_changed::<Orientation>)
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
set_relative_piece_positions.run_if(
|
|
|
|
|
|
|
|
any_component_added::<RelativePosition>
|
|
|
|
|
|
|
|
.or(any_component_changed::<RelativePosition>),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
update_relative_position,
|
|
|
|
|
|
|
|
update_position.after(update_relative_position),
|
|
|
|
|
|
|
|
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_piece.run_if(not(any_with_component::<Shape>)),
|
|
|
|
|
|
|
|
clear_line.run_if(any_component_changed::<LineBlocks>),
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(Update, draw_grid)
|
|
|
|
.add_systems(Update, draw_grid)
|
|
|
|
.add_observer(deactive_shape)
|
|
|
|
|
|
|
|
.run();
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const SCALE: f32 = 30.0;
|
|
|
|
const SCALE: f32 = 30.0;
|
|
|
|
|
|
|
|
|
|
|
|
// Declare the size of the play area
|
|
|
|
/// A shape, e.g., the long piece
|
|
|
|
const X_MAX: u32 = 10;
|
|
|
|
#[derive(Component, Debug, Default)]
|
|
|
|
const Y_MAX: u32 = 20;
|
|
|
|
enum Shape {
|
|
|
|
|
|
|
|
#[default]
|
|
|
|
// The blocks making up this shape
|
|
|
|
O,
|
|
|
|
#[derive(Component)]
|
|
|
|
T,
|
|
|
|
#[relationship_target(relationship = ShapeBlock)]
|
|
|
|
L,
|
|
|
|
struct ShapeBlocks(Vec<Entity>);
|
|
|
|
J,
|
|
|
|
|
|
|
|
S,
|
|
|
|
/// A part of a piece, i.e., a single square of a piece
|
|
|
|
Z,
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
I,
|
|
|
|
#[relationship(relationship_target = ShapeBlocks)]
|
|
|
|
|
|
|
|
struct ShapeBlock {
|
|
|
|
|
|
|
|
#[relationship]
|
|
|
|
|
|
|
|
shape: Entity,
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The blocks making up this shape
|
|
|
|
impl Display for Shape {
|
|
|
|
#[derive(Component, Default)]
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
#[relationship_target(relationship = LineBlock)]
|
|
|
|
match self {
|
|
|
|
struct LineBlocks(Vec<Entity>);
|
|
|
|
Shape::O => write!(f, "O"),
|
|
|
|
|
|
|
|
Shape::T => write!(f, "T"),
|
|
|
|
/// A part of a piece, i.e., a single square of a piece
|
|
|
|
Shape::L => write!(f, "L"),
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
Shape::J => write!(f, "J"),
|
|
|
|
#[require(Transform, Visibility)]
|
|
|
|
Shape::S => write!(f, "S"),
|
|
|
|
#[relationship(relationship_target = LineBlocks)]
|
|
|
|
Shape::Z => write!(f, "Z"),
|
|
|
|
struct LineBlock {
|
|
|
|
Shape::I => write!(f, "I"),
|
|
|
|
#[relationship]
|
|
|
|
}
|
|
|
|
line: Entity,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// A line holds up to 10 blocks before being cleared
|
|
|
|
/// A part of a piece, i.e., a single square of a piece
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
struct Line(u8);
|
|
|
|
struct ShapePiece;
|
|
|
|
|
|
|
|
|
|
|
|
// Just marks a block either of a shape or line
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
struct Block;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Event, Debug, Clone, Copy, PartialEq)]
|
|
|
|
|
|
|
|
#[require(GridPosition)]
|
|
|
|
|
|
|
|
struct RelativePosition {
|
|
|
|
struct RelativePosition {
|
|
|
|
x: i8,
|
|
|
|
x: i8,
|
|
|
|
y: i8,
|
|
|
|
y: i8,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl RelativePosition {
|
|
|
|
|
|
|
|
fn up() -> Self {
|
|
|
|
|
|
|
|
RelativePosition { x: 0, y: 1 }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn down() -> Self {
|
|
|
|
|
|
|
|
RelativePosition { x: 0, y: -1 }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn left() -> Self {
|
|
|
|
|
|
|
|
RelativePosition { x: -1, y: 0 }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn right() -> Self {
|
|
|
|
|
|
|
|
RelativePosition { x: 1, y: 0 }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl From<(i8, i8)> for RelativePosition {
|
|
|
|
impl From<(i8, i8)> for RelativePosition {
|
|
|
|
fn from((x, y): (i8, i8)) -> RelativePosition {
|
|
|
|
fn from((x, y): (i8, i8)) -> RelativePosition {
|
|
|
|
RelativePosition { x, y }
|
|
|
|
RelativePosition { x, y }
|
|
|
|
@ -118,14 +91,14 @@ impl From<(i8, i8)> for RelativePosition {
|
|
|
|
#[derive(Component, Debug, Clone, Copy, PartialEq)]
|
|
|
|
#[derive(Component, Debug, Clone, Copy, PartialEq)]
|
|
|
|
#[require(Transform, Visibility)]
|
|
|
|
#[require(Transform, Visibility)]
|
|
|
|
struct GridPosition {
|
|
|
|
struct GridPosition {
|
|
|
|
x: u32,
|
|
|
|
x: usize,
|
|
|
|
y: u32,
|
|
|
|
y: usize,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl GridPosition {
|
|
|
|
impl GridPosition {
|
|
|
|
fn move_up(&self) -> Self {
|
|
|
|
fn move_up(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
y: if self.y + 1 < Y_MAX {
|
|
|
|
y: if self.y + 1 < 20 {
|
|
|
|
self.y.saturating_add(1)
|
|
|
|
self.y.saturating_add(1)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
self.y
|
|
|
|
self.y
|
|
|
|
@ -150,7 +123,7 @@ impl GridPosition {
|
|
|
|
|
|
|
|
|
|
|
|
fn move_right(&mut self) -> Self {
|
|
|
|
fn move_right(&mut self) -> Self {
|
|
|
|
Self {
|
|
|
|
Self {
|
|
|
|
x: if self.x + 1 < X_MAX {
|
|
|
|
x: if self.x + 1 < 10 {
|
|
|
|
self.x.saturating_add(1)
|
|
|
|
self.x.saturating_add(1)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
self.x
|
|
|
|
self.x
|
|
|
|
@ -158,48 +131,11 @@ impl GridPosition {
|
|
|
|
y: self.y,
|
|
|
|
y: self.y,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn is_colliding_with(&self, other: &Self) -> bool {
|
|
|
|
|
|
|
|
self.x == other.x && self.y.saturating_sub(1) == other.y
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn add_relative(
|
|
|
|
|
|
|
|
&self,
|
|
|
|
|
|
|
|
RelativePosition { x: x1, y: y1 }: &RelativePosition,
|
|
|
|
|
|
|
|
) -> Result<Self, GameError> {
|
|
|
|
|
|
|
|
let x = self
|
|
|
|
|
|
|
|
.x
|
|
|
|
|
|
|
|
.checked_add_signed(*x1 as i32)
|
|
|
|
|
|
|
|
.ok_or(GameError::OutOfBoundsLeft)?;
|
|
|
|
|
|
|
|
let y = self
|
|
|
|
|
|
|
|
.y
|
|
|
|
|
|
|
|
.checked_add_signed(*y1 as i32)
|
|
|
|
|
|
|
|
.ok_or(GameError::OutOfBoundsDown)?;
|
|
|
|
|
|
|
|
if x >= X_MAX {
|
|
|
|
|
|
|
|
// TODO: y > Y_MAX?
|
|
|
|
|
|
|
|
Err(GameError::OutOfBoundsRight)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("Moving to {x},{y}");
|
|
|
|
|
|
|
|
Ok(GridPosition { x, y })
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
|
|
|
|
|
|
enum GameError {
|
|
|
|
|
|
|
|
#[error("Coordinates are out of bounds: Left")]
|
|
|
|
|
|
|
|
OutOfBoundsLeft,
|
|
|
|
|
|
|
|
#[error("Coordinates are out of bounds: Right")]
|
|
|
|
|
|
|
|
OutOfBoundsRight,
|
|
|
|
|
|
|
|
#[error("Coordinates are out of bounds: Down")]
|
|
|
|
|
|
|
|
OutOfBoundsDown,
|
|
|
|
|
|
|
|
#[error("Coordiante collision")]
|
|
|
|
|
|
|
|
Collision,
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for GridPosition {
|
|
|
|
impl Default for GridPosition {
|
|
|
|
fn default() -> Self {
|
|
|
|
fn default() -> Self {
|
|
|
|
GridPosition { x: 5, y: Y_MAX }
|
|
|
|
GridPosition { x: 5, y: 20 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -218,8 +154,8 @@ impl From<&GridPosition> for Vec3 {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<(u32, u32)> for GridPosition {
|
|
|
|
impl From<(usize, usize)> for GridPosition {
|
|
|
|
fn from((x, y): (u32, u32)) -> GridPosition {
|
|
|
|
fn from((x, y): (usize, usize)) -> GridPosition {
|
|
|
|
GridPosition { x, y }
|
|
|
|
GridPosition { x, y }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -241,7 +177,7 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Default, Event, Clone, Debug)]
|
|
|
|
#[derive(Component, Default, Debug)]
|
|
|
|
enum Orientation {
|
|
|
|
enum Orientation {
|
|
|
|
#[default]
|
|
|
|
#[default]
|
|
|
|
Up,
|
|
|
|
Up,
|
|
|
|
@ -294,7 +230,7 @@ struct Visuals {
|
|
|
|
mesh: Handle<Mesh>,
|
|
|
|
mesh: Handle<Mesh>,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn init_world(
|
|
|
|
fn init_pieces(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
@ -307,392 +243,205 @@ fn init_world(
|
|
|
|
mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
|
|
|
|
mesh: meshes.add(Rectangle::new(SCALE, SCALE)),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
(0..20).for_each(|i| {
|
|
|
|
commands.spawn((Orientation::default(), GridPosition::default(), Shape::T));
|
|
|
|
commands.spawn((Line(i), LineBlocks::default()));
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn init_debug_ui(mut commands: Commands) {
|
|
|
|
fn init_debug_ui(
|
|
|
|
commands
|
|
|
|
mut commands: Commands,
|
|
|
|
.spawn((
|
|
|
|
) {
|
|
|
|
Node {
|
|
|
|
commands.spawn((Node {
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
}, DebuggingState::On)).with_children(|parent| {
|
|
|
|
DebuggingState::On,
|
|
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
|
|
|
parent.spawn((
|
|
|
|
parent.spawn((
|
|
|
|
Node::default(),
|
|
|
|
Node::default(),
|
|
|
|
children![
|
|
|
|
children![
|
|
|
|
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
|
|
|
|
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
|
|
|
|
(
|
|
|
|
(Text::new("ORIENTATION"), SyncSingleton::<Orientation>::default()),
|
|
|
|
Text::new("ORIENTATION"),
|
|
|
|
]
|
|
|
|
SyncSingleton::<Orientation>::default()
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
));
|
|
|
|
));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
fn set_piece(
|
|
|
|
enum Shape {
|
|
|
|
query: Query<(Entity, &Shape, &Orientation), Or<(Added<Shape>, Changed<Shape>, Added<Orientation>, Changed<Orientation>)>>,
|
|
|
|
M4(Mat4),
|
|
|
|
|
|
|
|
M3(Mat3),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Default for Shape {
|
|
|
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
|
|
|
Self::new_t()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Display for Shape {
|
|
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
|
|
write!(f, "{}", self.as_ascii())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl Shape {
|
|
|
|
|
|
|
|
fn from_mat4(input: Mat4) -> Self {
|
|
|
|
|
|
|
|
Self::M4(input)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn from_mat3(input: Mat3) -> Self {
|
|
|
|
|
|
|
|
Self::M3(input)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_o() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_t() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat3(Mat3::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,1.,0.],
|
|
|
|
|
|
|
|
[1.,1.,1.],
|
|
|
|
|
|
|
|
[0.,0.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_l() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_j() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_s() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
[1.,1.,0.,0.],
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_z() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
[1.,1.,0.,0.],
|
|
|
|
|
|
|
|
[0.,1.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,0.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn new_i() -> Self {
|
|
|
|
|
|
|
|
Self::from_mat4(Mat4::from_cols_array_2d(&[
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
[0.,0.,1.,0.],
|
|
|
|
|
|
|
|
]))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Rotates 90 degrees to the right
|
|
|
|
|
|
|
|
// https://stackoverflow.com/a/8664879
|
|
|
|
|
|
|
|
fn rotated(&self) -> Self {
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
Self::M4(inner) => {
|
|
|
|
|
|
|
|
let mut new_self = inner.transpose();
|
|
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
|
|
let col = new_self.col_mut(i);
|
|
|
|
|
|
|
|
*col = Vec4::new(col[3], col[2], col[1], col[0]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::M4(new_self)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::M3(inner) => {
|
|
|
|
|
|
|
|
let mut new_self = inner.transpose();
|
|
|
|
|
|
|
|
for i in 0..3 {
|
|
|
|
|
|
|
|
let col = new_self.col_mut(i);
|
|
|
|
|
|
|
|
*col = Vec3::new(col[2], col[1], col[0]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::M3(new_self)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn rotate(&mut self) {
|
|
|
|
|
|
|
|
*self = self.rotated();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: return impl Iterator<Item = &RelativePosition>
|
|
|
|
|
|
|
|
fn relative_coordinates(&self) -> impl Iterator<Item = RelativePosition> {
|
|
|
|
|
|
|
|
let mut v: Vec<RelativePosition> = Vec::new();
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
Self::M4(inner) => {
|
|
|
|
|
|
|
|
for (i, y) in (-1..3).rev().enumerate() {
|
|
|
|
|
|
|
|
let c = inner.col(i);
|
|
|
|
|
|
|
|
for (j, x) in (-1..3).enumerate() {
|
|
|
|
|
|
|
|
if c[j] == 1.0 {
|
|
|
|
|
|
|
|
v.push(RelativePosition { x, y });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Self::M3(inner) => {
|
|
|
|
|
|
|
|
for (i, y) in (-1..2).rev().enumerate() {
|
|
|
|
|
|
|
|
let c = inner.col(i);
|
|
|
|
|
|
|
|
for (j, x) in (-1..2).enumerate() {
|
|
|
|
|
|
|
|
if c[j] == 1.0 {
|
|
|
|
|
|
|
|
v.push(RelativePosition { x, y });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
v.into_iter()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn computed_coordinates(&self, center: &GridPosition) -> impl Iterator<Item = Result<GridPosition, GameError>> {
|
|
|
|
|
|
|
|
self.relative_coordinates().map(|rp| center.add_relative(&rp))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn as_ascii(&self) -> String {
|
|
|
|
|
|
|
|
let mut output = String::default();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
Self::M4(this) => {
|
|
|
|
|
|
|
|
for i in 0..4 {
|
|
|
|
|
|
|
|
let col = this.col(i).to_array();
|
|
|
|
|
|
|
|
output += format!("{}{}{}{}\n", col[0], col[1], col[2], col[3]).as_str();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Self::M3(this) => {
|
|
|
|
|
|
|
|
for i in 0..3 {
|
|
|
|
|
|
|
|
let col = this.col(i).to_array();
|
|
|
|
|
|
|
|
output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
output
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
|
|
mod test {
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_shape_t() {
|
|
|
|
|
|
|
|
let mut shape = Shape::new_t();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_up = "010\n\
|
|
|
|
|
|
|
|
111\n\
|
|
|
|
|
|
|
|
000\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_right = "010\n\
|
|
|
|
|
|
|
|
011\n\
|
|
|
|
|
|
|
|
010\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_down = "000\n\
|
|
|
|
|
|
|
|
111\n\
|
|
|
|
|
|
|
|
010\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_left = "010\n\
|
|
|
|
|
|
|
|
110\n\
|
|
|
|
|
|
|
|
010\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_up);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_right);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_down);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_left);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_up);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_shape_i() {
|
|
|
|
|
|
|
|
let mut shape = Shape::new_i();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_up = "0010\n\
|
|
|
|
|
|
|
|
0010\n\
|
|
|
|
|
|
|
|
0010\n\
|
|
|
|
|
|
|
|
0010\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_right = "0000\n\
|
|
|
|
|
|
|
|
0000\n\
|
|
|
|
|
|
|
|
1111\n\
|
|
|
|
|
|
|
|
0000\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_down = "0100\n\
|
|
|
|
|
|
|
|
0100\n\
|
|
|
|
|
|
|
|
0100\n\
|
|
|
|
|
|
|
|
0100\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected_left = "0000\n\
|
|
|
|
|
|
|
|
1111\n\
|
|
|
|
|
|
|
|
0000\n\
|
|
|
|
|
|
|
|
0000\n";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_up);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_right);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_down);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_left);
|
|
|
|
|
|
|
|
shape.rotate();
|
|
|
|
|
|
|
|
assert_eq!(shape.as_ascii(), expected_up);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_relative_coordinates() {
|
|
|
|
|
|
|
|
let shape = Shape::new_t();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected: Vec<RelativePosition> = vec![
|
|
|
|
|
|
|
|
(0, 1).into(),
|
|
|
|
|
|
|
|
(-1, 0).into(),
|
|
|
|
|
|
|
|
(0, 0).into(),
|
|
|
|
|
|
|
|
(1, 0).into(),
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let actual: Vec<RelativePosition> = shape.relative_coordinates().collect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(actual, expected);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_computed_coordinates() {
|
|
|
|
|
|
|
|
let shape = Shape::new_t();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let center = GridPosition { x: 5, y: 5 };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let expected: Vec<Result<GridPosition, GameError>> = vec![
|
|
|
|
|
|
|
|
Ok((5, 6).into()),
|
|
|
|
|
|
|
|
Ok((4, 5).into()),
|
|
|
|
|
|
|
|
Ok((5, 5).into()),
|
|
|
|
|
|
|
|
Ok((6, 5).into()),
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let actual: Vec<Result<GridPosition, GameError>> = shape.computed_coordinates(¢er).collect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(actual, expected);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn set_shape(
|
|
|
|
|
|
|
|
query: Query<
|
|
|
|
|
|
|
|
(Entity, &Shape, &Orientation),
|
|
|
|
|
|
|
|
Or<(Added<Shape>, Changed<Shape>)>,
|
|
|
|
|
|
|
|
>,
|
|
|
|
|
|
|
|
mut blocks: Query<&mut RelativePosition, With<ShapeBlock>>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
visuals: Res<Visuals>,
|
|
|
|
visuals: Res<Visuals>,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
query.iter().for_each(|(e, s, o)| {
|
|
|
|
query.iter().for_each(|(e, s, o)| {
|
|
|
|
debug!("Setting piece: {e:?} {s:?} {o:?}");
|
|
|
|
debug!("{e:?} {s:?} {o:?}");
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
|
|
|
.entity(e)
|
|
|
|
|
|
|
|
.despawn_related::<Children>()
|
|
|
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
let mesh = visuals.mesh.clone();
|
|
|
|
let mesh = visuals.mesh.clone();
|
|
|
|
let mat = visuals.material.clone();
|
|
|
|
let mat = visuals.material.clone();
|
|
|
|
let positions: [RelativePosition;4] = todo!();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// todo: map positions to coordinates
|
|
|
|
#[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(),(1,0).into(),
|
|
|
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
|
|
|
(1,1).into(),
|
|
|
|
|
|
|
|
(-1,0).into(),(0,0).into(),(1,0).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 => [
|
|
|
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
|
|
|
(-1,-1).into(),(0,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
|
|
|
(0,1).into(),(1,1).into(),
|
|
|
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
|
|
|
(-1,1).into(),
|
|
|
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
|
|
|
(1,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Shape::Z => match o {
|
|
|
|
|
|
|
|
Orientation::Up => [
|
|
|
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
|
|
|
(0,-1).into(),(1,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
|
|
|
(-1,1).into(),(0,1).into(),
|
|
|
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
|
|
|
(1,1).into(),
|
|
|
|
|
|
|
|
(0,0).into(),(1,0).into(),
|
|
|
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
|
|
|
(-1,0).into(),(0,0).into(),
|
|
|
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
// TODO: This does not match tetris!
|
|
|
|
|
|
|
|
Shape::I => match o {
|
|
|
|
|
|
|
|
Orientation::Up => [
|
|
|
|
|
|
|
|
(0,2).into(),
|
|
|
|
|
|
|
|
(0,1).into(),
|
|
|
|
|
|
|
|
(0,0).into(),
|
|
|
|
|
|
|
|
(0,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Down => [
|
|
|
|
|
|
|
|
(-1,2).into(),
|
|
|
|
|
|
|
|
(-1,1).into(),
|
|
|
|
|
|
|
|
(-1,0).into(),
|
|
|
|
|
|
|
|
(-1,-1).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Left => [
|
|
|
|
|
|
|
|
(-2,0).into(),(-1,0).into(),(0,0).into(),(1,0).into(),
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
|
|
Orientation::Right => [
|
|
|
|
|
|
|
|
(-2,1).into(),(-1,1).into(),(0,1).into(),(1,1).into(),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if blocks.is_empty() {
|
|
|
|
piece_positions.into_iter().for_each(|rp| {
|
|
|
|
commands
|
|
|
|
parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp));
|
|
|
|
.entity(e)
|
|
|
|
|
|
|
|
.with_related_entities::<ShapeBlock>(|parent| {
|
|
|
|
|
|
|
|
positions.into_iter().for_each(|rp| {
|
|
|
|
|
|
|
|
parent
|
|
|
|
|
|
|
|
.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp, Block))
|
|
|
|
|
|
|
|
.observe(movement);
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
|
|
|
|
let mut p = positions.into_iter();
|
|
|
|
|
|
|
|
blocks.iter_mut().for_each(|mut rp| {
|
|
|
|
|
|
|
|
*rp = p.next().unwrap();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn update_position(
|
|
|
|
fn set_relative_piece_positions(
|
|
|
|
mut changed: Query<
|
|
|
|
query: Query<
|
|
|
|
(Entity, &GridPosition, &mut Transform),
|
|
|
|
(Entity, &RelativePosition),
|
|
|
|
Or<(Added<GridPosition>, Changed<GridPosition>)>,
|
|
|
|
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
|
|
|
|
>,
|
|
|
|
>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
changed.iter_mut().for_each(|(e, gp, mut t)| {
|
|
|
|
query.iter().for_each(|(e, rp)| {
|
|
|
|
let v3: Vec3 = gp.into();
|
|
|
|
commands.entity(e).insert(Transform::from_xyz(
|
|
|
|
debug!(
|
|
|
|
(rp.x as f32) * SCALE,
|
|
|
|
"Updating {e} with grid position {:?} to coordinates {:?}",
|
|
|
|
(rp.y as f32) * SCALE,
|
|
|
|
gp, v3
|
|
|
|
0.0,
|
|
|
|
);
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
debug_assert!(gp.x < X_MAX, "block x > x_max");
|
|
|
|
|
|
|
|
t.translation = gp.into();
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn update_relative_position(
|
|
|
|
fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) {
|
|
|
|
shape: Single<&GridPosition, With<ShapeBlocks>>,
|
|
|
|
query.iter_mut().for_each(|(gp, mut t)| {
|
|
|
|
mut query: Query<
|
|
|
|
t.translation = gp.into();
|
|
|
|
(Entity, &mut GridPosition, &RelativePosition),
|
|
|
|
debug!("Updating position {:?}", t.translation);
|
|
|
|
(
|
|
|
|
|
|
|
|
Without<ShapeBlocks>,
|
|
|
|
|
|
|
|
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
query.iter_mut().for_each(|(e, mut gp, rp)| {
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
|
|
|
|
"Updating {e} grid position to {:?} + {:?} = {:?}",
|
|
|
|
|
|
|
|
gp,
|
|
|
|
|
|
|
|
rp,
|
|
|
|
|
|
|
|
gp.add_relative(rp).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
*gp = (*shape).add_relative(rp).unwrap();
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn kb_input(
|
|
|
|
fn kb_input(
|
|
|
|
mut events: EventReader<KeyboardInput>,
|
|
|
|
mut events: EventReader<KeyboardInput>,
|
|
|
|
mut query: Query<(Entity, &Orientation, &mut Shape)>,
|
|
|
|
mut query: Query<(&mut GridPosition, &mut Orientation, &mut Shape)>,
|
|
|
|
curr: Res<State<Falling>>,
|
|
|
|
curr: Res<State<Falling>>,
|
|
|
|
mut next: ResMut<NextState<Falling>>,
|
|
|
|
mut next: ResMut<NextState<Falling>>,
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
events.read().for_each(
|
|
|
|
events.read().for_each(
|
|
|
|
|KeyboardInput {
|
|
|
|
|KeyboardInput {
|
|
|
|
@ -701,33 +450,25 @@ fn kb_input(
|
|
|
|
if let ButtonState::Pressed = state {
|
|
|
|
if let ButtonState::Pressed = state {
|
|
|
|
// TODO: Restict movement based on size/orientation of piece
|
|
|
|
// TODO: Restict movement based on size/orientation of piece
|
|
|
|
// Check if children would be outside play area...
|
|
|
|
// Check if children would be outside play area...
|
|
|
|
query.iter_mut().for_each(|(e, o, mut s)| {
|
|
|
|
query.iter_mut().for_each(|(mut gp, mut o, mut s)| {
|
|
|
|
match key_code {
|
|
|
|
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 => *o = o.prev(),
|
|
|
|
commands.entity(e).trigger(o.prev());
|
|
|
|
KeyCode::ArrowDown => *gp = gp.move_down(),
|
|
|
|
}
|
|
|
|
KeyCode::ArrowLeft => *gp = gp.move_left(),
|
|
|
|
KeyCode::ArrowDown => {
|
|
|
|
KeyCode::ArrowRight => *gp = gp.move_right(),
|
|
|
|
commands.entity(e).trigger(RelativePosition::down());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyCode::ArrowLeft => {
|
|
|
|
|
|
|
|
commands.entity(e).trigger(RelativePosition::left());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyCode::ArrowRight => {
|
|
|
|
|
|
|
|
commands.entity(e).trigger(RelativePosition::right());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyCode::Space => next.set(match curr.get() {
|
|
|
|
KeyCode::Space => next.set(match curr.get() {
|
|
|
|
Falling::On => Falling::Off,
|
|
|
|
Falling::On => Falling::Off,
|
|
|
|
Falling::Off => Falling::On,
|
|
|
|
Falling::Off => Falling::On,
|
|
|
|
}),
|
|
|
|
}),
|
|
|
|
KeyCode::Digit1 => *s = Shape::new_t(),
|
|
|
|
KeyCode::Digit1 => *s = Shape::T,
|
|
|
|
KeyCode::Digit2 => *s = Shape::new_o(),
|
|
|
|
KeyCode::Digit2 => *s = Shape::O,
|
|
|
|
KeyCode::Digit3 => *s = Shape::new_l(),
|
|
|
|
KeyCode::Digit3 => *s = Shape::L,
|
|
|
|
KeyCode::Digit4 => *s = Shape::new_j(),
|
|
|
|
KeyCode::Digit4 => *s = Shape::J,
|
|
|
|
KeyCode::Digit5 => *s = Shape::new_s(),
|
|
|
|
KeyCode::Digit5 => *s = Shape::S,
|
|
|
|
KeyCode::Digit6 => *s = Shape::new_z(),
|
|
|
|
KeyCode::Digit6 => *s = Shape::Z,
|
|
|
|
KeyCode::Digit7 => *s = Shape::new_i(),
|
|
|
|
KeyCode::Digit7 => *s = Shape::I,
|
|
|
|
_ => (),
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
@ -740,22 +481,25 @@ fn draw_grid(mut gizmos: Gizmos) {
|
|
|
|
gizmos
|
|
|
|
gizmos
|
|
|
|
.grid_2d(
|
|
|
|
.grid_2d(
|
|
|
|
Isometry2d::IDENTITY,
|
|
|
|
Isometry2d::IDENTITY,
|
|
|
|
UVec2::new(X_MAX, Y_MAX),
|
|
|
|
UVec2::new(10, 20),
|
|
|
|
Vec2::new(SCALE, SCALE),
|
|
|
|
Vec2::new(SCALE, SCALE),
|
|
|
|
GREEN,
|
|
|
|
GREEN,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.outer_edges();
|
|
|
|
.outer_edges();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
|
|
|
|
fn falling(mut query: Query<&mut GridPosition, With<Shape>>) {
|
|
|
|
shape.iter_mut().for_each(|e| {
|
|
|
|
query.iter_mut().for_each(|mut gp| {
|
|
|
|
info!("Making {:?} fall", e);
|
|
|
|
let next = gp.move_down();
|
|
|
|
commands.entity(e).trigger(RelativePosition::down());
|
|
|
|
if next != *gp {
|
|
|
|
|
|
|
|
*gp = next;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Remove the falling component from this entity
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Run condition that returns `true` every `n` seconds
|
|
|
|
// Run condition that returns `true` every `n` seconds
|
|
|
|
// TODO: Update a resource with the current tick
|
|
|
|
|
|
|
|
fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
|
|
|
|
fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
|
|
|
|
move |t: Res<Time>, mut buf: Local<f32>| -> bool {
|
|
|
|
move |t: Res<Time>, mut buf: Local<f32>| -> bool {
|
|
|
|
*buf += t.delta_secs();
|
|
|
|
*buf += t.delta_secs();
|
|
|
|
@ -767,106 +511,3 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn add_piece(mut commands: Commands) {
|
|
|
|
|
|
|
|
// TODO: Choose a different piece
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
|
|
|
.spawn((Orientation::default(), GridPosition::default(), Shape::default()))
|
|
|
|
|
|
|
|
.observe(movement)
|
|
|
|
|
|
|
|
.observe(rotation);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// When a line reaches 10 blocks, clear it
|
|
|
|
|
|
|
|
fn clear_line(
|
|
|
|
|
|
|
|
lines: Query<(Entity, &LineBlocks), (With<LineBlocks>, Changed<LineBlocks>)>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
lines.iter().for_each(|(e, lb)| {
|
|
|
|
|
|
|
|
if lb.0.len() == 10 {
|
|
|
|
|
|
|
|
commands.entity(e).despawn_related::<LineBlocks>();
|
|
|
|
|
|
|
|
// TODO: re-parent all blocks above this to the next line down
|
|
|
|
|
|
|
|
// TODO: Parent blocks to lines for movement
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn movement(
|
|
|
|
|
|
|
|
trigger: Trigger<RelativePosition>,
|
|
|
|
|
|
|
|
mut grid_positions: Query<&mut GridPosition, Or<(With<RelativePosition>, With<Shape>)>>,
|
|
|
|
|
|
|
|
parent: Query<&ShapeBlocks>,
|
|
|
|
|
|
|
|
inactive: Query<
|
|
|
|
|
|
|
|
&GridPosition,
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
Without<RelativePosition>,
|
|
|
|
|
|
|
|
Without<Shape>,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
// Do a bunch of checks if this move is valid
|
|
|
|
|
|
|
|
for block in parent.iter_descendants(trigger.target()) {
|
|
|
|
|
|
|
|
let block_gp = grid_positions.get(block).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
match block_gp.add_relative(trigger.event()) {
|
|
|
|
|
|
|
|
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => {
|
|
|
|
|
|
|
|
// Hit the left/right wall, just ignore this move
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(GameError::OutOfBoundsDown) => {
|
|
|
|
|
|
|
|
commands.entity(trigger.target()).remove::<Shape>();
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(GameError::Collision) => {
|
|
|
|
|
|
|
|
// Do nothing. add_relative does not return this variant
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(new_block_gp) => {
|
|
|
|
|
|
|
|
let collision = inactive
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.any(|inactive_block_gp| new_block_gp == *inactive_block_gp);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if collision {
|
|
|
|
|
|
|
|
commands.entity(trigger.target()).remove::<Shape>();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Move shape itself
|
|
|
|
|
|
|
|
if let Ok(mut shape_gp) = grid_positions.get_mut(trigger.target()) {
|
|
|
|
|
|
|
|
*shape_gp = shape_gp.add_relative(trigger.event()).unwrap();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Move the blocks of this shape
|
|
|
|
|
|
|
|
parent.iter_descendants(trigger.target()).for_each(|block| {
|
|
|
|
|
|
|
|
info!("Propogating movement {:?} to child {:?}", trigger.event(), block);
|
|
|
|
|
|
|
|
commands.entity(block).trigger(*trigger.event());
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) {
|
|
|
|
|
|
|
|
let mut o = q.get_mut(trigger.target()).unwrap();
|
|
|
|
|
|
|
|
// If children would go out of bounds going left, move slightly to the right
|
|
|
|
|
|
|
|
// If that would cause collision, deactive piece
|
|
|
|
|
|
|
|
// If children would go out of bounds going right, move slightly to the left
|
|
|
|
|
|
|
|
// If that would cause collision, deactive piece
|
|
|
|
|
|
|
|
// If children would not go out of bounds going down
|
|
|
|
|
|
|
|
*o = trigger.event().clone();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Just despawn?
|
|
|
|
|
|
|
|
fn deactive_shape(
|
|
|
|
|
|
|
|
trigger: Trigger<OnRemove, Shape>,
|
|
|
|
|
|
|
|
parent: Query<&ShapeBlocks>,
|
|
|
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
let v: Vec<Entity> = parent.iter_descendants(trigger.target()).collect();
|
|
|
|
|
|
|
|
parent.iter_descendants(trigger.target()).for_each(|block| {
|
|
|
|
|
|
|
|
commands.entity(block).remove::<RelativePosition>();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
|
|
|
.entity(trigger.target())
|
|
|
|
|
|
|
|
.remove_related::<ShapeBlock>(v.as_slice())
|
|
|
|
|
|
|
|
.despawn();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: When Orientation changed, perform matrix multiplication or something(?)
|
|
|
|
|
|
|
|
|