Merge pull request #155 from drewjohnson2/stateRefactor

State refactor
This commit is contained in:
MeirGavish
2025-10-17 11:51:24 +03:00
committed by GitHub
6 changed files with 653 additions and 612 deletions
+9
View File
@@ -0,0 +1,9 @@
// (stateEnum, on_init, on_update, on_exit)
DEF_STATE_INFO(GAME_STATE_SPLASH_SCREEN, splash_screen_on_init, splash_screen_on_update, _noop)
DEF_STATE_INFO(GAME_STATE_MAIN_MENU, game_main_menu_on_init, game_main_menu_on_update, _noop)
DEF_STATE_INFO(GAME_STATE_PLAYING, game_round_on_init, game_playing_on_update, _noop)
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, _noop, 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)
+15 -9
View File
@@ -26,16 +26,13 @@
#define PAUSE_GAME KEY_START // Not implemented #define PAUSE_GAME KEY_START // Not implemented
#define SELL_KEY KEY_L #define SELL_KEY KEY_L
// Enum value names in ../include/def_state_info_table.h
enum GameState enum GameState
{ {
GAME_SPLASH_SCREEN, #define DEF_STATE_INFO(stateEnum, on_init, on_update, on_exit) stateEnum,
GAME_MAIN_MENU, #include "def_state_info_table.h"
GAME_PLAYING, #undef DEF_STATE_INFO
GAME_ROUND_END, GAME_STATE_MAX
GAME_SHOP,
GAME_BLIND_SELECT,
GAME_LOSE,
GAME_WIN
}; };
enum HandState enum HandState
@@ -75,10 +72,18 @@ enum HandType
FLUSH_FIVE FLUSH_FIVE
}; };
typedef struct
{
int substate;
void (*on_init)();
void (*on_update)();
void (*on_exit)();
} StateInfo;
// Game functions // Game functions
void game_init(); void game_init();
void game_update(); void game_update();
void game_set_state(enum GameState new_game_state); void game_change_state(enum GameState new_game_state);
// Forward declaration // Forward declaration
struct List; struct List;
@@ -88,6 +93,7 @@ typedef struct List List;
typedef struct CardObject CardObject; // forward declaration, actually declared in card.h typedef struct CardObject CardObject; // forward declaration, actually declared in card.h
typedef struct Card Card; typedef struct Card Card;
typedef struct JokerObject JokerObject; typedef struct JokerObject JokerObject;
CardObject** get_hand_array(void); CardObject** get_hand_array(void);
int get_hand_top(void); int get_hand_top(void);
int hand_get_size(void); int hand_get_size(void);
+2 -2
View File
@@ -7,7 +7,7 @@
#define SPLASH_DURATION_SECONDS 10 #define SPLASH_DURATION_SECONDS 10
#define SPLASH_DURATION_FRAMES (SPLASH_FPS * SPLASH_DURATION_SECONDS) #define SPLASH_DURATION_FRAMES (SPLASH_FPS * SPLASH_DURATION_SECONDS)
void splash_screen_init(); void splash_screen_on_init();
void splash_screen_update(uint timer); void splash_screen_on_update();
#endif // SPLASH_SCREEN_H #endif // SPLASH_SCREEN_H
+301 -281
View File
@@ -2,11 +2,9 @@
#include <maxmod.h> #include <maxmod.h>
#include <tonc.h> #include <tonc.h>
#include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "tonc_memdef.h" #include "tonc_memdef.h"
#include "tonc_memmap.h"
#include "util.h" #include "util.h"
#include "sprite.h" #include "sprite.h"
#include "card.h" #include "card.h"
@@ -15,7 +13,6 @@
#include "joker.h" #include "joker.h"
#include "affine_background.h" #include "affine_background.h"
#include "graphic_utils.h" #include "graphic_utils.h"
#include "tonc_video.h"
#include "audio_utils.h" #include "audio_utils.h"
#include "selection_grid.h" #include "selection_grid.h"
#include "splash_screen.h" #include "splash_screen.h"
@@ -25,7 +22,6 @@
#include "background_blind_select_gfx.h" #include "background_blind_select_gfx.h"
#include "affine_background_gfx.h" #include "affine_background_gfx.h"
#include "background_main_menu_gfx.h" #include "background_main_menu_gfx.h"
#include "affine_main_menu_background_gfx.h"
#include "soundbank.h" #include "soundbank.h"
@@ -35,7 +31,8 @@ typedef enum
{ {
GAME_SHOP_INTRO, GAME_SHOP_INTRO,
GAME_SHOP_ACTIVE, GAME_SHOP_ACTIVE,
GAME_SHOP_EXIT GAME_SHOP_EXIT,
GAME_SHOP_MAX
} _GameShopStates; } _GameShopStates;
typedef enum typedef enum
@@ -57,19 +54,119 @@ typedef enum
START_ANIM_SEQ, START_ANIM_SEQ,
BLIND_SELECT, BLIND_SELECT,
BLIND_SELECTED_ANIM_SEQ, BLIND_SELECTED_ANIM_SEQ,
DISPLAY_BLIND_PANEL DISPLAY_BLIND_PANEL,
BLIND_SELECT_MAX
} _BlindSelectStates; } _BlindSelectStates;
typedef struct
{
int chips;
int mult;
char *display_name;
} HandValues;
typedef void (*SubStateActionFn)();
// Used as a No Operation for game states that have no init and/or exit function.
// ricfehr3 did the work of determining whether a noop or a NULL check was more
// efficient. Well, this is the answer.
// Thanks!
// https://github.com/cellos51/balatro-gba/issues/137#issuecomment-3322485129
static void _noop() {}
// These functions need to be forward declared
// so they're visible to the state_info array,
// and the sub-state function tables.
// This could be done, and maybe should be done,
// with an X macro, but I'll leave that to the
// reviewer(s).
static void game_main_menu_on_init();
static void game_main_menu_on_update();
static void game_round_on_init();
static void game_playing_on_update();
static void game_round_end_on_update();
static void game_round_end_on_exit();
static void game_shop_on_update();
static void game_shop_on_exit();
static void game_blind_select_on_update();
static void game_blind_select_on_exit();
static void game_lose_on_init();
static void game_lose_on_update();
static void game_over_on_exit();
static void game_win_on_init();
static void game_win_on_update();
static void game_shop_intro();
static void game_shop_process_user_input();
static void game_shop_outro();
static void game_blind_select_start_anim_seq();
static void game_blind_select_handle_input();
static void game_blind_select_selected_anim_seq();
static void game_blind_select_display_blind_panel();
static void game_round_end_start();
static void game_round_end_start_expand_popup();
static void game_round_end_display_finished_blind();
static void game_round_end_display_score_min();
static void game_round_end_update_blind_reward();
static void game_round_end_panel_exit();
static void game_round_end_display_rewards();
static void game_round_end_display_cashout();
static void game_round_end_dismiss_round_end_panel();
static uint rng_seed = 0; static uint rng_seed = 0;
static uint timer = 0; // This might already exist in libtonc but idk so i'm just making my own static uint timer = 0; // This might already exist in libtonc but idk so i'm just making my own
static int game_speed = 1; // BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the game aspects that should be sped up by speed, as in the original game. static int game_speed = 1; // BY DEFAULT IS SET TO 1, but if changed to 2 or more, should speed up all (or most) of the game aspects that should be sped up by speed, as in the original game.
static int background = 0; static int background = 0;
static enum GameState game_state = GAME_SPLASH_SCREEN; // The current game state, this is used to determine what the game is doing at any given time static StateInfo state_info[] =
{
#define DEF_STATE_INFO(stateEnum, init_fn, update_fn, exit_fn) \
{ .on_init = init_fn, .on_update = update_fn, .on_exit = exit_fn, .substate = 0 },
#include "../include/def_state_info_table.h"
#undef DEF_STATE_INFO
};
static const HandValues hand_base_values[] =
{
{ .chips = 0, .mult = 0, .display_name = NULL }, // NONE
{ .chips = 5, .mult = 1, .display_name = "HIGH C" }, // HIGH_CARD
{ .chips = 10, .mult = 2, .display_name = "PAIR" }, // PAIR
{ .chips = 20, .mult = 2, .display_name = "2 PAIR" }, // TWO_PAIR
{ .chips = 30, .mult = 3, .display_name = "3 OAK" }, // THREE_OF_A_KIND
{ .chips = 60, .mult = 7, .display_name = "4 OAK" }, // FOUR_OF_A_KIND
{ .chips = 30, .mult = 4, .display_name = "STRT" }, // STRAIGHT
{ .chips = 35, .mult = 4, .display_name = "FLUSH" }, // FLUSH
{ .chips = 40, .mult = 4, .display_name = "FULL H" }, // FULL_HOUSE
{ .chips = 100, .mult = 8, .display_name = "STRT F" }, // STRAIGHT_FLUSH
{ .chips = 100, .mult = 8, .display_name = "ROYAL F" }, // ROYAL_FLUSH
{ .chips = 120, .mult = 12, .display_name = "5 OAK" }, // FIVE_OF_A_KIND
{ .chips = 140, .mult = 14, .display_name = "FLUSH H" }, // FLUSH_HOUSE
{ .chips = 160, .mult = 16, .display_name = "FLUSH 5" } // FLUSH_FIVE
};
static const SubStateActionFn shop_state_actions[] =
{
game_shop_intro, game_shop_process_user_input, game_shop_outro
};
static const SubStateActionFn blind_select_state_actions[] =
{
game_blind_select_start_anim_seq, game_blind_select_handle_input,
game_blind_select_selected_anim_seq, game_blind_select_display_blind_panel
};
static const SubStateActionFn round_end_state_actions[] =
{
game_round_end_start, game_round_end_start_expand_popup,
game_round_end_display_finished_blind, game_round_end_display_score_min,
game_round_end_update_blind_reward, game_round_end_panel_exit,
game_round_end_display_rewards, game_round_end_display_cashout,
game_round_end_dismiss_round_end_panel
};
static enum GameState game_state = GAME_STATE_SPLASH_SCREEN; // The current game state, this is used to determine what the game is doing at any given time
static enum HandState hand_state = HAND_DRAW; static enum HandState hand_state = HAND_DRAW;
static enum PlayState play_state = PLAY_PLAYING; static enum PlayState play_state = PLAY_PLAYING;
static int state = 0; // General state variable, used for switch statements in each game state related function
static enum HandType hand_type = NONE; static enum HandType hand_type = NONE;
@@ -90,6 +187,10 @@ static enum BlindState blinds[BLIND_TYPE_MAX] =
BLIND_STATE_UPCOMING BLIND_STATE_UPCOMING
}; // The current state of the blinds, this is used to determine what the game is doing at any given time }; // The current state of the blinds, this is used to determine what the game is doing at any given time
static int blind_reward = 0;
static int hand_reward = 0;
static int interest_reward = 0;
// Red deck default (can later be moved to a deck.h file or something) // Red deck default (can later be moved to a deck.h file or something)
static int max_hands = 4; static int max_hands = 4;
static int max_discards = 4; static int max_discards = 4;
@@ -173,6 +274,20 @@ static inline Card *discard_pop()
return discard_pile[discard_top--]; return discard_pile[discard_top--];
} }
// Resets bottom row bg tiles of the top left panel (shop/blind) after
// it is dismissed to match the rest of the game panel background.
static inline void reset_top_left_panel_bottom_row()
{
int y = 6;
memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0006, 1);
memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0007, 2);
memset16(&se_mem[MAIN_BG_SBB][3 + 32 * (y - 1)], 0x0008, 1);
memset16(&se_mem[MAIN_BG_SBB][4 + 32 * (y - 1)], 0x0009, 3);
memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1);
memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1);
}
// get-functions, for other files to view game state (mainly for jokers) // get-functions, for other files to view game state (mainly for jokers)
CardObject **get_hand_array(void) { CardObject **get_hand_array(void) {
return hand; return hand;
@@ -351,6 +466,11 @@ static const BG_POINT MAIN_MENU_ACE_T = {88, 26};
#define PITCH_STEP_DRAW_SFX 24 #define PITCH_STEP_DRAW_SFX 24
#define PITCH_STEP_UNDISCARD_SFX 2*PITCH_STEP_DRAW_SFX #define PITCH_STEP_UNDISCARD_SFX 2*PITCH_STEP_DRAW_SFX
#define STARTING_ROUND 0
#define STARTING_ANTE 1
#define STARTING_MONEY 4
#define STARTING_SCORE 0
#define TEN_K 10000 #define TEN_K 10000
#define ONE_K 1000 #define ONE_K 1000
@@ -909,79 +1029,13 @@ void set_hand()
{ {
tte_erase_rect_wrapper(HAND_TYPE_RECT); tte_erase_rect_wrapper(HAND_TYPE_RECT);
hand_type = hand_get_type(); hand_type = hand_get_type();
switch (hand_type)
{
case HIGH_CARD:
print_hand_type("HIGH C");
chips = 5;
mult = 1;
break;
case PAIR:
print_hand_type("PAIR");
chips = 10;
mult = 2;
break;
case TWO_PAIR:
print_hand_type("2 PAIR");
chips = 20;
mult = 2;
break;
case THREE_OF_A_KIND:
print_hand_type("3 OAK");
chips = 30;
mult = 3;
break;
case STRAIGHT:
print_hand_type("STRT");
chips = 30;
mult = 4;
break;
case FLUSH:
print_hand_type("FLUSH");
chips = 35;
mult = 4;
break;
case FULL_HOUSE:
print_hand_type("FULL H");
chips = 40;
mult = 4;
break;
case FOUR_OF_A_KIND:
print_hand_type("4 OAK");
chips = 60;
mult = 7;
break;
case STRAIGHT_FLUSH:
print_hand_type("STRT F");
chips = 100;
mult = 8;
break;
case ROYAL_FLUSH:
print_hand_type("ROYAL F");
chips = 100;
mult = 8;
break;
case FIVE_OF_A_KIND:
print_hand_type("5 OAK");
chips = 120;
mult = 12;
break;
case FLUSH_HOUSE:
print_hand_type("FLUSH H");
chips = 140;
mult = 14;
break;
case FLUSH_FIVE:
print_hand_type("FLUSH 5");
chips = 160;
mult = 16;
break;
case NONE:
chips = 0;
mult = 0;
break;
}
HandValues hand = hand_base_values[hand_type];
chips = hand.chips;
mult = hand.mult;
print_hand_type(hand.display_name);
display_chips(chips); display_chips(chips);
display_mult(mult); display_mult(mult);
} }
@@ -1125,7 +1179,7 @@ void increment_blind(enum BlindState increment_reason)
} }
} }
static void game_round_init() static void game_round_on_init()
{ {
hand_state = HAND_DRAW; hand_state = HAND_DRAW;
cards_drawn = 0; cards_drawn = 0;
@@ -1174,7 +1228,7 @@ static void game_round_init()
deck_shuffle(); // Shuffle the deck at the start of the round deck_shuffle(); // Shuffle the deck at the start of the round
} }
static void game_main_menu_init() static void game_main_menu_on_init()
{ {
affine_background_change_background(AFFINE_BG_MAIN_MENU); affine_background_change_background(AFFINE_BG_MAIN_MENU);
change_background(BG_ID_MAIN_MENU); change_background(BG_ID_MAIN_MENU);
@@ -1195,7 +1249,7 @@ static void game_over_init()
main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS);
} }
static void game_lose_init() static void game_lose_on_init()
{ {
game_over_init(); game_over_init();
// Using the text color to match the "Game Over" text // Using the text color to match the "Game Over" text
@@ -1203,49 +1257,22 @@ static void game_lose_init()
} }
static void game_win_init() static void game_win_on_init()
{ {
game_over_init(); game_over_init();
// Using the text color to match the "You Win" text // Using the text color to match the "You Win" text
affine_background_set_color(TEXT_CLR_BLUE); affine_background_set_color(TEXT_CLR_BLUE);
} }
static void init_game_state(enum GameState game_state_to_init)
{
// Switch written out, add init for states as needed
switch (game_state_to_init)
{
case GAME_SPLASH_SCREEN:
splash_screen_init();
break;
case GAME_MAIN_MENU:
game_main_menu_init();
break;
case GAME_PLAYING:
game_round_init();
break;
case GAME_ROUND_END:
break;
case GAME_SHOP:
break;
case GAME_BLIND_SELECT:
break;
case GAME_LOSE:
game_lose_init();
break;
case GAME_WIN:
game_win_init();
break;
default:
break;
}
}
// Game functions // Game functions
void game_set_state(enum GameState new_game_state) void game_change_state(enum GameState new_game_state)
{ {
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
init_game_state(new_game_state);
state_info[game_state].substate = 0;
state_info[game_state].on_exit();
state_info[new_game_state].on_init();
game_state = new_game_state; game_state = new_game_state;
} }
@@ -1273,6 +1300,15 @@ void game_init()
hands = max_hands; hands = max_hands;
discards = max_discards; discards = max_discards;
timer = TM_ZERO;
current_blind = BLIND_TYPE_SMALL;
blinds[0] = BLIND_STATE_CURRENT;
blinds[1] = BLIND_STATE_UPCOMING;
blinds[2] = BLIND_STATE_UPCOMING;
round = STARTING_ROUND;
ante = STARTING_ANTE;
money = STARTING_MONEY;
score = STARTING_SCORE;
blind_select_tokens[BLIND_TYPE_SMALL] = blind_token_new(BLIND_TYPE_SMALL, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 3); blind_select_tokens[BLIND_TYPE_SMALL] = blind_token_new(BLIND_TYPE_SMALL, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 3);
blind_select_tokens[BLIND_TYPE_BIG] = blind_token_new(BLIND_TYPE_BIG, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 4); blind_select_tokens[BLIND_TYPE_BIG] = blind_token_new(BLIND_TYPE_BIG, CUR_BLIND_TOKEN_POS.x, CUR_BLIND_TOKEN_POS.y, MAX_SELECTION_SIZE + MAX_HAND_SIZE + 4);
@@ -1281,8 +1317,6 @@ void game_init()
obj_hide(blind_select_tokens[BLIND_TYPE_SMALL]->obj); obj_hide(blind_select_tokens[BLIND_TYPE_SMALL]->obj);
obj_hide(blind_select_tokens[BLIND_TYPE_BIG]->obj); obj_hide(blind_select_tokens[BLIND_TYPE_BIG]->obj);
obj_hide(blind_select_tokens[BLIND_TYPE_BOSS]->obj); obj_hide(blind_select_tokens[BLIND_TYPE_BOSS]->obj);
game_set_state(game_state);
} }
void game_start() void game_start()
@@ -1325,7 +1359,7 @@ void game_start()
tte_printf("#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, TTE_YELLOW_PB, ante, TTE_WHITE_PB, MAX_ANTE); // 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, ante, TTE_WHITE_PB, MAX_ANTE); // Ante
game_set_state(GAME_BLIND_SELECT); game_change_state(GAME_STATE_BLIND_SELECT);
} }
static void game_playing_process_hand_select_input() static void game_playing_process_hand_select_input()
@@ -1505,7 +1539,7 @@ static bool game_round_is_over()
static void game_playing_handle_round_over() static void game_playing_handle_round_over()
{ {
enum GameState next_state = GAME_ROUND_END; enum GameState next_state = GAME_STATE_ROUND_END;
if (score >= blind_get_requirement(current_blind, ante)) if (score >= blind_get_requirement(current_blind, ante))
{ {
@@ -1517,16 +1551,16 @@ static void game_playing_handle_round_over()
} }
else else
{ {
next_state = GAME_WIN; next_state = GAME_STATE_WIN;
} }
} }
} }
else if (hands == 0) else if (hands == 0)
{ {
next_state = GAME_LOSE; next_state = GAME_STATE_LOSE;
} }
game_set_state(next_state); game_change_state(next_state);
} }
static void game_playing_discarded_cards_loop() static void game_playing_discarded_cards_loop()
@@ -2124,7 +2158,7 @@ static void game_round_end_cashout()
display_score(score); // Set the score display display_score(score); // Set the score display
} }
void game_playing() static void game_playing_on_update()
{ {
// Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake) // Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake)
if (hand_state == HAND_DRAW || hand_state == HAND_DISCARD || hand_state == HAND_SELECT) if (hand_state == HAND_DRAW || hand_state == HAND_DISCARD || hand_state == HAND_SELECT)
@@ -2154,47 +2188,54 @@ void game_playing()
game_playing_ui_text_update(); game_playing_ui_text_update();
} }
void game_round_end_cleanup() static void game_round_end_on_exit()
{ {
// Cleanup blind tokens from this round to avoid accumulating // Cleanup blind tokens from this round to avoid accumulating
// allocated blind sprites each round // allocated blind sprites each round
blind_reward = 0;
hand_reward = 0;
interest_reward = 0;
sprite_destroy(&playing_blind_token); sprite_destroy(&playing_blind_token);
sprite_destroy(&round_end_blind_token); sprite_destroy(&round_end_blind_token);
// TODO: Reuse sprites for blind selection? // TODO: Reuse sprites for blind selection?
} }
void game_round_end() static void game_round_end_on_update()
{ {
static int blind_reward = 0; if (state_info[game_state].substate == ROUND_END_EXIT)
static int hand_reward = 0; {
static int interest_reward = 0; game_change_state(GAME_STATE_SHOP);
return;
}
switch (state) int substate = state_info[game_state].substate;
{ round_end_state_actions[substate]();
case ROUND_END_START: }
static void game_round_end_start()
{ {
if (timer == TM_RESET_STATIC_VARS) // Reset static variables to default values upon re-entering the round end state if (timer == TM_RESET_STATIC_VARS) // Reset static variables to default values upon re-entering the round end state
{ {
change_background(BG_ID_ROUND_END); // Change the background to the round end background change_background(BG_ID_ROUND_END); // Change the background to the round end background
state = START_EXPAND_POPUP; // Change the state to the next one state_info[game_state].substate = START_EXPAND_POPUP; // Change the state to the next one
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
blind_reward = blind_get_reward(current_blind); blind_reward = blind_get_reward(current_blind);
hand_reward = hands; hand_reward = hands;
} }
break;
} }
case START_EXPAND_POPUP: // This creates the top 16 by 7 tiles of the pop up. It places it in vram, moving it up one tile each frame, not clearing the previous row of tiles so they fill the blank space as it moves up.
static void game_round_end_start_expand_popup()
{ {
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP); main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
if (timer == TM_END_POP_MENU_ANIM) if (timer == TM_END_POP_MENU_ANIM)
{ {
state = DISPLAY_FINISHED_BLIND; state_info[game_state].substate = DISPLAY_FINISHED_BLIND;
timer = TM_ZERO; timer = TM_ZERO;
} }
break;
} }
case DISPLAY_FINISHED_BLIND: // Display the beaten blind, expand the panel border down a tile and wait until a bit until going to the next state
static void game_round_end_display_finished_blind()
{ {
obj_unhide(round_end_blind_token->obj, 0); obj_unhide(round_end_blind_token->obj, 0);
@@ -2217,19 +2258,16 @@ void game_round_end()
if (timer >= TM_END_DISPLAY_FIN_BLIND) if (timer >= TM_END_DISPLAY_FIN_BLIND)
{ {
state = DISPLAY_SCORE_MIN; state_info[game_state].substate = DISPLAY_SCORE_MIN;
timer = TM_ZERO; timer = TM_ZERO;
} }
break;
} }
case DISPLAY_SCORE_MIN: // Sequentially display the "score min" text over the next 4 frames
static void game_round_end_display_score_min()
{ {
const int timer_offset = timer - 1; const int timer_offset = timer - 1;
const int x_from = 0; const int x_from = 0;
const int y_from = 29; const int y_from = 29;
const int x_to = 13; const int x_to = 13;
const int y_to = 11; const int y_to = 11;
@@ -2237,14 +2275,14 @@ void game_round_end()
if (timer >= TM_END_DISPLAY_SCORE_MIN) if (timer >= TM_END_DISPLAY_SCORE_MIN)
{ {
state = UPDATE_BLIND_REWARD; state_info[game_state].substate = UPDATE_BLIND_REWARD;
timer = TM_ZERO; timer = TM_ZERO;
} }
break;
} }
case UPDATE_BLIND_REWARD: // Every 20 frames, display the blind reward and update the text until it reaches 0
static void game_round_end_update_blind_reward()
{ {
if (timer % FRAMES(20) != 0) break; if (timer % FRAMES(20) != 0) return;
// TODO: Add sound effect here // TODO: Add sound effect here
@@ -2260,12 +2298,12 @@ void game_round_end()
tte_erase_rect_wrapper(BLIND_REQ_TEXT_RECT); tte_erase_rect_wrapper(BLIND_REQ_TEXT_RECT);
obj_hide(playing_blind_token->obj); obj_hide(playing_blind_token->obj);
affine_background_load_palette(affine_background_gfxPal); affine_background_load_palette(affine_background_gfxPal);
state = BLIND_PANEL_EXIT; state_info[game_state].substate = BLIND_PANEL_EXIT;
timer = TM_ZERO; timer = TM_ZERO;
} }
break;
} }
case BLIND_PANEL_EXIT: // Slide the "small blind" panel out of view
static void game_round_end_panel_exit()
{ {
// TODO: make heads or tails of what's going on here and replace // TODO: make heads or tails of what's going on here and replace
// magic numbers. // magic numbers.
@@ -2275,31 +2313,25 @@ void game_round_end()
if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me personally to make one. if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me personally to make one.
{ {
int y = 6; reset_top_left_panel_bottom_row();
memset16(&se_mat[MAIN_BG_SBB][y - 1][0], 0x0006, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][1], 0x0007, 2);
memset16(&se_mat[MAIN_BG_SBB][y - 1][3], 0x0008, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][4], 0x0009, 4);
memset16(&se_mat[MAIN_BG_SBB][y - 1][7], 0x000A, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][8], SE_HFLIP | 0x0006, 1);
} }
else if (timer == 2) else if (timer == 2)
{ {
int y = 5; int y = 5;
memset16(&se_mat[MAIN_BG_SBB][y - 1][0], 0x0001, 1); memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][1], 0x0002, 7); memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7);
memset16(&se_mat[MAIN_BG_SBB][y - 1][8], SE_HFLIP | 0x0001, 1); memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1);
} }
} }
else if (timer > FRAMES(20)) else if (timer > FRAMES(20))
{ {
memset16(&pal_bg_mem[REWARD_PANEL_BORDER_PID], 0x1483, 1); memset16(&pal_bg_mem[REWARD_PANEL_BORDER_PID], 0x1483, 1);
state = DISPLAY_REWARDS; state_info[game_state].substate = DISPLAY_REWARDS;
timer = TM_ZERO; timer = TM_ZERO;
} }
break;
} }
case DISPLAY_REWARDS: // This state handles displaying the rewards earned from the completed round
static void game_round_end_display_rewards()
{ {
int hand_y = 0; int hand_y = 0;
@@ -2320,7 +2352,7 @@ void game_round_end()
if (hand_reward <= 0 && interest_reward <= 0) // Once all rewards are accounted for go to the next state if (hand_reward <= 0 && interest_reward <= 0) // Once all rewards are accounted for go to the next state
{ {
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
state = DISPLAY_CASHOUT; // Go to the next state state_info[game_state].substate = DISPLAY_CASHOUT; // Go to the next state
} }
else if (timer == TM_START_ROUND_END_MENU_AMIN) // Expand the black part of the panel down by one tile else if (timer == TM_START_ROUND_END_MENU_AMIN) // Expand the black part of the panel down by one tile
{ {
@@ -2355,10 +2387,9 @@ void game_round_end()
tte_printf("#{P:%d, %d; cx:0x%X000}$%d", HAND_REWARD_RECT.left, y, TTE_YELLOW_PB, hands - hand_reward); // Print the hand reward tte_printf("#{P:%d, %d; cx:0x%X000}$%d", HAND_REWARD_RECT.left, y, TTE_YELLOW_PB, hands - hand_reward); // Print the hand reward
} }
} }
break;
} }
case DISPLAY_CASHOUT:
static void game_round_end_display_cashout()
{ {
if (timer == FRAMES(40)) // Put the "cash out" button onto the round end panel if (timer == FRAMES(40)) // Put the "cash out" button onto the round end panel
{ {
@@ -2388,16 +2419,15 @@ void game_round_end()
{ {
game_round_end_cashout(); game_round_end_cashout();
state = DISMISS_ROUND_END_PANEL; // Go to the next state state_info[game_state].substate = DISMISS_ROUND_END_PANEL; // Go to the next state
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
obj_hide(round_end_blind_token->obj); // Hide the blind token object obj_hide(round_end_blind_token->obj); // Hide the blind token object
tte_erase_rect_wrapper(BLIND_TOKEN_TEXT_RECT); // Erase the blind token text tte_erase_rect_wrapper(BLIND_TOKEN_TEXT_RECT); // Erase the blind token text
} }
break;
} }
case DISMISS_ROUND_END_PANEL: // Shift the round end panel back out of view and go to the next state
static void game_round_end_dismiss_round_end_panel()
{ {
Rect round_end_down = ROUND_END_MENU_RECT; Rect round_end_down = ROUND_END_MENU_RECT;
round_end_down.top--; round_end_down.top--;
@@ -2406,19 +2436,7 @@ void game_round_end()
if (timer >= TM_DISMISS_ROUND_END_TM) if (timer >= TM_DISMISS_ROUND_END_TM)
{ {
timer = TM_ZERO; timer = TM_ZERO;
state = ROUND_END_EXIT; state_info[game_state].substate = ROUND_END_EXIT;
}
break;
}
default:
timer = TM_ZERO;
state = 0;
blind_reward = 0;
hand_reward = 0;
interest_reward = 0;
game_round_end_cleanup();
game_set_state(GAME_SHOP);
break;
} }
} }
@@ -2525,7 +2543,7 @@ static void game_shop_intro()
if (timer == TM_END_GAME_SHOP_INTRO) if (timer == TM_END_GAME_SHOP_INTRO)
{ {
state = GAME_SHOP_ACTIVE; state_info[game_state].substate = GAME_SHOP_ACTIVE;
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
} }
} }
@@ -2652,7 +2670,7 @@ static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* se
if (selection->x == NEXT_ROUND_BTN_SEL_X) if (selection->x == NEXT_ROUND_BTN_SEL_X)
{ {
// Go to next blind selection game state // Go to next blind selection game state
state = 2; // Go to the outro sequence state state_info[game_state].substate = GAME_SHOP_EXIT; // Go to the outro sequence state
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
reroll_cost = REROLL_BASE_COST; reroll_cost = REROLL_BASE_COST;
@@ -2818,13 +2836,7 @@ static void game_shop_outro()
} }
} }
int y = 6; reset_top_left_panel_bottom_row();
memset16(&se_mat[MAIN_BG_SBB][y - 1][0], 0x0006, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][1], 0x0007, 2);
memset16(&se_mat[MAIN_BG_SBB][y - 1][3], 0x0008, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][4], 0x0009, 4);
memset16(&se_mat[MAIN_BG_SBB][y - 1][7], 0x000A, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][8], SE_HFLIP | 0x0006, 1);
} }
else if (timer == 2) else if (timer == 2)
{ {
@@ -2836,12 +2848,12 @@ static void game_shop_outro()
if (timer >= MENU_POP_OUT_ANIM_FRAMES) if (timer >= MENU_POP_OUT_ANIM_FRAMES)
{ {
state = 3; // Go to the next state state_info[game_state].substate = GAME_SHOP_MAX; // Go to the next state
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
} }
} }
void game_shop() static void game_shop_on_update()
{ {
change_background(BG_ID_SHOP); change_background(BG_ID_SHOP);
@@ -2862,26 +2874,19 @@ void game_shop()
game_shop_lights_anim_frame(); game_shop_lights_anim_frame();
} }
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer if (state_info[game_state].substate == GAME_SHOP_MAX)
{ {
case GAME_SHOP_INTRO: game_change_state(GAME_STATE_BLIND_SELECT);
{ return;
game_shop_intro();
break;
} }
case GAME_SHOP_ACTIVE:
{
game_shop_process_user_input();
break;
}
case GAME_SHOP_EXIT:
{
game_shop_outro();
break;
}
default:
state = 0; // Reset the state
int substate = state_info[game_state].substate;
shop_state_actions[substate]();
}
static void game_shop_on_exit()
{
for (int i = 0; i < list_get_size(shop_jokers); i++) for (int i = 0; i < list_get_size(shop_jokers); i++)
{ {
JokerObject* joker_object = list_get(shop_jokers, i); JokerObject* joker_object = list_get(shop_jokers, i);
@@ -2896,17 +2901,21 @@ void game_shop()
list_destroy(&shop_jokers); list_destroy(&shop_jokers);
increment_blind(BLIND_STATE_DEFEATED); // TODO: Move to game_round_end()? increment_blind(BLIND_STATE_DEFEATED); // TODO: Move to game_round_end()?
game_set_state(GAME_BLIND_SELECT); // If we reach here, we should go to the blind select state
break;
}
} }
void game_blind_select() static void game_blind_select_on_update()
{ {
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer if (state_info[game_state].substate == BLIND_SELECT_MAX)
{ {
case START_ANIM_SEQ: // Intro sequence (menu coming into frame) game_change_state(GAME_STATE_PLAYING);
return;
}
int substate = state_info[game_state].substate;
blind_select_state_actions[substate]();
}
static void game_blind_select_start_anim_seq()
{ {
change_background(BG_ID_BLIND_SELECT); change_background(BG_ID_BLIND_SELECT);
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP); main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
@@ -2918,13 +2927,12 @@ void game_blind_select()
if (timer == TM_END_ANIM_SEQ) if (timer == TM_END_ANIM_SEQ)
{ {
state++; state_info[game_state].substate = BLIND_SELECT;
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
} }
break;
} }
case BLIND_SELECT: // Blind select input and selection
static void game_blind_select_handle_input()
{ {
if (timer == TM_BLIND_SELECT_START && current_blind == BLIND_TYPE_BOSS) if (timer == TM_BLIND_SELECT_START && current_blind == BLIND_TYPE_BOSS)
{ {
@@ -2944,7 +2952,7 @@ void game_blind_select()
{ {
if (selection_y == 0) // Blind selected if (selection_y == 0) // Blind selected
{ {
state++; state_info[game_state].substate = BLIND_SELECTED_ANIM_SEQ;
timer = TM_ZERO; timer = TM_ZERO;
display_round(++round); display_round(++round);
} }
@@ -2973,18 +2981,18 @@ void game_blind_select()
if (selection_y == 0) if (selection_y == 0)
{ {
// 5 is the multiplier palette color and the skip button color // 5 is the multiplier palette color and the skip button color
memset16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1); memset16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], 0xFFFF, 1);
memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SKIP_BTN_PID], 1); memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SKIP_BTN_PID], 1);
} }
else else
{ {
// 15 is the select button color
memcpy16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SELECT_BTN_PID], 1); memcpy16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SELECT_BTN_PID], 1);
memset16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1); memset16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], 0xFFFF, 1);
}
} }
break; static void game_blind_select_selected_anim_seq()
}
case BLIND_SELECTED_ANIM_SEQ: // Blind selected, perform menu popout animation
{ {
if (timer < 15) if (timer < 15)
{ {
@@ -3004,17 +3012,17 @@ void game_blind_select()
obj_hide(blind_select_tokens[i]->obj); obj_hide(blind_select_tokens[i]->obj);
} }
state++; // Reset the state state_info[game_state].substate = DISPLAY_BLIND_PANEL; // Reset the state
timer = TM_ZERO; // Reset the timer timer = TM_ZERO; // Reset the timer
} }
break;
} }
case DISPLAY_BLIND_PANEL: // Move the blind panel into view
static void game_blind_select_display_blind_panel()
{ {
if (timer >= TM_DISP_BLIND_PANEL_FINISH) if (timer >= TM_DISP_BLIND_PANEL_FINISH)
{ {
state++; state_info[game_state].substate = BLIND_SELECT_MAX;
break; return;
} }
if (timer == TM_DISP_BLIND_PANEL_START) // Switches to the selecting background and clears the blind panel area if (timer == TM_DISP_BLIND_PANEL_START) // Switches to the selecting background and clears the blind panel area
@@ -3034,13 +3042,7 @@ void game_blind_select()
main_bg_se_copy_rect(from, to); main_bg_se_copy_rect(from, to);
} }
int y = 6; reset_top_left_panel_bottom_row();
memset16(&se_mat[MAIN_BG_SBB][y - 1][0], 0x0006, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][1], 0x0007, 2);
memset16(&se_mat[MAIN_BG_SBB][y - 1][3], 0x0008, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][4], 0x0009, 4);
memset16(&se_mat[MAIN_BG_SBB][y - 1][7], 0x000A, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][8], SE_HFLIP | 0x0006, 1);
} }
for (int y = 0; y < timer; y++) // Shift the blind panel down onto screen for (int y = 0; y < timer; y++) // Shift the blind panel down onto screen
@@ -3053,20 +3055,15 @@ void game_blind_select()
main_bg_se_copy_rect(from, to); main_bg_se_copy_rect(from, to);
} }
break;
} }
default:
state = 0; static void game_blind_select_on_exit()
timer = TM_ZERO; {
selection_y = 0; selection_y = 0;
background = UNDEFINED; background = UNDEFINED;
game_set_state(GAME_PLAYING);
break;
}
} }
void game_main_menu() static void game_main_menu_on_update()
{ {
change_background(BG_ID_MAIN_MENU); change_background(BG_ID_MAIN_MENU);
@@ -3167,7 +3164,7 @@ static void game_over_anim_frame()
main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SE_UP); main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SE_UP);
} }
static void game_lose() static void game_lose_on_update()
{ {
if (timer < GAME_OVER_ANIM_FRAMES) if (timer < GAME_OVER_ANIM_FRAMES)
{ {
@@ -3177,9 +3174,56 @@ static void game_lose()
{ {
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); 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);
} }
if (key_hit(KEY_ANY)) game_change_state(GAME_STATE_BLIND_SELECT);
} }
static void game_win() // 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()
{
for (int i = 0; i < list_get_size(jokers); i ++)
{
JokerObject *joker_object = list_get(jokers, i);
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);
sprite_destroy(&blind_select_tokens[BLIND_TYPE_SMALL]);
sprite_destroy(&blind_select_tokens[BLIND_TYPE_BIG]);
sprite_destroy(&blind_select_tokens[BLIND_TYPE_BOSS]);
list_destroy(&jokers_available_to_shop);
game_init();
display_round(round);
display_score(score);
display_chips(chips);
display_mult(mult);
display_hands(hands);
display_discards(discards);
display_money(money);
tte_printf(
"#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d",
ANTE_TEXT_RECT.left,
ANTE_TEXT_RECT.top,
TTE_YELLOW_PB,
ante,
TTE_WHITE_PB,
MAX_ANTE
); // Ante
affine_background_load_palette(affine_background_gfxPal);
}
static void game_win_on_update()
{ {
if (timer < GAME_OVER_ANIM_FRAMES) if (timer < GAME_OVER_ANIM_FRAMES)
{ {
@@ -3189,6 +3233,8 @@ static void game_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); 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);
} }
if (key_hit(KEY_ANY)) game_change_state(GAME_STATE_BLIND_SELECT);
} }
void game_update() void game_update()
@@ -3197,31 +3243,5 @@ void game_update()
jokers_update_loop(); jokers_update_loop();
switch (game_state) state_info[game_state].on_update();
{
case GAME_SPLASH_SCREEN:
splash_screen_update(timer);
break;
case GAME_MAIN_MENU:
game_main_menu();
break;
case GAME_PLAYING:
game_playing();
break;
case GAME_ROUND_END:
game_round_end();
break;
case GAME_SHOP:
game_shop();
break;
case GAME_BLIND_SELECT:
game_blind_select();
break;
case GAME_LOSE:
game_lose();
break;
case GAME_WIN:
game_win();
break;
}
} }
+1
View File
@@ -77,6 +77,7 @@ void init()
blind_init(); blind_init();
joker_init(); joker_init();
game_init(); game_init();
game_change_state(GAME_STATE_SPLASH_SCREEN);
} }
void update() void update()
+8 -3
View File
@@ -6,16 +6,21 @@
#include "game.h" #include "game.h"
static const Rect COUNTDOWN_TIMER_RECT = {208, 144, 240, 152}; static const Rect COUNTDOWN_TIMER_RECT = {208, 144, 240, 152};
static uint timer = 0;
void splash_screen_init() void splash_screen_on_init()
{ {
timer = 0;
tte_printf("#{P:72,8; cx:0xF000}DISCLAIMER"); tte_printf("#{P:72,8; cx:0xF000}DISCLAIMER");
tte_printf("#{P:8,24; cx:0xF000}This project is NOT endorsed \n by or affiliated with \n Playstack or LocalThunk.\n\n If you have paid for this, \n you have been scammed \n and should request a refund \n IMMEDIATELY. \n\n The only official place \n to obtain this is from: \n\n 'github.com/\n cellos51/balatro-gba'"); tte_printf("#{P:8,24; cx:0xF000}This project is NOT endorsed \n by or affiliated with \n Playstack or LocalThunk.\n\n If you have paid for this, \n you have been scammed \n and should request a refund \n IMMEDIATELY. \n\n The only official place \n to obtain this is from: \n\n 'github.com/\n cellos51/balatro-gba'");
tte_printf("#{P:8,144; cx:0xF000}(Press any key to skip)"); tte_printf("#{P:8,144; cx:0xF000}(Press any key to skip)");
} }
void splash_screen_update(uint timer) void splash_screen_on_update()
{ {
timer++;
if (timer < SPLASH_DURATION_FRAMES) if (timer < SPLASH_DURATION_FRAMES)
{ {
tte_erase_rect_wrapper(COUNTDOWN_TIMER_RECT); tte_erase_rect_wrapper(COUNTDOWN_TIMER_RECT);
@@ -27,6 +32,6 @@ void splash_screen_update(uint timer)
} }
} }
game_set_state(GAME_MAIN_MENU); game_change_state(GAME_STATE_MAIN_MENU);
tte_erase_screen(); tte_erase_screen();
} }