@ -35,7 +35,6 @@ fn main() {
init_cameras ,
init_ui . after ( init_cameras ) ,
init_battler ,
init_deck ,
) ,
)
// Input and basic systems
@ -56,10 +55,10 @@ fn main() {
. after ( update_next_shapes ) ,
falling
. run_if ( in_state ( GameState ::Falling ) )
. run_if ( clock_cycle( 1.0 ) ) ,
. run_if ( on_timer( Duration ::from_secs ( 1 ) ) ) ,
update_position . run_if ( any_component_changed ::< GridPosition > ) ,
update_shape_blocks
. run_if ( any_component_ added::< Shape > . or ( any_component_changed ::< Shape > ) )
. run_if ( any_component_ changed::< ShapeLayout > . or ( any_component_changed ::< GridPosition > ) )
. after ( update_position ) ,
deactivate_shape
. run_if ( any_component_removed ::< Shape > )
@ -74,7 +73,7 @@ fn main() {
. run_if ( any_component_changed ::< Health > . or ( any_component_added ::< Health > ) ) ,
damage_on_place_shape . run_if ( any_component_removed ::< Shape > ) ,
damage_on_clear_line . run_if ( any_component_removed ::< LineBlock > ) ,
damage_over_time . run_if ( clock_cycle( 5.0 ) ) ,
damage_over_time . run_if ( on_timer( Duration ::from_secs ( 5 ) ) ) ,
) ,
)
// UI systems
@ -88,6 +87,8 @@ fn main() {
) ,
)
. add_observer ( deal_damage )
. add_observer ( on_add_shape_layout )
. add_observer ( on_add_health )
. run ( ) ;
}
@ -227,8 +228,14 @@ enum GameState {
#[ derive(Resource, Debug) ]
struct Visuals {
material : Handle < ColorMaterial > ,
mesh : Handle < Mesh > ,
material_o : Handle < ColorMaterial > ,
material_t : Handle < ColorMaterial > ,
material_l : Handle < ColorMaterial > ,
material_j : Handle < ColorMaterial > ,
material_s : Handle < ColorMaterial > ,
material_z : Handle < ColorMaterial > ,
material_i : Handle < ColorMaterial > ,
}
#[ derive(Resource, Debug, Default) ]
@ -266,7 +273,7 @@ struct ShapeStore(Option<Shape>);
impl Display for ShapeStore {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
match & self . 0 {
Some ( inner ) = > write! ( f , "{}" , inner . as_ascii ( ) ) ,
Some ( s ) = > write! ( f , "{}" , ShapeLayout::from_shape ( s ) . as_ascii ( ) ) ,
None = > write! ( f , "---" ) ,
}
}
@ -281,17 +288,36 @@ fn init_tetris(
mut materials : ResMut < Assets < ColorMaterial > > ,
) {
let mesh = meshes . add ( Rectangle ::new ( SCALE , SCALE ) ) ;
let block_material = materials . add ( ColorMaterial {
color : WHITE . into ( ) ,
.. default ( )
} ) ;
let grid_material = materials . add ( ColorMaterial {
color : BLACK . into ( ) ,
.. default ( )
} ) ;
commands . insert_resource ( Visuals {
material : block_material . clone ( ) ,
mesh : mesh . clone ( ) ,
material_o : materials . add ( ColorMaterial {
color : YELLOW . into ( ) ,
.. default ( )
} ) ,
material_t : materials . add ( ColorMaterial {
color : PURPLE . into ( ) ,
.. default ( )
} ) ,
material_l : materials . add ( ColorMaterial {
color : ORANGE . into ( ) ,
.. default ( )
} ) ,
material_j : materials . add ( ColorMaterial {
color : BLUE . into ( ) ,
.. default ( )
} ) ,
material_s : materials . add ( ColorMaterial {
color : LIME . into ( ) ,
.. default ( )
} ) ,
material_z : materials . add ( ColorMaterial {
color : RED . into ( ) ,
.. default ( )
} ) ,
material_i : materials . add ( ColorMaterial {
color : AQUA . into ( ) ,
.. default ( )
} ) ,
} ) ;
( 0 .. Y_MAX ) . for_each ( | y | {
@ -299,7 +325,10 @@ fn init_tetris(
( 0 .. X_MAX ) . for_each ( | x | {
commands . spawn ( (
Mesh2d ( mesh . clone ( ) ) ,
MeshMaterial2d ( grid_material . clone ( ) ) ,
MeshMaterial2d ( materials . add ( ColorMaterial {
color : BLACK . into ( ) ,
.. default ( )
} ) ) ,
GridPosition { x , y } ,
Transform ::from_xyz ( 0.0 , 0.0 , - 1.0 ) ,
GridBackground ,
@ -312,12 +341,6 @@ fn init_tetris(
} ) ;
}
fn init_deck (
mut commands : Commands ,
) {
todo! ( )
}
#[ derive(Component, Debug) ]
struct Protagonist ;
@ -579,32 +602,63 @@ fn init_debug_ui(mut commands: Commands) {
} ) ;
}
/// Enum describing a shape option
#[ derive(Component, Debug, Clone) ]
struct Shape {
layout : ShapeBlockLayout ,
enum Shape {
O ,
T ,
L ,
J ,
S ,
Z ,
I ,
}
impl Default for Shape {
fn default ( ) -> Self {
Self ::new_t ( )
Self ::T
}
}
impl Display for Shape {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
write! ( f , "{ }", self . layout . as_ascii ( ) )
write! ( f , "{ self:?}" )
}
}
impl From < Vec < Vec < u8 > > > for Shape {
fn from ( inner : Vec < Vec < u8 > > ) -> Shape {
Shape {
layout : ShapeBlockLayout { inner } ,
}
impl Shape {
fn reposition (
( x_offset , y_offset ) : ( isize , isize ) ,
center : & GridPosition ,
) -> Result < GridPosition , OutOfBoundsError > {
center . with_offset ( x_offset , y_offset )
}
}
impl Shape {
#[ derive(Component, Debug, Clone, PartialEq, Eq) ]
struct ShapeLayout {
inner : Vec < Vec < u8 > > ,
}
impl From < Vec < Vec < u8 > > > for ShapeLayout {
fn from ( inner : Vec < Vec < u8 > > ) -> Self {
Self { inner }
}
}
impl ShapeLayout {
fn from_shape ( s : & Shape ) -> Self {
match s {
Shape ::O = > Self ::new_o ( ) ,
Shape ::T = > Self ::new_t ( ) ,
Shape ::L = > Self ::new_l ( ) ,
Shape ::J = > Self ::new_j ( ) ,
Shape ::S = > Self ::new_s ( ) ,
Shape ::Z = > Self ::new_z ( ) ,
Shape ::I = > Self ::new_i ( ) ,
}
}
fn new_o ( ) -> Self {
vec! [ vec! [ 1 , 1 ] , vec! [ 1 , 1 ] ] . into ( )
}
@ -637,45 +691,6 @@ impl Shape {
vec! [ vec! [ 1 ] , vec! [ 1 ] , vec! [ 1 ] , vec! [ 1 ] ] . into ( )
}
// Rotates 90 degrees to the right
// https://stackoverflow.com/a/8664879
fn rotated ( & self ) -> Self {
Self {
layout : self . layout . rotated ( ) ,
}
}
fn reposition (
( x_offset , y_offset ) : ( isize , isize ) ,
center : & GridPosition ,
) -> Result < GridPosition , OutOfBoundsError > {
center . with_offset ( x_offset , y_offset )
}
fn coordinates (
& self ,
center : & GridPosition ,
) -> impl Iterator < Item = Result < GridPosition , OutOfBoundsError > > {
self . layout
. coordinates ( )
. map ( | ( x , y ) | Shape ::reposition ( ( x , y ) , center ) )
}
fn as_ascii ( & self ) -> String {
self . layout . as_ascii ( )
}
fn height ( & self ) -> usize {
self . layout . height ( )
}
}
#[ derive(Debug, Clone, PartialEq, Eq) ]
struct ShapeBlockLayout {
inner : Vec < Vec < u8 > > ,
}
impl ShapeBlockLayout {
fn rotated ( & self ) -> Self {
let mut inner = vec! [ ] ;
for _ in 0 .. self . inner [ 0 ] . len ( ) {
@ -686,7 +701,7 @@ impl ShapeBlockLayout {
inner [ j ] . insert ( 0 , * x ) ;
}
}
Shape Block Layout { inner }
Shape Layout { inner }
}
fn center ( & self ) -> ( usize , usize ) {
@ -709,6 +724,14 @@ impl ShapeBlockLayout {
( mid_x , mid_y )
}
fn coordinates_at (
& self ,
center : & GridPosition ,
) -> impl Iterator < Item = Result < GridPosition , OutOfBoundsError > > {
self . coordinates ( )
. map ( | ( x , y ) | Shape ::reposition ( ( x , y ) , center ) )
}
fn coordinates ( & self ) -> impl Iterator < Item = ( isize , isize ) > {
let ( mid_x , mid_y ) = self . center ( ) ;
// Loop over outer vec (i)
@ -771,45 +794,57 @@ fn update_position(
// TODO: Move to event
fn update_shape_blocks (
query : Query <
( Entity , & Shape , & GridPosition ) ,
( Entity , & Shape Layout , & GridPosition ) ,
Or < (
Added < Shape > ,
Changed < Shape > ,
Added < GridPosition > ,
Changed < ShapeLayout > ,
Changed < GridPosition > ,
) > ,
> ,
mut blocks : Query < & mut GridPosition , ( With < ShapeBlock > , Without < Shape > ) > ,
mut commands : Commands ,
visuals : Res < Visuals > ,
mut blocks : Query < & mut GridPosition , ( With < ShapeBlock > , Without < ShapeLayout > ) > ,
) {
query . iter ( ) . for_each ( | ( e , s , center ) | {
debug ! ( "Setting piece: {e:?} {center:?}\n{}" , s . as_ascii ( ) ) ;
query . iter ( ) . for_each ( | ( e , sl , center ) | {
debug ! ( "Setting piece: {e:?} {center:?}\n{}" , s l . as_ascii ( ) ) ;
if blocks . is_empty ( ) {
let mesh = Mesh2d ( visuals . mesh . clone ( ) ) ;
let mat = MeshMaterial2d ( visuals . material . clone ( ) ) ;
commands
. entity ( e )
. with_related_entities ::< ShapeBlock > ( | parent | {
s . coordinates ( center ) . for_each ( | gp | {
parent
. spawn ( ( mesh . clone ( ) , mat . clone ( ) , gp . unwrap ( ) , Block , TETRIS ) )
. observe ( movement ) ;
} ) ;
} ) ;
} else {
let mut p = s . coordinates ( center ) ;
blocks . iter_mut ( ) . for_each ( | mut gp | {
* gp = p . next ( ) . unwrap ( ) . unwrap ( ) ;
} ) ;
}
debug_assert! ( ! blocks . is_empty ( ) ) ;
let mut p = sl . coordinates_at ( center ) ;
blocks . iter_mut ( ) . for_each ( | mut gp | {
* gp = p . next ( ) . unwrap ( ) . unwrap ( ) ;
} ) ;
} ) ;
}
fn on_add_shape_layout (
trigger : On < Add , ShapeLayout > ,
visuals : Res < Visuals > ,
query : Query < ( Entity , & Shape , & ShapeLayout , & GridPosition ) > ,
mut commands : Commands ,
) {
let ( e , s , sl , center ) = query . get ( trigger . entity ) . unwrap ( ) ;
let mesh = Mesh2d ( visuals . mesh . clone ( ) ) ;
let mat = match s {
Shape ::O = > & visuals . material_o ,
Shape ::T = > & visuals . material_t ,
Shape ::L = > & visuals . material_l ,
Shape ::J = > & visuals . material_j ,
Shape ::S = > & visuals . material_s ,
Shape ::Z = > & visuals . material_z ,
Shape ::I = > & visuals . material_i ,
} ;
commands
. entity ( e )
. with_related_entities ::< ShapeBlock > ( | parent | {
sl . coordinates_at ( center ) . for_each ( | gp | {
parent
. spawn ( ( mesh . clone ( ) , MeshMaterial2d ( mat . clone ( ) ) , gp . unwrap ( ) , Block , TETRIS ) )
. observe ( movement ) ;
} ) ;
} ) ;
}
fn kb_input (
mut events : MessageReader < KeyboardInput > ,
mut query : Query < ( Entity , & mut Shape ) > ,
mut query : Query < Entity , With < Shape > > ,
curr : Res < State < GameState > > ,
mut next : ResMut < NextState < GameState > > ,
mut commands : Commands ,
@ -819,7 +854,7 @@ fn kb_input(
key_code , state , ..
} | {
if let ButtonState ::Pressed = state {
query . iter_mut ( ) . for_each ( | ( e , mut s ) | {
query . iter_mut ( ) . for_each ( | e | {
match key_code {
// Up arrow should rotate if in falling mode
// Only move up if in falling::off mode
@ -860,13 +895,6 @@ fn kb_input(
GameState ::Falling = > GameState ::Pause ,
GameState ::Pause = > GameState ::Falling ,
} ) ,
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 ( ) ,
_ = > ( ) ,
}
} ) ;
@ -885,29 +913,17 @@ fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
} ) ;
}
// 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 ( ) ;
if * buf > n {
* buf = 0.0 ;
true
} else {
false
}
}
}
fn add_piece ( mut commands : Commands , mut shapes : ResMut < ShapesBuffer > ) {
let this_shape = shapes . next . pop_front ( ) . unwrap ( ) ;
let shape = shapes . next . pop_front ( ) . unwrap ( ) ;
let shape_layout = ShapeLayout ::from_shape ( & shape ) ;
commands
. spawn ( (
GridPosition {
y : Y_MAX - this_ shape. height ( ) ,
y : Y_MAX - shape_layout . height ( ) ,
.. default ( )
} ,
this_shape ,
shape_layout ,
shape ,
TETRIS ,
) )
. observe ( movement )
@ -1025,12 +1041,12 @@ fn movement(
& mut GridPosition ,
Or < ( With < ShapeBlock > , With < ShapeBlocks > , Without < LineBlock > ) > ,
> ,
mut shape : Query < & mut Shape > ,
mut shape _layouts : Query < & mut Shape Layout > ,
inactive : Query < & GridPosition , ( Without < ShapeBlock > , Without < ShapeBlocks > , With < LineBlock > ) > ,
mut commands : Commands ,
) {
if let ( Ok ( this_shape ) , Ok ( center ) ) = (
shape . get_mut ( event . entity ) ,
if let ( Ok ( this_shape _layout ) , Ok ( center ) ) = (
shape _layouts . get_mut ( event . entity ) ,
grid_positions . get ( event . entity ) ,
) {
let new_positions = match event . event ( ) . direction {
@ -1042,17 +1058,17 @@ fn movement(
. map ( | i | center . with_offset ( 0 , - ( i as isize ) ) )
. collect ( ) ,
} ;
let new_shape = match event . event ( ) . direction {
let new_shape _layout = match event . event ( ) . direction {
MovementDirection ::Down
| MovementDirection ::Left
| MovementDirection ::Right
| MovementDirection ::Skip = > this_shape . clone ( ) ,
MovementDirection ::Rotate = > this_shape . rotated ( ) ,
| MovementDirection ::Skip = > this_shape _layout . clone ( ) ,
MovementDirection ::Rotate = > this_shape _layout . rotated ( ) ,
} ;
debug ! (
"Proposed change: {:?}\n{}" ,
new_positions ,
new_shape . as_ascii ( )
new_shape _layout . as_ascii ( )
) ;
for position in new_positions {
match position {
@ -1061,7 +1077,7 @@ fn movement(
commands . entity ( event . entity ) . remove ::< Shape > ( ) ;
}
Ok ( new_center ) = > {
let new_blocks = new_shape . coordinates ( & new_center ) ;
let new_blocks = new_shape _layout . coordinates _at ( & new_center ) ;
for block_gp in new_blocks {
match block_gp {
Err ( OutOfBoundsError ::Left ) | Err ( OutOfBoundsError ::Right ) = > {
@ -1094,8 +1110,8 @@ fn movement(
* gp = new_center ;
// Update shape/rotation
let mut s = shape . get_mut ( event . entity ) . unwrap ( ) ;
* s = new_shape . clone ( ) ;
let mut s l = shape _layouts . get_mut ( event . entity ) . unwrap ( ) ;
* s l = new_shape _layout . clone ( ) ;
}
}
}
@ -1161,13 +1177,13 @@ fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
while buffer . next . len ( ) < 8 {
// TODO: Shuffle these!
buffer . next . extend ( [
Shape ::new_o( ) ,
Shape ::new_t( ) ,
Shape ::new_l( ) ,
Shape ::new_j( ) ,
Shape ::new_s( ) ,
Shape ::new_z( ) ,
Shape ::new_i( ) ,
Shape ::O ,
Shape ::T ,
Shape ::L ,
Shape ::J ,
Shape ::S ,
Shape ::Z ,
Shape ::I ,
] ) ;
}
}
@ -1181,35 +1197,40 @@ fn assert_grid_position_uniqueness(
}
fn sync_health (
query : Query < ( Entity , & Health , & Mesh2d ), Or< ( Changed< Health > , Added < Health > ) > > ,
query : Query < ( Entity , & Health ), Changed< Health > > ,
parent : Query < & Children > ,
meshes : Res < Assets < Mesh > > ,
mut texts : Query < & mut Text2d > ,
mut commands : Commands ,
) {
query . iter ( ) . for_each ( | ( e , h , m )| {
if let Some ( child ) = parent
query . iter ( ) . for_each ( | ( e , h )| {
let child = parent
. iter_descendants ( e )
. find ( | child | texts . contains ( * child ) )
{
info ! ( "Updating health" ) ;
let mut t = texts . get_mut ( child ) . unwrap ( ) ;
t . 0 = format! ( "{}" , h . 0 ) ;
} else {
info ! ( "Creating health display" ) ;
commands . entity ( e ) . with_children ( | parent | {
let mesh = meshes . get ( & m . 0 ) . unwrap ( ) ;
let aabb = mesh . compute_aabb ( ) . unwrap ( ) ;
let offset = Vec3 ::new ( 0.0 , aabb . half_extents . y + 10.0 , 0.0 ) ;
parent . spawn ( (
Text2d ( format! ( "{}" , h . 0 ) ) ,
TextColor ( BLACK . into ( ) ) ,
Transform ::from_translation ( offset ) ,
BATTLER ,
) ) ;
} ) ;
}
} )
. unwrap ( ) ;
debug ! ( "Updating health" ) ;
let mut t = texts . get_mut ( child ) . unwrap ( ) ;
t . 0 = format! ( "{}" , h . 0 ) ;
} ) ;
}
fn on_add_health (
event : On < Add , Health > ,
query : Query < ( & Health , & Mesh2d ) > ,
meshes : Res < Assets < Mesh > > ,
mut commands : Commands ,
) {
let ( h , m ) = query . get ( event . entity ) . unwrap ( ) ;
info ! ( "Creating health display" ) ;
commands . entity ( event . entity ) . with_children ( | parent | {
let mesh = meshes . get ( & m . 0 ) . unwrap ( ) ;
let aabb = mesh . compute_aabb ( ) . unwrap ( ) ;
let offset = Vec3 ::new ( 0.0 , aabb . half_extents . y + 10.0 , 0.0 ) ;
parent . spawn ( (
Text2d ( format! ( "{}" , h . 0 ) ) ,
TextColor ( BLACK . into ( ) ) ,
Transform ::from_translation ( offset ) ,
BATTLER ,
) ) ;
} ) ;
}
#[ derive(Message, EntityEvent) ]