Replaced the substate switch statements with function tables.

This commit is contained in:
Drew
2025-10-14 10:24:42 -05:00
parent 4e1f418a8d
commit 6d66ccbb61
+146 -134
View File
@@ -32,7 +32,8 @@ typedef enum
{
GAME_SHOP_INTRO,
GAME_SHOP_ACTIVE,
GAME_SHOP_EXIT
GAME_SHOP_EXIT,
GAME_SHOP_MAX
} _GameShopStates;
typedef enum
@@ -54,7 +55,8 @@ typedef enum
START_ANIM_SEQ,
BLIND_SELECT,
BLIND_SELECTED_ANIM_SEQ,
DISPLAY_BLIND_PANEL
DISPLAY_BLIND_PANEL,
BLIND_SELECT_MAX
} _BlindSelectStates;
typedef struct
@@ -64,7 +66,7 @@ typedef struct
char *display_name;
} HandValues;
typedef void (*ActionFn)();
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
@@ -73,7 +75,8 @@ typedef void (*ActionFn)();
static void _noop() {}
// These functions need to be forward declared
// so they're visible to the state_info array.
// so they're visible to the state_info array,
// and the sub-state function tables.
static void game_main_menu_on_init();
static void game_main_menu_on_update();
static void game_round_on_init();
@@ -91,6 +94,19 @@ 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;
@@ -106,7 +122,7 @@ static const StateInfo state_info[GAME_STATE_MAX] =
#undef DEF_STATE_INFO
};
static const HandValues hand_values[] =
static const HandValues hand_base_values[] =
{
{ .chips = 0, .mult = 0, .display_name = NULL }, // NONE
{ .chips = 5, .mult = 1, .display_name = "HIGH C" }, // HIGH_CARD
@@ -124,13 +140,30 @@ static const HandValues hand_values[] =
{ .chips = 160, .mult = 16, .display_name = "FLUSH 5" } // FLUSH_FIVE
};
static const ActionFn shop_state_actions[] =
{ game_shop_intro, game_shop_process_user_input, game_shop_outro };
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 PlayState play_state = PLAY_PLAYING;
static int state = 0; // General state variable, used for switch statements in each game state related function
static int substate = 0; // maybe put this in StateInfo struct?
static enum HandType hand_type = NONE;
@@ -151,6 +184,10 @@ static enum BlindState blinds[BLIND_TYPE_MAX] =
BLIND_STATE_UPCOMING
}; // 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)
static int max_hands = 4;
static int max_discards = 4;
@@ -971,7 +1008,7 @@ void set_hand()
tte_erase_rect_wrapper(HAND_TYPE_RECT);
hand_type = hand_get_type();
HandValues hand = hand_values[hand_type];
HandValues hand = hand_base_values[hand_type];
chips = hand.chips;
mult = hand.mult;
@@ -2127,8 +2164,11 @@ static void game_round_end_on_exit()
{
// Cleanup blind tokens from this round to avoid accumulating
// allocated blind sprites each round
timer = 0;
state = 0;
timer = TM_ZERO;
substate = 0;
blind_reward = 0;
hand_reward = 0;
interest_reward = 0;
sprite_destroy(&playing_blind_token);
sprite_destroy(&round_end_blind_token);
// TODO: Reuse sprites for blind selection?
@@ -2136,41 +2176,44 @@ static void game_round_end_on_exit()
static void game_round_end_on_update()
{
static int blind_reward = 0;
static int hand_reward = 0;
static int interest_reward = 0;
switch (state)
if (substate == ROUND_END_EXIT)
{
case ROUND_END_START:
game_change_state(GAME_STATE_SHOP);
return;
}
round_end_state_actions[substate]();
}
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
{
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
substate = START_EXPAND_POPUP; // Change the state to the next one
timer = TM_ZERO; // Reset the timer
blind_reward = blind_get_reward(current_blind);
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);
if (timer == TM_END_POP_MENU_ANIM)
{
state = DISPLAY_FINISHED_BLIND;
substate = DISPLAY_FINISHED_BLIND;
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);
int current_ante = ante;
if (current_blind == BLIND_TYPE_BOSS) current_ante--; // Beating the boss blind increases the ante, so we need to display the previous ante value
if (current_blind == BOSS_BLIND) current_ante--; // Beating the boss blind increases the ante, so we need to display the previous ante value
Rect blind_req_rect = ROUND_END_BLIND_REQ_RECT;
int blind_req = blind_get_requirement(current_blind, current_ante);
@@ -2188,19 +2231,16 @@ static void game_round_end_on_update()
if (timer >= TM_END_DISPLAY_FIN_BLIND)
{
state = DISPLAY_SCORE_MIN;
substate = DISPLAY_SCORE_MIN;
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 x_from = 0;
const int y_from = 29;
const int x_to = 13;
const int y_to = 11;
@@ -2208,14 +2248,14 @@ static void game_round_end_on_update()
if (timer >= TM_END_DISPLAY_SCORE_MIN)
{
state = UPDATE_BLIND_REWARD;
substate = UPDATE_BLIND_REWARD;
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
@@ -2231,12 +2271,12 @@ static void game_round_end_on_update()
tte_erase_rect_wrapper(BLIND_REQ_TEXT_RECT);
obj_hide(playing_blind_token->obj);
affine_background_load_palette(affine_background_gfxPal);
state = BLIND_PANEL_EXIT;
substate = BLIND_PANEL_EXIT;
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
// magic numbers.
@@ -2247,30 +2287,30 @@ static void game_round_end_on_update()
if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me personally to make one.
{
int y = 6;
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);
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, 4);
memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1);
memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1);
}
else if (timer == 2)
{
int y = 5;
memset16(&se_mat[MAIN_BG_SBB][y - 1][0], 0x0001, 1);
memset16(&se_mat[MAIN_BG_SBB][y - 1][1], 0x0002, 7);
memset16(&se_mat[MAIN_BG_SBB][y - 1][8], SE_HFLIP | 0x0001, 1);
memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1);
memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7);
memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1);
}
}
else if (timer > FRAMES(20))
{
memset16(&pal_bg_mem[REWARD_PANEL_BORDER_PID], 0x1483, 1);
state = DISPLAY_REWARDS;
memset16(&pal_bg_mem[REWARD_PANEL_BORDER], 0x1483, 1);
substate = DISPLAY_REWARDS;
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;
@@ -2291,7 +2331,7 @@ static void game_round_end_on_update()
if (hand_reward <= 0 && interest_reward <= 0) // Once all rewards are accounted for go to the next state
{
timer = TM_ZERO; // Reset the timer
state = DISPLAY_CASHOUT; // Go to the next 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
{
@@ -2326,10 +2366,9 @@ static void game_round_end_on_update()
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
{
@@ -2359,16 +2398,15 @@ static void game_round_end_on_update()
{
game_round_end_cashout();
state = DISMISS_ROUND_END_PANEL; // Go to the next state
substate = DISMISS_ROUND_END_PANEL; // Go to the next state
timer = TM_ZERO; // Reset the timer
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
}
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;
round_end_down.top--;
@@ -2377,18 +2415,7 @@ static void game_round_end_on_update()
if (timer >= TM_DISMISS_ROUND_END_TM)
{
timer = TM_ZERO;
state = ROUND_END_EXIT;
}
break;
}
default:
timer = TM_ZERO;
state = 0;
blind_reward = 0;
hand_reward = 0;
interest_reward = 0;
game_change_state(GAME_STATE_SHOP);
break;
substate = ROUND_END_EXIT;
}
}
@@ -2495,7 +2522,7 @@ static void game_shop_intro()
if (timer == TM_END_GAME_SHOP_INTRO)
{
state = GAME_SHOP_ACTIVE;
substate = GAME_SHOP_ACTIVE;
timer = TM_ZERO; // Reset the timer
}
}
@@ -2622,7 +2649,7 @@ static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* se
if (selection->x == NEXT_ROUND_BTN_SEL_X)
{
// Go to next blind selection game state
state = 2; // Go to the outro sequence state
substate = 2; // Go to the outro sequence state
timer = TM_ZERO; // Reset the timer
reroll_cost = REROLL_BASE_COST;
@@ -2806,7 +2833,7 @@ static void game_shop_outro()
if (timer >= MENU_POP_OUT_ANIM_FRAMES)
{
state = 3; // Go to the next state
substate = 3; // Go to the next state
timer = TM_ZERO; // Reset the timer
}
}
@@ -2832,32 +2859,18 @@ static void game_shop_on_update()
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 (substate == GAME_SHOP_MAX)
{
case GAME_SHOP_INTRO:
{
game_shop_intro();
break;
}
case GAME_SHOP_ACTIVE:
{
game_shop_process_user_input();
break;
}
case GAME_SHOP_EXIT:
{
game_shop_outro();
break;
}
default:
game_change_state(GAME_STATE_BLIND_SELECT); // If we reach here, we should go to the blind select state
break;
game_change_state(GAME_STATE_BLIND_SELECT);
return;
}
shop_state_actions[substate]();
}
static void game_shop_on_exit()
{
state = 0; // Reset the state
substate = 0; // Reset the state
for (int i = 0; i < list_get_size(shop_jokers); i++)
{
@@ -2877,29 +2890,35 @@ static void game_shop_on_exit()
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 (substate == BLIND_SELECT_MAX)
{
case START_ANIM_SEQ: // Intro sequence (menu coming into frame)
game_change_state(GAME_STATE_PLAYING);
return;
}
blind_select_state_actions[substate]();
}
static void game_blind_select_start_anim_seq()
{
change_background(BG_ID_BLIND_SELECT);
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
for (int i = 0; i < BLIND_TYPE_MAX; i++)
for (int i = 0; i < MAX_BLINDS; i++)
{
sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y - TILE_SIZE);
}
if (timer == TM_END_ANIM_SEQ)
{
state++;
substate = BLIND_SELECT;
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 == BOSS_BLIND)
{
selection_y = 0;
}
@@ -2909,7 +2928,7 @@ static void game_blind_select_on_update()
{
selection_y = 0;
}
else if (key_hit(KEY_DOWN) && current_blind != BLIND_TYPE_BOSS)
else if (key_hit(KEY_DOWN) && current_blind != BOSS_BLIND)
{
selection_y = 1;
}
@@ -2917,13 +2936,13 @@ static void game_blind_select_on_update()
{
if (selection_y == 0) // Blind selected
{
state++;
substate = BLIND_SELECTED_ANIM_SEQ;
timer = TM_ZERO;
display_round(++round);
}
else if (current_blind != BLIND_TYPE_BOSS)
else if (current_blind != BOSS_BLIND)
{
increment_blind(BLIND_STATE_SKIPPED);
increment_blind(BLIND_SKIPPED);
background = UNDEFINED; // Force refresh of the background
change_background(BG_ID_BLIND_SELECT);
@@ -2934,7 +2953,7 @@ static void game_blind_select_on_update()
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
}
for (int i = 0; i < BLIND_TYPE_MAX; i++)
for (int i = 0; i < MAX_BLINDS; i++)
{
sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y - (TILE_SIZE * 12));
}
@@ -2946,18 +2965,18 @@ static void game_blind_select_on_update()
if (selection_y == 0)
{
// 5 is the multiplier palette color and the skip button color
memset16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER_PID], HIGHLIGHT_COLOR, 1);
memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER_PID], &pal_bg_mem[BLIND_SKIP_BTN_PID], 1);
memset16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER], 0xFFFF, 1);
memcpy16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER], &pal_bg_mem[BLIND_SKIP_BTN], 1);
}
else
{
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);
// 15 is the select button color
memcpy16(&pal_bg_mem[BLIND_SELECT_BTN_SELECTED_BORDER], &pal_bg_mem[BLIND_SELECT_BTN], 1);
memset16(&pal_bg_mem[BLIND_SKIP_BTN_SELECTED_BORDER], 0xFFFF, 1);
}
}
break;
}
case BLIND_SELECTED_ANIM_SEQ: // Blind selected, perform menu popout animation
static void game_blind_select_selected_anim_seq()
{
if (timer < 15)
{
@@ -2965,29 +2984,29 @@ static void game_blind_select_on_update()
blinds_rect.top -= 1; // Because of the raised blind
main_bg_se_move_rect_1_tile_vert(blinds_rect, SE_DOWN);
for (int i = 0; i < BLIND_TYPE_MAX; i++)
for (int i = 0; i < MAX_BLINDS; i++)
{
sprite_position(blind_select_tokens[i], blind_select_tokens[i]->pos.x, blind_select_tokens[i]->pos.y + TILE_SIZE);
}
}
else if (timer >= MENU_POP_OUT_ANIM_FRAMES)
{
for (int i = 0; i < BLIND_TYPE_MAX; i++)
for (int i = 0; i < MAX_BLINDS; i++)
{
obj_hide(blind_select_tokens[i]->obj);
}
state++; // Reset the state
substate = DISPLAY_BLIND_PANEL; // Reset the state
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)
{
state++;
break;
substate = BLIND_SELECT_MAX;
return;
}
if (timer == TM_DISP_BLIND_PANEL_START) // Switches to the selecting background and clears the blind panel area
@@ -3008,12 +3027,12 @@ static void game_blind_select_on_update()
}
int y = 6;
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);
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, 4);
memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1);
memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1);
}
for (int y = 0; y < timer; y++) // Shift the blind panel down onto screen
@@ -3026,18 +3045,11 @@ static void game_blind_select_on_update()
main_bg_se_copy_rect(from, to);
}
break;
}
default:
game_change_state(GAME_STATE_PLAYING);
break;
}
}
static void game_blind_select_on_exit()
{
state = 0;
substate = 0;
timer = 0;
selection_y = 0;
background = UNDEFINED;