diff --git a/graphics/joker_gfx3.png b/graphics/joker_gfx3.png index 959c457..9e84c1e 100644 Binary files a/graphics/joker_gfx3.png and b/graphics/joker_gfx3.png differ diff --git a/graphics/joker_gfx5.png b/graphics/joker_gfx5.png index 70c21b6..bc4e112 100644 Binary files a/graphics/joker_gfx5.png and b/graphics/joker_gfx5.png differ diff --git a/include/list.h b/include/list.h index 64341f7..fd1c366 100644 --- a/include/list.h +++ b/include/list.h @@ -3,23 +3,23 @@ #include -/* Defining an int list for simplicity - * If another list is needed, a generic void* - * list should be defined.... - */ -typedef struct IntList +typedef struct List { - int* _array; + void** _array; int size; int allocated_size; -} IntList; +} List; -IntList *int_list_new(int init_size); -void int_list_destroy(IntList **list); -bool int_list_append(IntList *list, int value); -bool int_list_remove_by_idx(IntList *list, int index); -int int_list_get(IntList *list, int index); -int int_list_get_size(IntList *list); -bool int_list_remove_by_value(IntList *list, int value); +List *list_new(int init_size); +void list_destroy(List **list); +bool list_append(List *list, void *value); +bool list_remove_by_idx(List *list, int index); +void* list_get(List *list, int index); +int list_get_size(List *list); +bool list_remove_by_value(List *list, void *value); + +bool int_list_append(List *list, intptr_t value); +intptr_t int_list_get(List *list, int index); +bool int_list_remove_by_value(List *list, intptr_t value); #endif diff --git a/include/selection_grid.h b/include/selection_grid.h new file mode 100644 index 0000000..9ddd2bf --- /dev/null +++ b/include/selection_grid.h @@ -0,0 +1,41 @@ +#ifndef SELECTION_GRID_H +#define SELECTION_GRID_H +#include + +typedef POINT Selection; + +struct SelectionGridRow; +struct SelectionGrid; +typedef struct SelectionGridRow SelectionGridRow; +typedef struct SelectionGrid SelectionGrid; + +// Called whenever there is a change in the selection cursor +// row_idx is the index of the row whose function is invoked - can be used to identify whether it is the previous or new selection row. +typedef void (*RowOnSelectionChangedFunc)(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection); +typedef int (*RowGetSizeFunc)(); +// Called for any non-directional key hit +// The key will not be passed, the function will have to check key_hit() etc. for the key it wants to check +typedef void (*RowOnKeyHitFunc)(SelectionGrid* selection_grid, Selection* selection); + +struct SelectionGridRow +{ + int row_idx; + RowGetSizeFunc get_size; + RowOnSelectionChangedFunc on_selection_changed; + RowOnKeyHitFunc on_key_hit; +}; + +struct SelectionGrid +{ + SelectionGridRow* rows; + const int num_rows; + Selection selection; + }; + + +void selection_grid_process_input(SelectionGrid* selection_grid); + +void selection_grid_move_selection_horz(SelectionGrid* selection_grid, int direction_tribool); +void selection_grid_move_selection_vert(SelectionGrid* selection_grid, int direction_tribool); + +#endif \ No newline at end of file diff --git a/include/sprite.h b/include/sprite.h index b4d6bca..b3f27b8 100644 --- a/include/sprite.h +++ b/include/sprite.h @@ -27,6 +27,8 @@ typedef struct FIXED rotation; FIXED vrotation; bool selected; + bool focused; + } SpriteObject; // Sprite methods @@ -58,6 +60,7 @@ void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id); void sprite_object_set_selected(SpriteObject* sprite_object, bool selected); bool sprite_object_is_selected(SpriteObject* sprite_object); Sprite* sprite_object_get_sprite(SpriteObject* sprite_object); - +void sprite_object_set_focus(SpriteObject* sprite_object, bool focus); +bool sprite_object_is_focused(SpriteObject* sprite_object); #endif // SPRITE_H \ No newline at end of file diff --git a/source/game.c b/source/game.c index a56b997..379f15b 100644 --- a/source/game.c +++ b/source/game.c @@ -15,6 +15,7 @@ #include "graphic_utils.h" #include "tonc_video.h" #include "audio_utils.h" +#include "selection_grid.h" #include "background_gfx.h" #include "background_shop_gfx.h" @@ -185,7 +186,7 @@ int get_money(void) { return money; } -IntList* jokers_available_to_shop; // List of joker IDs +List* jokers_available_to_shop; // List of joker IDs // Consts @@ -251,12 +252,15 @@ static const Rect ROUND_END_NUM_HANDS_RECT = {88, 116, UNDEFINED, UNDEF static const Rect HAND_REWARD_RECT = {168, UNDEFINED, UNDEFINED, UNDEFINED }; static const Rect CASHOUT_RECT = {88, 72, UNDEFINED, UNDEFINED }; static const Rect SHOP_REROLL_RECT = {88, 96, UNDEFINED, UNDEFINED }; +#define ITEM_SHOP_Y 71 // TODO: Needs to be a rect? //TODO: Properly define and use #define MENU_POP_OUT_ANIM_FRAMES 20 #define SCORED_CARD_TEXT_Y 48 +#define HIGHLIGHT_COLOR 0xFFFF + #define PITCH_STEP_DISCARD_SFX (-64) #define PITCH_STEP_DRAW_SFX 24 #define PITCH_STEP_UNDISCARD_SFX 2*PITCH_STEP_DRAW_SFX @@ -1010,11 +1014,11 @@ void game_set_state(enum GameState new_game_state) game_state = new_game_state; } -void jokers_avialable_to_shop_init() +void jokers_available_to_shop_init() { int num_defined_jokers = get_joker_registry_size(); - jokers_available_to_shop = int_list_new(num_defined_jokers); - for (int i = 0; i < num_defined_jokers; i++) + jokers_available_to_shop = list_new(num_defined_jokers); + for (intptr_t i = 0; i < num_defined_jokers; i++) { // Add all joker IDs sequentially int_list_append(jokers_available_to_shop, i); @@ -1023,7 +1027,7 @@ void jokers_avialable_to_shop_init() void game_init() { - jokers_avialable_to_shop_init(); + jokers_available_to_shop_init(); hands = max_hands; discards = max_discards; @@ -1797,11 +1801,6 @@ static void held_jokers_update_loop() jokers[i]->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i]); jokers[i]->sprite_object->ty = hand_y; - if (joker_object_is_selected(jokers[i])) - { - jokers[i]->sprite_object->ty -= int2fx(10); - } - joker_object_update(jokers[i]); } } @@ -2145,44 +2144,60 @@ void game_round_end() } } -static void game_shop_create_items(JokerObject *shop_jokers[]) +// Shop +static List *shop_jokers = NULL; +#define REROLL_BASE_COST 5 // Base cost for rerolling the shop items +static int reroll_cost = REROLL_BASE_COST; + +#define NEXT_ROUND_BTN_SEL_X 0 + +#define NEXT_ROUND_BTN_FRAME_PAL_IDX 5 +#define NEXT_ROUND_BTN_PAL_IDX 16 +#define REROLL_BTN_FRAME_PAL_IDX 7 +#define REROLL_BTN_PAL_IDX 3 + +static void game_shop_create_items() { tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); - if (int_list_get_size(jokers_available_to_shop) == 0) + if (list_get_size(jokers_available_to_shop) == 0) { // No jokers to create return; } + shop_jokers = list_new(MAX_SHOP_JOKERS); + for (int i = 0; i < MAX_SHOP_JOKERS; i++) { // TODO: weight the random choice by joker rarity - int joker_idx = random() % int_list_get_size(jokers_available_to_shop); - int joker_id = int_list_get(jokers_available_to_shop, joker_idx); - int_list_remove_by_idx(jokers_available_to_shop, joker_idx); + int joker_idx = random() % list_get_size(jokers_available_to_shop); + intptr_t joker_id = int_list_get(jokers_available_to_shop, joker_idx); + list_remove_by_idx(jokers_available_to_shop, joker_idx); - shop_jokers[i] = joker_object_new(joker_new(joker_id)); - shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32); - shop_jokers[i]->sprite_object->y = int2fx(160); - shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x; - shop_jokers[i]->sprite_object->ty = int2fx(71); + JokerObject *joker_object = joker_object_new(joker_new(joker_id)); - int x = fx2int(shop_jokers[i]->sprite_object->tx) + TILE_SIZE - (get_digits_even(shop_jokers[i]->joker->value) - 1) * TILE_SIZE; - int y = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; - tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value); + joker_object->sprite_object->x = int2fx(120 + i * CARD_SPRITE_SIZE); + joker_object->sprite_object->y = int2fx(160); + joker_object->sprite_object->tx = joker_object->sprite_object->x; + joker_object->sprite_object->ty = int2fx(ITEM_SHOP_Y); - sprite_position(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y)); + int x = fx2int(joker_object->sprite_object->tx) + TILE_SIZE - (get_digits_even(joker_object->joker->value) - 1) * TILE_SIZE; + int y = fx2int(joker_object->sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; + tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, joker_object->joker->value); + + sprite_position(joker_object_get_sprite(joker_object), fx2int(joker_object->sprite_object->x), fx2int(joker_object->sprite_object->y)); + list_append(shop_jokers, joker_object); } } // Intro sequence (menu and shop icon coming into frame) -static void game_shop_intro(JokerObject* shop_jokers[]) +static void game_shop_intro() { main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP); if (timer == 1) { - game_shop_create_items(shop_jokers); + game_shop_create_items(); } if (timer >= 7) // Shift the shop icon @@ -2209,27 +2224,31 @@ static void game_shop_intro(JokerObject* shop_jokers[]) } } -static void game_shop_reroll(JokerObject** shop_jokers, int *reroll_cost) +static void game_shop_reroll(int *reroll_cost) { money -= *reroll_cost; display_money(money); // Update the money display - for (int i = 0; i < MAX_SHOP_JOKERS; i++) + for (int i = 0; i < list_get_size(shop_jokers); i++) { - if (shop_jokers[i] != NULL) + JokerObject *joker_object = list_get(shop_jokers, i); + if (joker_object != NULL) { - int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id); - joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists + int_list_append(jokers_available_to_shop, joker_object->joker->id); + joker_object_destroy(&joker_object); // Destroy the joker object if it exists } } - game_shop_create_items(shop_jokers); + list_destroy(&shop_jokers); + + game_shop_create_items(); - for (int i = 0; i < MAX_SHOP_JOKERS; i++) + for (int i = 0; i < list_get_size(shop_jokers); i++) { - if (shop_jokers[i] != NULL) + JokerObject *joker_object = list_get(shop_jokers, i); + if (joker_object != NULL) { - shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->ty; // Set the y position to the target position - joker_object_shake(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation + joker_object->sprite_object->y = joker_object->sprite_object->ty; // Set the y position to the target position + joker_object_shake(joker_object, UNDEFINED); // Give the joker a little wiggle animation } } @@ -2237,125 +2256,156 @@ static void game_shop_reroll(JokerObject** shop_jokers, int *reroll_cost) tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, *reroll_cost); } -// Shop menu input and selection -static void game_shop_process_user_input(JokerObject** shop_jokers) +// Shop input +static int shop_top_row_get_size() { - static const int REROLL_BASE_COST = 5; // Base cost for rerolling the shop items - static int reroll_cost = REROLL_BASE_COST; + return list_get_size(shop_jokers) + 1; // + 1 to account for next round button +} - // temp variables for future implementation - const ushort max_items_top = MAX_SHOP_JOKERS; - const ushort max_items_bottom = 0; +static void game_shop_buy_joker(int shop_joker_idx) +{ + JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx); + joker_push(joker_object); + money -= joker_object->joker->value; // Deduct the money spent on the joker + display_money(money); // Update the money display + Rect joker_price_rect; + joker_price_rect.left = fx2int(joker_object->sprite_object->tx); + // 2*TILE_SIZE because the item is highlighted and raised by 1 additional tile + joker_price_rect.top = fx2int(joker_object->sprite_object->ty) + CARD_SPRITE_SIZE + 2 * TILE_SIZE; + joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3; + joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE; + + tte_erase_rect_wrapper(joker_price_rect); + + list_remove_by_idx(shop_jokers, shop_joker_idx); // Remove the joker from the shop +} + +static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection) +{ + if (!key_hit(SELECT_CARD)) + return; + + if (selection->x == NEXT_ROUND_BTN_SEL_X) + { + // Go to next blind selection game state + state = 2; // Go to the outro sequence state + timer = 0; // Reset the timer + reroll_cost = REROLL_BASE_COST; + + memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], &pal_bg_mem[6], 1); + + // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1); + // This changes the color of the button to a dark red. + // However, it shares a palette with the shop icon, so it will change the color of the shop icon as well. + // And I don't care enough to fix it right now. + } + else + { + int shop_joker_idx = selection->x - 1; // - 1 to account for next round button + JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx); + if (joker_object == NULL + || jokers_top >= MAX_JOKERS_HELD_SIZE - 1 + || money < joker_object->joker->value) + { + return; + } + + game_shop_buy_joker(shop_joker_idx); + + // In Balatro the selection actually stays on the purchased joker it's easier to just move it left + selection_grid_move_selection_horz(selection_grid, -1); + } +} + + + +static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx, + const Selection* prev_selection, + const Selection* new_selection) +{ + // The selection grid system only guarantees that the new selection is within bounds + if (prev_selection->y == row_idx && prev_selection->x >= 0 && prev_selection->x < shop_top_row_get_size()) + { + if (prev_selection->x == NEXT_ROUND_BTN_SEL_X) + { + // Remove next round button highlight + memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], &pal_bg_mem[NEXT_ROUND_BTN_PAL_IDX], 1); + } + else + { + JokerObject *joker = list_get(shop_jokers, prev_selection->x - 1); + sprite_object_set_focus(joker->sprite_object, false); + // -1 to account for next round button + } + } + + if (new_selection->y == row_idx) + { + if (new_selection->x == NEXT_ROUND_BTN_SEL_X) + { + // Highlight next round button + memset16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], HIGHLIGHT_COLOR, 1); + } + else + { + JokerObject *joker = list_get(shop_jokers, new_selection->x - 1); + sprite_object_set_focus(joker->sprite_object, true); + // -1 to account for next round button + } + } +} + +static int shop_bottom_row_get_size() +{ + return 1; // Currently only the reroll button +} + +static void shop_bottom_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection) +{ + if (row_idx == prev_selection->y) + { + // Remove highlight + memcpy16(&pal_bg_mem[REROLL_BTN_FRAME_PAL_IDX], &pal_bg_mem[REROLL_BTN_PAL_IDX], 1); + } + else if (row_idx == new_selection->y) + { + memset16(&pal_bg_mem[REROLL_BTN_FRAME_PAL_IDX], HIGHLIGHT_COLOR, 1); + } +} + +static void shop_bottom_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection) +{ + if (money >= reroll_cost) + { + game_shop_reroll(&reroll_cost); + } +} + +SelectionGridRow shop_selection_rows[] = +{ + {0, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_hit}, + {1, shop_bottom_row_get_size, shop_bottom_row_on_selection_changed, shop_bottom_row_on_key_hit} +}; + +static const Selection SHOP_INIT_SEL = {-1, 0}; + +SelectionGrid shop_selection_grid = {shop_selection_rows, NUM_ELEM_IN_ARR(shop_selection_rows), SHOP_INIT_SEL}; + +// Shop menu input and selection +static void game_shop_process_user_input() +{ if (timer == 1) { + // The selection grid is initialized outside of bounds and moved + // to trigger the selection change so the initial selection is visible + shop_selection_grid.selection = SHOP_INIT_SEL; + selection_grid_move_selection_horz(&shop_selection_grid, 1); tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost); } // Shop input logic - if (key_hit(KEY_UP)) - { - selection_y = 0; - - if (selection_x > max_items_top) - { - selection_x = max_items_top; - } - } - else if (key_hit(KEY_DOWN)) - { - selection_y = 1; - - if (selection_x > max_items_bottom) - { - selection_x = max_items_bottom; - } - } - else if (key_hit(KEY_LEFT)) - { - if (selection_x > 0) - { - selection_x--; - } - } - else if (key_hit(KEY_RIGHT)) - { - // TODO: Don't allow cursor on sold items - if (selection_y == 0 && selection_x < max_items_top) - { - selection_x++; - } - else if (selection_y == 1 && selection_x < max_items_bottom) - { - selection_x++; - } - } - - memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1); - memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1); - - // Shop selection logic - if (selection_x == 0 && selection_y == 0) // Next round button - { - memset16(&pal_bg_mem[5], 0xFFFF, 1); - - if (key_hit(SELECT_CARD)) - { - // Go to next blind selection game state - state = 2; // Go to the outro sequence state - timer = 0; // Reset the timer - reroll_cost = REROLL_BASE_COST; - - memcpy16(&pal_bg_mem[5], &pal_bg_mem[6], 1); - - // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1); - // This changes the color of the button to a dark red. - // However, it shares a palette with the shop icon, so it will change the color of the shop icon as well. - // And I don't care enough to fix it right now. - } - } - else if (selection_x == 0 && selection_y == 1) // Reroll button - { - memset16(&pal_bg_mem[7], 0xFFFF, 1); - - if (key_hit(SELECT_CARD) && money >= reroll_cost) - { - game_shop_reroll(shop_jokers, &reroll_cost); - } - } - - for (int i = 0; i < MAX_SHOP_JOKERS; i++) // Item selection logic - { - if (shop_jokers[i] != NULL) - { - if (i == selection_x - 1 && selection_y == 0) - { - shop_jokers[i]->sprite_object->ty = int2fx(61); - - if (key_hit(SELECT_CARD) && jokers_top < MAX_JOKERS_HELD_SIZE - 1 && money >= shop_jokers[i]->joker->value) - { - joker_push(shop_jokers[i]); - money -= shop_jokers[i]->joker->value; // Deduct the money spent on the joker - display_money(money); // Update the money display - - Rect joker_price_rect; - joker_price_rect.left = fx2int(shop_jokers[i]->sprite_object->tx); - // 2*TILE_SIZE because the item is highlighted and raised by 1 additional tile - joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + 2*TILE_SIZE; - joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3; - joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE; - - tte_erase_rect_wrapper(joker_price_rect); - - shop_jokers[i] = NULL; // Remove the joker from the shop - } - } - else - { - shop_jokers[i]->sprite_object->ty = int2fx(71); - } - } - } + selection_grid_process_input(&shop_selection_grid); } static void game_shop_lights_anim_frame() @@ -2383,20 +2433,66 @@ static void game_shop_lights_anim_frame() memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1); } +// Outro sequence (menu and shop icon going out of frame) +static void game_shop_outro() +{ + // Shift the shop panel + main_bg_se_move_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_DOWN); + + main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP); + + if (timer == 1) + { + tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text + + for (int i = 0; i < list_get_size(shop_jokers); i++) + { + JokerObject *joker_object = list_get(shop_jokers, i); + if (joker_object != NULL) + { + joker_object->sprite_object->ty = int2fx(160); + } + } + + 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, 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_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); + } + + if (timer >= MENU_POP_OUT_ANIM_FRAMES) + { + state = 3; // Go to the next state + timer = 0; // Reset the timer + } +} void game_shop() { change_background(BG_ID_SHOP); - static JokerObject *shop_jokers[MAX_SHOP_JOKERS] = {NULL}; - - for (int i = 0; i < MAX_SHOP_JOKERS; i++) + if (shop_jokers != NULL) { - if (shop_jokers[i] != NULL) + for (int i = 0; i < list_get_size(shop_jokers); i++) { - joker_object_update(shop_jokers[i]); + JokerObject *joker_object = list_get(shop_jokers, i); + if (joker_object != NULL) + { + joker_object_update(joker_object); + } } } + if (timer % 20 == 0) { @@ -2407,73 +2503,35 @@ void game_shop() { case 0: { - game_shop_intro(shop_jokers); + game_shop_intro(); break; } case 1: { - game_shop_process_user_input(shop_jokers); + game_shop_process_user_input(); break; } - case 2: // Outro sequence (menu and shop icon going out of frame) + case 2: { - // Shift the shop panel - main_bg_se_move_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_DOWN); - - main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP); - - if (timer == 1) - { - tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text - - for (int i = 0; i < MAX_SHOP_JOKERS; i++) - { - if (shop_jokers[i] != NULL) - { - shop_jokers[i]->sprite_object->ty = int2fx(160); - } - } - - 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, 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_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); - } - - if (timer >= MENU_POP_OUT_ANIM_FRAMES) - { - state = 3; // Go to the next state - timer = 0; // Reset the timer - } - + game_shop_outro(); break; } default: state = 0; // Reset the state - selection_x = 0; // Reset the selection - selection_y = 0; // Reset the selection - - for (int i = 0; i < MAX_SHOP_JOKERS; i++) + for (int i = 0; i < list_get_size(shop_jokers); i++) { - if (shop_jokers[i] != NULL) + JokerObject* joker_object = list_get(shop_jokers, i); + if (joker_object != NULL) { - // Make the joker available back to shop - int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id); + // Make the joker available back to shop + int_list_append(jokers_available_to_shop, (intptr_t)joker_object->joker->id); } - joker_object_destroy(&shop_jokers[i]); // Destroy the joker objects + joker_object_destroy(&joker_object); // Destroy the joker objects } + list_destroy(&shop_jokers); + increment_blind(BLIND_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 diff --git a/source/int_list.c b/source/int_list.c index 0b65f3d..1a39dc4 100644 --- a/source/int_list.c +++ b/source/int_list.c @@ -3,10 +3,10 @@ #include "list.h" #include "util.h" -IntList *int_list_new(int init_size) { - IntList *list = (IntList *)malloc(sizeof(IntList)); +List *list_new(int init_size) { + List *list = (List *)malloc(sizeof(List)); if (list == NULL) return NULL; - list->_array = (int *)malloc(sizeof(int) * init_size); + list->_array = (void **)malloc(sizeof(void*) * init_size); if (!list->_array) { free(list); @@ -17,7 +17,7 @@ IntList *int_list_new(int init_size) { return list; } -void int_list_destroy(IntList **list) { +void list_destroy(List **list) { if (list == NULL || *list == NULL) return; { @@ -28,22 +28,28 @@ void int_list_destroy(IntList **list) { *list = NULL; } -bool int_list_append(IntList *list, int value) +bool int_list_append(List *list, intptr_t value) +{ + return list_append(list, (void*)value); +} + +bool list_append(List *list, void *value) { if (list->size >= list->allocated_size) { int new_size = list->allocated_size * 2; - int *new_arr = (int *)realloc(list->_array, sizeof(int) * new_size); + void **new_arr = (void **)realloc(list->_array, sizeof(void*) * new_size); if (new_arr == NULL) return false; list->_array = new_arr; list->allocated_size = new_size; } + list->_array[list->size++] = value; return true; } -bool int_list_remove_by_idx(IntList *list, int index) { +bool list_remove_by_idx(List *list, int index) { if (index < 0 || index >= list->size) return false; for (int i = index; i < list->size - 1; ++i) @@ -54,27 +60,37 @@ bool int_list_remove_by_idx(IntList *list, int index) { return true; } -bool int_list_remove_by_value(IntList *list, int value) +bool list_remove_by_value(List *list, void* value) { for (int i = 0; i < list->size; i++) { if (list->_array[i] == value) { - return int_list_remove_by_idx(list, i); + return list_remove_by_idx(list, i); } } return false; } -int int_list_get(IntList *list, int index) +bool int_list_remove_by_value(List *list, intptr_t value) +{ + return list_remove_by_value(list, (void*)value); +} + +void* list_get(List *list, int index) { if (index < 0 || index >= list->size) - return 0; + return NULL; return list->_array[index]; } -int int_list_get_size(IntList *list) +intptr_t int_list_get(List *list, int index) +{ + return (intptr_t)list_get(list, index); +} + +int list_get_size(List *list) { if (list == NULL) { diff --git a/source/selection_grid.c b/source/selection_grid.c new file mode 100644 index 0000000..8acc017 --- /dev/null +++ b/source/selection_grid.c @@ -0,0 +1,82 @@ +#include "selection_grid.h" + + +static void selection_grid_process_directional_input(SelectionGrid *selection_grid) +{ + int horz_tri_input = bit_tribool(key_hit(KEY_ANY), KI_RIGHT, KI_LEFT); + + if (horz_tri_input != 0) + { + selection_grid_move_selection_horz(selection_grid, horz_tri_input); + /* Avoid handling both vertical and horizontal input at the same time, + * it creates all sorts of difficult edge cases. + */ + return; + } + + int vert_tri_input = bit_tribool(key_hit(KEY_ANY), KI_DOWN, KI_UP); + + + if (vert_tri_input != 0) + { + selection_grid_move_selection_vert(selection_grid, vert_tri_input); + } + +} + +void selection_grid_move_selection_horz(SelectionGrid* selection_grid, + int direction_tribool) +{ + Selection new_selection = selection_grid->selection; + new_selection.x += direction_tribool; + if( selection_grid->selection.y >= 0 + && selection_grid->selection.y < selection_grid->num_rows + && new_selection.x >= 0 + && new_selection.x < selection_grid->rows[new_selection.y].get_size()) + { + selection_grid->rows[new_selection.y].on_selection_changed(selection_grid, new_selection.y, &selection_grid->selection, &new_selection); + selection_grid->selection = new_selection; + } +} + +void selection_grid_move_selection_vert(SelectionGrid *selection_grid, int direction_tribool) +{ + Selection selection = selection_grid->selection; + Selection new_selection = selection; + new_selection.y += direction_tribool; + + if (new_selection.y >= 0 + && new_selection.y < selection_grid->num_rows) + { + int new_row_size = selection_grid->rows[new_selection.y].get_size(); + if (selection.x >= new_row_size) + { + // TODO: Maintain relative horizontal position + // Clip selection to row size + new_selection.x = new_row_size - 1; + } + if (selection.y >= 0 && selection.y < selection_grid->num_rows) + { + selection_grid->rows[selection.y].on_selection_changed(selection_grid, selection.y, &selection, &new_selection); + } + selection_grid->rows[new_selection.y].on_selection_changed(selection_grid, new_selection.y, &selection, &new_selection); + selection_grid->selection = new_selection; + } +} + +void selection_grid_process_input(SelectionGrid *selection_grid) +{ + if (selection_grid == NULL || selection_grid->rows == NULL) + return; + + selection_grid_process_directional_input(selection_grid); + + u32 non_directional_key = KEY_ANY & ~KEY_DIR; + if (key_hit(non_directional_key)) + { + Selection* selection = &selection_grid->selection; // To make the next line shorter and more readable + selection_grid->rows[selection->y].on_key_hit(selection_grid, selection); + } +} + + diff --git a/source/sprite.c b/source/sprite.c index df1e917..90cb7b5 100644 --- a/source/sprite.c +++ b/source/sprite.c @@ -1,6 +1,7 @@ #include "sprite.h" #include "util.h" #include "audio_utils.h" +#include "soundbank.h" #include #include @@ -8,6 +9,7 @@ #define MAX_SPRITES 128 #define MAX_AFFINES 32 +#define SPRITE_FOCUS_RAISE_PX 10 OBJ_ATTR obj_buffer[MAX_SPRITES]; OBJ_AFFINE *obj_aff_buffer = (OBJ_AFFINE*)obj_buffer; @@ -239,3 +241,20 @@ Sprite* sprite_object_get_sprite(SpriteObject* sprite_object) return NULL; return sprite_object->sprite; } + +void sprite_object_set_focus(SpriteObject* sprite_object, bool focus) +{ + if (sprite_object->focused == focus) + { + return; + } + sprite_object->focused = focus; + + play_sfx(SFX_CARD_FOCUS , MM_BASE_PITCH_RATE + rand() % 512); + sprite_object->ty = sprite_object->ty + int2fx((focus ? -1 : 1) * SPRITE_FOCUS_RAISE_PX); +} + +bool sprite_object_is_focused(SpriteObject* sprite_object) +{ + return sprite_object->focused; +}