diff --git a/include/font.h b/include/font.h index 23c97a5..951e459 100644 --- a/include/font.h +++ b/include/font.h @@ -6,24 +6,16 @@ #ifndef FONT_H #define FONT_H -#include - -extern const TFont gbalatro_sys8Font; -extern const unsigned int gbalatro_sys8Glyphs[192]; - /** @name Decimal Point Fonts * @brief A set of macros to map the fonts decimal-point values (e.g. ".1") * to their replaced characters. * * "FP#" -> Font "Point" "number" * - * This is used at compile time directly with the help of libtonc's XSTR() macro - * **Note**: This should **ONLY** be used with the XSTR() macro, otherwise it - * is undefined behavior. * * For example: * ```c - * tte_printf("Testing " XSTR(FP0_CHAR) " Something!"); + * tte_printf("Testing " FP0_STR " Something!"); * ``` * prints "Testing .0 Something!" * @@ -44,16 +36,16 @@ extern const unsigned int gbalatro_sys8Glyphs[192]; * * @{ */ -#define FP0_CHAR & // .0 -#define FP1_CHAR ^ // .1 -#define FP2_CHAR } // .2 -#define FP3_CHAR { // .3 -#define FP4_CHAR | // .4 -#define FP5_CHAR ` // .5 -#define FP6_CHAR < // .6 -#define FP7_CHAR > // .7 -#define FP8_CHAR _ // .8 -#define FP9_CHAR ; // .9 +#define FP0_STR "&" // .0 +#define FP1_STR "^" // .1 +#define FP2_STR "}" // .2 +#define FP3_STR "{" // .3 +#define FP4_STR "|" // .4 +#define FP5_STR "`" // .5 +#define FP6_STR "<" // .6 +#define FP7_STR ">" // .7 +#define FP8_STR "_" // .8 +#define FP9_STR ";" // .9 /** @} */ /** @@ -67,4 +59,15 @@ extern const unsigned int gbalatro_sys8Glyphs[192]; */ const char* get_font_point_str(int val); +/** + * @brief Get the decimal point char equivalent of 0-9 from char param. + * + * @param digit_char A char of a digit between '0'-'9' to get '.0' to '.9' equivalents. Note that + * if a char outside of range is passed it the return value will be within limits + * but is not defined with any relationship to the input. + * + * @return A char representing a value in the font within the range of '.0' to '.9' + */ +char digit_char_to_font_point(char digit_char); + #endif // FONT_H diff --git a/include/gbalatro_sys8.h b/include/gbalatro_sys8.h new file mode 100644 index 0000000..c16c6d0 --- /dev/null +++ b/include/gbalatro_sys8.h @@ -0,0 +1,9 @@ +#ifndef GBALATRO_SYS8_FONT_H +#define GBALATRO_SYS8_FONT_H + +#include + +extern const TFont gbalatro_sys8Font; +extern const unsigned int gbalatro_sys8Glyphs[192]; + +#endif \ No newline at end of file diff --git a/source/font.c b/source/font.c index 7220357..1fd5e72 100644 --- a/source/font.c +++ b/source/font.c @@ -1,22 +1,29 @@ #include "font.h" -#include "tonc.h" +#include "util.h" + +#include static const char* s_font_point_lookup[] = { - XSTR(FP0_CHAR), - XSTR(FP1_CHAR), - XSTR(FP2_CHAR), - XSTR(FP3_CHAR), - XSTR(FP4_CHAR), - XSTR(FP5_CHAR), - XSTR(FP6_CHAR), - XSTR(FP7_CHAR), - XSTR(FP8_CHAR), - XSTR(FP9_CHAR), + FP0_STR, + FP1_STR, + FP2_STR, + FP3_STR, + FP4_STR, + FP5_STR, + FP6_STR, + FP7_STR, + FP8_STR, + FP9_STR, }; const char* get_font_point_str(int val) { - val = ABS(val) % 10; + val = abs(val) % 10; return s_font_point_lookup[val]; } + +char digit_char_to_font_point(char digit_char) +{ + return get_font_point_str(digit_char - '0')[0]; +} diff --git a/source/game.c b/source/game.c index 882b995..b3ef316 100644 --- a/source/game.c +++ b/source/game.c @@ -118,6 +118,9 @@ #define SHOP_BOTTOM_PANEL_BORDER_PID 26 // Naming the stage where cards return from the discard pile to the deck "undiscard" +/* This needs to stay a power of 2 and small enough + * for the lerping to be done before the next hand is drawn. + */ #define NUM_SCORE_LERP_STEPS 32 // Shop @@ -2948,11 +2951,6 @@ static inline int hand_get_max_size(void) return hand_size; } -// TODO: Help this comment find its way back to its variable -/* This needs to stay a power of 2 and small enough - * for the lerping to be done before the next hand is drawn. - */ - static inline void game_playing_process_input_and_state(void) { if (hand_state == HAND_SELECT) @@ -2998,10 +2996,11 @@ static inline void game_playing_process_input_and_state(void) if (lerped_temp_score > 0) { - display_temp_score(fx2uint(lerped_temp_score)); + // Set the score display first because it's more important + // in case there isn't enough time within the frame to display both + display_score(fx2uint(lerped_score)); - // We actually don't need to erase this because the score only increases - display_score(fx2uint(lerped_score)); // Set the score display + display_temp_score(fx2uint(lerped_temp_score)); } else { diff --git a/source/main.c b/source/main.c index 18610e7..7390f8c 100644 --- a/source/main.c +++ b/source/main.c @@ -3,6 +3,7 @@ #include "card.h" #include "font.h" #include "game.h" +#include "gbalatro_sys8.h" #include "graphic_utils.h" #include "joker.h" #include "sprite.h" @@ -33,7 +34,10 @@ void init() CLR_WHITE, TTE_BIT_UNPACK_OFFSET, &gbalatro_sys8Font, - NULL + + // Explicitly use 8x8 tile text drawing function to improve performance + // See https://gbadev.net/tonc/tte.html#ssec-map-reg + se_drawg_w8h8 ); tte_erase_screen(); tte_init_con(); diff --git a/source/util.c b/source/util.c index 23840f8..db8c227 100644 --- a/source/util.c +++ b/source/util.c @@ -1,8 +1,11 @@ #include "util.h" +#include "font.h" + #include #include #include +#include int int_arr_max(int int_arr[], int size) { @@ -18,45 +21,153 @@ int int_arr_max(int int_arr[], int size) return max; } +/** + * @brief Remove trailing zeros from a string. + * Currently static single use, may be unstaticed if needed but use with caution. + * + * @param num_str The string to remove trailing zeros from, modified in-place. + * @param size The size of the string - strlen is not used and no checks are performed, + * the function relies on the caller to provide the correct size, + * if it's larger than the actual string length or negative it will result in + * an invalid write. + */ +static inline void num_str_truncate_trailing_zeros(char* num_str, int size) +{ + while (size > 0 && num_str[size - 1] == '0') + { + size--; + } + num_str[size] = '\0'; +} + +/** + * @brief Build a truncated decimal remainder string. + * Helper function for truncate_uint_to_suffixed_str() + * + * @param decimal_remainder Integer remainder (the `num % divisor`) used to + * produce the fractional digits after the decimal point; formatted and + * padded before truncation. + * @param truncated_num Integer part reduced by the divisor (`num / divisor`); + * Used to compute how many fractional characters may be kept. + * @param num_req_chars Total character budget for the final string (truncated + * number, fractional digits, and suffix). + * Used to compute how many fractional characters may be kept. + * @param suffix_char One of 'K', 'M', or 'B' used for selecting the suffix scale and + * padding width. If not one of the expected, the string may be incorrectly formatted. + * @param remainder_str Output buffer (size >= UINT_MAX_DIGITS + 1) where the + * formatted fractional digits (including leading '.' special character) are written + * as a NULL-terminated string; may be empty if nothing remains. + */ +static inline void truncate_num_get_remainder_string( + uint32_t decimal_remainder, + uint32_t truncated_num, + int num_req_chars, + char suffix_char, + char remainder_str[UINT_MAX_DIGITS + 1] +) +{ + // Truncating the remainder in string form rather than number to avoid divisions + char* remainder_str_format; + + switch (suffix_char) + { + // Pad with 0s to not lose leading zeros after decimal point + case 'B': + remainder_str_format = "%09lu"; + break; + case 'M': + remainder_str_format = "%06lu"; + break; + case 'K': + remainder_str_format = "%03lu"; + break; + default: + // Should not reach here + remainder_str_format = "%lu"; + } + + snprintf(remainder_str, UINT_MAX_DIGITS + 1, remainder_str_format, decimal_remainder); + + // Truncate overflow + int remaining_chars = num_req_chars - u32_get_digits(truncated_num) - 1; // - 1 for suffix + + // If there is no room for any fractional characters, leave the remainder string empty. + if (remaining_chars <= 0) + { + remainder_str[0] = '\0'; + return; + } + + // Ensure we never write past the end of the buffer. + if (remaining_chars > UINT_MAX_DIGITS) + { + remaining_chars = UINT_MAX_DIGITS; + } + remainder_str[remaining_chars] = '\0'; + + num_str_truncate_trailing_zeros(remainder_str, remaining_chars); + + if (remainder_str[0] != '\0') + { + remainder_str[0] = digit_char_to_font_point(remainder_str[0]); + } +} + 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; - } - + uint32_t truncated_num = num; int num_digits = u32_get_digits(num); - int overflow_size = num_digits - num_req_chars; + uint32_t decimal_remainder = 0; + bool overflow = num_digits > num_req_chars; char* suffix = ""; + char remainder_str[UINT_MAX_DIGITS + 1]; + remainder_str[0] = '\0'; - /* 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) + if (overflow) { - 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"; + /* 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. + */ + uint32_t divisor = 1; + if (num >= ONE_B) + { + divisor = ONE_B; + suffix = "B"; + } + else if (num >= ONE_M) + { + divisor = ONE_M; + suffix = "M"; + } + else if (num >= ONE_K) + { + divisor = ONE_K; + suffix = "K"; + } + + // The compiler optimizes these into a single operation + truncated_num = num / divisor; + decimal_remainder = num % divisor; } - snprintf(out_str_buff, UINT_MAX_DIGITS + 1, "%lu%s", num, suffix); + if (suffix[0] != '\0' && decimal_remainder != 0) + { + truncate_num_get_remainder_string( + decimal_remainder, + truncated_num, + num_req_chars, + suffix[0], + remainder_str + ); + } + + snprintf(out_str_buff, UINT_MAX_DIGITS + 1, "%lu%s%s", truncated_num, remainder_str, suffix); } // Avoid uint overflow when add/multiplying score diff --git a/tests/util/Makefile b/tests/util/Makefile index fb9a3ad..3942889 100644 --- a/tests/util/Makefile +++ b/tests/util/Makefile @@ -3,7 +3,7 @@ CC := gcc CFLAGS := -I../../include -I. \ -g -O3 -std=gnu23 -Wall -Werror -Wno-format -SRC := util_test.c ../../source/util.c +SRC := util_test.c ../../source/util.c ../../source/font.c OUT := build/util_test $(OUT): $(SRC) | build diff --git a/tests/util/util_test.c b/tests/util/util_test.c index d32a249..ac54512 100644 --- a/tests/util/util_test.c +++ b/tests/util/util_test.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -25,6 +26,39 @@ void test_truncate_uint_to_suffixed_str() 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, 1, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000, 0, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1K") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(0, 0, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "0") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(100, 2, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "100") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(100, 1, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "100") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123, 1, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123") == 0); + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1234, 3, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP2_STR "K") == 0); // "1.2K" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1600, 3, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP6_STR "K") == 0); // "1.6K" + + suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(1000, 4, suffixed_str_buff); assert(strcmp(suffixed_str_buff, "1000") == 0); @@ -35,7 +69,7 @@ void test_truncate_uint_to_suffixed_str() suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(12123, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "12K") == 0); + assert(strcmp(suffixed_str_buff, "12" FP1_STR "K") == 0); // "12.1K" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123, 4, suffixed_str_buff); @@ -43,7 +77,7 @@ void test_truncate_uint_to_suffixed_str() suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123, 5, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "123K") == 0); + assert(strcmp(suffixed_str_buff, "123" FP1_STR "K") == 0); // "123.1K" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123, 6, suffixed_str_buff); @@ -55,19 +89,36 @@ void test_truncate_uint_to_suffixed_str() suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(12345123, 6, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "12345K") == 0); + assert(strcmp(suffixed_str_buff, "12" FP3_STR "45M") == 0); // "12.345M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(12123123, 5, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "12M") == 0); + assert(strcmp(suffixed_str_buff, "12" FP1_STR "2M") == 0); // "12.12M" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1008000, 5, suffixed_str_buff); + + assert(strcmp(suffixed_str_buff, "1" FP0_STR "08M") == 0); // "1.008M" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(3029000, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "3" FP0_STR "29M") == 0); // "3.029M" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(10007000, 5, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "10M") == 0); // Decimal point fully truncated + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(10005123, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "10" FP0_STR "05M") == 0); // "10.005M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(12123123, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "12M") == 0); + assert(strcmp(suffixed_str_buff, "12" FP1_STR "M") == 0); // "12.1M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(54123123, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "54M") == 0); + assert(strcmp(suffixed_str_buff, "54" FP1_STR "M") == 0); // "54.1M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123123, 4, suffixed_str_buff); @@ -75,28 +126,64 @@ void test_truncate_uint_to_suffixed_str() suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123123, 6, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "123M") == 0); + assert(strcmp(suffixed_str_buff, "123" FP1_STR "2M") == 0); // "123.12M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(987123123, 6, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "987M") == 0); + assert(strcmp(suffixed_str_buff, "987" FP1_STR "2M") == 0); // "987.12M" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(123123123, 7, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "123123K") == 0); + assert(strcmp(suffixed_str_buff, "123" FP1_STR "23M") == 0); // "123.123M" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123123, 8, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123" FP1_STR "231M") == 0); // "123.1231M" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(123123123, 9, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "123123123") == 0); suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(1123123123, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "1B") == 0); + assert(strcmp(suffixed_str_buff, "1" FP1_STR "2B") == 0); // "1.12B" suffixed_str_buff[0] = '\0'; - truncate_uint_to_suffixed_str((uint32_t)3123123123, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "3B") == 0); + truncate_uint_to_suffixed_str((uint32_t)3012012012, 4, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "3" FP0_STR "1B") == 0); // "3.01B" suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(1234123123, 5, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "1234M") == 0); + assert(strcmp(suffixed_str_buff, "1" FP2_STR "34B") == 0); // "1.234B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000512345, 6, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP0_STR "005B") == 0); // "1.0005B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1234561234, 7, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP2_STR "3456B") == 0); // "1.23456B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000061234, 7, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP0_STR "0006B") == 0); // "1.00006B" + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1234567123, 8, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP2_STR "34567B") == 0); // "1.234567B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000007123, 8, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP0_STR "00007B") == 0); // "1.000007B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1234567812, 9, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP2_STR "345678B") == 0); // "1.2345678B" + + suffixed_str_buff[0] = '\0'; + truncate_uint_to_suffixed_str(1000000812, 9, suffixed_str_buff); + assert(strcmp(suffixed_str_buff, "1" FP0_STR "000008B") == 0); // "1.0000008B" + suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(1123123123, 10, suffixed_str_buff); assert(strcmp(suffixed_str_buff, "1123123123") == 0); @@ -111,12 +198,12 @@ void test_truncate_uint_to_suffixed_str() suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(UINT32_MAX, 4, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "4B") == 0); + assert(strcmp(suffixed_str_buff, "4" FP2_STR "9B") == 0); // "4.29B" - // This is the only test that checks rounding down, don't add any more + // This is one of the few tests that checks rounding down, try not to add many more suffixed_str_buff[0] = '\0'; truncate_uint_to_suffixed_str(UINT32_MAX, 5, suffixed_str_buff); - assert(strcmp(suffixed_str_buff, "4294M") == 0); + assert(strcmp(suffixed_str_buff, "4" FP2_STR "94B") == 0); // "4.294B" char max_uint_str_buff[UINT_MAX_DIGITS + 1] = {'\0'}; snprintf(max_uint_str_buff, sizeof(max_uint_str_buff), "%lu", UINT32_MAX);