Truncate Scores (#269)

* Fixed score truncation erasure, and centering for chips, mult, score and "temp score

* Added centering for money

* Added tests for truncate_uint_to_suffixed_str()

* Fixed u32 bug with get_digits

* Fixed score flames for large numbers

* Small change to update documentation

* Added 1 pixel on to score rect on the left to make room for full character tile + reset_top_left_panel_bottom_row() refactor

* Replaced directional defines with enums

* Updated ante_lut comment

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
MeirGavish
2025-12-02 08:28:36 +02:00
committed by GitHub
co-authored by Copilot
parent 07cab81ba1
commit 43d837571e
14 changed files with 411 additions and 148 deletions
+2
View File
@@ -7,3 +7,5 @@
/tests/bitset/build
/tests/list/build
/tests/pool/build
tests/util/.vscode
tests/util/build
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

+1 -1
View File
@@ -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);
+46 -25
View File
@@ -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
* overflow_direction determines the direction the string will overflow
* if it's too large to fit inside the rect.
* Should be either OVERFLOW_LEFT or OVERFLOW_RIGHT.
*
* 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.
+40 -11
View File
@@ -3,9 +3,10 @@
#include <stdint.h>
#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);
+5 -4
View File
@@ -8,9 +8,10 @@
#include <tonc.h>
// +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)
+84 -84
View File
@@ -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];
if(value >= TEN_K)
{
score_suffix = 'k';
display_value = value / ONE_K; // 12,986 = 12k
}
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);
// 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);
char blind_req_str_buff[UINT_MAX_DIGITS + 1];
score_suffix = 'k';
blind_requirement /= ONE_K; // 11,000 = 11k
}
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);
update_text_rect_to_right_align_str(&blind_req_text_rect, blind_req_str_buff, 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'
}
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);
tte_printf("#{P:%d,%d; cx:0x%X000}%d", blind_req_rect.left, blind_req_rect.top, TTE_RED_PB, blind_req);
/* 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}%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()
+38 -18
View File
@@ -4,6 +4,7 @@
#include "util.h"
#include "graphic_utils.h"
#include <string.h>
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));
+40
View File
@@ -1,5 +1,7 @@
#include "util.h"
#include <limits.h>
#include <stdio.h>
#include <stdbool.h>
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)
+1
View File
@@ -19,3 +19,4 @@ run_test() {
run_test bitset
run_test pool
run_test list
run_test util
+16
View File
@@ -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)
+133
View File
@@ -0,0 +1,133 @@
#include <util.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
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;
}