diff --git a/.gitignore b/.gitignore index fc00239..e30fdf5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ /build /.vs *.sln -*.vcxproj* -/balatro-gba/x64/** +*.vcxproj* +/balatro-gba/x64/** /tests/bitset/build /tests/list/build /tests/pool/build +tests/util/.vscode +tests/util/build diff --git a/graphics/background_blind_select_gfx.png b/graphics/background_blind_select_gfx.png index d7335e5..b842183 100644 Binary files a/graphics/background_blind_select_gfx.png and b/graphics/background_blind_select_gfx.png differ diff --git a/graphics/background_gfx.png b/graphics/background_gfx.png index 432ac04..a7b3722 100644 Binary files a/graphics/background_gfx.png and b/graphics/background_gfx.png differ diff --git a/graphics/background_shop_gfx.png b/graphics/background_shop_gfx.png index c06ebdb..0de4cd3 100644 Binary files a/graphics/background_shop_gfx.png and b/graphics/background_shop_gfx.png differ diff --git a/include/blind.h b/include/blind.h index d001348..09a99c6 100644 --- a/include/blind.h +++ b/include/blind.h @@ -72,7 +72,7 @@ void blind_init(); void blind_set_boss_graphics(const unsigned int* tiles, const u16* palette); -int blind_get_requirement(enum BlindType type, int ante); +u32 blind_get_requirement(enum BlindType type, int ante); int blind_get_reward(enum BlindType type); u16 blind_get_color(enum BlindType type, enum BlindColorIndex index); diff --git a/include/graphic_utils.h b/include/graphic_utils.h index f0da0f1..66d6710 100644 --- a/include/graphic_utils.h +++ b/include/graphic_utils.h @@ -50,16 +50,23 @@ #define SE_COL_LEN 32 // Since y direction goes from the top of the screen to the bottom -#define SCREEN_UP -1 -#define SCREEN_DOWN 1 -#define SCREEN_LEFT -1 -#define SCREEN_RIGHT 1 +enum ScreenVertDir +{ + SCREEN_UP = -1, + SCREEN_DOWN = 1 +}; -#define SE_UP SCREEN_UP -#define SE_DOWN SCREEN_DOWN +enum ScreenHorzDir +{ + SCREEN_LEFT = -1, + SCREEN_RIGHT = 1 +}; -#define OVERFLOW_LEFT SCREEN_LEFT -#define OVERFLOW_RIGHT SCREEN_RIGHT +enum OverflowDir +{ + OVERFLOW_LEFT = SCREEN_LEFT, + OVERFLOW_RIGHT = SCREEN_RIGHT +}; // Tile size in pixels, both height and width as tiles are square #define TILE_SIZE 8 @@ -79,25 +86,21 @@ SE main_bg_se_get_se(BG_POINT pos); INLINE int rect_width(const Rect* rect) { - /* Extra parens to avoid issues in case compiler turns INLINE into macro - * Not sure if necessary, could be just paranoia - */ - return (((rect)->right) - ((rect)->left) + 1); + return max(0, rect->right - rect->left + 1); } INLINE int rect_height(const Rect* rect) { - return (((rect)->bottom) - ((rect)->top) + 1); + return max(0, rect->bottom - rect->top + 1); } /* Copies an SE rect vertically in direction by a single tile. * bg_sbb is the SBB of the background in which to move the rect - * direction must be either SE_UP or SE_DOWN. * se_rect dimensions are in number of tiles. * * NOTE: This does not work with TTE_SBB, probably because it's 4BPP... */ -void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction); +void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction); /* Clears a rect in the main background. * The se_rect dimensions need to be in number of tiles. @@ -105,10 +108,9 @@ void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction); void main_bg_se_clear_rect(Rect se_rect); /* Copies a rect in the main background vertically in direction by a single tile. - * direction must be either SE_UP or SE_DOWN. * se_rect dimensions are in number of tiles. */ -void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction); +void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction); /* Copies a rect in the main background from se_rect to the position (x, y). * se_rect dimensions are in number of tiles. @@ -134,27 +136,46 @@ void main_bg_se_copy_expand_3x3_rect(Rect se_rect_dest, BG_POINT se_rect_src_3x3 /* Moves a rect in the main background vertically in direction by a single tile. * Note that tiles in the previous location will be transparent (0x000) * so maybe copy would be a better choice if you don't want to delete things - * direction must be either SE_UP or SE_DOWN. * se_rect dimensions are in number of tiles. */ -void main_bg_se_move_rect_1_tile_vert(Rect se_rect, int direction); +void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction); // A wrapper for tte_erase_rect that would use the rect struct void tte_erase_rect_wrapper(Rect rect); -/* Changes rect->left so it fits the digits of num exactly when right aligned to rect->right. - * Assumes num is not negative. +/* Changes rect->left so it fits a string exactly when right aligned to rect->right. * - * overflow_direction determines the direction the number will overflow - * if it's too large to fit inside the rect. - * Should be either OVERFLOW_LEFT or OVERFLOW_RIGHT. + * overflow_direction determines the direction the string will overflow + * if it's too large to fit inside the rect. * - * The rect is in number of pixels but should be a multiple of TILE_SIZE + * The rect is in number of pixels but should be a multiple of TTE_CHAR_SIZE * so it's a whole number of tiles to fit TTE characters * * Note that both rect->left and rect-right need to be defined, top and bottom don't matter */ -void update_text_rect_to_right_align_num(Rect* rect, int num, int overflow_direction); +void update_text_rect_to_right_align_str(Rect* rect, const char* str, enum OverflowDir overflow_direction); + + +/** + * @brief Updates a rect so a string is centered within it. + * + * @param rect The rect provided, the provided values are used to determine the center + * and it is then updated so the string starting in rect->left is centered + * The rect is in number of pixels but should be a multiple of TTE_CHAR_SIZE + * so it's a whole number of tiles to fit TTE characters. + * + * @param str The string, the center of the string will be at the center of the updated rect. + * + * @param bias_direction Which direction to bias when the string can't be evenly centered + * with respect to char tiles. + * Examples: + * | |S|T|R| | - Can be evenly centered, bias has no effect + * | | |S|T|R| | - Bias right + * | |S|T|R| | | - Bias left + * |A|B|C|D| | - Bias left + * | |A|B|C|D| - Bias right + */ +void update_text_rect_to_center_str(Rect* rect, const char* str, enum ScreenHorzDir bias_direction); /*Copies 16 bit data from src to dst, applying a palette offset to the data. * This is intended solely for use with tile8/8bpp data for dst and src. diff --git a/include/util.h b/include/util.h index 4c2fd7e..413f0d9 100644 --- a/include/util.h +++ b/include/util.h @@ -3,9 +3,10 @@ #include + #define GBLA_UNUSED __attribute__((unused)) -static inline int get_digits(int n) // https://stackoverflow.com/questions/1068849/how-do-i-determine-the-number-of-digits-of-an-integer-in-c +static inline int u32_get_digits(uint32_t n) // https://stackoverflow.com/questions/1068849/how-do-i-determine-the-number-of-digits-of-an-integer-in-c { if (n < 10) return 1; if (n < 100) return 2; @@ -19,7 +20,7 @@ static inline int get_digits(int n) // https://stackoverflow.com/questions/10688 return 10; } -static inline int get_digits_odd(int n) +static inline int get_digits_even(int n) { if (n < 100) return 1; if (n < 10000) return 2; @@ -28,15 +29,6 @@ static inline int get_digits_odd(int n) return 5; } -static inline int get_digits_even(int n) -{ - if (n < 10) return 1; - if (n < 1000) return 2; - if (n < 100000) return 3; - if (n < 10000000) return 4; - return 5; -} - #define UNDEFINED -1 #define NUM_ELEM_IN_ARR(arr) (sizeof(arr) / sizeof((arr)[0])) @@ -45,8 +37,45 @@ static inline int get_digits_even(int n) #define UINT_MAX_DIGITS 10 // strlen(str(UINT32_MAX)) = strlen("4294967295") #define UINT8_MAX_DIGITS 3 // strlen(str(UINT8_MAX)) = strlen("255") +#define ONE_K 1000 +#define ONE_M 1000000 +#define ONE_B 1000000000 + +#define ONE_K_ZEROS 3 +#define ONE_M_ZEROS 6 +#define ONE_B_ZEROS 9 + +// The suffix replaces everything past the third digit, e.g. "999K" -> "1M" +// so it needs at least this number of chars to be able to display any suffixed number +#define SUFFIXED_NUM_MIN_REQ_CHARS 4 + int int_arr_max(int int_arr[], int size); +/** + * @brief Truncate an unsigned number into a suffixed string representation e.g. 12000 -> "12K" + * The least significant digits are rounded down e.g. 12345 -> "12K", 12987 -> "12K" + * + * @param num The number to truncate, can be anything from 0 to UINT32_MAX. + * + * @param num_req_chars The number of characters to constrain the string to. + * The function will use up as much characters as it can + * in order to maintain as much accuracy as possible. + * So numbers are not fully truncated if not necessary, + * e.g. 123123000 -> "123123K" for example value 7, + * and if num_req_chars > u32_get_digits(num) the number will not + * be truncated at all. + * Passing less than SUFFIXED_NUM_MIN_REQ_CHARS may result in an + * output string longer than num_req_chars but + * can be done to truncate 1000s -> "1K", 2000 -> "2K" etc. + * which wouldn't be otherwise. + * + * @param out_str An output buffer to write the resulting string to. + * Must be of size UINT_MAX_DIGITS + 1. + 1 for null-terminator. + * At that size the suffix character will always be accounted for since + * a number with more digits than UINT_MAX_DIGITS will not be handled nor truncated. + */ +void truncate_uint_to_suffixed_str(uint32_t num, int num_req_chars, char out_str[UINT_MAX_DIGITS + 1]); + uint32_t u32_protected_add (uint32_t a, uint32_t b); uint16_t u16_protected_add (uint16_t a, uint16_t b); uint32_t u32_protected_mult(uint32_t a, uint32_t b); diff --git a/source/blind.c b/source/blind.c index d1f1078..55326c1 100644 --- a/source/blind.c +++ b/source/blind.c @@ -8,9 +8,10 @@ #include -// +1 is added because we'll actually be indexing at 1, but if something causes you to go to ante 0, there will still be -// a value there. -static const int ante_lut[MAX_ANTE + 1] = {100, 300, 800, 2000, 5000, 11000, 20000, 35000, 50000}; +// Maps the ante number to the base blind requirement for that ante. +// The game starts at ante 1 which is at index 1 for base requirement 300. +// Ante 0 is also there in case it is ever reached. +static const u32 ante_lut[] = {100, 300, 800, 2000, 5000, 11000, 20000, 35000, 50000}; // Palettes for the blinds (Transparency, Text Color, Shadow, Highlight, Main Color) Use this: // http://www.budmelvin.com/dev/15bitconverter.html @@ -69,7 +70,7 @@ void blind_init() return; } -int blind_get_requirement(enum BlindType type, int ante) +u32 blind_get_requirement(enum BlindType type, int ante) { // Ensure ante is within valid range if (ante < 0 || ante > MAX_ANTE) diff --git a/source/game.c b/source/game.c index b84fe3b..5da5bca 100644 --- a/source/game.c +++ b/source/game.c @@ -342,20 +342,6 @@ static inline Card *discard_pop() 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) CardObject **get_hand_array(void) { @@ -552,6 +538,7 @@ static const Rect TOP_LEFT_PANEL_ANIM_RECT = {0, 0, 8, 4 }; * TOP_LEFT_PANEL_ANIM_RECT should be used for animations, * TOP_LEFT_PANEL_RECT for copies etc. but mind the overlap */ +static const Rect TOP_LEFT_PANEL_BOTTOM_ROW_RESET_RECT = {0, 28, 8, 28 }; static const BG_POINT TOP_LEFT_BLIND_TITLE_POINT = {0, 21, }; static const Rect BIG_BLIND_TITLE_SRC_RECT = {0, 26, 8, 26 }; static const Rect BOSS_BLIND_TITLE_SRC_RECT = {0, 27, 8, 27 }; @@ -580,7 +567,7 @@ static const Rect HAND_SIZE_RECT_PLAYING = {128, 152, 152, 160 }; static const Rect HAND_TYPE_RECT = {8, 64, 64, 72 }; // Score displayed in the same place as the hand type static const Rect TEMP_SCORE_RECT = {8, 64, 64, 72 }; -static const Rect SCORE_RECT = {32, 48, 64, 56 }; +static const Rect SCORE_RECT = {24, 48, 64, 56 }; static const Rect PLAYED_CARDS_SCORES_RECT = {72, 48, 240, 56 }; static const Rect HELD_CARDS_SCORES_RECT = {72, 108, 240, 116 }; @@ -647,9 +634,6 @@ static const BG_POINT MAIN_MENU_ACE_T = {88, 26}; #define STARTING_MONEY 4 #define STARTING_SCORE 0 -#define TEN_K 10000 -#define ONE_K 1000 - #define CARD_FOCUSED_UNSEL_Y 10 #define CARD_UNFOCUSED_SEL_Y 15 #define CARD_FOCUSED_SEL_Y 20 @@ -886,6 +870,16 @@ void bg_copy_current_item_to_top_left_panel() main_bg_se_copy_rect(TOP_LEFT_ITEM_SRC_RECT, TOP_LEFT_PANEL_POINT); } +// 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() +{ + BG_POINT top_left_panel_bottom_row_pos = TOP_LEFT_PANEL_POINT; + // Use the source rect height to offset to the bottom row point + top_left_panel_bottom_row_pos.y += rect_height(&TOP_LEFT_ITEM_SRC_RECT) - 1; + main_bg_se_copy_rect(TOP_LEFT_PANEL_BOTTOM_ROW_RESET_RECT, top_left_panel_bottom_row_pos); +} + void change_background(enum BackgroundId id) { if (background == id) @@ -948,7 +942,7 @@ void change_background(enum BackgroundId id) for (int i = 0; i <= 2; i++) { - main_bg_se_move_rect_1_tile_vert(HAND_BG_RECT_SELECTING, SE_DOWN); + main_bg_se_move_rect_1_tile_vert(HAND_BG_RECT_SELECTING, SCREEN_DOWN); } tte_erase_rect_wrapper(HAND_SIZE_RECT_SELECT); @@ -1046,7 +1040,7 @@ void change_background(enum BackgroundId id) int x_from = 0; int y_from = 27; - main_bg_se_copy_rect_1_tile_vert(curr_blind_rect, SE_UP); + main_bg_se_copy_rect_1_tile_vert(curr_blind_rect, SCREEN_UP); int x_to = curr_blind_rect.left; int y_to = 31; @@ -1129,53 +1123,49 @@ void change_background(enum BackgroundId id) void display_temp_score(u32 value) { - int x_offset = 40 - get_digits_even(value) * TILE_SIZE; + char temp_score_str_buff[UINT_MAX_DIGITS + 1]; + Rect temp_score_rect = TEMP_SCORE_RECT; + truncate_uint_to_suffixed_str(value, rect_width(&temp_score_rect)/TTE_CHAR_SIZE, temp_score_str_buff); + update_text_rect_to_center_str(&temp_score_rect, temp_score_str_buff, SCREEN_RIGHT); + tte_erase_rect_wrapper(TEMP_SCORE_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000}%lu", x_offset, TEMP_SCORE_RECT.top, TTE_WHITE_PB, value); + tte_printf("#{P:%d,%d; cx:0x%X000}%s", temp_score_rect.left, temp_score_rect.top, TTE_WHITE_PB, temp_score_str_buff); } void display_score(u32 value) { + Rect score_rect = SCORE_RECT; // Clear the existing text before redrawing tte_erase_rect_wrapper(SCORE_RECT); - char score_suffix = ' '; - u32 display_value = value; + char score_str_buff[UINT_MAX_DIGITS + 1]; + + truncate_uint_to_suffixed_str(value, rect_width(&score_rect)/TTE_CHAR_SIZE, score_str_buff); + update_text_rect_to_center_str(&score_rect, score_str_buff, SCREEN_RIGHT); - if(value >= TEN_K) - { - score_suffix = 'k'; - display_value = value / ONE_K; // 12,986 = 12k - } - - // Calculate text width: digits + suffix character (if 'k') - int num_digits = get_digits(display_value); - int text_width = num_digits * TILE_SIZE; - if(score_suffix == 'k') - { - text_width += TILE_SIZE; // Add width for 'k' suffix - } - - // Calculate center position within SCORE_RECT - int rect_width = SCORE_RECT.right - SCORE_RECT.left; - int x_offset = SCORE_RECT.left + (rect_width - text_width) / 2; - - tte_printf("#{P:%d,48; cx:0x%X000}%lu%c", x_offset, TTE_WHITE_PB, display_value, score_suffix); + tte_printf("#{P:%d,%d; cx:0x%X000}%s", score_rect.left, score_rect.top, TTE_WHITE_PB, score_str_buff); } void display_money() { - int x_offset = 32 - get_digits_odd(money) * TILE_SIZE; + Rect money_text_rect = MONEY_TEXT_RECT; tte_erase_rect_wrapper(MONEY_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x_offset, MONEY_TEXT_RECT.top, TTE_YELLOW_PB, money); + + char money_str_buff[INT_MAX_DIGITS + 2]; // + 2 for null terminator and "$" sign + snprintf(money_str_buff, sizeof(money_str_buff), "$%d", money); + + // Bias left so the number is centered and the "$" sign is on the left + update_text_rect_to_center_str(&money_text_rect, money_str_buff, SCREEN_LEFT); + + tte_printf("#{P:%d,%d; cx:0x%X000}%s", money_text_rect.left, money_text_rect.top, TTE_YELLOW_PB, money_str_buff); } // Show/Hide flaming score effect if we will score // more than the required amount or not void check_flaming_score() { - int curr_score = chips * mult; - int required_score = blind_get_requirement(current_blind, ante); + u32 curr_score = u32_protected_mult(chips, mult); + u32 required_score = blind_get_requirement(current_blind, ante); if (curr_score >= required_score && !score_flames_active) { // start flaming score @@ -1198,16 +1188,32 @@ void check_flaming_score() void display_chips() { Rect chips_text_rect = CHIPS_TEXT_RECT; - tte_erase_rect_wrapper(CHIPS_TEXT_RECT); - update_text_rect_to_right_align_num(&chips_text_rect, chips, OVERFLOW_LEFT); - tte_printf("#{P:%d,%d; cx:0x%X000;}%lu", chips_text_rect.left, chips_text_rect.top, TTE_WHITE_PB, chips); + + // In case of overflow, the rect overflow left by 1 char + Rect chips_text_overflow_rect = chips_text_rect; + chips_text_overflow_rect.left -= TTE_CHAR_SIZE; + tte_erase_rect_wrapper(chips_text_overflow_rect); + + char chips_str_buff[UINT_MAX_DIGITS + 1]; + truncate_uint_to_suffixed_str(chips, rect_width(&chips_text_rect)/TTE_CHAR_SIZE, chips_str_buff); + + update_text_rect_to_right_align_str(&chips_text_rect, chips_str_buff, OVERFLOW_LEFT); + + tte_printf("#{P:%d,%d; cx:0x%X000;}%s", chips_text_rect.left, chips_text_rect.top, TTE_WHITE_PB, chips_str_buff); check_flaming_score(); } void display_mult() { - tte_erase_rect_wrapper(MULT_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0x%X000;}%lu", MULT_TEXT_RECT.left, MULT_TEXT_RECT.top, TTE_WHITE_PB, mult); + Rect mult_text_overflow_rect = MULT_TEXT_RECT; + // In case of overflow the rect will overflow right by 1 char + mult_text_overflow_rect.right += TTE_CHAR_SIZE; + tte_erase_rect_wrapper(mult_text_overflow_rect); + + char mult_str_buff[UINT_MAX_DIGITS + 1]; + truncate_uint_to_suffixed_str(mult, rect_width(&MULT_TEXT_RECT)/TTE_CHAR_SIZE, mult_str_buff); + + tte_printf("#{P:%d,%d; cx:0x%X000;}%s", MULT_TEXT_RECT.left, MULT_TEXT_RECT.top, TTE_WHITE_PB, mult_str_buff); check_flaming_score(); } @@ -1415,30 +1421,16 @@ static void game_round_on_init() } Rect blind_req_text_rect = BLIND_REQ_TEXT_RECT; - int blind_requirement = blind_get_requirement(current_blind, ante); + u32 blind_requirement = blind_get_requirement(current_blind, ante); - // TODO: Address Copilot review at - // https://github.com/cellos51/balatro-gba/pull/46#pullrequestreview-3045772903 - char score_suffix = ' '; - if(blind_requirement >= TEN_K) - { - // clear existing text - tte_erase_rect_wrapper(blind_req_text_rect); - - score_suffix = 'k'; - blind_requirement /= ONE_K; // 11,000 = 11k - } + char blind_req_str_buff[UINT_MAX_DIGITS + 1]; + + truncate_uint_to_suffixed_str(blind_requirement, rect_width(&BLIND_REQ_TEXT_RECT)/TTE_CHAR_SIZE, blind_req_str_buff); // Update text rect for right alignment AFTER shortening the number - update_text_rect_to_right_align_num(&blind_req_text_rect, blind_requirement, OVERFLOW_RIGHT); - - // If we added a suffix, adjust position to account for the extra character - if(score_suffix == 'k') - { - blind_req_text_rect.left -= TILE_SIZE; // Move left by one character width to make room for 'k' - } + update_text_rect_to_right_align_str(&blind_req_text_rect, blind_req_str_buff, OVERFLOW_RIGHT); - tte_printf("#{P:%d,%d; cx:0x%X000}%d%c", blind_req_text_rect.left, blind_req_text_rect.top, TTE_RED_PB, blind_requirement, score_suffix); // Blind requirement + tte_printf("#{P:%d,%d; cx:0x%X000}%s", blind_req_text_rect.left, blind_req_text_rect.top, TTE_RED_PB, blind_req_str_buff); tte_printf("#{P:%d,%d; cx:0x%X000}$%d", BLIND_REWARD_RECT.left, BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_get_reward(current_blind)); // Blind reward deck_shuffle(); // Shuffle the deck at the start of the round @@ -2748,7 +2740,7 @@ static void game_round_end_start() 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, SCREEN_UP); if (timer == TM_END_POP_MENU_ANIM) { @@ -2762,7 +2754,7 @@ static void game_round_end_extend_black_panel_down(int black_panel_bottom) Rect single_line_rect = ROUND_END_MENU_RECT; single_line_rect.bottom = black_panel_bottom; single_line_rect.top = single_line_rect.bottom - 1; - main_bg_se_copy_rect_1_tile_vert(single_line_rect, SE_DOWN); + main_bg_se_copy_rect_1_tile_vert(single_line_rect, SCREEN_DOWN); } static void game_round_end_display_finished_blind() @@ -2773,10 +2765,18 @@ static void game_round_end_display_finished_blind() if (current_blind == BLIND_TYPE_BOSS) 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); - update_text_rect_to_right_align_num(&blind_req_rect, blind_req, OVERFLOW_RIGHT); + u32 blind_req = blind_get_requirement(current_blind, current_ante); + + /* Not bothering to truncate here because there are 8 tiles + * and the blind requirement will not increase past ante 8 + * so there's enough room for sure. + */ + char blind_req_str_buff[UINT_MAX_DIGITS + 1]; + snprintf(blind_req_str_buff, sizeof(blind_req_str_buff), "%lu", blind_req); + + update_text_rect_to_right_align_str(&blind_req_rect, blind_req_str_buff, OVERFLOW_RIGHT); - tte_printf("#{P:%d,%d; cx:0x%X000}%d", blind_req_rect.left, blind_req_rect.top, TTE_RED_PB, blind_req); + tte_printf("#{P:%d,%d; cx:0x%X000}%s", blind_req_rect.left, blind_req_rect.top, TTE_RED_PB, blind_req_str_buff); if (timer == TM_START_ROUND_END_REWARDS_ANIM) { @@ -2836,7 +2836,7 @@ static void game_round_end_panel_exit() // magic numbers. if (timer < 8) { - main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP); + main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SCREEN_UP); if (timer == 1) // Copied from shop. Feels slightly too niche of a function for me personally to make one. { @@ -2989,7 +2989,7 @@ static void game_round_end_dismiss_round_end_panel() { Rect round_end_down = ROUND_END_MENU_RECT; round_end_down.top--; - main_bg_se_copy_rect_1_tile_vert(round_end_down, SE_DOWN); + main_bg_se_copy_rect_1_tile_vert(round_end_down, SCREEN_DOWN); if (timer >= TM_DISMISS_ROUND_END_TM) { @@ -3115,7 +3115,7 @@ static void game_shop_create_items() // Intro sequence (menu and shop icon coming into frame) static void game_shop_intro() { - 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, SCREEN_UP); if (timer == TM_CREATE_SHOP_ITEMS_WAIT) { @@ -3426,9 +3426,9 @@ static void game_shop_lights_anim_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_move_rect_1_tile_vert(POP_MENU_ANIM_RECT, SCREEN_DOWN); - main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP); + main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SCREEN_UP); // TODO: make heads or tails of what's going on here and replace // magic numbers. @@ -3531,7 +3531,7 @@ static void game_blind_select_on_update() static void game_blind_select_start_anim_seq() { change_background(BG_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, SCREEN_UP); for (int i = 0; i < BLIND_TYPE_MAX; i++) { @@ -3579,7 +3579,7 @@ static void game_blind_select_handle_input() // TODO: Create a generic vertical move by any number of tiles to avoid for loops? for (int i = 0; i < 12; i++) { - 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, SCREEN_UP); } for (int i = 0; i < BLIND_TYPE_MAX; i++) @@ -3611,7 +3611,7 @@ static void game_blind_select_selected_anim_seq() { Rect blinds_rect = POP_MENU_ANIM_RECT; blinds_rect.top -= 1; // Because of the raised blind - main_bg_se_move_rect_1_tile_vert(blinds_rect, SE_DOWN); + main_bg_se_move_rect_1_tile_vert(blinds_rect, SCREEN_DOWN); for (int i = 0; i < BLIND_TYPE_MAX; i++) { @@ -3817,7 +3817,7 @@ static void jokers_update_loop() 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, SCREEN_UP); } static void game_lose_on_update() diff --git a/source/graphic_utils.c b/source/graphic_utils.c index d85f55b..9e2cbf1 100644 --- a/source/graphic_utils.c +++ b/source/graphic_utils.c @@ -4,6 +4,7 @@ #include "util.h" #include "graphic_utils.h" +#include const Rect FULL_SCREENBLOCK_RECT = { 0, 0, SE_ROW_LEN - 1, SE_COL_LEN - 1}; @@ -31,14 +32,14 @@ SE main_bg_se_get_se(BG_POINT pos) // Clips a rect of screenblock entries to be within one step of // screenblock boundaries vertically depending on direction. -static void clip_se_rect_within_step_of_full_screen_vert(Rect* se_rect, int direction) +static void clip_se_rect_within_step_of_full_screen_vert(Rect* se_rect, enum ScreenVertDir direction) { Rect bounding_rect = FULL_SCREENBLOCK_RECT; - if (direction == SE_UP) + if (direction == SCREEN_UP) { bounding_rect.top += 1; } - else if (direction == SE_DOWN) + else if (direction == SCREEN_DOWN) { bounding_rect.bottom -= 1; } @@ -60,10 +61,10 @@ void main_bg_se_clear_rect(Rect se_rect) } // Internal static function to merge implementation of move/copy functions. -static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction, bool move) +static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction, bool move) { if (se_rect.left > se_rect.right - || (direction != SE_UP && direction != SE_DOWN)) + || (direction != SCREEN_UP && direction != SCREEN_DOWN)) { return; } @@ -71,8 +72,8 @@ static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int di // Clip to avoid read/write overflow of the screenblock clip_se_rect_within_step_of_full_screen_vert(&se_rect, direction); - int start = (direction == SE_UP) ? se_rect.top : se_rect.bottom; - int end = (direction == SE_UP) ? se_rect.bottom : se_rect.top; + int start = (direction == SCREEN_UP) ? se_rect.top : se_rect.bottom; + int end = (direction == SCREEN_UP) ? se_rect.bottom : se_rect.top; for (int y = start; y != end - direction; y -= direction) { @@ -87,27 +88,27 @@ static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int di } } -static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, int direction, bool move) +static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction, bool move) { bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, move); } -void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction) +void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction) { bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, false); } -void bg_se_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction) +void bg_se_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, enum ScreenVertDir direction) { bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, true); } -void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction) +void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction) { main_bg_se_copy_or_move_rect_1_tile_vert(se_rect, direction, false); } -void main_bg_se_move_rect_1_tile_vert(Rect se_rect, int direction) +void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction) { main_bg_se_copy_or_move_rect_1_tile_vert(se_rect, direction, true); } @@ -235,22 +236,41 @@ void tte_erase_rect_wrapper(Rect rect) tte_erase_rect(rect.left, rect.top, rect.right, rect.bottom); } -void update_text_rect_to_right_align_num(Rect* rect, int num, int overflow_direction) +void update_text_rect_to_right_align_str(Rect* rect, const char* str, enum OverflowDir overflow_direction) { - int num_digits = get_digits(num); + // TODO: Allow passing string length to avoid calling strlen()? + int str_len = strlen(str); if (overflow_direction == OVERFLOW_LEFT) { - rect->left = max(0, rect->right - num_digits * TILE_SIZE); + rect->left = max(0, rect->right - str_len * TTE_CHAR_SIZE); } else if (overflow_direction == OVERFLOW_RIGHT) { - int num_fitting_digits = rect_width(rect) / TILE_SIZE; - if (num_digits < num_fitting_digits) - rect->left += (num_fitting_digits - num_digits) * TILE_SIZE; + int num_fitting_chars = rect_width(rect) / TTE_CHAR_SIZE; + if (str_len < num_fitting_chars) + rect->left += (num_fitting_chars - str_len) * TTE_CHAR_SIZE; //else nothing is to be updated, entire rect is filled and may overflow } } +void update_text_rect_to_center_str(Rect* rect, const char* str, enum ScreenHorzDir bias_direction) +{ + if (rect == NULL || str == NULL) + { + return; + } + + int text_width_chars = strlen(str); + int rect_width_chars = rect_width(rect) / TTE_CHAR_SIZE; + + bool bias_right = (bias_direction == SCREEN_RIGHT); + + /* Adding bias_right makes sure that we round up when biased right + * but round down when biased left. + */ + rect->left += max(0, (rect_width_chars - text_width_chars + bias_right) / 2) * TTE_CHAR_SIZE; +} + void memcpy16_tile8_with_palette_offset(u16* dst, const u16* src, uint hwcount, u8 palette_offset) { const u16 offset = (((palette_offset) << 8) | (palette_offset)); diff --git a/source/util.c b/source/util.c index 6e8afff..2f623ad 100644 --- a/source/util.c +++ b/source/util.c @@ -1,5 +1,7 @@ #include "util.h" #include +#include +#include int int_arr_max(int int_arr[], int size) { @@ -15,6 +17,44 @@ int int_arr_max(int int_arr[], int size) return max; } +void truncate_uint_to_suffixed_str(uint32_t num, int num_req_chars, char out_str_buff[UINT_MAX_DIGITS + 1]) +{ + bool inevitable_overflow = num_req_chars < SUFFIXED_NUM_MIN_REQ_CHARS; + if (inevitable_overflow) + { + num_req_chars = SUFFIXED_NUM_MIN_REQ_CHARS; + } + + int num_digits = u32_get_digits(num); + int overflow_size = num_digits - num_req_chars; + char* suffix = ""; + + /* If there is overflow, divide by the next suffixed power of 10 + * to truncate the number back within num_req_chars. + * UINT32_MAX is in the billions so no need to check larger numbers + * or perform complex mathematical operations. + */ + if (overflow_size >= ONE_M_ZEROS) + { + num /= ONE_B; + suffix = "B"; + } + else if (overflow_size >= ONE_K_ZEROS) + { + num /= ONE_M; + suffix = "M"; + } + else if (overflow_size > 0 + || (inevitable_overflow && num_digits == SUFFIXED_NUM_MIN_REQ_CHARS)) + // Special case - alleviate inevitable overflow for 1000s and truncate them to "1K"s + { + num /= ONE_K; + suffix = "K"; + } + + snprintf(out_str_buff, UINT_MAX_DIGITS + 1, "%lu%s", num, suffix); +} + // Avoid uint overflow when add/multiplying score uint32_t u32_protected_add(uint32_t a, uint32_t b) diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 14cd473..93ba3ec 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -19,3 +19,4 @@ run_test() { run_test bitset run_test pool run_test list +run_test util diff --git a/tests/util/Makefile b/tests/util/Makefile new file mode 100644 index 0000000..56bbfc2 --- /dev/null +++ b/tests/util/Makefile @@ -0,0 +1,16 @@ + +CC := gcc +CFLAGS := -I../../include -I. \ + -g -O3 -Wall -Werror -Wno-format + +SRC := util_test.c ../../source/util.c +OUT := build/util_test + +$(OUT): $(SRC) | build + $(CC) $(CFLAGS) -o $@ $^ + +build: + mkdir -p build + +clean: + rm -f $(OUT) diff --git a/tests/util/util_test.c b/tests/util/util_test.c new file mode 100644 index 0000000..d32a249 --- /dev/null +++ b/tests/util/util_test.c @@ -0,0 +1,133 @@ +#include +#include +#include +#include + + +void test_truncate_uint_to_suffixed_str() +{ + /* + * I want to avoid testing the rounding so it can be easily changed + * so all tests are numbers that are rounded down regardless of rounding method. + * That way the function can be modified to round to nearest integer easily. + */ + + char suffixed_str_buff[UINT_MAX_DIGITS + 1] = {'\0'}; + + truncate_uint_to_suffixed_str(100, 3, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "100") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000, 3, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000, 2, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1000") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1000") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(12123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "12K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123, 7, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(12345123, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "12345K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(12123123, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "12M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(12123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "12M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(54123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "54M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123123, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(987123123, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "987M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123123, 7, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123123K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1123123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1B") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str((uint32_t)3123123123, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "3B") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1234123123, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1234M") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1123123123, 10, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1123123123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1123123123, UINT_MAX_DIGITS, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1123123123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1123123123, 100, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1123123123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(UINT32_MAX, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "4B") == 0); + + // This is the only test that checks rounding down, don't add any more + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(UINT32_MAX, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "4294M") == 0); + + char max_uint_str_buff[UINT_MAX_DIGITS + 1] = {'\0'}; + snprintf(max_uint_str_buff, sizeof(max_uint_str_buff), "%lu", UINT32_MAX); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(UINT32_MAX, UINT_MAX_DIGITS, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, max_uint_str_buff) == 0); +} + +int main() +{ + test_truncate_uint_to_suffixed_str(); + return 0; +} \ No newline at end of file