1df8402900
* initial commit * wip * WIP * WIP * clang format * Choose Seed navigation mostly done * seed input navigation done * seed input done * cleanup and clang format * Implement base 10 to 36 conversions * Enable seeded runs * clang format * show default deck sprite, name and description * Rename Deck enum to DeckType * Implement arbitrary 9-patch expand system * Make 9-patch example smaller in the docs * Improve NinePatchRect docs * fix Deck back side sprite palette * clang format * Add last deck description * clang format * Fix 'Z' in seed input keyboard * Fix rebase issue * fix deck sprite not updating * Documents static funcs in run_setup.c * more documentation * clang format * Move Play button to the left in its row * Tests WIP * removing tests for random.h * use the new StateMachine struct * First wave of fixes * Fix the 9-patch again * Changed the base 36 logic so it's in big endian * properly set end of base 36 string to '\0' * typos * fix cursor position after rolling a seed + fix random seed generation * Make keyboard more intuitive to use * static const var * Address more comment from @ricfehr3 * clang format * clang format * clang format * clang format * clang format * clang format * clang format * fix deck palette * create the `DEF_SEED_KEYBOARD_BUTTON_OBJECT` macro * Replace `expand_3x3` by a call to `expand_9-patch` * clang format * clang format * clang format * fix rebase iussue in state_machine.h --------- Co-authored-by: MathisMartin31 <mathis.martin31@gmail.com>
285 lines
8.1 KiB
C
285 lines
8.1 KiB
C
#include "util.h"
|
|
|
|
#include "font.h"
|
|
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
int int_arr_max(int int_arr[], int size)
|
|
{
|
|
int max = INT_MIN;
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
if (int_arr[i] > max)
|
|
{
|
|
max = int_arr[i];
|
|
}
|
|
}
|
|
|
|
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 '.<digit>' 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]
|
|
)
|
|
{
|
|
uint32_t truncated_num = num;
|
|
int num_digits = u32_get_digits(num);
|
|
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 (overflow)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
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
|
|
|
|
uint32_t u32_protected_add(uint32_t a, uint32_t b)
|
|
{
|
|
return (a > (UINT32_MAX - b)) ? UINT32_MAX : (a + b);
|
|
}
|
|
|
|
uint16_t u16_protected_add(uint16_t a, uint16_t b)
|
|
{
|
|
return (a > (UINT16_MAX - b)) ? UINT16_MAX : (a + b);
|
|
}
|
|
|
|
uint32_t u32_protected_mult(uint32_t a, uint32_t b)
|
|
{
|
|
return (a == 0 || b == 0) ? 0 : (a > (UINT32_MAX / b) ? UINT32_MAX : a * b);
|
|
}
|
|
|
|
uint16_t u16_protected_mult(uint16_t a, uint16_t b)
|
|
{
|
|
return (a == 0 || b == 0) ? 0 : (a > (UINT16_MAX / b) ? UINT16_MAX : a * b);
|
|
}
|
|
|
|
/**
|
|
* @brief Get the numerical value of a base-36 digit.
|
|
* Allowed values are [0-9, A-Z], with letters mapped to values of 10-35.
|
|
* Lowercase letters are mapped to the same values as the uppercase ones.
|
|
* Any other char is invalid and will be attributed a value of 0.
|
|
*
|
|
* @param c the char representing a digit in base-36
|
|
* @return u32
|
|
*/
|
|
static inline uint32_t base36_digit_value(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '0' ... '9':
|
|
return c - '0';
|
|
case 'A' ... 'Z':
|
|
return 10 + c - 'A';
|
|
case 'a' ... 'z':
|
|
return 10 + c - 'a';
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the char corresponding to a base-36 digit's numerical value.
|
|
* Inverse operation of base36_digit_value
|
|
*
|
|
* @param n decimal value of the base-36 char we want to get
|
|
* @return char
|
|
*
|
|
* @sa base36_digit_value
|
|
*/
|
|
static inline char base36_digit_char(uint32_t n)
|
|
{
|
|
switch (n)
|
|
{
|
|
case 0 ... 9:
|
|
return n + '0';
|
|
case 10 ... 35:
|
|
return n - 10 + 'A';
|
|
default:
|
|
return '\0';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get 36 to the power `i`
|
|
*
|
|
* @param i power of 36 we want, between 0 and `BASE36_MAX_DIGITS - 1`
|
|
* @return 36 to the power `i`
|
|
*
|
|
* @sa base36_digit_value
|
|
*/
|
|
static inline uint32_t get_base36_power(uint8_t i)
|
|
{
|
|
if (i >= BASE36_MAX_DIGITS)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const uint32_t powers_of_36[BASE36_MAX_DIGITS] = {1, 36, 1296, 46656, 1679616, 60466176};
|
|
return powers_of_36[i];
|
|
}
|
|
|
|
uint32_t base36_to_u32(const char b36_str[])
|
|
{
|
|
uint32_t res = 0;
|
|
|
|
for (uint8_t i = 0; i < BASE36_MAX_DIGITS; i++)
|
|
{
|
|
res += base36_digit_value(b36_str[i]) * get_base36_power(BASE36_MAX_DIGITS - i - 1);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void u32_to_base36(const uint32_t n, char b36_str[])
|
|
{
|
|
uint32_t power;
|
|
uint32_t acc = (n > MAX_BASE36) ? MAX_BASE36 : n;
|
|
for (int i = 0; i < BASE36_MAX_DIGITS; i++)
|
|
{
|
|
power = get_base36_power(BASE36_MAX_DIGITS - i - 1);
|
|
b36_str[i] = base36_digit_char(acc / power);
|
|
acc = acc % power;
|
|
}
|
|
// Properly end the string
|
|
b36_str[BASE36_MAX_DIGITS] = '\0';
|
|
}
|