@ -3,7 +3,7 @@
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 line is "full" (has 10 children) clear line and add to score
@ -16,72 +16,99 @@ fn main() {
.. default ( )
} )
. init_state ::< Falling > ( )
. add_systems ( Startup , ( init_ pieces , init_debug_ui ) )
. add_systems ( Startup , ( init_ world , init_debug_ui ) )
. add_systems (
Update ,
(
kb_input . run_if ( on_event ::< KeyboardInput > ) ,
update_position ,
falling
. run_if ( in_state ( Falling ::On ) )
. run_if ( clock_cycle ( 1.0 ) ) ,
set_ piece
. run_if ( any_component_added ::< Shape >
set_ shape. 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 > ) ,
. or ( any_component_changed ::< Orientation > ) ,
) ,
update_relative_position ,
update_position . after ( update_relative_position ) ,
sync_singleton_to_ui ::< Shape > . run_if ( any_component_changed ::< Shape > ) ,
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_observer ( deactive_shape )
. run ( ) ;
}
const SCALE : f32 = 30.0 ;
/// A shape, e.g., the long piece
#[ derive(Component, Debug, Default) ]
enum Shape {
#[ default ]
O ,
T ,
L ,
J ,
S ,
Z ,
I ,
}
// Declare the size of the play area
const X_MAX : u32 = 10 ;
const Y_MAX : u32 = 20 ;
impl Display for Shape {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
match self {
Shape ::O = > write! ( f , "O" ) ,
Shape ::T = > write! ( f , "T" ) ,
Shape ::L = > write! ( f , "L" ) ,
Shape ::J = > write! ( f , "J" ) ,
Shape ::S = > write! ( f , "S" ) ,
Shape ::Z = > write! ( f , "Z" ) ,
Shape ::I = > write! ( f , "I" ) ,
}
}
// The blocks making up this shape
#[ derive(Component) ]
#[ relationship_target(relationship = ShapeBlock) ]
struct ShapeBlocks ( Vec < Entity > ) ;
/// A part of a piece, i.e., a single square of a piece
#[ derive(Component, Debug) ]
#[ relationship(relationship_target = ShapeBlocks) ]
struct ShapeBlock {
#[ relationship ]
shape : Entity ,
}
// The blocks making up this shape
#[ derive(Component, Default) ]
#[ relationship_target(relationship = LineBlock) ]
struct LineBlocks ( Vec < Entity > ) ;
/// A part of a piece, i.e., a single square of a piece
#[ derive(Component, Debug) ]
struct ShapePiece ;
#[ require(Transform, Visibility) ]
#[ relationship(relationship_target = LineBlocks) ]
struct LineBlock {
#[ relationship ]
line : Entity ,
}
// A line holds up to 10 blocks before being cleared
#[ derive(Component, Debug) ]
struct Line ( u8 ) ;
// Just marks a block either of a shape or line
#[ derive(Component, Debug) ]
struct Block ;
#[ derive(Component, Event, Debug, Clone, Copy, PartialEq) ]
#[ require(GridPosition) ]
struct RelativePosition {
x : 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 {
fn from ( ( x , y ) : ( i8 , i8 ) ) -> RelativePosition {
RelativePosition { x , y }
@ -91,14 +118,14 @@ impl From<(i8, i8)> for RelativePosition {
#[ derive(Component, Debug, Clone, Copy, PartialEq) ]
#[ require(Transform, Visibility) ]
struct GridPosition {
x : u size ,
y : u size ,
x : u 32 ,
y : u 32 ,
}
impl GridPosition {
fn move_up ( & self ) -> Self {
Self {
y : if self . y + 1 < 20 {
y : if self . y + 1 < Y_MAX {
self . y . saturating_add ( 1 )
} else {
self . y
@ -123,7 +150,7 @@ impl GridPosition {
fn move_right ( & mut self ) -> Self {
Self {
x : if self . x + 1 < 10 {
x : if self . x + 1 < X_MAX {
self . x . saturating_add ( 1 )
} else {
self . x
@ -131,11 +158,48 @@ impl GridPosition {
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 {
fn default ( ) -> Self {
GridPosition { x : 5 , y : 20 }
GridPosition { x : 5 , y : Y_MAX }
}
}
@ -154,8 +218,8 @@ impl From<&GridPosition> for Vec3 {
}
}
impl From < ( u size, usize ) > for GridPosition {
fn from ( ( x , y ) : ( u size, usize ) ) -> GridPosition {
impl From < ( u 32, u32 ) > for GridPosition {
fn from ( ( x , y ) : ( u 32, u32 ) ) -> GridPosition {
GridPosition { x , y }
}
}
@ -177,7 +241,7 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition {
}
}
#[ derive(Component, Default, Debug)]
#[ derive(Component, Default, Event, Clone, Debug)]
enum Orientation {
#[ default ]
Up ,
@ -230,7 +294,7 @@ struct Visuals {
mesh : Handle < Mesh > ,
}
fn init_ pieces (
fn init_ world (
mut commands : Commands ,
mut meshes : ResMut < Assets < Mesh > > ,
mut materials : ResMut < Assets < ColorMaterial > > ,
@ -243,205 +307,392 @@ fn init_pieces(
mesh : meshes . add ( Rectangle ::new ( SCALE , SCALE ) ) ,
} ) ;
commands . spawn ( ( Orientation ::default ( ) , GridPosition ::default ( ) , Shape ::T ) ) ;
( 0 .. 20 ) . for_each ( | i | {
commands . spawn ( ( Line ( i ) , LineBlocks ::default ( ) ) ) ;
} ) ;
}
fn init_debug_ui (
mut commands : Commands ,
) {
commands . spawn ( ( Node {
fn init_debug_ui ( mut commands : Commands ) {
commands
. spawn ( (
Node {
top : Val ::Px ( 0.0 ) ,
left : Val ::Px ( 0.0 ) ,
.. default ( )
} , DebuggingState ::On ) ) . with_children ( | parent | {
} ,
DebuggingState ::On ,
) )
. with_children ( | parent | {
parent . spawn ( (
Node ::default ( ) ,
children ! [
( Text ::new ( "SHAPE" ) , SyncSingleton ::< Shape > ::default ( ) ) ,
( Text ::new ( "ORIENTATION" ) , SyncSingleton ::< Orientation > ::default ( ) ) ,
]
(
Text ::new ( "ORIENTATION" ) ,
SyncSingleton ::< Orientation > ::default ( )
) ,
] ,
) ) ;
} ) ;
}
fn set_piece (
query : Query < ( Entity , & Shape , & Orientation ) , Or < ( Added < Shape > , Changed < Shape > , Added < Orientation > , Changed < Orientation > ) > > ,
#[ derive(Component, Debug) ]
enum Shape {
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 ( & center ) . 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 ,
visuals : Res < Visuals > ,
) {
query . iter ( ) . for_each ( | ( e , s , o ) | {
debug ! ( "{e:?} {s:?} {o:?}" ) ;
commands
. entity ( e )
. despawn_related ::< Children > ( )
. with_children ( | parent | {
debug ! ( "Setting piece: {e:?} {s:?} {o:?}" ) ;
let mesh = visuals . mesh . clone ( ) ;
let mat = visuals . material . clone ( ) ;
let positions : [ RelativePosition ; 4 ] = todo! ( ) ;
#[ 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 ( ) ,
]
}
} ;
// todo: map positions to coordinates
piece_positions . into_iter ( ) . for_each ( | rp | {
parent . spawn ( ( Mesh2d ( mesh . clone ( ) ) , MeshMaterial2d ( mat . clone ( ) ) , rp ) ) ;
if blocks . is_empty ( ) {
commands
. 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 set_relative_piece_positions (
query : Query <
( Entity , & RelativePosition ) ,
Or < ( Added < RelativePosition > , Changed < RelativePosition > ) > ,
fn update_position (
mut changed : Query <
( Entity , & GridPosition , & mut Transform ) ,
Or < ( Added < GridPosition > , Changed < GridPosition > ) > ,
> ,
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 ,
) ) ;
changed . iter_mut ( ) . for_each ( | ( e , gp , mut t ) | {
let v3 : Vec3 = gp . into ( ) ;
debug ! (
"Updating {e} with grid position {:?} to coordinates {:?}" ,
gp , v3
) ;
debug_assert! ( gp . x < X_MAX , "block x > x_max" ) ;
t . translation = gp . into ( ) ;
} ) ;
}
fn update_position ( mut query : Query < ( & GridPosition , & mut Transform ) , Changed < GridPosition > > ) {
query . iter_mut ( ) . for_each ( | ( gp , mut t ) | {
t . translation = gp . into ( ) ;
debug ! ( "Updating position {:?}" , t . translation ) ;
fn update_relative_position (
shape : Single < & GridPosition , With < ShapeBlocks > > ,
mut query : Query <
( Entity , & mut GridPosition , & RelativePosition ) ,
(
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 (
mut events : EventReader < KeyboardInput > ,
mut query : Query < ( & mut GridPosition , & mut Orientation , & mut Shape ) > ,
mut query : Query < ( Entity , & Orientation , & mut Shape ) > ,
curr : Res < State < Falling > > ,
mut next : ResMut < NextState < Falling > > ,
mut commands : Commands ,
) {
events . read ( ) . for_each (
| KeyboardInput {
@ -450,25 +701,33 @@ fn kb_input(
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 , mut s ) | {
query . iter_mut ( ) . for_each ( | ( e , o , mut s ) | {
match key_code {
// Up arrow should rotate if in falling mode
// Only move up if in falling::off mode
KeyCode ::ArrowUp = > * o = o . prev ( ) ,
KeyCode ::ArrowDown = > * gp = gp . move_down ( ) ,
KeyCode ::ArrowLeft = > * gp = gp . move_left ( ) ,
KeyCode ::ArrowRight = > * gp = gp . move_right ( ) ,
KeyCode ::ArrowUp = > {
commands . entity ( e ) . trigger ( o . prev ( ) ) ;
}
KeyCode ::ArrowDown = > {
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 ( ) {
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 ,
KeyCode ::Digit1 = > * s = Shape ::new_t( ) ,
KeyCode ::Digit2 = > * s = Shape ::new_o( ) ,
KeyCode ::Digit3 = > * s = Shape ::new_l( ) ,
KeyCode ::Digit4 = > * s = Shape ::new_j( ) ,
KeyCode ::Digit5 = > * s = Shape ::new_s( ) ,
KeyCode ::Digit6 = > * s = Shape ::new_z( ) ,
KeyCode ::Digit7 = > * s = Shape ::new_i( ) ,
_ = > ( ) ,
}
} ) ;
@ -481,25 +740,22 @@ fn draw_grid(mut gizmos: Gizmos) {
gizmos
. grid_2d (
Isometry2d ::IDENTITY ,
UVec2 ::new ( 10 , 20 ) ,
UVec2 ::new ( X_MAX , Y_MAX ) ,
Vec2 ::new ( SCALE , SCALE ) ,
GREEN ,
)
. outer_edges ( ) ;
}
fn falling ( mut query : Query < & mut GridPosition , With < Shape > > ) {
query . iter_mut ( ) . for_each ( | mut gp | {
let next = gp . move_down ( ) ;
if next ! = * gp {
* gp = next ;
} else {
// Remove the falling component from this entity
}
fn falling ( mut shape : Query < Entity , With < Shape > > , mut commands : Commands ) {
shape . iter_mut ( ) . for_each ( | e | {
info ! ( "Making {:?} fall" , e ) ;
commands . entity ( e ) . trigger ( RelativePosition ::down ( ) ) ;
} ) ;
}
// 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 {
move | t : Res < Time > , mut buf : Local < f32 > | -> bool {
* buf + = t . delta_secs ( ) ;
@ -511,3 +767,106 @@ 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(?)