From e0cffab7685ac40908f961b5e34b31e8de286ede Mon Sep 17 00:00:00 2001 From: Rickey Date: Tue, 11 Nov 2025 07:56:21 -0800 Subject: [PATCH] Update list implementation to work with memory pools (#168) * Introduce indexed list implementation * Fix CI tests for pool * Take bitset out of pool * Replace joker bitset interactions with wrappers * Add bitset tests * Add test to gitignore --------- Co-authored-by: rfehr-idexx --- .gitignore | 2 + include/bitset.h | 204 ++++++++++++++++++++++ include/def_balatro_mempool.h | 2 + include/game.h | 5 +- include/joker.h | 3 +- include/list.h | 214 ++++++++++++++++++++--- include/pool.h | 44 +++-- scripts/get_memory_map.sh | 11 +- source/bitset.c | 166 ++++++++++++++++++ source/game.c | 315 ++++++++++++++++++++-------------- source/joker.c | 1 - source/joker_effects.c | 39 ++--- source/list.c | 272 +++++++++++++++++++---------- source/pool.c | 43 ----- tests/bitset/Makefile | 16 ++ tests/bitset/bitset_test.c | 122 +++++++++++++ tests/list/Makefile | 19 ++ tests/list/def_test_mempool.h | 5 + tests/list/list_test.c | 307 +++++++++++++++++++++++++++++++++ tests/pool/Makefile | 4 +- tests/pool/pool_test.c | 6 +- tests/run_tests.sh | 22 ++- 22 files changed, 1466 insertions(+), 356 deletions(-) create mode 100644 include/bitset.h create mode 100644 source/bitset.c create mode 100644 tests/bitset/Makefile create mode 100644 tests/bitset/bitset_test.c create mode 100644 tests/list/Makefile create mode 100644 tests/list/def_test_mempool.h create mode 100644 tests/list/list_test.c diff --git a/.gitignore b/.gitignore index 38bce4e..fc00239 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ *.sln *.vcxproj* /balatro-gba/x64/** +/tests/bitset/build +/tests/list/build /tests/pool/build diff --git a/include/bitset.h b/include/bitset.h new file mode 100644 index 0000000..2589edd --- /dev/null +++ b/include/bitset.h @@ -0,0 +1,204 @@ +/** @file bitset.h + * + * @brief A bitset for operating on flags + */ +#ifndef BITSET_H +#define BITSET_H + +#include +#include + +/** + * @def BITSET_BITS_PER_WORD + * @brief Number of bits in a word for a bitset. + * + * Number of bits in a word for a bitset. Will always be 32 here. + */ +#define BITSET_BITS_PER_WORD 32 + +/** + * @def BITSET_ARRAY_SIZE + * @brief Number of words in a bitset + * + * Number of words in every bitset. This represents the maximum number and each + * bitset will always use this number of words, though it's capacity can be any length + * from `1` to `BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE` + */ +#define BITSET_ARRAY_SIZE 8 + +/** + * @def BITSET_MAX_BITS + * @brief Maximum number of bits in a bitset + */ +#define BITSET_MAX_BITS BITSET_BITS_PER_WORD * BITSET_ARRAY_SIZE + +/** + * @brief A bitset spread across multiple `uint32_t` words + */ +typedef struct Bitset +{ + /** + * @brief Word array of `uint32_t` to hold the bitset data + */ + uint32_t *w; + + /** + * @brief Number of bits in a word, will be 32 + */ + uint32_t nbits; + + /** + * @brief Number of words int the `w` array + */ + uint32_t nwords; + + /** + * @brief Number of actual flags (nbits * nwords) + */ + uint32_t cap; +} Bitset; + +/** + * @brief An iterator into a @ref Bitset + * + * This iterator will parse and find the next index to a '1' bit as efficiently as possible. + * + * There is no implementation of the following (yet): + * - Reverse iteration + * - Bit-by-bit iteration + * - Iterating on offsets to '0' bits + */ +typedef struct +{ + /** + * @brief @ref Bitset this is iterating through + */ + const Bitset *bitset; + + /** + * @brief Current word the iterator is on + */ + int word; + + /** + * @brief Current bit the iterator is on + */ + int bit; + + /** + * @brief Number of bits that have been iterated through in total + */ + int itr; +} BitsetItr; + +/** + * @brief Set a flag in a bitset to a value + * + * @param bitset A @ref Bitset to operate on + * @param idx the index of the flag to set + * @param on the value to set the flag to + */ +void bitset_set_idx(Bitset *bitset, int idx, bool on); + +/** + * @brief Get the value of a flag + * + * @param bitset A @ref Bitset to operate on + * @param idx the index of the flag to get + * + * @return the value of the flag as `true` or `false` + */ +bool bitset_get_idx(Bitset *bitset, int idx); + +// Get the next free (set to 0) index in the bitset. +// It also sets the bit which it maybe should do... It really shouldn't do two things +// But it's such a fast operation idk. // TODO: decide what you wanna do +int bitset_allocate_idx(Bitset *bitset); + +/** + * @brief Clear the bitset, all to 0 + * + * @param bitset A @ref Bitset to operate on + */ +void bitset_clear(Bitset *bitset); + +/** + * @brief Check if a bitset is empty (all 0's) + * + * @param bitset A @ref Bitset to operate on + * + * @return `true` if empty, `false` otherwise + */ +bool bitset_is_empty(Bitset *bitset); + +/** + * @brief Count how many bits are set to `1` in a bitset + * + * @param bitset A @ref Bitset to operate on + * + * @return The number of flags set to `1` in a bitset + */ +int bitset_num_set_bits(Bitset *bitset); + +/** + * @brief Find the index of the nth set bit + * + * Find the index of the nth flag set to `1`. This function is useful to get one value quickly, + * but does not operate iteratively well. Use a @BitsetItr for iterative access to a bitset. + * + * @param bitset A @ref Bitset to operate on + * + * @return The index of the nth flag set to `1` in the bitset + */ +int bitset_find_idx_of_nth_set(const Bitset *bitset, int n); + +/** + * @brief Declare a @ref BitsetItr + * + * @param bitset A @ref Bitset to operate on + * + * @return A newly constructed BitsetItr + */ +BitsetItr bitset_itr_create(const Bitset* bitset); + +/** + * @brief Get the index of the next set bit in the bitset from a @ref BitsetItr + * + * @param itr A @ref BitsetItr to operate on + * + * @return a positive number if successful, UNDEFINED otherwise (out-of-bounds) + */ +int bitset_itr_next(BitsetItr* itr); + +/** + * @def BITSET_DEFINE + * @brief Make a standard bitset + * + * Make a bitset with a valid static array to store it's array of words. + * + * Use this to define bitsets in the code, specifically as a `static` scoped + * variable. The passed `name` will be the same name as the bitset. + * + * Usage example: + * + * ```c + * BITSET_DEFINE(_my_bitset, 128); + * // normal operation... + * bitset_clear(&_my_bitset); + * ``` + * + * @param name the name of the bitset + * @param capacity the capacity of the bitset + */ +#define BITSET_DEFINE(name, capacity) \ + static uint32_t name##_w[BITSET_ARRAY_SIZE] = {0}; \ + static Bitset name = \ + { \ + .w = name##_w, \ + .nbits = BITSET_BITS_PER_WORD, \ + .nwords = BITSET_ARRAY_SIZE, \ + .cap = capacity, \ + }; + + +#endif // BITSET_H diff --git a/include/def_balatro_mempool.h b/include/def_balatro_mempool.h index 6844105..99b887f 100644 --- a/include/def_balatro_mempool.h +++ b/include/def_balatro_mempool.h @@ -1,6 +1,7 @@ #include "sprite.h" #include "joker.h" #include "card.h" +#include "list.h" POOL_ENTRY(Sprite, MAX_SPRITES); POOL_ENTRY(SpriteObject, MAX_SPRITE_OBJECTS); @@ -8,3 +9,4 @@ POOL_ENTRY(Joker, MAX_ACTIVE_JOKERS); POOL_ENTRY(JokerObject, MAX_ACTIVE_JOKERS); POOL_ENTRY(Card, MAX_CARDS); POOL_ENTRY(CardObject, MAX_CARDS_ON_SCREEN); +POOL_ENTRY(ListNode, MAX_LIST_NODES); diff --git a/include/game.h b/include/game.h index 1efaa24..4710d26 100644 --- a/include/game.h +++ b/include/game.h @@ -91,7 +91,6 @@ void game_init(); void game_update(); void game_change_state(enum GameState new_game_state); -// Forward declaration struct List; typedef struct List List; @@ -106,9 +105,9 @@ int hand_get_size(void); CardObject** get_played_array(void); int get_played_top(void); int get_scored_card_index(void); -List* get_jokers(void); bool is_joker_owned(int joker_id); bool card_is_face(Card *card); +List* get_jokers_list(void); int get_deck_top(void); int get_num_discards_remaining(void); @@ -122,4 +121,4 @@ void set_game_speed(int new_game_speed); bool is_shortcut_joker_active(void); int get_straight_and_flush_size(void); -#endif // GAME_H \ No newline at end of file +#endif // GAME_H diff --git a/include/joker.h b/include/joker.h index 9bd0b88..35ac65f 100644 --- a/include/joker.h +++ b/include/joker.h @@ -12,6 +12,8 @@ // plus the amount that can fit in the shop, 8 should be fine. For now... #define MAX_ACTIVE_JOKERS 8 +#define MAX_DEFINABLE_JOKERS 150 + #define JOKER_TID (MAX_HAND_SIZE + MAX_SELECTION_SIZE) * JOKER_SPRITE_OFFSET // Tile ID for the starting index in the tile memory #define JOKER_SPRITE_OFFSET 16 // Offset for the joker sprites #define JOKER_BASE_PB 4 // The starting palette index for the jokers @@ -68,7 +70,6 @@ enum JokerEvent #define SHORTCUT_JOKER_ID 26 #define FOUR_FINGERS_JOKER_ID 48 - typedef struct { u8 id; // Unique ID for the joker, used to identify different jokers diff --git a/include/list.h b/include/list.h index 42ff1cd..6f6f129 100644 --- a/include/list.h +++ b/include/list.h @@ -1,27 +1,187 @@ -#ifndef LIST_H -#define LIST_H - -#include - -typedef struct List -{ - void** _array; - int size; - int allocated_size; -} List; - -List *list_new(int init_size); -void list_destroy(List **list); -bool list_append(List *list, void *value); -bool list_remove_by_idx(List *list, int index); -void* list_get(List *list, int index); -int list_get_size(List *list); -bool list_remove_by_value(List *list, void *value); -bool list_exists(List *list, void *value); - -bool int_list_append(List *list, intptr_t value); -intptr_t int_list_get(List *list, int index); -bool int_list_remove_by_value(List *list, intptr_t value); -bool int_list_exists(List *list, intptr_t value); - -#endif +/** @file list.h + * + * @brief A doubly-linked list + * + * List Implementation + * =================== + * + * - This @ref List operates as a linked list @ref ListNodes. It operates as a regular doubly-linked list + * but doesn't allocate memory and rather gets @ref ListNodes from a pool. + */ +#ifndef LIST_H +#define LIST_H + +#include + +#define MAX_LIST_NODES 128 + +typedef struct ListNode ListNode; + +/** + * @brief A single entry in a @ref List + */ +struct ListNode +{ + /** + * @brief The previous @ref ListNode in the associated @ref List, NULL if at the `head` of the list + */ + ListNode* prev; + + /** + * @brief The next @ref ListNode in the associated @ref List, NULL if at the `tail` of the list + */ + ListNode* next; + + /** + * @brief Pointer to generic data stored in this node + */ + void* data; +}; + +/** + * @brief A doubly-linked list + */ +typedef struct List +{ + /** + * @brief The first entry in the list + */ + ListNode* head; + + /** + * @brief The last entry in the list + */ + ListNode* tail; + + /** + * @brief Number of elements in list + */ + int len; +} List; + +/** + * @brief An iterator into a list + */ +typedef struct +{ + /** + * @brief A pointer to the @ref List this is iterating through + */ + List* list; + + /** + * @brief The next node in the list + */ + ListNode* next_node; + + /** + * @brief The current node in the list iterator + * + * The node of the most recently returned data from @ref list_itr_next() . + */ + ListNode* current_node; +} ListItr; + +/** + * Create a list. + * + * While this function does not allocate memory for the list itself, the list does allocate memory for each element. + * So every created list must be freed with @ref list_clear to ensure the list's nodes are deleted properly. + * + * @return A @ref List with head and tail reset. + */ +List list_create(void); + +/** + * Clear a list. + * + * Go through the list and free each node and set the `head` and `tail` to `NULL`. + * Note, it doesn't "free" the data at the node. + * + * @param list pointer to a @ref List to clear + */ +void list_clear(List* list); + +/** + * Check if a list is empty + * + * @param list pointer to a @ref List + * + * @return `true` if the `list` is empty, `false` otherwise. + */ +bool list_is_empty(const List* list); + +/** + * Prepend an entry to the `head` of a @ref list + * + * @param list pointer to a @ref List + * @param data pointer to data to put into the @ref List + */ +void list_push_front(List* list, void* data); + +/** + * Append an entry to the `tail` of a @ref list + * + * @param list pointer to a @ref List + * @param data pointer to data to put into the @ref List + */ +void list_push_back(List* list, void* data); + +/** + * Get a List's node at it's nth index + * + * @param list pointer to a @ref List + * @param n index of the desired @ref ListNode in the list + * + * @return a pointer to the data at the nth @ref ListNode, or NULL if out-of-bounds + */ +void* list_get_at_idx(List *list, int n); + +/** + * Remove a List's node at it's nth index + * + * @param list pointer to a @ref List + * @param n index of the desired @ref ListNode in the list + * + * @return `true` if successfully removed, `false` if out-of-bounds + */ +bool list_remove_at_idx(List *list, int n); + +/** + * Get the number of elements in a @ref List + * + * @param list pointer to a @ref List + * + * @return The number of elements in the list + */ +int list_get_len(const List* list); + +/** + * Declare a @ref ListItr + * + * @param list pointer to a @ref List + * + * @return A new @ref ListItr + */ +ListItr list_itr_create(List* list); + +/** + * Get the next data entry in a @ref ListItr + * + * @param itr pointer to the @ref ListItr + * + * @return A pointer to the data pointer at the next @ref ListNode if valid, otherwise return NULL. + */ +void* list_itr_next(ListItr* itr); + +/** + * Remove the current @ref ListNode from the iterator. + * + * The "current node" corresponds to the list node associated with the + * most recently returned valu from @ref list_itr_next() + * + * @param itr pointer to the @ref ListItr + */ +void list_itr_remove_current_node(ListItr* itr); + +#endif diff --git a/include/pool.h b/include/pool.h index a8bb555..e58d579 100644 --- a/include/pool.h +++ b/include/pool.h @@ -2,6 +2,9 @@ #define POOL_H #include +#include + +#include "bitset.h" #ifdef POOLS_TEST_ENV #define POOLS_DEF_FILE "def_test_mempool.h" @@ -9,44 +12,28 @@ #define POOLS_DEF_FILE "def_balatro_mempool.h" #endif -#define POOL_BITS_PER_WORD 32 -#define POOL_BITMAP_BYTES 8 - -typedef struct PoolBitmap { - uint32_t *w; - uint32_t nbits; - uint32_t nwords; - uint32_t cap; -} PoolBitmap; - -void pool_bm_clear_idx(PoolBitmap *bm, int idx); -int pool_bm_get_free_idx(PoolBitmap *bm); - #define POOL_DECLARE_TYPE(type) \ typedef struct \ { \ - PoolBitmap bm; \ + Bitset* bitset; \ type * objects; \ } type##Pool; \ type *pool_get_##type(); \ void pool_free_##type(type *obj); \ + int pool_idx_##type(type *obj); \ + type *pool_at_##type(int idx); #define POOL_DEFINE_TYPE(type, capacity) \ + BITSET_DEFINE(type##_bitset, capacity) \ static type type##_storage[capacity]; \ - static uint32_t type##_bitmap_w[POOL_BITMAP_BYTES] = {0}; \ static type##Pool type##_pool = \ { \ - .bm = { \ - .w = type##_bitmap_w, \ - .nbits = POOL_BITS_PER_WORD, \ - .nwords = POOL_BITMAP_BYTES, \ - .cap = capacity, \ - }, \ + .bitset = & type##_bitset, \ .objects = type##_storage, \ }; \ type * pool_get_##type() \ { \ - int free_offset = pool_bm_get_free_idx(&type##_pool.bm); \ + int free_offset = bitset_allocate_idx(type##_pool.bitset); \ if(free_offset == -1) return NULL; \ return &type##_pool.objects[free_offset]; \ } \ @@ -54,11 +41,22 @@ int pool_bm_get_free_idx(PoolBitmap *bm); { \ if(entry == NULL) return; \ int offset = entry - &type##_pool.objects[0]; \ - pool_bm_clear_idx(&type##_pool.bm, offset); \ + bitset_set_idx(type##_pool.bitset, offset, false); \ + } \ + int pool_idx_##type(type *entry) \ + { \ + return entry - &type##_pool.objects[0]; \ + } \ + type *pool_at_##type(int idx) \ + { \ + if(idx < 0 || idx >= (type##_pool.bitset)->cap) return NULL; \ + return &type##_pool.objects[idx]; \ } #define POOL_GET(type) pool_get_##type() #define POOL_FREE(type, obj) pool_free_##type(obj) +#define POOL_IDX(type, obj) pool_idx_##type(obj) // the index of the object +#define POOL_AT(type, idx) pool_at_##type(idx) // the object at #define POOL_ENTRY(name, capacity) \ POOL_DECLARE_TYPE(name); diff --git a/scripts/get_memory_map.sh b/scripts/get_memory_map.sh index 095be49..ad3e349 100755 --- a/scripts/get_memory_map.sh +++ b/scripts/get_memory_map.sh @@ -55,23 +55,22 @@ for name in $(get_pool_names); do sed -E 's@ +@ @g; s@^ @@' | \ tr -d '\n' \ )" - output_bm="$( \ + output_bitset="$( \ "$READELF" -sW "$ELF_FILE" | \ - grep -E "${name}_bitmap_w" | \ + grep -E "${name}_bitset_w" | \ grep OBJECT | \ sed -E 's@ +@ @g; s@^ @@' | \ tr -d '\n' \ )" - address="$(cut -d ' ' -f 2 <<< $output_pool)" pool_size="$(cut -d ' ' -f 3 <<< $output_pool)" func_size="$(cut -d ' ' -f 3 <<< $output_func)" - bm_size="$(cut -d ' ' -f 3 <<< $output_bm)" + bitset_size="$(cut -d ' ' -f 3 <<< $output_bitset)" - TOTAL_BYTES=$(( TOTAL_BYTES + pool_size + func_size + bm_size )) + TOTAL_BYTES=$(( TOTAL_BYTES + pool_size + func_size + bitset_size )) - printf "%-16s| 0x%8s | %-10u | %-10u | %-10u \n" "$name" "$address" "$pool_size" "$func_size" "$bm_size" + printf "%-16s| 0x%8s | %-10u | %-10u | %-10u \n" "$name" "$address" "$pool_size" "$func_size" "$bitset_size" done print_line_break diff --git a/source/bitset.c b/source/bitset.c new file mode 100644 index 0000000..035ca04 --- /dev/null +++ b/source/bitset.c @@ -0,0 +1,166 @@ +#include "bitset.h" +#include "util.h" + +void bitset_set_idx(Bitset *bitset, int idx, bool on) +{ + uint32_t i = idx / BITSET_BITS_PER_WORD; + uint32_t b = idx % BITSET_BITS_PER_WORD; + + // Below are the "fast" forms of the above operations, respectively. + // These are more efficient, but removed for readability + // See: https://github.com/cellos51/balatro-gba/pull/132#discussion_r2365966071 + // Divide by 32 to get the word index + //uint32_t i = idx >> 5; + // Get last 5-bits, same as a modulo (% 32) operation on positive numbers + //uint32_t b = idx & 0x1F; + if(on) + { + bitset->w[i] |= (uint32_t)1 << b; + } + else + { + bitset->w[i] &= ~((uint32_t)1 << b); + } +} + +int bitset_allocate_idx(Bitset *bitset) +{ + for (uint32_t i = 0; i < bitset->nwords; i++) + { + uint32_t inv = ~bitset->w[i]; + + // guard so we don't call `ctz` with 0, since __builtin_ctz(0) is undefined + // https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fctz + // + // By using the bitwise inverse of the word, you can skip words that are full + // quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value greater + // than 0 indicates there is a free slot. Then, when counting the trailing 0's, you can test very quickly + // where the first free slot is. This operation prevents looping through every bit of filled flags, and + // will instead operate only on the first word with free slots. + if (inv) + { + int bit = __builtin_ctz(inv); + bitset->w[i] |= ((uint32_t)1 << bit); + int idx = i * BITSET_BITS_PER_WORD + bit; + return (idx < bitset->cap) ? idx : UNDEFINED; + } + } + + return UNDEFINED; +} + +void bitset_clear(Bitset *bitset) +{ + for(int i = 0; i < bitset->nwords; i++) + { + bitset->w[i] = 0; + } +} + +bool bitset_is_empty(Bitset *bitset) +{ + for(int i = 0; i < bitset->nwords; i++) + { + if(bitset->w[i]) return false; + } + return true; +} + +bool bitset_get_idx(Bitset *bitset, int idx) +{ + uint32_t i = idx / BITSET_BITS_PER_WORD; + uint32_t b = idx % BITSET_BITS_PER_WORD; + + return bitset->w[i] & (uint32_t)1 << b; +} + +int bitset_num_set_bits(Bitset *bitset) +{ + int sum = 0; + + for(int i = 0; i < bitset->nwords; i++) + { + sum += __builtin_popcount(bitset->w[i]); + } + + return sum; +} + +int bitset_find_idx_of_nth_set(const Bitset *bitset, int n) +{ + int tracker = 0; + int prev_tracker = 0; + + for(int i = 0; i < bitset->nwords; i++) + { + tracker += __builtin_popcount(bitset->w[i]); + + if(tracker > n) + { + // The index is here somewhere + int base = prev_tracker - 1; // this one is to count the 1's not the offset, underflow to -1 is good for finding the 0 index + int offset = bitset->nbits * i; // this one is for the actual offset we want to map the id to + for (int j = 0; j < bitset->nbits; j++) + { + if(base == n) + { + return offset - 1; + } + base += (bitset->w[i] >> j) & 0x01; + offset++; + } + + break; + } + + prev_tracker = tracker; + } + + return UNDEFINED; +} + +BitsetItr bitset_itr_create(const Bitset* bitset) +{ + BitsetItr itr = + { + .bitset = bitset, + .word = 0, + .bit = 0, + .itr = 0, + }; + + return itr; +} + +int bitset_itr_next(BitsetItr* itr) +{ + // So, worst case scenario for this is one bit at the end of the last + // word in the bitset. You would look (32 * 7) + 31 times! + // This can be sped up with by checking if the word is empty first. + // Then the worst enemy of this method would be something like a set bit at the end + // of every word. In that case you would need to loop 31 times maximum. + // So one last thing you could do is something like `bitset_allocate_idx` does with the + // __builtin_ctz function as well. + // + // The point being, this can be very slow, but it's simple and can be much faster. + for (; itr->word < itr->bitset->nwords; itr->word++) + { + for (; itr->bit < itr->bitset->nbits; itr->bit++) + { + itr->itr++; + if(itr->bitset->w[itr->word] & (1 << itr->bit)) + { + // if itr->bit == nbits on the next run, the for loop will handle it + itr->bit++; + // above we always make it one more than it is + // it's so we can return without mutating the actual iterator + // once it gets here. Just subtract one + return itr->itr - 1; + } + } + itr->bit = 0; + } + itr->word = 0; + + return UNDEFINED; +} diff --git a/source/game.c b/source/game.c index 48aa61f..63442fb 100644 --- a/source/game.c +++ b/source/game.c @@ -1,9 +1,11 @@ #include "game.h" #include +#include #include #include +#include "bitset.h" #include "tonc_memdef.h" #include "util.h" #include "sprite.h" @@ -221,17 +223,18 @@ static int hand_selections = 0; static int scored_card_index = 0; // Keeping track of what Jokers are scored at each step -static int joker_scored_index = 0; -static int joker_round_end_index = 0; +static ListItr _joker_scored_itr; static int selection_x = 0; static int selection_y = 0; static bool sort_by_suit = false; -static List *jokers = NULL; -static List *discarded_jokers = NULL; -static List *jokers_available_to_shop; // List of joker IDs +static List _owned_jokers_list; +static List _discarded_jokers_list; + +BITSET_DEFINE(_avail_jokers_bitset, MAX_DEFINABLE_JOKERS) +static List _shop_jokers_list; // Stacks static CardObject *played[MAX_SELECTION_SIZE] = {NULL}; @@ -263,6 +266,37 @@ int get_straight_and_flush_size(void) return straight_and_flush_size; } +static inline void _set_shop_joker_avail(int joker_id, bool avail) +{ + bitset_set_idx(&_avail_jokers_bitset, joker_id, avail); +} + +__attribute__((unused)) +static inline bool _get_shop_joker_avail(int joker_id) +{ + return bitset_get_idx(&_avail_jokers_bitset, joker_id); +} + +static inline int _get_num_shop_jokers_avail(void) +{ + return bitset_num_set_bits(&_avail_jokers_bitset); +} + +static inline void _reset_shop_jokers(void) +{ + int num_jokers = get_joker_registry_size(); + bitset_clear(&_avail_jokers_bitset); + for(int i = 0; i < num_jokers; i++) + { + bitset_set_idx(&_avail_jokers_bitset, i, true); + } +} + +static inline bool _no_avail_jokers(void) +{ + return bitset_is_empty(&_avail_jokers_bitset); +} + // Played stack static inline void played_push(CardObject *card_object) { @@ -347,16 +381,18 @@ int get_scored_card_index(void) return scored_card_index; } -List *get_jokers(void) +List* get_jokers_list(void) { - return jokers; + return &_owned_jokers_list; } bool is_joker_owned(int joker_id) { - for (int k = 0; k < list_get_size(jokers); k++) + ListItr itr = list_itr_create(&_owned_jokers_list); + JokerObject* joker; + + while((joker = list_itr_next(&itr))) { - JokerObject *joker = list_get(jokers, k); if (joker->joker->id == joker_id) { return true; @@ -367,7 +403,7 @@ bool is_joker_owned(int joker_id) void add_joker(JokerObject *joker_object) { - list_append(jokers, joker_object); + list_push_back(&_owned_jokers_list, joker_object); // TODO: Extract to on_joker_added() callback // In case the player gets multiple Four Fingers Jokers, @@ -387,10 +423,10 @@ void add_joker(JokerObject *joker_object) } } -void remove_held_joker(int joker_idx) +void remove_owned_joker(int owned_joker_idx) { // TODO: Extract to on_joker_removed() callback - JokerObject* joker_object = list_get(jokers, joker_idx); + JokerObject* joker_object = list_get_at_idx(&_owned_jokers_list, owned_joker_idx); // In case the player gets multiple Four Fingers Jokers, // and only reset the size when all of them have been removed if (joker_object->joker->id == FOUR_FINGERS_JOKER_ID) @@ -407,7 +443,8 @@ void remove_held_joker(int joker_idx) shortcut_joker_count--; } - list_remove_by_idx(jokers, joker_idx); + _set_shop_joker_avail(joker_object->joker->id, true); + list_remove_at_idx(&_owned_jokers_list, owned_joker_idx); } int get_deck_top(void) @@ -1421,26 +1458,20 @@ void game_change_state(enum GameState new_game_state) void jokers_available_to_shop_init() { - int num_defined_jokers = get_joker_registry_size(); - jokers_available_to_shop = list_new(num_defined_jokers); - for (intptr_t i = 0; i < num_defined_jokers; i++) - { - // Add all joker IDs sequentially - int_list_append(jokers_available_to_shop, i); - } + _reset_shop_jokers(); } void game_init() { + // Initialize all jokers list once + _owned_jokers_list = list_create(); + _discarded_jokers_list = list_create(); + _shop_jokers_list = list_create(); + // TODO: Move this to an initialization of the play scoring states + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + jokers_available_to_shop_init(); - // Initialize jokers list - if (jokers) list_destroy(&jokers); - jokers = list_new(MAX_JOKERS_HELD_SIZE); - - if (discarded_jokers != NULL) list_destroy(&discarded_jokers); - discarded_jokers = list_new(MAX_JOKERS_HELD_SIZE); - hands = max_hands; discards = max_discards; timer = TM_ZERO; @@ -1465,6 +1496,7 @@ void game_init() void game_start() { set_seed(rng_seed); + //set_seed(9); // 9 is a full house affine_background_change_background(AFFINE_BG_GAME); @@ -2149,12 +2181,12 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio } // returns true if a joker was scored, false otherwise -static bool check_and_score_joker_for_event(int* iteration_start, Card* played_card, enum JokerEvent joker_event) +static bool check_and_score_joker_for_event(ListItr* starting_joker_itr, Card* played_card, enum JokerEvent joker_event) { - for (int k = *iteration_start; k < list_get_size(jokers); k++) + JokerObject* joker; + + while((joker = list_itr_next(starting_joker_itr))) { - (*iteration_start)++; - JokerObject *joker = list_get(jokers, k); if (joker_object_score(joker, played_card, joker_event, &chips, &mult, &money, &retrigger)) { display_chips(); @@ -2225,7 +2257,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection retrigger = false; scored_card_index--; (*played_selections)--; - joker_scored_index = 0; + _joker_scored_itr = list_itr_create(&_owned_jokers_list); } // So pretend "played_selections" is now called "scored_card_index" and it counts the number of cards that have been scored @@ -2236,15 +2268,15 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection // Trigger all Jokers after each card scored if (*played_selections > 0) { - if (check_and_score_joker_for_event(&joker_scored_index, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED)) + if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED)) { return; } // Trigger all Jokers that have an effect when a card finishes scoring // (e.g. retriggers) after activating all the other scored_card Jokers normally - joker_scored_index = 0; - if (check_and_score_joker_for_event(&joker_scored_index, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED_END)) + _joker_scored_itr = list_itr_create(&_owned_jokers_list); + if (check_and_score_joker_for_event(&_joker_scored_itr, played[*played_selections - 1]->card, JOKER_EVENT_ON_CARD_SCORED_END)) { return; } @@ -2272,7 +2304,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection display_chips(); // Allow Joker scoring - joker_scored_index = 0; + _joker_scored_itr = list_itr_create(&_owned_jokers_list); return; } @@ -2280,7 +2312,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection // advance state after going past the last card (exited the loop without returning) play_state = PLAY_SCORING_JOKERS; - joker_scored_index = 0; + _joker_scored_itr = list_itr_create(&_owned_jokers_list); scored_card_index = 0; // reuse this variable for held cards return; } @@ -2299,13 +2331,14 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT); - if (check_and_score_joker_for_event(&joker_scored_index, NULL, JOKER_EVENT_INDEPENDENT)) + if (check_and_score_joker_for_event(&_joker_scored_itr, NULL, JOKER_EVENT_INDEPENDENT)) { return; } // Trigger hand end effect for all jokers once they are done scoring - if (check_and_score_joker_for_event(&joker_round_end_index, NULL, JOKER_EVENT_ON_HAND_SCORED_END)) + ListItr round_end_itr = list_itr_create(&_owned_jokers_list); + if (check_and_score_joker_for_event(&round_end_itr, NULL, JOKER_EVENT_ON_HAND_SCORED_END)) { return; } @@ -2380,8 +2413,7 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection *played_selections = 0; played_top = -1; // Reset the played stack scored_card_index = 0; - joker_scored_index = 0; - joker_round_end_index = 0; + _joker_scored_itr = list_itr_create(&_owned_jokers_list); timer = TM_ZERO; break; // Break out of the loop to avoid accessing an invalid index } @@ -2795,7 +2827,6 @@ static void game_round_end_dismiss_round_end_panel() } // Shop -static List *shop_jokers = NULL; #define REROLL_BASE_COST 5 // Base cost for rerolling the shop items static int reroll_cost = REROLL_BASE_COST; @@ -2823,78 +2854,76 @@ void erase_price_under_sprite_object(SpriteObject *sprite_object) tte_erase_rect_wrapper(price_rect); } -static int game_shop_get_random_joker_idx() +static int game_shop_get_rand_available_joker_id(void) { // Roll for what rarity the joker will be int joker_rarity = joker_get_random_rarity(); // Now determine how many jokers are available based on the rarity - int jokers_avail_size = list_get_size(jokers_available_to_shop); - int matching_indices[jokers_avail_size]; + int jokers_avail_size = _get_num_shop_jokers_avail(); + + if(jokers_avail_size == 0) return UNDEFINED; + + int matching_joker_ids[jokers_avail_size]; + int fallback_random_idx = random() % jokers_avail_size; + int fallback_random_joker_id = UNDEFINED; int match_count = 0; - for (int i = 0; i < jokers_avail_size; i++) + BitsetItr itr = bitset_itr_create(&_avail_jokers_bitset); + + int i = 0; + int joker_id = UNDEFINED; + while((joker_id = bitset_itr_next(&itr)) != UNDEFINED) { - intptr_t joker_id = int_list_get(jokers_available_to_shop, i); + if(i++ == fallback_random_idx) fallback_random_joker_id = joker_id; const JokerInfo *info = get_joker_registry_entry(joker_id); if (info->rarity == joker_rarity) { - matching_indices[match_count] = i; - match_count++; + matching_joker_ids[match_count++] = joker_id; } } - int selected_joker_idx = 0; - if (match_count > 0) - { - // If we counted at least one joker with matching rarity, pick one of them randomly - selected_joker_idx = matching_indices[random() % match_count]; - } - else - { - // Didn't find any jokers of matching rarity, just pick one at random instead - selected_joker_idx = random() % jokers_avail_size; - } + int selected_joker_id = (match_count > 0) ? + matching_joker_ids[random() % match_count] : + fallback_random_joker_id; - return selected_joker_idx; + return selected_joker_id; } static void game_shop_create_items() { tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); - if (list_get_size(jokers_available_to_shop) == 0) - { - // No jokers to create - return; - } - shop_jokers = list_new(MAX_SHOP_JOKERS); + if (_no_avail_jokers()) return; + + list_clear(&_shop_jokers_list); + _shop_jokers_list = list_create(); for (int i = 0; i < MAX_SHOP_JOKERS; i++) { - intptr_t joker_id = 0; + int joker_id = 0; #ifdef TEST_JOKER_ID0 // Allow defining an ID for a joker to always appear in shop and be tested - if (int_list_exists(jokers_available_to_shop, TEST_JOKER_ID0)) + if (_get_shop_joker_avail(TEST_JOKER_ID0)) { joker_id = TEST_JOKER_ID0; - int_list_remove_by_value(jokers_available_to_shop, joker_id); } else #endif #ifdef TEST_JOKER_ID1 - if (int_list_exists(jokers_available_to_shop, TEST_JOKER_ID1)) + if (_get_shop_joker_avail(TEST_JOKER_ID1)) { joker_id = TEST_JOKER_ID1; - int_list_remove_by_value(jokers_available_to_shop, joker_id); } else #endif { - int joker_idx = game_shop_get_random_joker_idx(); - joker_id = int_list_get(jokers_available_to_shop, joker_idx); - list_remove_by_idx(jokers_available_to_shop, joker_idx); + joker_id = game_shop_get_rand_available_joker_id(); } - + + // If for some reason only no joker is left, don't make another + if(joker_id == UNDEFINED) break; + + _set_shop_joker_avail(joker_id, false); JokerObject *joker_object = joker_object_new(joker_new(joker_id)); @@ -2906,7 +2935,8 @@ static void game_shop_create_items() print_price_under_sprite_object(joker_object->sprite_object, joker_object->joker->value); sprite_position(joker_object_get_sprite(joker_object), fx2int(joker_object->sprite_object->x), fx2int(joker_object->sprite_object->y)); - list_append(shop_jokers, joker_object); + + list_push_back(&_shop_jokers_list, joker_object); } } @@ -2948,23 +2978,28 @@ static void game_shop_reroll(int *reroll_cost) { money -= *reroll_cost; display_money(money); // Update the money display - for (int i = 0; i < list_get_size(shop_jokers); i++) + + ListItr itr = list_itr_create(&_shop_jokers_list); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) { - JokerObject *joker_object = list_get(shop_jokers, i); if (joker_object != NULL) { - int_list_append(jokers_available_to_shop, joker_object->joker->id); + _set_shop_joker_avail(joker_object->joker->id, true); joker_object_destroy(&joker_object); // Destroy the joker object if it exists } } - list_destroy(&shop_jokers); + list_clear(&_shop_jokers_list); + _shop_jokers_list = list_create(); game_shop_create_items(); - for (int i = 0; i < list_get_size(shop_jokers); i++) + itr = list_itr_create(&_shop_jokers_list); + + while((joker_object = list_itr_next(&itr))) { - JokerObject *joker_object = list_get(shop_jokers, i); if (joker_object != NULL) { joker_object->sprite_object->y = joker_object->sprite_object->ty; // Set the y position to the target position @@ -2978,7 +3013,7 @@ static void game_shop_reroll(int *reroll_cost) static int jokers_sel_row_get_size() { - return list_get_size(jokers); + return list_get_len(&_owned_jokers_list); } static void jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid, @@ -2988,16 +3023,22 @@ static void jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid, { if (prev_selection->y == row_idx) { - JokerObject* joker_object = list_get(jokers, prev_selection->x); - erase_price_under_sprite_object(joker_object->sprite_object); - sprite_object_set_focus(joker_object->sprite_object, false); + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, prev_selection->x); + if(joker_object != NULL) + { + erase_price_under_sprite_object(joker_object->sprite_object); + sprite_object_set_focus(joker_object->sprite_object, false); + } } if (new_selection->y == row_idx) { - JokerObject* joker_object = list_get(jokers, new_selection->x); - sprite_object_set_focus(joker_object->sprite_object, true); - print_price_under_sprite_object(joker_object->sprite_object, joker_get_sell_value(joker_object->joker)); + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, new_selection->x); + if(joker_object != NULL) + { + sprite_object_set_focus(joker_object->sprite_object, true); + print_price_under_sprite_object(joker_object->sprite_object, joker_get_sell_value(joker_object->joker)); + } } } @@ -3005,21 +3046,20 @@ void joker_start_discard_animation(JokerObject *joker_object) { joker_object->sprite_object->tx = int2fx(JOKER_DISCARD_TARGET.x); joker_object->sprite_object->ty = int2fx(JOKER_DISCARD_TARGET.y); - list_append(discarded_jokers, joker_object); + list_push_back(&_discarded_jokers_list, joker_object); } void game_sell_joker(int joker_idx) { - if (joker_idx < 0 || joker_idx > list_get_size(jokers)) + if (joker_idx < 0 || joker_idx >= list_get_len(&_owned_jokers_list)) return; - JokerObject *joker_object = list_get(jokers, joker_idx); + JokerObject* joker_object = (JokerObject*)list_get_at_idx(&_owned_jokers_list, joker_idx); money += joker_get_sell_value(joker_object->joker); display_money(money); erase_price_under_sprite_object(joker_object->sprite_object); - remove_held_joker(joker_idx); - int_list_append(jokers_available_to_shop, (intptr_t)joker_object->joker->id); + remove_owned_joker(joker_idx); joker_start_discard_animation(joker_object); } @@ -3037,7 +3077,7 @@ static void jokers_sel_row_on_key_hit(SelectionGrid* selection_grid, Selection* // Shop input static int shop_top_row_get_size() { - return list_get_size(shop_jokers) + 1; // + 1 to account for next round button + return list_get_len(&_shop_jokers_list) + 1; // + 1 to account for next round button } static void add_to_held_jokers(JokerObject *joker_object) @@ -3048,14 +3088,14 @@ static void add_to_held_jokers(JokerObject *joker_object) static void game_shop_buy_joker(int shop_joker_idx) { - JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx); + JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); money -= joker_object->joker->value; // Deduct the money spent on the joker display_money(money); // Update the money display erase_price_under_sprite_object(joker_object->sprite_object); sprite_object_set_focus(joker_object->sprite_object, false); add_to_held_jokers(joker_object); - list_remove_by_idx(shop_jokers, shop_joker_idx); // Remove the joker from the shop + list_remove_at_idx(&_shop_jokers_list, shop_joker_idx); // Remove the joker from the shop } static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection) @@ -3080,9 +3120,9 @@ static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* se else { int shop_joker_idx = selection->x - 1; // - 1 to account for next round button - JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx); + JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, shop_joker_idx); if (joker_object == NULL - || list_get_size(jokers) >= MAX_JOKERS_HELD_SIZE + || list_get_len(&_owned_jokers_list) >= MAX_JOKERS_HELD_SIZE || money < joker_object->joker->value) { return; @@ -3109,8 +3149,9 @@ static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int } else { - JokerObject *joker = list_get(shop_jokers, prev_selection->x - 1); - sprite_object_set_focus(joker->sprite_object, false); + int idx = prev_selection->x - 1; // -1 to account for next round button + JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); + sprite_object_set_focus(joker_object->sprite_object, false); // -1 to account for next round button } } @@ -3124,9 +3165,9 @@ static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int } else { - JokerObject *joker = list_get(shop_jokers, new_selection->x - 1); - sprite_object_set_focus(joker->sprite_object, true); - // -1 to account for next round button + int idx = new_selection->x - 1; // -1 to account for next round button + JokerObject *joker_object = (JokerObject*)list_get_at_idx(&_shop_jokers_list, idx); + sprite_object_set_focus(joker_object->sprite_object, true); } } } @@ -3223,9 +3264,10 @@ static void game_shop_outro() { tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text - for (int i = 0; i < list_get_size(shop_jokers); i++) + ListItr itr = list_itr_create(&_shop_jokers_list); + JokerObject* joker_object; + while((joker_object = list_itr_next(&itr))) { - JokerObject *joker_object = list_get(shop_jokers, i); if (joker_object != NULL) { joker_object->sprite_object->ty = int2fx(160); @@ -3253,11 +3295,12 @@ static void game_shop_on_update() { change_background(BG_ID_SHOP); - if (shop_jokers != NULL) + if (!list_is_empty(&_shop_jokers_list)) { - for (int i = 0; i < list_get_size(shop_jokers); i++) + ListItr itr = list_itr_create(&_shop_jokers_list); + JokerObject* joker_object; + while((joker_object = list_itr_next(&itr))) { - JokerObject *joker_object = list_get(shop_jokers, i); if (joker_object != NULL) { joker_object_update(joker_object); @@ -3283,18 +3326,20 @@ static void game_shop_on_update() static void game_shop_on_exit() { - for (int i = 0; i < list_get_size(shop_jokers); i++) + ListItr itr = list_itr_create(&_shop_jokers_list); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) { - JokerObject* joker_object = list_get(shop_jokers, i); if (joker_object != NULL) { - // Make the joker available back to shop - int_list_append(jokers_available_to_shop, (intptr_t)joker_object->joker->id); + // Make the joker available back to shop + _set_shop_joker_avail(joker_object->joker->id, true); } joker_object_destroy(&joker_object); // Destroy the joker objects } - - list_destroy(&shop_jokers); + + list_clear(&_shop_jokers_list); increment_blind(BLIND_STATE_DEFEATED); // TODO: Move to game_round_end()? } @@ -3509,20 +3554,23 @@ static void game_main_menu_on_update() static void discarded_jokers_update_loop() { - if (discarded_jokers == NULL) + if(list_is_empty(&_discarded_jokers_list)) { return; - - // Iterating backwards because of removal within loop - for (int i = list_get_size(discarded_jokers) - 1; i >= 0; i--) + } + + ListItr itr = list_itr_create(&_discarded_jokers_list); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) { - JokerObject* joker_object = list_get(discarded_jokers, i); joker_object_update(joker_object); if (joker_object->sprite_object->x == joker_object->sprite_object->tx && joker_object->sprite_object->y == joker_object->sprite_object->ty) { - list_remove_by_idx(discarded_jokers, i); - joker_object_destroy(&joker_object); + list_itr_remove_current_node(&itr); + joker_object_destroy(&joker_object); } + } } @@ -3539,11 +3587,13 @@ static void held_jokers_update_loop() FIXED hand_x = int2fx(HELD_JOKERS_POS.x); - int jokers_top = list_get_size(jokers) - 1; - for (int i = jokers_top; i >= 0; i--) + ListItr itr = list_itr_create(&_owned_jokers_list); + JokerObject* joker; + int jokers_top = list_get_len(&_owned_jokers_list) - 1; + int i = 0; + while((joker = list_itr_next(&itr))) { - JokerObject *joker = list_get(jokers, i); - joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i]); + joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i++]); joker_object_update(joker); } @@ -3579,9 +3629,11 @@ static void game_lose_on_update() // util we decide what we want to do after a game over. static void game_over_on_exit() { - for (int i = 0; i < list_get_size(jokers); i ++) + ListItr itr = list_itr_create(&_owned_jokers_list); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) { - JokerObject *joker_object = list_get(jokers, i); joker_object_destroy(&joker_object); } @@ -3595,7 +3647,10 @@ static void game_over_on_exit() sprite_destroy(&blind_select_tokens[BLIND_TYPE_SMALL]); sprite_destroy(&blind_select_tokens[BLIND_TYPE_BIG]); sprite_destroy(&blind_select_tokens[BLIND_TYPE_BOSS]); - list_destroy(&jokers_available_to_shop); + + list_clear(&_owned_jokers_list); + list_clear(&_discarded_jokers_list); + list_clear(&_shop_jokers_list); game_init(); diff --git a/source/joker.c b/source/joker.c index da64276..d5c5a1d 100644 --- a/source/joker.c +++ b/source/joker.c @@ -15,7 +15,6 @@ #define JOKER_SCORE_TEXT_Y 48 #define NUM_JOKERS_PER_SPRITESHEET 2 -#define MAX_DEFINABLE_JOKERS 150 static const unsigned int *joker_gfxTiles[] = { diff --git a/source/joker_effects.c b/source/joker_effects.c index aaf46b7..b7fe5fe 100644 --- a/source/joker_effects.c +++ b/source/joker_effects.c @@ -3,6 +3,7 @@ #include "util.h" #include "hand_analysis.h" #include "list.h" +#include "pool.h" #include @@ -266,22 +267,20 @@ static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card, enum Jo SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) - List* jokers = get_jokers(); + List* jokers = get_jokers_list(); // +1 xmult per empty joker slot... - int num_jokers = list_get_size(jokers); + int num_jokers = list_get_len(jokers); effect.xmult = (MAX_JOKERS_HELD_SIZE) - num_jokers; // ...and also each stencil_joker adds +1 xmult - - for (int i = 0; i < num_jokers; i++ ) + ListItr itr = list_itr_create(jokers); + JokerObject* joker_object; + + while((joker_object = list_itr_next(&itr))) { - JokerObject* joker_object = list_get(jokers, i); - if (joker_object->joker->id == JOKER_STENCIL_ID) - { - effect.xmult++; - } + if (joker_object->joker->id == JOKER_STENCIL_ID) effect.xmult++; } return effect; @@ -491,7 +490,7 @@ static JokerEffect abstract_joker_effect(Joker *joker, Card *scored_card, enum J SCORE_ON_EVENT_ONLY(JOKER_EVENT_INDEPENDENT, joker_event, effect) // +1 xmult per occupied joker slot - int num_jokers = list_get_size(get_jokers()); + int num_jokers = list_get_len(get_jokers_list()); effect.mult = num_jokers * 3; return effect; @@ -795,7 +794,6 @@ static JokerEffect triboulet_joker_effect(Joker *joker, Card *scored_card, enum return effect; } - static JokerEffect dusk_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) { JokerEffect effect = {0}; @@ -832,15 +830,16 @@ static JokerEffect dusk_joker_effect(Joker *joker, Card *scored_card, enum Joker static JokerEffect blueprint_joker_effect(Joker *joker, Card *scored_card, enum JokerEvent joker_event) { JokerEffect effect = {0}; - List* jokers = get_jokers(); - int list_size = list_get_size(jokers); - - for (int i = 0; i < list_size - 1; i++ ) + List* jokers = get_jokers_list(); + + ListItr itr = list_itr_create(jokers); + JokerObject* curr_joker_object; + + while((curr_joker_object = list_itr_next(&itr))) { - JokerObject* curr_joker_object = list_get(jokers, i); if (curr_joker_object->joker == joker) { - JokerObject* next_joker_object = list_get(jokers, i + 1); + JokerObject* next_joker_object = list_itr_next(&itr); effect = joker_get_score_effect(next_joker_object->joker, scored_card, joker_event); break; } @@ -859,8 +858,8 @@ static JokerEffect brainstorm_joker_effect(Joker *joker, Card *scored_card, enum return effect; } - List* jokers = get_jokers(); - JokerObject* first_joker = list_get(jokers, 0); + List* jokers = get_jokers_list(); + JokerObject* first_joker = list_get_at_idx(jokers, 0); if (first_joker != NULL && first_joker->joker->id != JOKER_BRAINSTORM_ID) { @@ -1083,7 +1082,7 @@ const JokerInfo joker_registry[] = { { UNCOMMON_JOKER, 7, NULL,/* Four Fingers */ on_joker_created_noop }, // 48 { COMMON_JOKER, 4, scholar_joker_effect, on_joker_created_noop }, // 49 - // The following jokers don't have sprites yet, + // The following jokers don't have sprites yet, // uncomment them when their sprites are added. #if 0 { COMMON_JOKER, 5, photograph_joker_effect, photograph_on_joker_created }, diff --git a/source/list.c b/source/list.c index cf66125..e8c55e8 100644 --- a/source/list.c +++ b/source/list.c @@ -1,121 +1,213 @@ -#include #include #include #include "list.h" -#include "util.h" +#include "pool.h" -List *list_new(int init_size) { - List *list = (List *)malloc(sizeof(List)); - if (list == NULL) return NULL; - list->_array = (void **)malloc(sizeof(void*) * init_size); - if (!list->_array) - { - free(list); - return NULL; - } - list->size = 0; - list->allocated_size = init_size; +/** + * Remove a node from a list. + * + * Remove a @ref ListNode from a @ref List. There are no checks to ensure that the + * passed `node` is actually part of the passed `list`. Handle with care. + * This is used with the @ref ListItr specifically. + * + * @param list pointer to a @ref List + * @param node pointer to a @ref ListNode + */ +static void _list_remove_node(List *list, ListNode *node); + +/** + * Get the next @ref ListNode in a @ref ListItr + * + * Note: Use of this function outside of testing is strongly discouraged. Unless + * you really want to access the @ref ListNode itself, it's preferred to just use + * @ref list_itr_next . + * + * @param itr pointer to the @ref ListItr + * + * @return A pointer to the @ref ListNode in the itr, otherwise return NULL. + */ +static ListNode* _list_itr_node_next(ListItr* itr); + + +List list_create(void) +{ + List list = { .head = NULL, .tail = NULL, .len = 0 }; return list; } -void list_destroy(List **list) { - if (list == NULL || *list == NULL) - return; +void list_clear(List* list) +{ + if(list_is_empty(list)) return; + + ListItr itr = list_itr_create(list); + ListNode* ln; + + while((ln = _list_itr_node_next(&itr))) { - free((*list)->_array); - free(*list); + POOL_FREE(ListNode, ln); } - *list = NULL; + list->head = NULL; + list->tail = NULL; + list->len = 0; } -bool int_list_append(List *list, intptr_t value) +bool list_is_empty(const List* list) { - return list_append(list, (void*)value); + return list->len == 0; } -bool list_append(List *list, void *value) +void list_push_front(List *list, void* data) { - if (list->size >= list->allocated_size) + ListNode *node = POOL_GET(ListNode); + + node->data = data; + node->prev = NULL; + node->next = list->head; + + if (list_is_empty(list)) { - int new_size = list->allocated_size * 2; - void **new_arr = (void **)realloc(list->_array, sizeof(void*) * new_size); - if (new_arr == NULL) - return false; - list->_array = new_arr; - list->allocated_size = new_size; + list->tail = node; + } + else + { + list->head->prev = node; } - list->_array[list->size++] = value; - return true; + list->head = node; + + list->len++; } -bool list_remove_by_idx(List *list, int index) { - if (index < 0 || index >= list->size) - return false; - for (int i = index; i < list->size - 1; ++i) - { - list->_array[i] = list->_array[i + 1]; - } - list->size--; - return true; -} - -bool list_remove_by_value(List *list, void* value) +void list_push_back(List *list, void* data) { - for (int i = 0; i < list->size; i++) + ListNode* node = POOL_GET(ListNode); + node->data = data; + node->prev = list->tail; + node->next = NULL; + + if (list_is_empty(list)) { - if (list->_array[i] == value) - { - return list_remove_by_idx(list, i); - } - } - - return false; -} - -bool int_list_remove_by_value(List *list, intptr_t value) -{ - return list_remove_by_value(list, (void*)value); -} - -void* list_get(List *list, int index) -{ - if (index < 0 || index >= list->size) - return NULL; - return list->_array[index]; -} - -intptr_t int_list_get(List *list, int index) -{ - return (intptr_t)list_get(list, index); -} - -int list_get_size(List *list) -{ - if (list == NULL) - { - return UNDEFINED; - } - return list->size; -} - -bool list_exists(List *list, void *value) -{ - if (list == NULL) return false; - - for (int i = 0; i < list->size; i++) - { - if (list->_array[i] == value) + list->head = node; + } + else + { + list->tail->next = node; + } + + list->tail = node; + + list->len++; +} + +static void _list_remove_node(List *list, ListNode *node) +{ + if(node->prev && !node->next) // end of list + { + node->prev->next = NULL; + list->tail = node->prev; + } + else if(node->prev && node->next) // somewhere in between + { + node->prev->next = node->next; + node->next->prev = node->prev; + } + else if(node->next && !node->prev) // beginning of list + { + node->next->prev = NULL; + list->head = node->next; + } + else if(!node->prev && !node->next) // only element in list + { + list->head = NULL; + list->tail = NULL; + } + + POOL_FREE(ListNode, node); + + list->len--; +} + +int list_get_len(const List* list) +{ + return list->len; +} + +void* list_get_at_idx(List* list, int n) +{ + if(n >= list_get_len(list) || n < 0) return NULL; + + int curr_idx = 0; + ListItr itr = list_itr_create(list); + void* data = NULL; + + while((data = list_itr_next(&itr))) + { + if (n == curr_idx++) return data; + } + + return NULL; +} + +bool list_remove_at_idx(List* list, int n) +{ + if(n >= list_get_len(list) || n < 0) return false; + + int len = 0; + ListItr itr = list_itr_create(list); + ListNode* ln; + + while((ln = _list_itr_node_next(&itr))) + { + if(n == len++) { + _list_remove_node(list, ln); return true; } } - return false; } -bool int_list_exists(List *list, intptr_t value) +ListItr list_itr_create(List* list) { - return list_exists(list, (void*)value); -} \ No newline at end of file + ListItr itr = + { + .list = list, + .next_node = !list_is_empty(list) ? list->head : NULL, + .current_node = NULL, + }; + + return itr; +} + +void* list_itr_next(ListItr* itr) +{ + ListNode* ln = _list_itr_node_next(itr); + return ln ? ln->data : NULL; +} + +static ListNode* _list_itr_node_next(ListItr* itr) +{ + if(!itr->next_node) return NULL; + + itr->current_node = itr->next_node; + + ListNode* ln = itr->next_node; + + if(ln->next) + { + itr->next_node = ln->next; + return ln; + } + + itr->next_node = NULL; + return ln; +} + +void list_itr_remove_current_node(ListItr* itr) +{ + if(!itr || !itr->current_node) return; + ListNode* tmp_prev = itr->current_node->prev; + _list_remove_node(itr->list, itr->current_node); + itr->current_node = tmp_prev; +} diff --git a/source/pool.c b/source/pool.c index 8191413..0d10721 100644 --- a/source/pool.c +++ b/source/pool.c @@ -1,47 +1,4 @@ #include "pool.h" -#include "util.h" - -void pool_bm_clear_idx(PoolBitmap *bm, int idx) -{ - uint32_t i = idx / POOL_BITS_PER_WORD; - uint32_t b = idx % POOL_BITS_PER_WORD; - - // Below are the "fast" forms of the above operations, respectively. - // These are more efficient, but removed for readability - // See: https://github.com/cellos51/balatro-gba/pull/132#discussion_r2365966071 - // Divide by 32 to get the word index - //uint32_t i = idx >> 5; - // Get last 5-bits, same as a modulo (% 32) operation on positive numbers - //uint32_t b = idx & 0x1F; - bm->w[i] &= ~((uint32_t)1 << b); -} - -int pool_bm_get_free_idx(PoolBitmap *bm) -{ - for (uint32_t i = 0; i < bm->nwords; i++) - { - uint32_t inv = ~bm->w[i]; - - // guard so we don't call `ctz` with 0, since __builtin_ctz(0) is undefined - // https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html#index-_005f_005fbuiltin_005fctz - // - // By using the bitwise inverse of the word, you can skip words that are full - // quickly (where the value is 0 or 'false' since all bits are '1', or 'in use'). Any value greater - // than 0 indicates there is a free slot. Then, when counting the trailing 0's, you can test very quickly - // where the first free slot is. This operation prevents looping through every bit of filled flags, and - // will instead operate only on the first word with free slots. - if (inv) - { - int bit = __builtin_ctz(inv); - bm->w[i] |= ((uint32_t)1 << bit); - int idx = i * POOL_BITS_PER_WORD + bit; - return (idx < bm->cap) ? idx : UNDEFINED; - } - } - - return UNDEFINED; -} - #define POOL_ENTRY(name, capacity) \ POOL_DEFINE_TYPE(name, capacity); diff --git a/tests/bitset/Makefile b/tests/bitset/Makefile new file mode 100644 index 0000000..2eed24d --- /dev/null +++ b/tests/bitset/Makefile @@ -0,0 +1,16 @@ + +CC := gcc +CFLAGS := -I../../include -I. \ + -g -O3 -Wall -Werror +SRC := bitset_test.c \ + ../../source/bitset.c +OUT := build/bitset_test + +$(OUT): $(SRC) | build + $(CC) $(CFLAGS) -o $@ $^ + +build: + mkdir -p build + +clean: + rm -f $(OUT) diff --git a/tests/bitset/bitset_test.c b/tests/bitset/bitset_test.c new file mode 100644 index 0000000..b323dfc --- /dev/null +++ b/tests/bitset/bitset_test.c @@ -0,0 +1,122 @@ +#include "bitset.h" +#include "util.h" + +#include +#include +#include + +BITSET_DEFINE(test_bitset, BITSET_MAX_BITS) + +// bitset_set_idx +// bitset_get_idx +// bitset_num_set_bits +// bitset_is_empty +// bitset_clear +void test_bitset_fill_all_and_empty(void) +{ + assert(bitset_is_empty(&test_bitset)); + + for(int i = 0; i < BITSET_MAX_BITS; i++) + { + bitset_set_idx(&test_bitset, i, true); + assert(bitset_num_set_bits(&test_bitset) == (i + 1)); + } + + for(int i = 0; i < BITSET_MAX_BITS; i++) + { + assert(bitset_get_idx(&test_bitset, i)); + } + + assert(!bitset_is_empty(&test_bitset)); + + bitset_clear(&test_bitset); + + for(int i = 0; i < BITSET_MAX_BITS; i++) + { + assert(!bitset_get_idx(&test_bitset, i)); + } + + assert(bitset_is_empty(&test_bitset)); +} + +// bitset_set_idx +// bitset_num_set_bits +// bitset_find_idx_of_nth_set +// bitset_is_empty +// bitset_clear +void test_bitset_insertions_at_boundry(void) +{ + assert(bitset_is_empty(&test_bitset)); + + bitset_set_idx(&test_bitset, 30, true); + bitset_set_idx(&test_bitset, 31, true); + bitset_set_idx(&test_bitset, 32, true); + + assert(bitset_num_set_bits(&test_bitset) == 3); + + bitset_set_idx(&test_bitset, 31, false); + + assert(bitset_num_set_bits(&test_bitset) == 2); + + assert(bitset_find_idx_of_nth_set(&test_bitset, 0) == 30); + assert(bitset_find_idx_of_nth_set(&test_bitset, 1) == 32); + + bitset_set_idx(&test_bitset, 0, true); + + assert(bitset_find_idx_of_nth_set(&test_bitset, 0) == 0); + + bitset_set_idx(&test_bitset, 0, false); + bitset_set_idx(&test_bitset, 0, 30); + + bitset_clear(&test_bitset); + + assert(bitset_is_empty(&test_bitset)); +} + +// bitset_set_idx +// bitset_is_empty +// bitset_clear +// bitset_itr_create +// bitset_itr_next +void test_bitset_iterator(void) +{ + assert(bitset_is_empty(&test_bitset)); + + int test_indices[6] = {0, 31, 32, 63, 64, 100}; + + for(int i = 0; i < 6; i++) + { + bitset_set_idx(&test_bitset, test_indices[i], true); + } + + BitsetItr itr = bitset_itr_create(&test_bitset); + + int test_val = UNDEFINED; + int index = 0; + while((test_val = bitset_itr_next(&itr)) != UNDEFINED) + { + assert(test_val == test_indices[index++]); + } + + assert(index == 6); + + bitset_clear(&test_bitset); + + assert(bitset_is_empty(&test_bitset)); +} + +int main(void) +{ + printf("Testing Bitset Fill All and Empty.\n"); + test_bitset_fill_all_and_empty(); + printf("Testing Bitset Insertions At Boundry.\n"); + test_bitset_insertions_at_boundry(); + printf("Testing Bitset Iterator.\n"); + test_bitset_iterator(); + + printf("-------------------------------------------------------------------------------\n"); + printf("Bitset Tests Passed :)\n"); + printf("-------------------------------------------------------------------------------\n"); + + return 0; +} diff --git a/tests/list/Makefile b/tests/list/Makefile new file mode 100644 index 0000000..0a433b6 --- /dev/null +++ b/tests/list/Makefile @@ -0,0 +1,19 @@ + +CC := gcc +CFLAGS := -I../../include -I. \ + -g -O3 -Wall -Werror -DPOOLS_TEST_ENV=yes + +SRC := list_test.c \ + ../../source/list.c \ + ../../source/pool.c \ + ../../source/bitset.c +OUT := build/list_test + +$(OUT): $(SRC) | build + $(CC) $(CFLAGS) -o $@ $^ + +build: + mkdir -p build + +clean: + rm -f $(OUT) diff --git a/tests/list/def_test_mempool.h b/tests/list/def_test_mempool.h new file mode 100644 index 0000000..7cccecf --- /dev/null +++ b/tests/list/def_test_mempool.h @@ -0,0 +1,5 @@ +#include "list.h" +#include + +POOL_ENTRY(ListNode, MAX_LIST_NODES); + diff --git a/tests/list/list_test.c b/tests/list/list_test.c new file mode 100644 index 0000000..647df13 --- /dev/null +++ b/tests/list/list_test.c @@ -0,0 +1,307 @@ +#include "list.h" + +#include +#include +#include + +// As simple as it gets, just needs to be initialized correctly +// - list_create +// - list_is_empty +// - list_get_len +// - list_clear +void create_and_clear_list(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_get_len(&my_cool_list) == 0); + assert(list_is_empty(&my_cool_list)); + + list_clear(&my_cool_list); + + assert(list_is_empty(&my_cool_list)); +} + +// Push back one entry, make sure it looks as expected +// tests: +// - list_create +// - list_push_back +// - list_is_empty +// - list_get_len +// - list_get_at_idx +// - list_clear +void push_back_one_entry(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_get_len(&my_cool_list) == 0); + assert(list_is_empty(&my_cool_list)); + + int test_data = 1337; + + list_push_back(&my_cool_list, &test_data); + + // should have data and be the same head/tail + assert(my_cool_list.head != NULL); + assert(my_cool_list.tail != NULL); + assert(my_cool_list.head == my_cool_list.tail); + assert(list_get_len(&my_cool_list) == 1); + + // pointer should be the same + assert(my_cool_list.head->data == &test_data); + // and consequently the value should be the same + assert(*(int*)(my_cool_list.head->data) == test_data); + assert(list_get_at_idx(&my_cool_list, 0) == &test_data); + assert(list_get_at_idx(&my_cool_list, 1) == NULL); + assert(!list_is_empty(&my_cool_list)); + // clear the list + list_clear(&my_cool_list); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_is_empty(&my_cool_list)); +} + +// Push front one entry, make sure it looks as expected +// - list_create +// - list_push_front +// - list_is_empty +// - list_get_len +// - list_get_at_idx +// - list_clear +void push_front_one_entry(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_get_len(&my_cool_list) == 0); + assert(list_is_empty(&my_cool_list)); + + int test_data = 1337; + + list_push_front(&my_cool_list, &test_data); + + // should have data and be the same head/tail + assert(my_cool_list.head != NULL); + assert(my_cool_list.tail != NULL); + assert(my_cool_list.head == my_cool_list.tail); + assert(list_get_len(&my_cool_list) == 1); + assert(!list_is_empty(&my_cool_list)); + + // pointer should be the same + assert(my_cool_list.head->data == &test_data); + // and consequently the value should be the same + assert(*(int*)(my_cool_list.head->data) == test_data); + assert(list_get_at_idx(&my_cool_list, 0) == &test_data); + assert(list_get_at_idx(&my_cool_list, 1) == NULL); + // clear the list + list_clear(&my_cool_list); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_is_empty(&my_cool_list)); +} + +// This is meant to be a full test of every function including the use of +// the ListItr iterator +// +// Push back three entries. To cover the 3 states of nodes outside of single node +// lists. 3 states being, node at the front, node at the middle, and node at the end +// +// Afterwards remove one entry with `list_remove_at_idx` and then remove another +// with `list_remove_node` +// +// Lastly, push to the front the same 3 entries and verify the state of the list +// before detroying. +// +// - list_create +// - list_push_front +// - list_is_empty +// - list_get_len +// - list_get_at_idx +// - list_remove_at_idx +// - list_itr_create +// - list_itr_next +// - list_clear +// - list_itr_remove_node_current; +void push_back_three_remove_push_front_three_entries(void) +{ + List my_cool_list = list_create(); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + + int test_data[3] = {0, 1, 2}; + + list_push_back(&my_cool_list, &test_data[0]); + + // Make sure with one entry it looks right + assert(my_cool_list.head == my_cool_list.tail); + + list_push_back(&my_cool_list, &test_data[1]); + + // Now with two it should be different + assert(my_cool_list.head != my_cool_list.tail); + + list_push_back(&my_cool_list, &test_data[2]); + + // 3 entries? + assert(list_get_len(&my_cool_list) == 3); + + // now use an iterator to examine each node + int* data; + + ListItr list_itr = list_itr_create(&my_cool_list); + + int itr = 0; + + ListNode* prev_ln = NULL; + + while((data = list_itr_next(&list_itr))) + { + assert(data != NULL); + assert(data == &test_data[itr]); + ListNode* ln = list_itr.current_node; + switch(itr) + { + case 0: + assert(ln->prev == NULL); + assert(ln->next != NULL); + assert(ln->next->prev == ln); + break; + case 1: + assert(ln->prev == prev_ln); + assert(ln->prev != NULL); + assert(ln->next != NULL); + assert(ln->next->prev == ln); + assert(ln->prev->next == ln); + break; + case 2: + assert(ln->prev != NULL); + assert(ln->next == NULL); + assert(ln->prev->next == ln); + assert(ln->prev == prev_ln); + break; + default: + assert(false); // shouldn't get here + break; + } + itr++; + prev_ln = ln; + } + + // remove the middle entry + assert(list_remove_at_idx(&my_cool_list, 1)); + // can't remove entry 2 now (doesn't exist) + assert(!list_remove_at_idx(&my_cool_list, 2)); + assert(list_get_len(&my_cool_list) == 2); + + list_itr = list_itr_create(&my_cool_list); + data = NULL; + itr = 0; + prev_ln = NULL; + while((data = list_itr_next(&list_itr))) + { + assert(data != NULL); + ListNode* ln = list_itr.current_node; + switch(itr) + { + case 0: + assert(ln->prev == NULL); + assert(ln->next != NULL); + assert(ln->next->prev == ln); + break; + case 1: + assert(ln->prev != NULL); + assert(ln->next == NULL); + assert(ln->prev->next == ln); + assert(ln->prev == prev_ln); + list_itr_remove_current_node(&list_itr); + break; + default: + assert(false); // shouldn't get here + break; + } + itr++; + prev_ln = ln; + } + + assert(list_get_len(&my_cool_list) == 1); + assert(my_cool_list.head == my_cool_list.tail); + + // now push the same 3 pointers to the front of the list + list_push_front(&my_cool_list, &test_data[0]); + list_push_front(&my_cool_list, &test_data[1]); + list_push_front(&my_cool_list, &test_data[2]); + + // now, the list should be in the order... + // test_data[2] -> test_data[1] -> test_data[0] -> test_data[0] + list_itr = list_itr_create(&my_cool_list); + data = NULL; + itr = 0; + while((data = list_itr_next(&list_itr))) + { + assert(data != NULL); + ListNode* ln = list_itr.current_node; + + switch(itr) + { + case 0: + assert(ln->data == &test_data[2]); + break; + case 1: + assert(ln->data == &test_data[1]); + break; + case 2: + assert(ln->data == &test_data[0]); + break; + case 3: + assert(ln->data == &test_data[0]); + break; + default: + assert(false); // shouldn't get here + break; + } + itr++; + } + + assert(list_get_len(&my_cool_list) == 4); + + // clear the list + list_clear(&my_cool_list); + + // verify no data + assert(my_cool_list.head == NULL); + assert(my_cool_list.tail == NULL); + assert(list_is_empty(&my_cool_list)); +} + +int main(void) +{ + printf("Testing List Create and Clear.\n"); + create_and_clear_list(); + + printf("Testing List Push Back.\n"); + push_back_one_entry(); + + printf("Testing List Push Front.\n"); + push_front_one_entry(); + printf("Testing List Complete Exercise.\n"); + push_back_three_remove_push_front_three_entries(); + + printf("-------------------------------------------------------------------------------\n"); + printf("List Tests Passed :)\n"); + printf("-------------------------------------------------------------------------------\n"); + + return 0; +} diff --git a/tests/pool/Makefile b/tests/pool/Makefile index d2d7fb7..f2f6093 100644 --- a/tests/pool/Makefile +++ b/tests/pool/Makefile @@ -3,8 +3,8 @@ CC := gcc CFLAGS := -I../../include -I. \ -g -O3 -Wall -Werror -DPOOLS_TEST_ENV=yes -SRC := pool_test.c ../../source/pool.c -OUT := build/pool_test +SRC := pool_test.c ../../source/pool.c ../../source/bitset.c +OUT := build/pool_test $(OUT): $(SRC) | build $(CC) $(CFLAGS) -o $@ $^ diff --git a/tests/pool/pool_test.c b/tests/pool/pool_test.c index 398d7ee..7f38942 100644 --- a/tests/pool/pool_test.c +++ b/tests/pool/pool_test.c @@ -107,9 +107,9 @@ int main(void) printf("Testing Pool Fill and Empty.\n"); if(!test_fill_and_empty()) return UNDEFINED; - printf("---------------------------------------------------------\n"); - printf("Pool Tests Passed\n"); - printf("---------------------------------------------------------\n"); + printf("-------------------------------------------------------------------------------\n"); + printf("Pool Tests Passed :)\n"); + printf("-------------------------------------------------------------------------------\n"); printf("Testing execution time for fun :)\n\n"); timestamp_t t1 = get_time(); diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 86b69f7..14cd473 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -2,12 +2,20 @@ set -eu -run_pool_test() { - cd pool - make clean - make - ./build/pool_test - cd - > /dev/null +run_test() { + name="$1" + + echo "===============================================================================" + echo "Running test for: $1" + echo "===============================================================================" + + cd "$name" 2>&1 > /dev/null + make clean > /dev/null + make > /dev/null + ./build/"$name"_test + cd - 2>&1 > /dev/null } -run_pool_test +run_test bitset +run_test pool +run_test list