diff --git a/include/def_state_info_table.h b/include/def_state_info_table.h index cb79c4d..1577790 100644 --- a/include/def_state_info_table.h +++ b/include/def_state_info_table.h @@ -8,6 +8,6 @@ DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing DEF_STATE_INFO(GAME_STATE_ROUND_END, noop, game_round_end_on_update, game_round_end_on_exit ) DEF_STATE_INFO(GAME_STATE_SHOP, noop, game_shop_on_update, game_shop_on_exit ) DEF_STATE_INFO(GAME_STATE_BLIND_SELECT, game_blind_select_on_init, game_blind_select_on_update, game_blind_select_on_exit) -DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_lose_on_update, game_over_on_exit ) -DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_win_on_update, game_over_on_exit ) +DEF_STATE_INFO(GAME_STATE_LOSE, game_lose_on_init, game_over_on_update, game_over_on_exit ) +DEF_STATE_INFO(GAME_STATE_WIN, game_win_on_init, game_over_on_update, game_over_on_exit ) // clang-format on diff --git a/include/game.h b/include/game.h index 857e3f6..e6dfc76 100644 --- a/include/game.h +++ b/include/game.h @@ -134,6 +134,17 @@ typedef struct // Game functions void game_init(); + +/** + * @brief Called when exiting the Game Over screen (both win or lose) to reset game variables + * and start a fresh new run. + * + * WARNING: This function is currently only meant to be called from the "GAME_OVER" state + * and shouldn't be called from other states, otherwise some data such as shop jokers + * may not be properly reset. + */ +void game_reset(); + void game_update(); void game_change_state(enum GameState new_game_state); diff --git a/include/game/game_over.h b/include/game/game_over.h new file mode 100644 index 0000000..68c2516 --- /dev/null +++ b/include/game/game_over.h @@ -0,0 +1,29 @@ +/** + * @file game_over.h + * + * @brief Game Over screen state functions + */ +#ifndef GAME_OVER_H +#define GAME_OVER_H + +/** + * @brief Game Over screen state initialization when losing + */ +void game_lose_on_init(void); + +/** + * @brief Game Over screen state initialization when winning + */ +void game_win_on_init(void); + +/** + * @brief Game Over screen state update + */ +void game_over_on_update(void); + +/** + * @brief Game Over screen cleanup, common to both losing and winning + */ +void game_over_on_exit(void); + +#endif // GAME_OVER_H \ No newline at end of file diff --git a/source/game.c b/source/game.c index 9b78657..5b4b722 100644 --- a/source/game.c +++ b/source/game.c @@ -12,6 +12,7 @@ #include "card.h" #include "game/blind_select.h" #include "game/common_ui.h" +#include "game/game_over.h" #include "game/main_menu.h" #include "game/options_menu.h" #include "game_variables.h" @@ -45,9 +46,6 @@ // SE sizes #define ROUND_END_BLACK_PANEL_INIT_BOTTOM_SE 12 -// TODO: Properly define and use -#define GAME_OVER_ANIM_FRAMES 15 - #define SHOP_LIGHTS_1_CLR 0xFFFF #define SHOP_LIGHTS_2_CLR 0x32BE #define SHOP_LIGHTS_3_CLR 0x4B5F @@ -184,11 +182,6 @@ static void game_round_end_on_update(void); static void game_round_end_on_exit(void); static void game_shop_on_update(void); static void game_shop_on_exit(void); -static void game_lose_on_init(void); -static void game_lose_on_update(void); -static void game_over_on_exit(void); -static void game_win_on_init(void); -static void game_win_on_update(void); static void game_shop_intro(void); static void game_shop_process_user_input(void); static void game_shop_outro(void); @@ -211,14 +204,12 @@ static void display_discards(int value); static void set_hand(void); static int deck_get_size(void); static int deck_get_max_size(void); -static void game_over_init(void); static bool check_and_score_joker_for_event( ListItr* starting_joker_itr, CardObject* card_object, enum JokerEvent joker_event ); static int calculate_interest_reward(void); -static void game_over_anim_frame(void); static void game_playing_discard_on_pressed(void); static void game_playing_execute_discard(void); @@ -317,11 +308,6 @@ static const Rect BIG_BLIND_TITLE_SRC_RECT = {0, 26, 8, 26 }; static const Rect BOSS_BLIND_TITLE_SRC_RECT = {0, 27, 8, 27 }; static const Rect CASHOUT_DEST_RECT = {10, 8, 23, 10 }; static const BG_POINT CASHOUT_SRC_3X3_RECT_POS = {5, 29}; -static const BG_POINT GAME_OVER_SRC_RECT_3X3_POS = {25, 29}; -static const Rect GAME_OVER_DIALOG_DEST_RECT= {11, 21, 23, 28}; -static const Rect GAME_OVER_ANIM_RECT = {11, 8, 23, 28}; -static const BG_POINT NEW_RUN_BTN_DEST_POS = {15, 26}; -static const Rect NEW_RUN_BTN_SRC_RECT = {0, 30, 4, 31}; static const BG_POINT ROUND_END_REWARDS_ELLIPSIS_POS = {10, 13}; // Flaming score animation frames @@ -363,9 +349,6 @@ static const Rect ROUND_END_BLIND_REQ_RECT = {104, 96, 136, UNDEF static const Rect ROUND_END_BLIND_REWARD_RECT = { 168, 96, UNDEFINED, UNDEFINED }; static const Rect CASHOUT_TEXT_RECT = {88, 72, UNDEFINED, UNDEFINED }; static const Rect SHOP_REROLL_RECT = {88, 96, UNDEFINED, UNDEFINED }; -static const Rect GAME_LOSE_MSG_TEXT_RECT = {104, 72, UNDEFINED, UNDEFINED}; -// 1 character to the right of GAME_LOSE -static const Rect GAME_WIN_MSG_TEXT_RECT = {112, 72, UNDEFINED, UNDEFINED}; static const BG_POINT HELD_JOKERS_POS = {108, 10}; static const BG_POINT JOKER_DISCARD_TARGET = {240, 30}; @@ -697,6 +680,51 @@ void game_init() init_unbeaten_blinds_list(true); } +void game_reset() +{ + while (list_get_len(&_owned_jokers_list) > 0) + { + JokerObject* joker_object = list_get_at_idx(&_owned_jokers_list, 0); + remove_owned_joker(0); + joker_object_destroy(&joker_object); + } + + tte_erase_screen(); + + // For some reason that I haven't figured out yet, + // if I don't destroy the blind tokens they won't + // show up on the next run. + sprite_destroy(&playing_blind_token); + sprite_destroy(&round_end_blind_token); + + list_clear(&_owned_jokers_list); + list_clear(&_discarded_jokers_list); + list_clear(&_expired_jokers_list); + list_clear(&_shop_jokers_list); + + game_init(); + + display_round(); + display_score(score); + display_chips(); + display_mult(); + display_hands(hands); + display_discards(discards); + display_money(); + // Ante + tte_printf( + "#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", + ANTE_TEXT_RECT.left, + ANTE_TEXT_RECT.top, + TTE_YELLOW_PB, + g_game_vars.ante, + TTE_WHITE_PB, + MAX_ANTE + ); + + affine_background_load_palette(affine_background_gfxPal); +} + static inline void discarded_jokers_update_loop(void) { if (list_is_empty(&_discarded_jokers_list)) @@ -1458,6 +1486,11 @@ void change_background_legacy(enum BackgroundId id) background_legacy = id; } +void reset_background(void) +{ + background_legacy = BG_NONE; +} + static void display_temp_score(u32 value) { char temp_score_str_buff[UINT_MAX_DIGITS + 1]; @@ -1682,28 +1715,6 @@ static void game_round_on_init(void) game_playing_selection_grid.selection = GAME_PLAYING_INIT_SEL; } -static void game_over_init(void) -{ - // Clears the round end menu - main_bg_se_clear_rect(POP_MENU_ANIM_RECT); - main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); - main_bg_se_copy_rect(NEW_RUN_BTN_SRC_RECT, NEW_RUN_BTN_DEST_POS); -} - -static void game_lose_on_init(void) -{ - game_over_init(); - // Using the text color to match the "Game Over" text - affine_background_set_color(TEXT_CLR_RED); -} - -static void game_win_on_init(void) -{ - game_over_init(); - // Using the text color to match the "You Win" text - affine_background_set_color(TEXT_CLR_BLUE); -} - // General functions static inline void set_seed(int seed) { @@ -4292,108 +4303,3 @@ void game_start(void) game_change_state(GAME_STATE_BLIND_SELECT); } - -static void game_over_anim_frame(void) -{ - main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SCREEN_UP); -} - -static inline void game_over_process_user_input() -{ - if (key_hit(SELECT_CARD)) - { - play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME); - game_change_state(GAME_STATE_BLIND_SELECT); - } -} - -static void game_lose_on_update(void) -{ - if (g_game_vars.timer < GAME_OVER_ANIM_FRAMES) - { - game_over_anim_frame(); - } - else if (g_game_vars.timer == GAME_OVER_ANIM_FRAMES) - { - tte_printf( - "#{P:%d,%d; cx:0x%X000}GAME OVER", - GAME_LOSE_MSG_TEXT_RECT.left, - GAME_LOSE_MSG_TEXT_RECT.top, - TTE_RED_PB - ); - } - - game_over_process_user_input(); -} - -// This function isn't set in stone. This is just a placeholder -// allowing the player to restart the game. Thought it would be nice to have -// util we decide what we want to do after a game over. -static void game_over_on_exit(void) -{ - while (list_get_len(&_owned_jokers_list) > 0) - { - JokerObject* joker_object = list_get_at_idx(&_owned_jokers_list, 0); - remove_owned_joker(0); - joker_object_destroy(&joker_object); - } - - tte_erase_screen(); - - // For some reason that I haven't figured out yet, - // if I don't destroy the blind tokens they won't - // show up on the next run. - sprite_destroy(&playing_blind_token); - sprite_destroy(&round_end_blind_token); - - list_clear(&_owned_jokers_list); - list_clear(&_discarded_jokers_list); - list_clear(&_expired_jokers_list); - list_clear(&_shop_jokers_list); - - game_init(); - - display_round(); - display_score(score); - display_chips(); - display_mult(); - display_hands(hands); - display_discards(discards); - display_money(); - // Ante - tte_printf( - "#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", - ANTE_TEXT_RECT.left, - ANTE_TEXT_RECT.top, - TTE_YELLOW_PB, - g_game_vars.ante, - TTE_WHITE_PB, - MAX_ANTE - ); - - affine_background_load_palette(affine_background_gfxPal); -} - -static void game_win_on_update(void) -{ - if (g_game_vars.timer < GAME_OVER_ANIM_FRAMES) - { - game_over_anim_frame(); - } - else if (g_game_vars.timer == GAME_OVER_ANIM_FRAMES) - { - tte_printf( - "#{P:%d,%d; cx:0x%X000}YOU WIN", - GAME_WIN_MSG_TEXT_RECT.left, - GAME_WIN_MSG_TEXT_RECT.top, - TTE_BLUE_PB - ); - } - - game_over_process_user_input(); -} - -void reset_background(void) -{ - background_legacy = BG_NONE; -} diff --git a/source/game/blind_select.c b/source/game/blind_select.c index 9e0fb64..5dc633f 100644 --- a/source/game/blind_select.c +++ b/source/game/blind_select.c @@ -222,6 +222,7 @@ static void game_blind_select_handle_input() } game_blind_select_print_blinds_reqs_and_rewards(); + highlight_select_button(); timer = TM_ZERO; } diff --git a/source/game/game_over.c b/source/game/game_over.c new file mode 100644 index 0000000..71679e5 --- /dev/null +++ b/source/game/game_over.c @@ -0,0 +1,131 @@ +#include "game/game_over.h" + +#include "affine_background.h" +#include "audio_utils.h" +#include "button.h" +#include "game.h" +#include "game/main_menu.h" +#include "graphic_utils.h" +#include "layout.h" +#include "soundbank.h" +#include "timer.h" +#include "util.h" + +#include + +enum EndCondition +{ + END_CONDITION_NONE, + END_CONDITION_WIN, + END_CONDITION_LOSS +}; + +// clang-format off +static const BG_POINT GAME_OVER_SRC_RECT_3X3_POS = { 25, 29}; +static const Rect GAME_OVER_DIALOG_DEST_RECT = { 11, 21, 23, 28}; +static const Rect GAME_OVER_ANIM_RECT = { 11, 8, 23, 28}; +static const Rect GAME_LOSE_MSG_TEXT_RECT = {104, 72, UNDEFINED, UNDEFINED}; +// 1 character to the right of GAME_LOSE +static const Rect GAME_WIN_MSG_TEXT_RECT = {112, 72, UNDEFINED, UNDEFINED}; +static const BG_POINT NEW_RUN_BTN_DEST_POS = { 15, 26}; +static const Rect NEW_RUN_BTN_SRC_RECT = { 0, 30, 4, 31}; +// clang-format on + +static const u32 GAME_OVER_ANIM_FRAMES = 15; + +static enum EndCondition condition = END_CONDITION_NONE; +static u32 timer = TM_ZERO; + +static void game_over_init(enum EndCondition init_condition) +{ + condition = init_condition; + // Clears the round end menu + main_bg_se_clear_rect(POP_MENU_ANIM_RECT); + main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); + main_bg_se_copy_rect(NEW_RUN_BTN_SRC_RECT, NEW_RUN_BTN_DEST_POS); + + // Using the text color to match the Win/Loss condition + switch (condition) + { + case END_CONDITION_WIN: + affine_background_set_color(TEXT_CLR_BLUE); + break; + case END_CONDITION_LOSS: + affine_background_set_color(TEXT_CLR_RED); + break; + default: + break; + } + + timer = TM_ZERO; +} + +void game_win_on_init(void) +{ + game_over_init(END_CONDITION_WIN); +} + +void game_lose_on_init(void) +{ + game_over_init(END_CONDITION_LOSS); +} + +/** + * @brief Makes the message background frame move 1 tile up, called until it is in its place. + */ +static inline void game_over_anim_frame(void) +{ + main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SCREEN_UP); +} + +/** + * @brief Polls user input to detect "Retry" button press. + */ +static inline void game_over_process_user_input() +{ + if (key_hit(SELECT_CARD)) + { + play_sfx(SFX_BUTTON, MM_BASE_PITCH_RATE, BUTTON_SFX_VOLUME); + game_change_state(GAME_STATE_BLIND_SELECT); + } +} + +void game_over_on_update(void) +{ + timer++; + if (timer < GAME_OVER_ANIM_FRAMES) + { + game_over_anim_frame(); + } + else if (timer == GAME_OVER_ANIM_FRAMES) + { + switch (condition) + { + case END_CONDITION_WIN: + tte_printf( + "#{P:%d,%d; cx:0x%X000}YOU WIN", + GAME_WIN_MSG_TEXT_RECT.left, + GAME_WIN_MSG_TEXT_RECT.top, + TTE_BLUE_PB + ); + break; + case END_CONDITION_LOSS: + tte_printf( + "#{P:%d,%d; cx:0x%X000}GAME OVER", + GAME_LOSE_MSG_TEXT_RECT.left, + GAME_LOSE_MSG_TEXT_RECT.top, + TTE_RED_PB + ); + break; + default: + break; + } + } + game_over_process_user_input(); +} + +void game_over_on_exit(void) +{ + condition = END_CONDITION_NONE; + game_reset(); +}