@ -27,6 +27,7 @@ fn main() {
. init_resource ::< ShapeStore > ( )
. init_resource ::< Score > ( )
. init_resource ::< OutputImages > ( )
. init_resource ::< LevelGoal > ( )
. add_systems (
Startup ,
(
@ -37,6 +38,10 @@ fn main() {
init_battler ,
) ,
)
. add_systems (
Update ,
game_state_machine . run_if ( state_changed ::< GameState > )
)
// Input and basic systems
. add_systems (
Update ,
@ -52,9 +57,10 @@ fn main() {
. run_if ( resource_changed ::< ShapesBuffer > . or ( resource_added ::< ShapesBuffer > ) ) ,
add_piece
. run_if ( not ( any_with_component ::< Shape > ) )
. run_if ( in_state ( GameState ::Play ) )
. after ( update_next_shapes ) ,
falling
. run_if ( in_state ( GameState ::Falling ) )
. run_if ( in_state ( GameState ::Play ) )
. run_if ( on_timer ( Duration ::from_secs ( 1 ) ) ) ,
update_position . run_if ( any_component_changed ::< GridPosition > ) ,
update_shape_blocks
@ -74,6 +80,7 @@ fn main() {
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 ( on_timer ( Duration ::from_secs ( 5 ) ) ) ,
check_level_goal . run_if ( resource_changed ::< Score > ) ,
) ,
)
// UI systems
@ -83,6 +90,7 @@ fn main() {
sync_resource_to_ui ::< ShapesBuffer > . run_if ( resource_changed ::< ShapesBuffer > ) ,
sync_resource_to_ui ::< ShapeStore > . run_if ( resource_changed ::< ShapeStore > ) ,
sync_resource_to_ui ::< Score > . run_if ( resource_changed ::< Score > ) ,
sync_resource_to_ui ::< LevelGoal > . run_if ( resource_changed ::< LevelGoal > ) ,
sync_singleton_to_ui ::< Shape > . run_if ( any_component_changed ::< Shape > ) ,
) ,
)
@ -221,9 +229,87 @@ impl std::ops::AddAssign<&GridPosition> for GridPosition {
#[ derive(States, Clone, Eq, PartialEq, Debug, Hash, Default, Component) ]
enum GameState {
// Sets up a new game
#[ default ]
Falling ,
NewGame ,
// Actively playing the game
Play ,
// Temporarily paused gameplay
Pause ,
// Reached level goal
LevelComplete ,
// Sets up next level
NextLevel ,
// Failed level
GameOver ,
}
// The core of the Game state machine, coordinates other stuff happening
fn game_state_machine (
curr : Res < State < GameState > > ,
mut next : ResMut < NextState < GameState > > ,
mut goal : ResMut < LevelGoal > ,
mut score : ResMut < Score > ,
items : Query < Entity , ( With < GridPosition > , Or < ( With < Block > , With < Shape > ) > ) > ,
mut commands : Commands ,
) {
match curr . get ( ) {
GameState ::NewGame = > {
// Set new game goal
* goal = LevelGoal ::Score ( 3 ) ;
// Reset score
* score = Score ( 0 ) ;
// Clear board
items . iter ( ) . for_each ( | i | {
commands . entity ( i ) . despawn ( ) ;
} ) ;
// Set goal based on current goal
next . set ( GameState ::Play ) ;
} ,
GameState ::Play = > ( ) , // handled by user input
GameState ::Pause = > ( ) , // handled by user input
GameState ::LevelComplete = > ( ) , // hanled by user input
GameState ::NextLevel = > {
// Increase goal
* goal = match * goal {
LevelGoal ::Score ( n ) = > LevelGoal ::Score ( n + 5 ) ,
} ;
// Reset score
* score = Score ( 0 ) ;
// Clear board
items . iter ( ) . for_each ( | i | {
commands . entity ( i ) . despawn ( ) ;
} ) ;
// Progress to play
next . set ( GameState ::Play )
}
GameState ::GameOver = > ( ) , // handled by user input
}
}
#[ derive(Resource) ]
enum LevelGoal {
Score ( usize ) ,
}
impl Default for LevelGoal {
fn default ( ) -> Self {
Self ::Score ( 0 )
}
}
impl Display for LevelGoal {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < ' _ > ) -> std ::fmt ::Result {
match self {
LevelGoal ::Score ( n ) = > write! ( f , "{n}" )
}
}
}
#[ derive(Resource, Debug) ]
@ -516,8 +602,54 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, images: Res
parent . spawn ( Text ::new ( "Score:" ) ) ;
parent . spawn ( ( Text ::new ( "???" ) , SyncResource ::< Score > ::default ( ) ) ) ;
} ) ;
parent
. spawn ( ( Node {
flex_direction : FlexDirection ::Column ,
align_items : AlignItems ::Center ,
.. default ( )
} , ) )
. with_children ( | parent | {
parent . spawn ( Text ::new ( "Goal:" ) ) ;
parent . spawn ( ( Text ::new ( "???" ) , SyncResource ::< LevelGoal > ::default ( ) ) ) ;
} ) ;
} ) ;
commands
. spawn ( (
Node {
align_self : AlignSelf ::Center ,
justify_self : JustifySelf ::Center ,
.. default ( )
} ,
ZIndex ( 1 ) ,
GameState ::Pause ,
children ! [ ( Text ::new ( "paused" ) , TextColor ( WHITE . into ( ) ) , GameState ::Pause ) ] ,
) ) ;
commands
. spawn ( (
Node {
align_self : AlignSelf ::Center ,
justify_self : JustifySelf ::Center ,
.. default ( )
} ,
ZIndex ( 1 ) ,
GameState ::LevelComplete ,
children ! [ ( Text ::new ( "you did it!" ) , TextColor ( WHITE . into ( ) ) , GameState ::LevelComplete ) ] ,
) ) ;
commands
. spawn ( (
Node {
align_self : AlignSelf ::Center ,
justify_self : JustifySelf ::Center ,
.. default ( )
} ,
ZIndex ( 1 ) ,
GameState ::GameOver ,
children ! [ ( Text ::new ( "aww, you suck. i'm sorry." ) , TextColor ( WHITE . into ( ) ) , GameState ::GameOver ) ] ,
) ) ;
commands
. spawn ( (
Node {
@ -569,19 +701,6 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, images: Res
} ,
) ) ;
} ) ;
commands
. spawn ( (
Node {
align_self : AlignSelf ::Center ,
justify_self : JustifySelf ::Center ,
.. default ( )
} ,
GameState ::Pause ,
) )
. with_children ( | parent | {
parent . spawn ( Text ::new ( "Paused" ) ) ;
} ) ;
}
fn init_debug_ui ( mut commands : Commands ) {
@ -772,7 +891,6 @@ impl ShapeLayout {
}
}
// TODO: move to event
fn update_position (
mut changed : Query <
( Entity , & GridPosition , & mut Transform ) ,
@ -791,7 +909,6 @@ fn update_position(
} ) ;
}
// TODO: Move to event
fn update_shape_blocks (
query : Query <
( Entity , & ShapeLayout , & GridPosition ) ,
@ -809,7 +926,13 @@ fn update_shape_blocks(
let mut p = sl . coordinates_at ( center ) ;
blocks . iter_mut ( ) . for_each ( | mut gp | {
* gp = p . next ( ) . unwrap ( ) . unwrap ( ) ;
match p . next ( ) {
Some ( next ) = > match next {
Ok ( next_gp ) = > * gp = next_gp ,
Err ( _ ) = > warn ! ( "Next coordinate was an Err?" ) ,
} ,
None = > warn ! ( "Next coordinate was a None?" ) ,
}
} ) ;
} ) ;
}
@ -844,7 +967,7 @@ fn on_add_shape_layout(
fn kb_input (
mut events : MessageReader < KeyboardInput > ,
mut query : Query < Entity , With < Shape > > ,
mut query : Query < Entity , With < Shape > > , // Single?
curr : Res < State < GameState > > ,
mut next : ResMut < NextState < GameState > > ,
mut commands : Commands ,
@ -855,47 +978,61 @@ fn kb_input(
} | {
if let ButtonState ::Pressed = state {
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
KeyCode ::ArrowUp = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Rotate ,
} ) ;
}
KeyCode ::ArrowDown = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Down ,
} ) ;
}
KeyCode ::ArrowLeft = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Left ,
} ) ;
}
KeyCode ::ArrowRight = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Right ,
} ) ;
}
KeyCode ::Enter = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Skip ,
} ) ;
}
KeyCode ::Space = > {
commands . entity ( e ) . trigger ( | entity | Swap { entity } ) ;
match curr . get ( ) {
GameState ::Play = > match key_code {
// Up arrow should rotate if in falling mode
// Only move up if in falling::off mode
KeyCode ::ArrowUp = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Rotate ,
} ) ;
}
KeyCode ::ArrowDown = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Down ,
} ) ;
}
KeyCode ::ArrowLeft = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Left ,
} ) ;
}
KeyCode ::ArrowRight = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Right ,
} ) ;
}
KeyCode ::Enter = > {
commands . entity ( e ) . trigger ( | entity | Movement {
entity ,
direction : MovementDirection ::Skip ,
} ) ;
}
KeyCode ::Space = > {
commands . entity ( e ) . trigger ( | entity | Swap { entity } ) ;
}
KeyCode ::Escape = > next . set ( GameState ::Pause ) ,
_ = > ( ) ,
} ,
GameState ::Pause = > match key_code {
KeyCode ::Escape | KeyCode ::Enter = > next . set ( GameState ::Play ) ,
_ = > ( )
}
KeyCode ::Escape = > next . set ( match curr . get ( ) {
GameState ::Falling = > GameState ::Pause ,
GameState ::Pause = > GameState ::Falling ,
} ) ,
_ = > ( ) ,
GameState ::GameOver = > match key_code {
KeyCode ::Escape = > todo! ( "Quit the game" ) ,
KeyCode ::Enter = > next . set ( GameState ::NewGame ) ,
_ = > ( )
} ,
GameState ::LevelComplete = > match key_code {
KeyCode ::Escape = > todo! ( "Quit the game" ) ,
KeyCode ::Enter = > next . set ( GameState ::NextLevel ) ,
_ = > ( )
} ,
GameState ::NewGame | GameState ::NextLevel = > ( ) ,
}
} ) ;
}
@ -1189,10 +1326,17 @@ fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
}
fn assert_grid_position_uniqueness (
grid_positions : Query < & GridPosition , ( Without < GridBackground > , Without < Shape > ) > ,
grid_positions : Query < ( Entity , & GridPosition , Option < & LineBlock > , Option < & ShapeBlock > ) , With < Block > > ,
mut next : ResMut < NextState < GameState > > ,
) {
grid_positions . iter_combinations ( ) . for_each ( | [ a , b ] | {
assert_ne! ( a , b , "Two entities are in the same grid position!" ) ;
grid_positions . iter_combinations ( ) . for_each ( | [ ( e1 , a , lb1 , sb1 ) , ( e2 , b , lb2 , sb2 ) ] | {
if a = = b {
error ! ( "Two entities are in the same grid position: {a:?} <-> {b:?}!" ) ;
error ! ( "\tE1: {:?} (Parent Block: {:?}| Parent Line: {:?})!" , e1 , lb1 , sb1 ) ;
error ! ( "\tE2: {:?} (Parent Block: {:?}| Parent Line: {:?})!" , e2 , lb2 , sb2 ) ;
next . set ( GameState ::Pause ) ;
}
// assert_ne!(a, b, "Two entities are in the same grid position!");
} ) ;
}
@ -1279,3 +1423,17 @@ fn damage_over_time(protagonist: Single<Entity, With<Protagonist>>, mut commands
quantity : 1.0 ,
} ) ;
}
fn check_level_goal (
goal : Res < LevelGoal > ,
score : Res < Score > ,
mut next : ResMut < NextState < GameState > > ,
) {
match * goal {
LevelGoal ::Score ( g ) = > {
if score . 0 > = g {
next . set ( GameState ::LevelComplete ) ;
}
}
}
}