├── .gitignore ├── res ├── cards.png ├── font.png ├── logo.png ├── jokers1.png └── jokers2.png ├── .clang-format ├── utils.h ├── debug.h ├── renderer.h ├── CMakeLists.txt ├── content ├── spectral.h ├── tarot.h ├── joker.h ├── joker.c ├── spectral.c └── tarot.c ├── random.h ├── CREDITS.md ├── text.h ├── system.h ├── .github └── workflows │ └── build.yml ├── debug.c ├── gfx.h ├── main.c ├── state.h ├── README.md ├── random.c ├── renderer.c ├── utils.c ├── state.c ├── game.h ├── system.c ├── LICENSE.md ├── text.c ├── lib └── cvector.h └── gfx.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | .cache 4 | 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /res/cards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwerenta/joker-poker/HEAD/res/cards.png -------------------------------------------------------------------------------- /res/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwerenta/joker-poker/HEAD/res/font.png -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwerenta/joker-poker/HEAD/res/logo.png -------------------------------------------------------------------------------- /res/jokers1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwerenta/joker-poker/HEAD/res/jokers1.png -------------------------------------------------------------------------------- /res/jokers2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwerenta/joker-poker/HEAD/res/jokers2.png -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 120 3 | DerivePointerAlignment: false 4 | PointerAlignment: Right 5 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include "renderer.h" 5 | #include "state.h" 6 | 7 | CustomElementData create_spread_item_element(NavigationSection section, uint8_t i); 8 | 9 | void get_shop_item_tooltip_content(Clay_String *name, Clay_String *description, ShopItem *item); 10 | void get_nav_item_tooltip_content(Clay_String *name, Clay_String *description, NavigationSection section); 11 | 12 | uint8_t get_item_price(NavigationSection section, uint8_t i); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | typedef enum { LOG_INFO, LOG_WARNING, LOG_ERROR } LogLevel; 5 | 6 | #ifdef DEBUG_BUILD 7 | // Only define functions if in debug mode 8 | void log_init(void); 9 | void log_shutdown(void); 10 | void log_message(LogLevel level, const char *format, ...); 11 | #else 12 | // Empty macros for release builds (compiler will optimize these out) 13 | #define log_init() 14 | #define log_shutdown() 15 | #define log_message(level, format, ...) 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H 2 | #define RENDERER_H 3 | 4 | #include 5 | 6 | #include "game.h" 7 | 8 | typedef enum { 9 | CUSTOM_ELEMENT_CARD, 10 | CUSTOM_ELEMENT_JOKER, 11 | CUSTOM_ELEMENT_CONSUMABLE, 12 | CUSTOM_ELEMENT_VOUCHER, 13 | CUSTOM_ELEMENT_BOOSTER_PACK, 14 | CUSTOM_ELEMENT_DECK, 15 | } CustomElementType; 16 | 17 | typedef struct __attribute__((aligned(16))) { 18 | CustomElementType type; 19 | union { 20 | Card card; 21 | Joker joker; 22 | Consumable consumable; 23 | Voucher voucher; 24 | BoosterPackItem booster_pack; 25 | Deck deck; 26 | }; 27 | } CustomElementData; 28 | 29 | void renderer_init(); 30 | void execute_render_commands(Clay_RenderCommandArray render_commands); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | 3 | project(joker-poker) 4 | 5 | add_executable(${PROJECT_NAME} main.c gfx.c game.c system.c state.c text.c renderer.c debug.c random.c utils.c content/joker.c content/tarot.c content/spectral.c) 6 | target_include_directories(${PROJECT_NAME} PRIVATE lib) 7 | 8 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 9 | target_compile_definitions(${PROJECT_NAME} PRIVATE DEBUG_BUILD) 10 | endif() 11 | 12 | target_link_libraries(${PROJECT_NAME} PRIVATE 13 | pspgu 14 | pspdisplay 15 | pspge 16 | pspctrl 17 | ) 18 | 19 | # Create an EBOOT.PBP file 20 | create_pbp_file( 21 | TARGET ${PROJECT_NAME} 22 | ICON_PATH NULL 23 | BACKGROUND_PATH NULL 24 | PREVIEW_PATH NULL 25 | TITLE Joker\ Poker 26 | VERSION 0.40 27 | ) 28 | -------------------------------------------------------------------------------- /content/spectral.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECTRAL_H 2 | #define SPECTRAL_H 3 | 4 | #include 5 | 6 | typedef enum { 7 | SPECTRAL_FAMILIAR, 8 | SPECTRAL_GRIM, 9 | SPECTRAL_INCANTATION, 10 | SPECTRAL_TALISMAN, 11 | SPECTRAL_AURA, 12 | SPECTRAL_WRAITH, 13 | SPECTRAL_SIGIL, 14 | SPECTRAL_OUIJA, 15 | SPECTRAL_ECTOPLASM, 16 | SPECTRAL_IMMOLATE, 17 | SPECTRAL_ANKH, 18 | SPECTRAL_DEJA_VU, 19 | SPECTRAL_HEX, 20 | SPECTRAL_TRANCE, 21 | SPECTRAL_MEDIUM, 22 | SPECTRAL_CRYPTID, 23 | SPECTRAL_SOUL, 24 | SPECTRAL_BLACK_HOLE 25 | } Spectral; 26 | 27 | const char *get_spectral_card_name(Spectral spectral); 28 | const char *get_spectral_card_description(Spectral spectral); 29 | 30 | uint8_t get_spectral_max_selected(Spectral spectral); 31 | uint8_t use_spectral_card(Spectral spectral); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /content/tarot.h: -------------------------------------------------------------------------------- 1 | #ifndef TAROT_H 2 | #define TAROT_H 3 | 4 | #include 5 | 6 | typedef enum { 7 | TAROT_FOOL, 8 | TAROT_MAGICIAN, 9 | TAROT_HIGH_PRIESTESS, 10 | TAROT_EMPRESS, 11 | TAROT_EMPEROR, 12 | TAROT_HIEROPHANT, 13 | TAROT_LOVERS, 14 | TAROT_CHARIOT, 15 | TAROT_JUSTICE, 16 | TAROT_HERMIT, 17 | TAROT_WHEEL_OF_FORTUNE, 18 | TAROT_STRENGTH, 19 | TAROT_HANGED_MAN, 20 | TAROT_DEATH, 21 | TAROT_TEMPERANCE, 22 | TAROT_DEVIL, 23 | TAROT_TOWER, 24 | TAROT_STAR, 25 | TAROT_MOON, 26 | TAROT_SUN, 27 | TAROT_JUDGEMENT, 28 | TAROT_WORLD 29 | } Tarot; 30 | 31 | const char *get_tarot_card_name(Tarot tarot); 32 | const char *get_tarot_card_description(Tarot tarot); 33 | 34 | uint8_t get_tarot_max_selected(Tarot tarot); 35 | uint8_t use_tarot_card(Tarot tarot); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /random.h: -------------------------------------------------------------------------------- 1 | #ifndef RANDOM_H 2 | #define RANDOM_H 3 | 4 | #include 5 | #include 6 | 7 | #include "cvector.h" 8 | #include "game.h" 9 | 10 | #define random_vector_index(vec) random_max_value(cvector_size(vec) - 1) 11 | #define random_vector_item(vec) vec[random_vector_index(vec)] 12 | 13 | typedef bool (*RangeFilter)(uint8_t index); 14 | 15 | void rng_init(); 16 | 17 | int16_t random_filtered_range_pick(uint8_t start, uint8_t end, RangeFilter filter); 18 | int16_t random_filtered_vector_pick(cvector_vector_type(void) vec, RangeFilter filter); 19 | int16_t random_weighted(uint16_t *weights, uint8_t count); 20 | bool random_percent(float probability); 21 | bool random_chance(uint8_t numerator, uint8_t denominator); 22 | uint8_t random_max_value(uint8_t max_value); 23 | uint8_t random_in_range(uint8_t min_value, uint8_t max_value); 24 | 25 | Joker random_available_joker(); 26 | Joker random_available_joker_by_rarity(Rarity rarity); 27 | 28 | Card random_card(); 29 | Card random_shop_card(); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | ## References 2 | 3 | The following software libraries and other references are utilized in the creation this repository. 4 | 5 | - [c-vector](https://github.com/eteran/c-vector?tab=readme-ov-file) by [Evan Teran](https://github.com/eteran) is licensed under [MIT](https://github.com/eteran/c-vector/blob/master/LICENSE) 6 | - [Clay](https://github.com/nicbarker/clay) by [Nic Barker](https://github.com/nicbarker) is licensed under [zlib](https://github.com/nicbarker/clay/blob/main/LICENSE.md) 7 | - [Poker cards asset pack](https://ivoryred.itch.io/pixel-poker-cards) by [IvoryRed](https://ivoryred.itch.io) is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) / Changed placement of sprites, removed backgrounds, added custom elements 8 | - [Pixel Bitmap Fonts](https://frostyfreeze.itch.io/pixel-bitmap-fonts-png-xml) by [frostyfreeze](https://frostyfreeze.itch.io) is licensed under [CC0](https://creativecommons.org/publicdomain/zero/1.0/) 9 | - Logo by Grzybson4 is licensed under [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/) 10 | -------------------------------------------------------------------------------- /text.h: -------------------------------------------------------------------------------- 1 | #ifndef TEXT_H 2 | #define TEXT_H 3 | 4 | #include 5 | 6 | #include "game.h" 7 | 8 | char *get_poker_hand_name(uint16_t hand_union); 9 | char *get_planet_card_name(Planet planet); 10 | 11 | char *get_card_suit_name(Suit suit); 12 | char *get_card_rank_name(Rank rank); 13 | Clay_String get_full_card_name(Suit suit, Rank rank); 14 | 15 | char *get_booster_pack_size_name(BoosterPackSize size); 16 | char *get_booster_pack_type_name(BoosterPackType type); 17 | char *get_booster_pack_description_suffix(BoosterPackType type); 18 | Clay_String get_full_booster_pack_name(BoosterPackSize size, BoosterPackType type); 19 | 20 | char *get_voucher_name(Voucher voucher); 21 | char *get_voucher_description(Voucher voucher); 22 | 23 | char *get_deck_name(Deck deck); 24 | char *get_deck_description(Deck deck); 25 | 26 | char *get_stake_name(Stake stake); 27 | char *get_stake_description(Stake stake); 28 | 29 | char *get_blind_name(BlindType blind_type); 30 | char *get_blind_description(BlindType blind_type); 31 | 32 | char *get_tag_name(Tag tag); 33 | char *get_tag_description(Tag tag); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /system.h: -------------------------------------------------------------------------------- 1 | #ifndef SYSTEM_H 2 | #define SYSTEM_H 3 | 4 | #include 5 | 6 | #define BUFFER_WIDTH (512) 7 | #define BUFFER_HEIGHT SCREEN_HEIGHT 8 | 9 | #define RENDER_BATCH_SIZE (512) 10 | 11 | #define RGB(r, g, b) RGBA(r, g, b, 255) 12 | #define RGBA(r, g, b, a) ((a << 24) | (b << 16) | (g << 8) | r) 13 | 14 | typedef struct { 15 | float x, y, w, h; 16 | } Rect; 17 | 18 | typedef struct { 19 | float x, y; 20 | } Vector2; 21 | 22 | typedef struct { 23 | float u, v; 24 | uint32_t color; 25 | float x, y, z; 26 | } Vertex; 27 | 28 | typedef struct { 29 | int width, height; 30 | uint32_t *data; 31 | } Texture; 32 | 33 | typedef struct { 34 | uint8_t is_angled; 35 | Vertex *vertices; 36 | uint16_t count; 37 | Texture *texture; 38 | } RenderBatch; 39 | 40 | Texture *load_texture(const char *filename); 41 | Texture *init_texture(uint32_t width, uint32_t height); 42 | 43 | void flush_render_batch(); 44 | 45 | void draw_rectangle(Rect *rect, uint32_t color); 46 | void draw_texture(Texture *texture, Rect *src, Rect *dst, uint32_t color, float angle); 47 | Vector2 draw_text(const char *text, const Vector2 *pos, uint32_t color); 48 | Vector2 draw_text_len(const char *text, uint32_t len, const Vector2 *pos, uint32_t color); 49 | 50 | void handle_controls(); 51 | 52 | int setup_callbacks(); 53 | void init_gu(char list[]); 54 | void end_gu(); 55 | 56 | void start_frame(char list[]); 57 | void end_frame(); 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Game 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Install dependencies 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install build-essential cmake pkgconf libreadline8 libusb-0.1 libgpgme11 libarchive-tools fakeroot zip 21 | 22 | - name: Install pspdev toolchain 23 | run: | 24 | curl -L -O https://github.com/pspdev/pspdev/releases/latest/download/pspdev-ubuntu-latest-x86_64.tar.gz 25 | tar -xzf pspdev-ubuntu-latest-x86_64.tar.gz 26 | echo "PSPDEV=$GITHUB_WORKSPACE/pspdev" >> "$GITHUB_ENV" 27 | 28 | - name: Build the game 29 | run: | 30 | $PSPDEV/bin/psp-cmake -DBUILD_PRX=1 -DENC_PRX=1 -B build 31 | cmake --build build 32 | 33 | - name: Prepare release zip 34 | run: | 35 | mkdir -p dist/joker-poker 36 | cp build/EBOOT.PBP dist/joker-poker/EBOOT.PBP 37 | cp -r res dist/joker-poker/ 38 | cd dist 39 | zip -r joker-poker.zip joker-poker 40 | 41 | - name: Create GitHub Release with Asset 42 | uses: softprops/action-gh-release@v2 43 | with: 44 | generate_release_notes: true 45 | files: ./dist/joker-poker.zip 46 | -------------------------------------------------------------------------------- /debug.c: -------------------------------------------------------------------------------- 1 | #include "debug.h" 2 | 3 | #ifdef DEBUG_BUILD 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static const char *LOG_FILENAME = "ms0:/joker_poker_debug.log"; 11 | static const char *LOG_LEVEL_NAMES[] = {"INFO", "WARNING", "ERROR"}; 12 | 13 | void log_init() { 14 | SceUID file = sceIoOpen(LOG_FILENAME, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_TRUNC, 0777); 15 | if (file >= 0) { 16 | const char *header = "=== Joker Poker Debug Log ===\n"; 17 | sceIoWrite(file, header, strlen(header)); 18 | sceIoClose(file); 19 | } 20 | } 21 | 22 | void log_shutdown() { 23 | SceUID file = sceIoOpen(LOG_FILENAME, PSP_O_WRONLY | PSP_O_APPEND, 0777); 24 | if (file >= 0) { 25 | const char *footer = "\n=== Log Closed ===\n"; 26 | sceIoWrite(file, footer, strlen(footer)); 27 | sceIoClose(file); 28 | } 29 | } 30 | 31 | void log_message(LogLevel level, const char *format, ...) { 32 | char buffer[512]; 33 | char time_str[32]; 34 | 35 | va_list args; 36 | va_start(args, format); 37 | vsnprintf(buffer, sizeof(buffer), format, args); 38 | va_end(args); 39 | 40 | size_t len = strlen(buffer); 41 | if (len > 0 && buffer[len - 1] != '\n') { 42 | if (len < sizeof(buffer) - 2) { 43 | buffer[len] = '\n'; 44 | buffer[len + 1] = '\0'; 45 | } 46 | } 47 | 48 | SceUID file = sceIoOpen(LOG_FILENAME, PSP_O_WRONLY | PSP_O_CREAT | PSP_O_APPEND, 0777); 49 | if (file >= 0) { 50 | sceIoWrite(file, buffer, strlen(buffer)); 51 | sceIoClose(file); 52 | } 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /gfx.h: -------------------------------------------------------------------------------- 1 | #ifndef GFX_H 2 | #define GFX_H 3 | 4 | #include "game.h" 5 | #include "state.h" 6 | #include "system.h" 7 | 8 | #define SCREEN_WIDTH (480) 9 | #define SCREEN_HEIGHT (272) 10 | 11 | #define BG_NOISE_SIZE 256 12 | #define BG_PERIOD 256 13 | #define BG_TEXTURE_WIDTH (60) 14 | #define BG_TEXTURE_HEIGHT (34) 15 | 16 | #define CARD_WIDTH (48) 17 | #define CARD_HEIGHT (64) 18 | 19 | #define CHAR_WIDTH (6) 20 | #define CHAR_HEIGHT (10) 21 | 22 | #define SIDEBAR_WIDTH (100) 23 | #define SIDEBAR_GAP (4) 24 | #define SECTION_PADDING (4) 25 | 26 | #define COLOR_WHITE (Clay_Color){255, 255, 255, 255} 27 | #define COLOR_BLACK (Clay_Color){0, 0, 0, 255} 28 | #define COLOR_MULT (Clay_Color){255, 63, 52, 255} 29 | #define COLOR_CHIPS (Clay_Color){15, 188, 249, 255} 30 | #define COLOR_MONEY (Clay_Color){255, 168, 1, 255} 31 | #define COLOR_CARD_BG_ALPHA(alpha) \ 32 | (Clay_Color) { 30, 39, 46, alpha } 33 | #define COLOR_CARD_BG COLOR_CARD_BG_ALPHA(255) 34 | #define COLOR_SECTION_BG (Clay_Color){0, 0, 0, 60} 35 | #define COLOR_CARD_LIGHT_BG (Clay_Color){72, 84, 96, 255} 36 | 37 | #define WHITE_TEXT_CONFIG CLAY_TEXT_CONFIG({.textColor = COLOR_WHITE}) 38 | 39 | void update_render_commands(); 40 | 41 | void render_main_menu(); 42 | void render_select_deck(); 43 | void render_credits(); 44 | 45 | void render_card_atlas_sprite(Vector2 *sprite_index, Rect *dst); 46 | void render_card(Card *card, Rect *dst); 47 | void render_joker(Joker *joker, Rect *dst); 48 | void render_consumable(Consumable *consumable, Rect *dst); 49 | void render_voucher(Voucher voucher, Rect *dst); 50 | void render_booster_pack(BoosterPackItem *booster_pack, Rect *dst); 51 | void render_deck(Deck deck, Rect *dst); 52 | 53 | void render_tooltip(Clay_String *title, Clay_String *description, float y_offset, 54 | Clay_FloatingAttachPoints *attach_points, Clay_String *other_description); 55 | void render_spread_items(NavigationSection section, Clay_String parent_id); 56 | 57 | void render_hand(); 58 | void render_sidebar(); 59 | void render_topbar(); 60 | 61 | void render_shop(); 62 | 63 | void render_booster_pack_content(); 64 | 65 | void render_cash_out(); 66 | void render_select_blind(); 67 | 68 | void render_game_over(); 69 | 70 | void render_overlay_menu(); 71 | void render_overlay_select_stake(); 72 | void render_overlay_poker_hands(); 73 | 74 | void render_background(); 75 | void init_background(); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /content/joker.h: -------------------------------------------------------------------------------- 1 | #ifndef JOKER_H 2 | #define JOKER_H 3 | 4 | #include 5 | #include 6 | 7 | struct Card; 8 | 9 | #define TRIGGER_JOKER(joker, TYPE, ...) \ 10 | do { \ 11 | if (!(joker->status & CARD_STATUS_DEBUFFED)) { \ 12 | if (joker->scale_##TYPE) joker->scale_##TYPE(joker, ##__VA_ARGS__); \ 13 | if (joker->activate_##TYPE) joker->activate_##TYPE(joker, ##__VA_ARGS__); \ 14 | } \ 15 | } while (0) 16 | 17 | typedef enum { 18 | JOKER_JOKER = 1, 19 | JOKER_GREEDY, 20 | JOKER_LUSTY, 21 | JOKER_WRATHFUL, 22 | JOKER_GLUTTONOUS, 23 | JOKER_JOLLY, 24 | JOKER_ZANY, 25 | JOKER_MAD, 26 | JOKER_CRAZY, 27 | JOKER_DROLL, 28 | JOKER_SLY, 29 | JOKER_WILY, 30 | JOKER_CLEVER, 31 | JOKER_DEVIOUS, 32 | JOKER_CRAFTY, 33 | } JokerId; 34 | 35 | typedef enum { EDITION_BASE, EDITION_FOIL, EDITION_HOLOGRAPHIC, EDITION_POLYCHROME, EDITION_NEGATIVE } Edition; 36 | 37 | typedef enum { RARITY_COMMON, RARITY_UNCOMMON, RARITY_RARE, RARITY_LEGENDARY } Rarity; 38 | 39 | typedef enum { CARD_STATUS_NORMAL = 0, CARD_STATUS_FACE_DOWN = 1 << 0, CARD_STATUS_DEBUFFED = 1 << 1 } CardStatus; 40 | 41 | typedef struct Joker { 42 | JokerId id; 43 | const char *name; 44 | const char *description; 45 | void (*get_scaling_description)(struct Joker *self, Clay_String *dest); 46 | uint8_t base_price; 47 | 48 | Rarity rarity; 49 | Edition edition; 50 | CardStatus status; 51 | bool is_non_copyable; 52 | 53 | void (*activate_on_played)(struct Joker *self); 54 | void (*activate_on_scored)(struct Joker *self, struct Card *card); 55 | void (*activate_on_held)(struct Joker *self, struct Card *card); 56 | void (*activate_independent)(struct Joker *self); 57 | void (*activate_on_other_jokers)(struct Joker *self, struct Joker *other); 58 | void (*activate_on_discard)(struct Joker *self, struct Card *card); 59 | void (*activate_on_blind_select)(struct Joker *self); 60 | void (*activate_passive)(struct Joker *self); 61 | 62 | void (*scale_on_played)(struct Joker *self); 63 | void (*scale_on_scored)(struct Joker *self, struct Card *card); 64 | void (*scale_on_held)(struct Joker *self, struct Card *card); 65 | void (*scale_independent)(struct Joker *self); 66 | void (*scale_on_other_jokers)(struct Joker *self, struct Joker *other); 67 | void (*scale_on_discard)(struct Joker *self, struct Card *card); 68 | void (*scale_on_blind_select)(struct Joker *self); 69 | void (*scale_passive)(struct Joker *self); 70 | 71 | union { 72 | double mult; 73 | uint16_t chips; 74 | uint8_t counter; 75 | // Suit enum 76 | uint8_t suit; 77 | // PokerHand enum 78 | uint16_t hand; 79 | }; 80 | } Joker; 81 | 82 | extern const Joker JOKERS[]; 83 | extern const uint8_t JOKER_COUNT; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #include 5 | 6 | #define CLAY_IMPLEMENTATION 7 | #include 8 | 9 | #include "debug.h" 10 | #include "game.h" 11 | #include "gfx.h" 12 | #include "renderer.h" 13 | #include "state.h" 14 | #include "system.h" 15 | 16 | PSP_MODULE_INFO("Joker Poker", 0, 0, 40); 17 | PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER); 18 | 19 | static char __attribute__((aligned(16))) list[262144]; 20 | 21 | State state; 22 | 23 | void init() { 24 | log_init(); 25 | 26 | setup_callbacks(); 27 | 28 | init_gu(list); 29 | renderer_init(); 30 | 31 | state.cards_atlas = load_texture("res/cards.png"); 32 | state.jokers_atlas1 = load_texture("res/jokers1.png"); 33 | state.jokers_atlas2 = load_texture("res/jokers2.png"); 34 | state.font = load_texture("res/font.png"); 35 | state.logo = load_texture("res/logo.png"); 36 | 37 | init_background(); 38 | 39 | sceCtrlSetSamplingCycle(0); 40 | sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG); 41 | 42 | state.delta = 0; 43 | state.running = 1; 44 | 45 | log_message(LOG_INFO, "Application has been initialized."); 46 | } 47 | 48 | void destroy() { 49 | game_destroy(); 50 | end_gu(); 51 | 52 | stbi_image_free(state.cards_atlas->data); 53 | free(state.cards_atlas); 54 | 55 | stbi_image_free(state.jokers_atlas1->data); 56 | free(state.jokers_atlas1); 57 | stbi_image_free(state.jokers_atlas2->data); 58 | free(state.jokers_atlas2); 59 | 60 | stbi_image_free(state.font->data); 61 | free(state.font); 62 | 63 | stbi_image_free(state.logo->data); 64 | free(state.logo); 65 | 66 | free(state.bg); 67 | 68 | log_message(LOG_INFO, "Application has been destroyed."); 69 | } 70 | 71 | int main(int argc, char *argv[]) { 72 | init(); 73 | uint64_t last_time = sceKernelGetSystemTimeWide(); 74 | #ifdef DEBUG_BUILD 75 | float frame_time = 0.0f; 76 | #endif 77 | 78 | log_message(LOG_INFO, "Starting main loop..."); 79 | 80 | update_render_commands(); 81 | 82 | while (state.running) { 83 | handle_controls(); 84 | 85 | uint64_t curr_time = sceKernelGetSystemTimeWide(); 86 | 87 | state.delta = (curr_time - last_time) / 1000000.0f; 88 | state.time += state.delta; 89 | last_time = curr_time; 90 | 91 | state.frame_arena.offset = 0; 92 | 93 | start_frame(list); 94 | 95 | render_background(); 96 | 97 | execute_render_commands(state.render_commands); 98 | 99 | #ifdef DEBUG_BUILD 100 | Clay_String fps_counter; 101 | append_clay_string(&fps_counter, "%.2f FPS [%.2f ms]", 1 / state.delta, frame_time); 102 | draw_text_len(fps_counter.chars, fps_counter.length, &(Vector2){360, 0}, 0xFFFFFFFF); 103 | 104 | frame_time = (sceKernelGetSystemTimeWide() - curr_time) / 1000.0f; 105 | #endif 106 | 107 | end_frame(); 108 | } 109 | 110 | destroy(); 111 | log_shutdown(); 112 | 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /state.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_H 2 | #define STATE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "game.h" 8 | #include "system.h" 9 | 10 | #define FRAME_ARENA_CAPACITY (5120) 11 | 12 | #define MAX_NAV_ROWS 3 13 | #define MAX_NAV_SECTIONS_PER_ROW 2 14 | 15 | typedef struct { 16 | SceCtrlData data; 17 | unsigned int state; 18 | } Controls; 19 | 20 | typedef enum { 21 | STAGE_MAIN_MENU, 22 | STAGE_SELECT_DECK, 23 | STAGE_CREDITS, 24 | 25 | STAGE_GAME, 26 | STAGE_CASH_OUT, 27 | STAGE_SHOP, 28 | STAGE_BOOSTER_PACK, 29 | STAGE_SELECT_BLIND, 30 | STAGE_GAME_OVER, 31 | } Stage; 32 | 33 | typedef enum { OVERLAY_NONE, OVERLAY_MENU, OVERLAY_SELECT_STAKE, OVERLAY_POKER_HANDS } Overlay; 34 | 35 | typedef enum { 36 | NAVIGATION_NONE, 37 | NAVIGATION_MAIN_MENU, 38 | NAVIGATION_SELECT_DECK, 39 | NAVIGATION_SELECT_STAKE, 40 | NAVIGATION_SELECT_BLIND, 41 | NAVIGATION_HAND, 42 | NAVIGATION_SHOP_ITEMS, 43 | NAVIGATION_SHOP_VOUCHER, 44 | NAVIGATION_SHOP_BOOSTER_PACKS, 45 | NAVIGATION_BOOSTER_PACK, 46 | NAVIGATION_CONSUMABLES, 47 | NAVIGATION_JOKERS, 48 | NAVIGATION_OVERLAY_MENU 49 | } NavigationSection; 50 | 51 | typedef struct { 52 | uint8_t count; 53 | NavigationSection sections[MAX_NAV_SECTIONS_PER_ROW]; 54 | } NavigationRow; 55 | 56 | typedef struct { 57 | uint8_t row_count; 58 | NavigationRow rows[MAX_NAV_ROWS]; 59 | } NavigationLayout; 60 | 61 | typedef struct { 62 | uint8_t row; 63 | uint8_t col; 64 | } NavigationCursor; 65 | 66 | typedef enum { NAVIGATION_UP, NAVIGATION_DOWN, NAVIGATION_LEFT, NAVIGATION_RIGHT } NavigationDirection; 67 | 68 | typedef struct { 69 | uint8_t hovered; 70 | NavigationCursor cursor; 71 | } Navigation; 72 | 73 | typedef struct { 74 | uint8_t data[FRAME_ARENA_CAPACITY]; 75 | size_t offset; 76 | } Arena; 77 | 78 | void *frame_arena_allocate(size_t size); 79 | int append_clay_string(Clay_String *dest, const char *format, ...); 80 | 81 | uint8_t get_nav_section_size(NavigationSection section); 82 | uint8_t is_nav_section_horizontal(NavigationSection section); 83 | void move_nav_cursor(NavigationDirection direction); 84 | NavigationSection get_current_section(); 85 | 86 | void set_nav_hovered(int8_t new_hovered); 87 | void move_nav_hovered(uint8_t new_position); 88 | 89 | void change_stage(Stage stage); 90 | void change_overlay(Overlay overlay); 91 | 92 | void overlay_menu_button_click(); 93 | void main_menu_button_click(); 94 | void select_blind_button_click(); 95 | 96 | typedef struct { 97 | Arena frame_arena; 98 | Clay_RenderCommandArray render_commands; 99 | 100 | Texture *cards_atlas; 101 | Texture *jokers_atlas1; 102 | Texture *jokers_atlas2; 103 | Texture *font; 104 | Texture *bg; 105 | Texture *logo; 106 | 107 | Controls controls; 108 | 109 | Stage stage; 110 | Stage prev_stage; 111 | Overlay overlay; 112 | Navigation navigation; 113 | Navigation prev_navigation; 114 | 115 | float delta; 116 | float time; 117 | uint8_t running; 118 | 119 | Game game; 120 | } State; 121 | 122 | extern State state; 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logo JOKER POKER PSP 2 | 3 | # Joker Poker 4 | 5 | Joker Poker is homebrew card game for PSP inspired by [Balatro](https://www.playbalatro.com). 6 | 7 | Currently, development is focused on nailing down the basic systems. 8 | Once these are solid, content will be expanded to include most of the jokers, boss blinds, tags, etc. 9 | 10 | > [!NOTE] 11 | > This project was written in plain C for the sake of writting it in C. 12 | 13 |

14 | Screenshot of the game running on an emulator 15 |

16 | 17 | ## Installation 18 | 19 | ### Installation from zip file 20 | 21 | 1. Go to [releases page](https://github.com/kwerenta/joker-poker/releases) 22 | 1. Download latest `joker-poker.zip` file 23 | 1. Extract downloaded zip file on the Memory Stick in `/PSP/GAME/` 24 | 1. Game is ready to launch 25 | 26 | ### Installation from source 27 | 28 | 1. Compile the game (see [Compiling](#compiling)) 29 | 1. On the Memory Stick of the physical PSP or emulator create a new folder in `/PSP/GAME/` 30 | 1. Copy both `EBOOT.PBP` file from `build` directory and `res` folder to newly created folder 31 | 1. Game is ready to launch 32 | 33 | ## Compiling 34 | 35 | ### Prerequisites 36 | 37 | - [PSP SDK](https://pspdev.github.io) 38 | 39 | ### Steps 40 | 41 | 1. Clone the repository 42 | 43 | 2. Then generate Makefile inside `build` directory, to do this just enter at the command line: 44 | 45 | ```sh 46 | psp-cmake -B build 47 | ``` 48 | 49 | - In order to make game work on unmodded PSP (with official firmware), add following flags to above command: 50 | 51 | ```sh 52 | -DBUILD_PRX=1 -DENC_PRX=1 53 | ``` 54 | 55 | - In order to make game work in debug mode (Game will generate log file), add following flag to above command: 56 | ```sh 57 | -DCMAKE_BUILD_TYPE=Debug 58 | ``` 59 | 60 | 3. Now, you can build game with: 61 | ```sh 62 | cmake --build build 63 | ``` 64 | 65 | After the first build, running last step is enough to get an `EBOOT.PBP` file which is main game binary. 66 | 67 | ## Controls 68 | 69 | There are currently no in-game control hints. 70 | 71 | - General 72 | - UP/DOWN/LEFT/RIGHT - move between different sections 73 | - LEFT/RIGHT - change currently hovered item 74 | - CROSS - select/buy/use currently hovered item/button 75 | - LTRIGGER/RTRIGGER - move hovered item to the left/right 76 | - START - open/close overlay menu 77 | - TRIANGLE - sell currently hovered item 78 | - Game 79 | - SELECT - sort hand by rank/suit 80 | - SQUARE - play selected cards 81 | - TRIANGLE - discard selected cards 82 | - CIRCLE - deselect all cards 83 | - Choose blind 84 | - SQUARE - select current blind 85 | - TRIANGLE - skip current blind 86 | - SELECT - reroll boss blind if has the necessary voucher 87 | - Shop 88 | - SELECT - reroll shop 89 | - SQUARE - buy&use currently hovered item if possible 90 | - Booster Pack 91 | - CIRCLE - skip booster pack 92 | -------------------------------------------------------------------------------- /random.c: -------------------------------------------------------------------------------- 1 | #include "random.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "cvector.h" 7 | #include "game.h" 8 | #include "state.h" 9 | 10 | void rng_init() { srand(time((NULL))); } 11 | 12 | int16_t random_filtered_range_pick(uint8_t start, uint8_t end, RangeFilter filter) { 13 | if (start > end) return -1; 14 | 15 | uint8_t candidates[end - start + 1]; 16 | uint8_t count = 0; 17 | 18 | for (uint8_t i = start; i <= end; i++) { 19 | if (!filter || filter(i)) candidates[count++] = i; 20 | } 21 | 22 | if (count == 0) return -1; 23 | 24 | return candidates[random_max_value(count - 1)]; 25 | } 26 | 27 | int16_t random_filtered_vector_pick(cvector_vector_type(void) vec, RangeFilter filter) { 28 | if (cvector_size(vec) <= 0) return -1; 29 | return random_filtered_range_pick(0, cvector_size(vec) - 1, filter); 30 | } 31 | 32 | int16_t random_weighted(uint16_t *weights, uint8_t count) { 33 | if (weights == NULL || count == 0) return -1; 34 | 35 | float total_weight = 0.0f; 36 | for (uint8_t i = 0; i < count; i++) { 37 | total_weight += weights[i]; 38 | } 39 | 40 | if (total_weight <= 0) return -1; 41 | 42 | float random_value = ((double)rand() / RAND_MAX) * total_weight; 43 | 44 | total_weight = 0.0f; 45 | for (uint8_t i = 0; i < count; i++) { 46 | total_weight += weights[i]; 47 | if (random_value <= total_weight) return i; 48 | } 49 | 50 | return -1; 51 | } 52 | 53 | bool random_percent(float probability) { 54 | if (probability <= 0.0) return false; 55 | if (probability >= 1.0) return true; 56 | 57 | float random_value = rand() / (float)RAND_MAX; 58 | return random_value < probability; 59 | } 60 | 61 | bool random_chance(uint8_t numerator, uint8_t denominator) { 62 | if (numerator <= 0 || denominator <= 0) return 0; 63 | if (numerator >= denominator) return 1; 64 | 65 | return random_max_value(denominator - 1) < numerator; 66 | } 67 | 68 | uint8_t random_max_value(uint8_t max_value) { return random_in_range(0, max_value); } 69 | 70 | uint8_t random_in_range(uint8_t min_value, uint8_t max_value) { 71 | return min_value + rand() / (RAND_MAX / (max_value - min_value + 1) + 1); 72 | } 73 | 74 | Joker random_weighted_joker(uint16_t rarity_weights[4]) { 75 | uint16_t weights[JOKER_COUNT]; 76 | bool has_any_weights = false; 77 | 78 | for (uint8_t i = 0; i < JOKER_COUNT; i++) { 79 | bool has_this_joker = false; 80 | cvector_for_each(state.game.jokers.cards, Joker, joker) { 81 | if (joker->id == JOKERS[i].id) { 82 | has_this_joker = true; 83 | break; 84 | } 85 | } 86 | 87 | if (has_this_joker) { 88 | weights[i] = 0; 89 | continue; 90 | } 91 | 92 | weights[i] = rarity_weights[JOKERS[i].rarity]; 93 | has_any_weights = true; 94 | }; 95 | 96 | // TODO According to Wiki "Joker" is returned when there are no more Jokers available and this should be changed when 97 | // more jokers will be added 98 | // Allow duplicates if there are no more jokers available 99 | if (!has_any_weights) 100 | for (uint8_t i = 0; i < JOKER_COUNT; i++) weights[i] = rarity_weights[JOKERS[i].rarity]; 101 | 102 | Joker joker = JOKERS[random_weighted(weights, JOKER_COUNT)]; 103 | 104 | // Base, Foil, Holographic, Polychrome, Negative 105 | uint16_t edition_weights[5] = {960, 20, 14, 3, 3}; 106 | for (uint8_t i = 1; i < 4; i++) { 107 | uint8_t multiplier = 1; 108 | if (state.game.vouchers & VOUCHER_GLOW_UP) 109 | multiplier = i == 3 ? 7 : 4; 110 | else if (state.game.vouchers & VOUCHER_HONE) 111 | multiplier = i == 3 ? 3 : 2; 112 | 113 | edition_weights[0] -= (multiplier - 1) * edition_weights[i]; 114 | edition_weights[i] *= multiplier; 115 | } 116 | joker.edition = random_weighted(edition_weights, 5); 117 | 118 | return joker; 119 | } 120 | 121 | Joker random_available_joker() { 122 | // Common, Uncommon, Rare, Legendary 123 | uint16_t rarity_weights[] = {70, 25, 5, 0}; 124 | return random_weighted_joker(rarity_weights); 125 | } 126 | 127 | Joker random_available_joker_by_rarity(Rarity rarity) { 128 | // Common, Uncommon, Rare, Legendary 129 | uint16_t base_rarity_weights[] = {70, 25, 5, 0}; 130 | uint16_t rarity_weights[4] = {0}; 131 | rarity_weights[rarity] = base_rarity_weights[rarity]; 132 | return random_weighted_joker(rarity_weights); 133 | } 134 | 135 | Card random_card() { 136 | uint16_t edition_weights[5] = {920, 12, 28, 40, 0}; 137 | for (uint8_t i = 1; i < 4; i++) { 138 | uint8_t multiplier = (state.game.vouchers & VOUCHER_GLOW_UP) ? 4 : (state.game.vouchers & VOUCHER_HONE) ? 2 : 1; 139 | edition_weights[0] -= (multiplier - 1) * edition_weights[i]; 140 | edition_weights[i] *= multiplier; 141 | } 142 | Edition edition = random_weighted(edition_weights, 5); 143 | Enhancement enhancement = ENHANCEMENT_NONE; 144 | Seal seal = SEAL_NONE; 145 | 146 | if (random_chance(4, 10)) enhancement = random_in_range(ENHANCEMENT_BONUS, ENHANCEMENT_LUCKY); 147 | if (random_chance(2, 10)) seal = random_in_range(SEAL_GOLD, SEAL_PURPLE); 148 | 149 | return create_card(random_max_value(3), random_max_value(12), edition, enhancement, seal); 150 | } 151 | 152 | Card random_shop_card() { 153 | if (!(state.game.vouchers & VOUCHER_ILLUSION)) 154 | return create_card(random_max_value(3), random_max_value(12), EDITION_BASE, ENHANCEMENT_NONE, SEAL_NONE); 155 | 156 | Card card = random_card(); 157 | card.edition = EDITION_BASE; 158 | 159 | if (random_chance(2, 10)) card.edition = random_in_range(EDITION_FOIL, EDITION_POLYCHROME); 160 | 161 | return card; 162 | } 163 | -------------------------------------------------------------------------------- /content/joker.c: -------------------------------------------------------------------------------- 1 | #include "joker.h" 2 | 3 | #include "../state.h" 4 | 5 | static void activate_joker_joker(Joker *self) { state.game.selected_hand.score_pair.mult += 4; } 6 | 7 | static void activate_basic_suit_plus_mult(Joker *self, Card *card) { 8 | if (card->suit == self->suit) state.game.selected_hand.score_pair.mult += 3; 9 | } 10 | 11 | static void activate_basic_hand_plus_mult(Joker *self) { 12 | if (!does_poker_hand_contain(state.game.selected_hand.hand_union, self->hand)) return; 13 | 14 | uint8_t mult = 0; 15 | switch (self->hand) { 16 | case HAND_PAIR: 17 | mult = 8; 18 | break; 19 | case HAND_TWO_PAIR: 20 | case HAND_FLUSH: 21 | mult = 10; 22 | break; 23 | case HAND_THREE_OF_KIND: 24 | case HAND_STRAIGHT: 25 | mult = 12; 26 | break; 27 | } 28 | 29 | state.game.selected_hand.score_pair.mult += mult; 30 | } 31 | 32 | static void activate_basic_hand_plus_chips(Joker *self) { 33 | if (!does_poker_hand_contain(state.game.selected_hand.hand_union, self->hand)) return; 34 | 35 | uint8_t chips = 0; 36 | switch (self->hand) { 37 | case HAND_PAIR: 38 | chips = 50; 39 | break; 40 | case HAND_TWO_PAIR: 41 | case HAND_FLUSH: 42 | chips = 80; 43 | break; 44 | case HAND_THREE_OF_KIND: 45 | case HAND_STRAIGHT: 46 | chips = 100; 47 | break; 48 | } 49 | 50 | state.game.selected_hand.score_pair.chips += chips; 51 | } 52 | 53 | const Joker JOKERS[] = { 54 | { 55 | .id = JOKER_JOKER, 56 | .name = "Joker", 57 | .description = "+4 mult when scored", 58 | .base_price = 2, 59 | .rarity = RARITY_COMMON, 60 | .activate_independent = activate_joker_joker, 61 | }, 62 | { 63 | .id = JOKER_GREEDY, 64 | .name = "Greedy Joker", 65 | .description = "Played cards with Diamond suit give +3 Mult when scored", 66 | .base_price = 5, 67 | .rarity = RARITY_COMMON, 68 | .suit = SUIT_DIAMONDS, 69 | .activate_on_scored = activate_basic_suit_plus_mult, 70 | }, 71 | { 72 | .id = JOKER_LUSTY, 73 | .name = "Lusty Joker", 74 | .description = "Played cards with Heart suit give +3 Mult when scored", 75 | .base_price = 5, 76 | .rarity = RARITY_COMMON, 77 | .suit = SUIT_HEARTS, 78 | .activate_on_scored = activate_basic_suit_plus_mult, 79 | }, 80 | { 81 | .id = JOKER_WRATHFUL, 82 | .name = "Wrathful Joker", 83 | .description = "Played cards with Spade suit give +3 Mult when scored", 84 | .base_price = 5, 85 | .rarity = RARITY_COMMON, 86 | .suit = SUIT_SPADES, 87 | .activate_on_scored = activate_basic_suit_plus_mult, 88 | }, 89 | { 90 | .id = JOKER_GLUTTONOUS, 91 | .name = "Gluttonous Joker", 92 | .description = "Played cards with Club suit give +3 Mult when scored", 93 | .base_price = 5, 94 | .rarity = RARITY_COMMON, 95 | .suit = SUIT_CLUBS, 96 | .activate_on_scored = activate_basic_suit_plus_mult, 97 | }, 98 | { 99 | .id = JOKER_JOLLY, 100 | .name = "Jolly Joker", 101 | .description = "+8 Mult if played hand contains a Pair", 102 | .base_price = 3, 103 | .rarity = RARITY_COMMON, 104 | .hand = HAND_PAIR, 105 | .activate_independent = activate_basic_hand_plus_mult, 106 | }, 107 | { 108 | .id = JOKER_ZANY, 109 | .name = "Zany Joker", 110 | .description = "+12 Mult if played hand contains a Three of a Kind", 111 | .base_price = 4, 112 | .rarity = RARITY_COMMON, 113 | .hand = HAND_THREE_OF_KIND, 114 | .activate_independent = activate_basic_hand_plus_mult, 115 | }, 116 | { 117 | .id = JOKER_MAD, 118 | .name = "Mad Joker", 119 | .description = "+10 Mult if played hand contains a Two Pair", 120 | .base_price = 4, 121 | .rarity = RARITY_COMMON, 122 | .hand = HAND_TWO_PAIR, 123 | .activate_independent = activate_basic_hand_plus_mult, 124 | }, 125 | { 126 | .id = JOKER_CRAZY, 127 | .name = "Crazy Joker", 128 | .description = "+12 Mult if played hand contains a Straight", 129 | .base_price = 4, 130 | .rarity = RARITY_COMMON, 131 | .hand = HAND_STRAIGHT, 132 | .activate_independent = activate_basic_hand_plus_mult, 133 | }, 134 | { 135 | .id = JOKER_DROLL, 136 | .name = "Droll Joker", 137 | .description = "+10 Mult if played hand contains a Flush", 138 | .base_price = 4, 139 | .rarity = RARITY_COMMON, 140 | .hand = HAND_FLUSH, 141 | .activate_independent = activate_basic_hand_plus_mult, 142 | }, 143 | { 144 | .id = JOKER_SLY, 145 | .name = "Sly Joker", 146 | .description = "+50 Chips if played hand contains a Pair", 147 | .base_price = 3, 148 | .rarity = RARITY_COMMON, 149 | .hand = HAND_PAIR, 150 | .activate_independent = activate_basic_hand_plus_chips, 151 | }, 152 | { 153 | .id = JOKER_WILY, 154 | .name = "Wily Joker", 155 | .description = "+100 Chips if played hand contains a Three of a Kind", 156 | .base_price = 4, 157 | .rarity = RARITY_COMMON, 158 | .hand = HAND_THREE_OF_KIND, 159 | .activate_independent = activate_basic_hand_plus_chips, 160 | }, 161 | { 162 | .id = JOKER_CLEVER, 163 | .name = "Clever Joker", 164 | .description = "+80 Chips if played hand contains a Two Pair", 165 | .base_price = 4, 166 | .rarity = RARITY_COMMON, 167 | .hand = HAND_TWO_PAIR, 168 | .activate_independent = activate_basic_hand_plus_chips, 169 | }, 170 | { 171 | .id = JOKER_DEVIOUS, 172 | .name = "Devious Joker", 173 | .description = "+100 Chips if played hand contains a Straight", 174 | .base_price = 4, 175 | .rarity = RARITY_COMMON, 176 | .hand = HAND_STRAIGHT, 177 | .activate_independent = activate_basic_hand_plus_chips, 178 | }, 179 | { 180 | .id = JOKER_CRAFTY, 181 | .name = "Craft Joker", 182 | .description = "+80 Chips if played hand contains a Flush", 183 | .base_price = 4, 184 | .rarity = RARITY_COMMON, 185 | .hand = HAND_FLUSH, 186 | .activate_independent = activate_basic_hand_plus_chips, 187 | }, 188 | }; 189 | 190 | const uint8_t JOKER_COUNT = sizeof(JOKERS) / sizeof(Joker); 191 | -------------------------------------------------------------------------------- /renderer.c: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "debug.h" 7 | #include "gfx.h" 8 | #include "state.h" 9 | #include "system.h" 10 | 11 | #define CLAY_COLOR_TO_PSP(color) RGBA((uint8_t)(color.r), (uint8_t)(color.g), (uint8_t)(color.b), (uint8_t)(color.a)) 12 | 13 | void error_handler(Clay_ErrorData error) { 14 | log_message(LOG_ERROR, "Clay error: %s", error.errorText.chars); 15 | state.running = 0; 16 | } 17 | 18 | static inline Clay_Dimensions measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *user_data) { 19 | return (Clay_Dimensions){.width = text.length * CHAR_WIDTH, .height = CHAR_HEIGHT}; 20 | } 21 | 22 | void renderer_init() { 23 | Clay_SetMaxElementCount(1024); 24 | uint64_t total_memory = Clay_MinMemorySize(); 25 | Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(total_memory, malloc(total_memory)); 26 | Clay_Initialize(arena, (Clay_Dimensions){SCREEN_WIDTH, SCREEN_HEIGHT}, (Clay_ErrorHandler){error_handler}); 27 | 28 | log_message(LOG_INFO, "Initialized Clay with %llu byte memory arena.", total_memory); 29 | 30 | Clay_SetMeasureTextFunction(measure_text, NULL); 31 | } 32 | 33 | void execute_render_commands(Clay_RenderCommandArray render_commands) { 34 | for (int i = 0; i < render_commands.length; i++) { 35 | Clay_RenderCommand *render_command = Clay_RenderCommandArray_Get(&render_commands, i); 36 | Clay_BoundingBox bounding_box = render_command->boundingBox; 37 | 38 | switch (render_command->commandType) { 39 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 40 | Clay_RectangleRenderData *config = &render_command->renderData.rectangle; 41 | draw_rectangle( 42 | &(Rect){.x = bounding_box.x, .y = bounding_box.y, .w = bounding_box.width, .h = bounding_box.height}, 43 | CLAY_COLOR_TO_PSP(config->backgroundColor)); 44 | break; 45 | } 46 | 47 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 48 | Clay_TextRenderData *text_data = &render_command->renderData.text; 49 | draw_text_len(text_data->stringContents.chars, text_data->stringContents.length, 50 | &(Vector2){.x = bounding_box.x, .y = bounding_box.y}, CLAY_COLOR_TO_PSP(text_data->textColor)); 51 | break; 52 | } 53 | 54 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 55 | Clay_BorderRenderData *border_data = &render_command->renderData.border; 56 | 57 | if (border_data->width.left > 0) 58 | draw_rectangle(&(Rect){.x = bounding_box.x, 59 | .y = bounding_box.y + border_data->cornerRadius.topLeft, 60 | .w = border_data->width.left, 61 | .h = bounding_box.height - border_data->cornerRadius.topLeft - 62 | border_data->cornerRadius.bottomLeft}, 63 | CLAY_COLOR_TO_PSP(border_data->color)); 64 | 65 | if (border_data->width.right > 0) 66 | draw_rectangle(&(Rect){.x = bounding_box.x + bounding_box.width - border_data->width.right, 67 | .y = bounding_box.y + border_data->cornerRadius.topRight, 68 | .w = border_data->width.right, 69 | .h = bounding_box.height - border_data->cornerRadius.topRight - 70 | border_data->cornerRadius.bottomRight}, 71 | CLAY_COLOR_TO_PSP(border_data->color)); 72 | 73 | if (border_data->width.top > 0) 74 | draw_rectangle( 75 | &(Rect){.x = bounding_box.x + border_data->cornerRadius.topLeft, 76 | .y = bounding_box.y, 77 | .w = bounding_box.width - border_data->cornerRadius.topLeft - border_data->cornerRadius.topRight, 78 | .h = border_data->width.top}, 79 | CLAY_COLOR_TO_PSP(border_data->color)); 80 | 81 | if (border_data->width.bottom > 0) 82 | draw_rectangle(&(Rect){.x = bounding_box.x + border_data->cornerRadius.bottomLeft, 83 | .y = bounding_box.y + bounding_box.height - border_data->width.bottom, 84 | .w = bounding_box.width - border_data->cornerRadius.bottomLeft - 85 | border_data->cornerRadius.bottomRight, 86 | .h = border_data->width.bottom}, 87 | CLAY_COLOR_TO_PSP(border_data->color)); 88 | 89 | break; 90 | } 91 | 92 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 93 | Texture *texture = (Texture *)render_command->renderData.image.imageData; 94 | draw_texture(texture, &(Rect){0, 0, texture->width, texture->height}, 95 | &(Rect){ 96 | bounding_box.x, 97 | bounding_box.y, 98 | bounding_box.width, 99 | bounding_box.height, 100 | }, 101 | 0xFFFFFFFF, 0); 102 | break; 103 | } 104 | 105 | case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { 106 | CustomElementData *custom_element = render_command->renderData.custom.customData; 107 | if (!custom_element) continue; 108 | 109 | Rect dst = {.x = bounding_box.x, .y = bounding_box.y, .w = bounding_box.width, .h = bounding_box.height}; 110 | 111 | switch (custom_element->type) { 112 | case CUSTOM_ELEMENT_CARD: 113 | render_card(&custom_element->card, &dst); 114 | break; 115 | 116 | case CUSTOM_ELEMENT_JOKER: 117 | render_joker(&custom_element->joker, &dst); 118 | break; 119 | 120 | case CUSTOM_ELEMENT_CONSUMABLE: 121 | render_consumable(&custom_element->consumable, &dst); 122 | break; 123 | 124 | case CUSTOM_ELEMENT_VOUCHER: 125 | render_voucher(custom_element->voucher, &dst); 126 | break; 127 | 128 | case CUSTOM_ELEMENT_BOOSTER_PACK: 129 | render_booster_pack(&custom_element->booster_pack, &dst); 130 | break; 131 | 132 | case CUSTOM_ELEMENT_DECK: 133 | render_deck(custom_element->deck, &dst); 134 | break; 135 | }; 136 | break; 137 | } 138 | 139 | default: 140 | log_message(LOG_ERROR, "Tried to render unsupported Clay element. (ID=%d)", render_command->commandType); 141 | state.running = 0; 142 | break; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include "content/spectral.h" 4 | #include "content/tarot.h" 5 | #include "game.h" 6 | #include "renderer.h" 7 | #include "state.h" 8 | #include "text.h" 9 | 10 | static CustomElementData create_shop_item_custom_element(ShopItem *item) { 11 | switch (item->type) { 12 | case SHOP_ITEM_CARD: 13 | return (CustomElementData){.type = CUSTOM_ELEMENT_CARD, .card = item->card}; 14 | case SHOP_ITEM_JOKER: 15 | return (CustomElementData){.type = CUSTOM_ELEMENT_JOKER, .joker = item->joker}; 16 | case SHOP_ITEM_PLANET: 17 | return (CustomElementData){.type = CUSTOM_ELEMENT_CONSUMABLE, 18 | .consumable = (Consumable){.type = CONSUMABLE_PLANET, .planet = item->planet}}; 19 | case SHOP_ITEM_TAROT: 20 | return (CustomElementData){.type = CUSTOM_ELEMENT_CONSUMABLE, 21 | .consumable = (Consumable){.type = CONSUMABLE_TAROT, .tarot = item->tarot}}; 22 | case SHOP_ITEM_SPECTRAL: 23 | return (CustomElementData){.type = CUSTOM_ELEMENT_CONSUMABLE, 24 | .consumable = (Consumable){.type = CONSUMABLE_SPECTRAL, .spectral = item->spectral}}; 25 | } 26 | } 27 | 28 | CustomElementData create_spread_item_element(NavigationSection section, uint8_t i) { 29 | switch (section) { 30 | case NAVIGATION_NONE: 31 | case NAVIGATION_MAIN_MENU: 32 | case NAVIGATION_SELECT_DECK: 33 | case NAVIGATION_SELECT_STAKE: 34 | case NAVIGATION_SELECT_BLIND: 35 | case NAVIGATION_OVERLAY_MENU: 36 | return (CustomElementData){0}; 37 | 38 | case NAVIGATION_HAND: 39 | return (CustomElementData){.type = CUSTOM_ELEMENT_CARD, .card = state.game.hand.cards[i]}; 40 | 41 | case NAVIGATION_CONSUMABLES: 42 | return (CustomElementData){.type = CUSTOM_ELEMENT_CONSUMABLE, .consumable = state.game.consumables.items[i]}; 43 | 44 | case NAVIGATION_JOKERS: 45 | return (CustomElementData){.type = CUSTOM_ELEMENT_JOKER, .joker = state.game.jokers.cards[i]}; 46 | 47 | case NAVIGATION_SHOP_ITEMS: 48 | return create_shop_item_custom_element(&state.game.shop.items[i]); 49 | 50 | case NAVIGATION_SHOP_VOUCHER: 51 | return (CustomElementData){.type = CUSTOM_ELEMENT_VOUCHER, .voucher = state.game.shop.vouchers[i]}; 52 | 53 | case NAVIGATION_SHOP_BOOSTER_PACKS: 54 | return (CustomElementData){.type = CUSTOM_ELEMENT_BOOSTER_PACK, .booster_pack = state.game.shop.booster_packs[i]}; 55 | 56 | case NAVIGATION_BOOSTER_PACK: 57 | return create_shop_item_custom_element(&state.game.booster_pack.content[i]); 58 | } 59 | } 60 | 61 | void get_shop_item_tooltip_content(Clay_String *name, Clay_String *description, ShopItem *item) { 62 | switch (item->type) { 63 | case SHOP_ITEM_CARD: 64 | *name = get_full_card_name(item->card.suit, item->card.rank); 65 | append_clay_string(description, "+%d chips", item->card.chips); 66 | break; 67 | 68 | case SHOP_ITEM_JOKER: 69 | *name = (Clay_String){.chars = item->joker.name, .length = strlen(item->joker.name)}; 70 | *description = (Clay_String){.chars = item->joker.description, .length = strlen(item->joker.description)}; 71 | break; 72 | 73 | case SHOP_ITEM_PLANET: 74 | *name = (Clay_String){.chars = get_planet_card_name(item->planet), 75 | .length = strlen(get_planet_card_name(item->planet))}; 76 | uint16_t hand_union = 1 << item->planet; 77 | ScorePair upgrade = get_planet_card_base_score(hand_union); 78 | append_clay_string(description, "%s (+%u chips, +%0.lf mult)", get_poker_hand_name(hand_union), upgrade.chips, 79 | upgrade.mult); 80 | break; 81 | 82 | case SHOP_ITEM_TAROT: 83 | *name = 84 | (Clay_String){.chars = get_tarot_card_name(item->tarot), .length = strlen(get_tarot_card_name(item->tarot))}; 85 | *description = (Clay_String){.chars = get_tarot_card_description(item->tarot), 86 | .length = strlen(get_tarot_card_description(item->tarot))}; 87 | break; 88 | 89 | case SHOP_ITEM_SPECTRAL: 90 | *name = (Clay_String){.chars = get_spectral_card_name(item->spectral), 91 | .length = strlen(get_spectral_card_name(item->spectral))}; 92 | *description = (Clay_String){.chars = get_spectral_card_description(item->spectral), 93 | .length = strlen(get_spectral_card_description(item->spectral))}; 94 | break; 95 | } 96 | } 97 | 98 | void get_nav_item_tooltip_content(Clay_String *name, Clay_String *description, NavigationSection section) { 99 | switch (section) { 100 | case NAVIGATION_NONE: 101 | case NAVIGATION_MAIN_MENU: 102 | case NAVIGATION_SELECT_DECK: 103 | case NAVIGATION_SELECT_STAKE: 104 | case NAVIGATION_SELECT_BLIND: 105 | case NAVIGATION_OVERLAY_MENU: 106 | return; 107 | 108 | case NAVIGATION_HAND: { 109 | ShopItem item = {.type = SHOP_ITEM_CARD, .card = state.game.hand.cards[state.navigation.hovered]}; 110 | get_shop_item_tooltip_content(name, description, &item); 111 | break; 112 | } 113 | 114 | case NAVIGATION_JOKERS: { 115 | ShopItem item = {.type = SHOP_ITEM_JOKER, .joker = state.game.jokers.cards[state.navigation.hovered]}; 116 | get_shop_item_tooltip_content(name, description, &item); 117 | break; 118 | } 119 | 120 | case NAVIGATION_CONSUMABLES: { 121 | Consumable *consumable = &state.game.consumables.items[state.navigation.hovered]; 122 | 123 | switch (consumable->type) { 124 | case CONSUMABLE_PLANET: 125 | get_shop_item_tooltip_content(name, description, 126 | &(ShopItem){.type = SHOP_ITEM_PLANET, .planet = consumable->planet}); 127 | break; 128 | case CONSUMABLE_TAROT: 129 | get_shop_item_tooltip_content(name, description, 130 | &(ShopItem){.type = SHOP_ITEM_TAROT, .tarot = consumable->tarot}); 131 | break; 132 | case CONSUMABLE_SPECTRAL: 133 | get_shop_item_tooltip_content(name, description, 134 | &(ShopItem){.type = SHOP_ITEM_SPECTRAL, .spectral = consumable->spectral}); 135 | } 136 | break; 137 | } 138 | 139 | case NAVIGATION_SHOP_ITEMS: { 140 | ShopItem *item = &state.game.shop.items[state.navigation.hovered]; 141 | get_shop_item_tooltip_content(name, description, item); 142 | break; 143 | } 144 | 145 | case NAVIGATION_SHOP_VOUCHER: { 146 | Voucher voucher = state.game.shop.vouchers[state.navigation.hovered]; 147 | *name = (Clay_String){.chars = get_voucher_name(voucher), .length = strlen(get_voucher_name(voucher))}; 148 | *description = 149 | (Clay_String){.chars = get_voucher_description(voucher), .length = strlen(get_voucher_description(voucher))}; 150 | break; 151 | } 152 | 153 | case NAVIGATION_SHOP_BOOSTER_PACKS: { 154 | BoosterPackItem *booster_pack = &state.game.shop.booster_packs[state.navigation.hovered]; 155 | *name = get_full_booster_pack_name(booster_pack->size, booster_pack->type); 156 | append_clay_string(description, "Choose %d of up to %d %s", booster_pack->size == BOOSTER_PACK_MEGA ? 2 : 1, 157 | get_booster_pack_items_count(booster_pack), 158 | get_booster_pack_description_suffix(booster_pack->type)); 159 | break; 160 | } 161 | 162 | case NAVIGATION_BOOSTER_PACK: 163 | get_shop_item_tooltip_content(name, description, &state.game.booster_pack.content[state.navigation.hovered]); 164 | break; 165 | } 166 | } 167 | 168 | uint8_t get_item_price(NavigationSection section, uint8_t i) { 169 | switch (section) { 170 | case NAVIGATION_SHOP_ITEMS: 171 | return get_shop_item_price(&state.game.shop.items[i]); 172 | case NAVIGATION_SHOP_BOOSTER_PACKS: 173 | return get_booster_pack_price(&state.game.shop.booster_packs[i]); 174 | case NAVIGATION_SHOP_VOUCHER: 175 | return get_voucher_price(state.game.shop.vouchers[i]); 176 | default: 177 | return UINT8_MAX; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /content/spectral.c: -------------------------------------------------------------------------------- 1 | #include "spectral.h" 2 | 3 | #include "../random.h" 4 | #include "../state.h" 5 | #include "cvector.h" 6 | 7 | const char *get_spectral_card_name(Spectral spectral) { 8 | switch (spectral) { 9 | case SPECTRAL_FAMILIAR: 10 | return "Familiar"; 11 | case SPECTRAL_GRIM: 12 | return "Grim"; 13 | case SPECTRAL_INCANTATION: 14 | return "Incantation"; 15 | case SPECTRAL_TALISMAN: 16 | return "Talisman"; 17 | case SPECTRAL_AURA: 18 | return "Aura"; 19 | case SPECTRAL_WRAITH: 20 | return "Wraith NOT IMPLEMENTED"; 21 | case SPECTRAL_SIGIL: 22 | return "Sigil"; 23 | case SPECTRAL_OUIJA: 24 | return "Ouija"; 25 | case SPECTRAL_ECTOPLASM: 26 | return "Ectoplasm NOT IMPLEMENTED"; 27 | case SPECTRAL_IMMOLATE: 28 | return "Immolate"; 29 | case SPECTRAL_ANKH: 30 | return "Ankh"; 31 | case SPECTRAL_DEJA_VU: 32 | return "Deja Vu"; 33 | case SPECTRAL_HEX: 34 | return "Hex"; 35 | case SPECTRAL_TRANCE: 36 | return "Trance"; 37 | case SPECTRAL_MEDIUM: 38 | return "Medium"; 39 | case SPECTRAL_CRYPTID: 40 | return "Cryptid"; 41 | case SPECTRAL_SOUL: 42 | return "The Soul NOT IMPLEMENTED"; 43 | case SPECTRAL_BLACK_HOLE: 44 | return "Black Hole"; 45 | } 46 | }; 47 | 48 | const char *get_spectral_card_description(Spectral spectral) { 49 | switch (spectral) { 50 | case SPECTRAL_FAMILIAR: 51 | return "Destroy 1 random card in your hand, but add 3 random Enhanced face cards instead."; 52 | case SPECTRAL_GRIM: 53 | return "Destroy 1 random card in your hand, but add 2 random Enhanced Aces instead."; 54 | case SPECTRAL_INCANTATION: 55 | return "Destroy 1 random card in your hand, but add 4 random Enhanced numbered cards instead."; 56 | case SPECTRAL_TALISMAN: 57 | return "Add a Gold Seal to 1 selected card."; 58 | case SPECTRAL_AURA: 59 | return "Add Foil, Holographic, or Polychrome edition (determined at random) to 1 selected card in hand."; 60 | case SPECTRAL_WRAITH: 61 | return "Creates a random Rare Joker (must have room), but sets money to $0."; 62 | case SPECTRAL_SIGIL: 63 | return "Converts all cards in hand to a single random suit."; 64 | case SPECTRAL_OUIJA: 65 | return "Converts all cards in hand to a single random rank, but -1 Hand Size."; 66 | case SPECTRAL_ECTOPLASM: 67 | return "Add Negative to a random Joker, but -1 Hand Size, plus another -1 hand size for each time Ectoplasm has " 68 | "been used this run"; 69 | case SPECTRAL_IMMOLATE: 70 | return "Destroys 5 random cards in hand, but gain $20."; 71 | case SPECTRAL_ANKH: 72 | return "Creates a copy of 1 of your Jokers at random, then destroys the others, leaving you with two identical " 73 | "Jokers."; 74 | case SPECTRAL_DEJA_VU: 75 | return "Adds a Red Seal to 1 selected card."; 76 | case SPECTRAL_HEX: 77 | return "Adds Polychrome to a random Joker, and destroys the rest."; 78 | case SPECTRAL_TRANCE: 79 | return "Adds a Blue Seal to 1 selected card."; 80 | case SPECTRAL_MEDIUM: 81 | return "Adds a Purple Seal to 1 selected card."; 82 | case SPECTRAL_CRYPTID: 83 | return "Creates 2 exact copies (including Enhancements, Editions and Seals) of a selected card in your hand."; 84 | case SPECTRAL_SOUL: 85 | return "Creates a Legendary Joker (Must have room)"; 86 | case SPECTRAL_BLACK_HOLE: 87 | return "Upgrades every poker hand (including secret hands not yet discovered) by one level."; 88 | } 89 | }; 90 | 91 | uint8_t get_spectral_max_selected(Spectral spectral) { 92 | switch (spectral) { 93 | case SPECTRAL_TALISMAN: 94 | case SPECTRAL_AURA: 95 | case SPECTRAL_DEJA_VU: 96 | case SPECTRAL_TRANCE: 97 | case SPECTRAL_MEDIUM: 98 | case SPECTRAL_CRYPTID: 99 | return 1; 100 | 101 | default: 102 | return 0; 103 | } 104 | } 105 | 106 | void destroy_random_card() { 107 | if (cvector_size(state.game.hand.cards) == 0) return; 108 | 109 | uint8_t destroy_index = random_vector_index(state.game.hand.cards); 110 | 111 | for (uint8_t i = 0; i < cvector_size(state.game.full_deck); i++) { 112 | if (compare_cards(&state.game.full_deck[i], &state.game.hand.cards[destroy_index])) { 113 | cvector_erase(state.game.full_deck, i); 114 | break; 115 | } 116 | } 117 | 118 | cvector_erase(state.game.hand.cards, destroy_index); 119 | } 120 | void add_card_to_deck(Suit suit, Rank rank, Edition edition, Enhancement enhancement, Seal seal) { 121 | Card card = create_card(suit, rank, edition, enhancement, seal); 122 | cvector_push_back(state.game.hand.cards, card); 123 | cvector_push_back(state.game.full_deck, card); 124 | } 125 | 126 | uint8_t use_spectral_card(Spectral spectral) { 127 | Card *selected_cards[2] = {0}; 128 | uint8_t selected_count = 0; 129 | 130 | cvector_for_each(state.game.hand.cards, Card, card) { 131 | if (card->selected == 0) continue; 132 | 133 | selected_cards[selected_count] = card; 134 | selected_count++; 135 | if (selected_count > 2) return 0; 136 | } 137 | 138 | uint8_t max_selected_count = get_spectral_max_selected(spectral); 139 | if (selected_count != max_selected_count) return 0; 140 | 141 | switch (spectral) { 142 | case SPECTRAL_FAMILIAR: 143 | if (state.game.hand.size == 1) return 0; 144 | destroy_random_card(); 145 | for (uint8_t i = 0; i < 3; i++) 146 | add_card_to_deck(random_max_value(3), random_in_range(10, 12), EDITION_BASE, random_in_range(1, 8), SEAL_NONE); 147 | break; 148 | 149 | case SPECTRAL_GRIM: 150 | if (state.game.hand.size == 1) return 0; 151 | destroy_random_card(); 152 | for (uint8_t i = 0; i < 2; i++) 153 | add_card_to_deck(random_max_value(3), RANK_ACE, EDITION_BASE, random_in_range(1, 8), SEAL_NONE); 154 | break; 155 | 156 | case SPECTRAL_INCANTATION: 157 | if (state.game.hand.size == 1) return 0; 158 | destroy_random_card(); 159 | for (uint8_t i = 0; i < 4; i++) 160 | add_card_to_deck(random_max_value(3), random_in_range(1, 9), EDITION_BASE, random_in_range(1, 8), SEAL_NONE); 161 | break; 162 | 163 | case SPECTRAL_TALISMAN: 164 | selected_cards[0]->seal = SEAL_GOLD; 165 | break; 166 | 167 | case SPECTRAL_AURA: 168 | selected_cards[0]->edition = random_weighted((uint16_t[3]){50, 35, 15}, 3) + 1; 169 | break; 170 | 171 | case SPECTRAL_WRAITH: 172 | // TODO Add this when some rare jokers will be added 173 | break; 174 | 175 | case SPECTRAL_SIGIL: { 176 | if (state.game.hand.size == 1) return 0; 177 | Suit new_suit = random_max_value(3); 178 | 179 | cvector_for_each(state.game.hand.cards, Card, hand_card) { 180 | cvector_for_each(state.game.full_deck, Card, card) { 181 | if (compare_cards(hand_card, card)) { 182 | card->suit = new_suit; 183 | card->was_played = 0; 184 | hand_card->suit = new_suit; 185 | hand_card->was_played = 0; 186 | break; 187 | } 188 | } 189 | } 190 | break; 191 | } 192 | 193 | case SPECTRAL_OUIJA: { 194 | if (state.game.hand.size == 1) return 0; 195 | Rank new_rank = random_max_value(12); 196 | state.game.hand.size--; 197 | 198 | cvector_for_each(state.game.hand.cards, Card, hand_card) { 199 | cvector_for_each(state.game.full_deck, Card, card) { 200 | if (compare_cards(hand_card, card)) { 201 | card->rank = new_rank; 202 | card->was_played = 0; 203 | hand_card->rank = new_rank; 204 | hand_card->was_played = 0; 205 | break; 206 | } 207 | } 208 | } 209 | break; 210 | } 211 | 212 | case SPECTRAL_ECTOPLASM: 213 | // TODO Add when negative jokers will be implemented 214 | break; 215 | 216 | case SPECTRAL_IMMOLATE: 217 | if (state.game.hand.size == 1) return 0; 218 | state.game.money += 20; 219 | for (uint8_t i = 0; i < 5; i++) destroy_random_card(); 220 | break; 221 | 222 | case SPECTRAL_ANKH: { 223 | uint8_t joker_to_copy = random_vector_index(state.game.jokers.cards); 224 | Joker joker = state.game.jokers.cards[joker_to_copy]; 225 | // TODO Don't remove eternal jokers when they will be added 226 | cvector_clear(state.game.jokers.cards); 227 | for (uint8_t i = 0; i < 2; i++) cvector_push_back(state.game.jokers.cards, joker); 228 | break; 229 | } 230 | 231 | case SPECTRAL_DEJA_VU: 232 | selected_cards[0]->seal = SEAL_RED; 233 | break; 234 | 235 | case SPECTRAL_HEX: { 236 | // TODO Ignore jokers with editions 237 | uint8_t joker_to_upgrade = random_vector_index(state.game.jokers.cards); 238 | Joker joker = state.game.jokers.cards[joker_to_upgrade]; 239 | // TODO Don't remove eternal jokers when they will be added 240 | cvector_clear(state.game.jokers.cards); 241 | 242 | joker.edition = EDITION_POLYCHROME; 243 | cvector_push_back(state.game.jokers.cards, joker); 244 | break; 245 | } 246 | 247 | case SPECTRAL_TRANCE: 248 | selected_cards[0]->seal = SEAL_BLUE; 249 | break; 250 | 251 | case SPECTRAL_MEDIUM: 252 | selected_cards[0]->seal = SEAL_PURPLE; 253 | break; 254 | 255 | case SPECTRAL_CRYPTID: { 256 | Card *card = selected_cards[0]; 257 | for (uint8_t i = 0; i < 2; i++) 258 | add_card_to_deck(card->suit, card->rank, card->edition, card->enhancement, card->seal); 259 | break; 260 | } 261 | 262 | case SPECTRAL_SOUL: 263 | // TODO Add this when legendary jokers will be added 264 | break; 265 | 266 | case SPECTRAL_BLACK_HOLE: 267 | for (uint8_t i = 0; i < 12; i++) state.game.poker_hands[i].level++; 268 | break; 269 | } 270 | 271 | if (max_selected_count != 0) deselect_all_cards(); 272 | if (state.stage == STAGE_GAME && state.game.current_blind->type > BLIND_BIG) { 273 | disable_boss_blind(); 274 | enable_boss_blind(); 275 | } 276 | return 1; 277 | } 278 | -------------------------------------------------------------------------------- /state.c: -------------------------------------------------------------------------------- 1 | #include "state.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "debug.h" 10 | #include "game.h" 11 | #include "gfx.h" 12 | 13 | const NavigationRow jokers_consumables_row = {2, {NAVIGATION_JOKERS, NAVIGATION_CONSUMABLES}}; 14 | 15 | static const NavigationLayout stage_nav_layouts[] = { 16 | {.row_count = 1, .rows = {{1, {NAVIGATION_MAIN_MENU}}}}, 17 | {.row_count = 1, .rows = {{1, {NAVIGATION_SELECT_DECK}}}}, 18 | {.row_count = 0}, 19 | {.row_count = 2, 20 | .rows = 21 | { 22 | jokers_consumables_row, 23 | {1, {NAVIGATION_HAND}}, 24 | }}, 25 | {.row_count = 1, .rows = {jokers_consumables_row}}, 26 | {.row_count = 3, 27 | .rows = 28 | { 29 | jokers_consumables_row, 30 | {1, {NAVIGATION_SHOP_ITEMS}}, 31 | {2, {NAVIGATION_SHOP_VOUCHER, NAVIGATION_SHOP_BOOSTER_PACKS}}, 32 | }}, 33 | {.row_count = 3, 34 | .rows = 35 | { 36 | jokers_consumables_row, 37 | {1, {NAVIGATION_HAND}}, 38 | {1, {NAVIGATION_BOOSTER_PACK}}, 39 | }}, 40 | {.row_count = 1, .rows = {{1, {NAVIGATION_SELECT_BLIND}}}}, 41 | {.row_count = 0}, 42 | }; 43 | 44 | static const NavigationLayout overlay_nav_layouts[] = { 45 | {.row_count = 0}, 46 | {.row_count = 1, .rows = {{1, {NAVIGATION_OVERLAY_MENU}}}}, 47 | {.row_count = 1, .rows = {{1, {NAVIGATION_SELECT_STAKE}}}}, 48 | {.row_count = 0}, 49 | }; 50 | 51 | int append_clay_string(Clay_String *dest, const char *format, ...) { 52 | size_t remaining = FRAME_ARENA_CAPACITY - state.frame_arena.offset; 53 | char *dst = (char *)state.frame_arena.data + state.frame_arena.offset; 54 | 55 | va_list args; 56 | va_start(args, format); 57 | int written = vsnprintf(dst, remaining, format, args); 58 | va_end(args); 59 | 60 | if (written < 0 || (size_t)written >= remaining) { 61 | log_message(LOG_ERROR, "Frame arena overflow: Failed to append Clay string."); 62 | written = (remaining > 0) ? remaining - 1 : 0; 63 | } 64 | 65 | dest->isStaticallyAllocated = 0; 66 | dest->chars = dst; 67 | dest->length = written; 68 | 69 | state.frame_arena.offset += written; 70 | 71 | return written; 72 | } 73 | 74 | void *frame_arena_allocate(size_t size) { 75 | // Align offset to 16 bytes as it is required by PSP device 76 | size_t alignment = 16; 77 | size_t padding = (alignment - (state.frame_arena.offset % alignment)) % alignment; 78 | state.frame_arena.offset += padding; 79 | 80 | if (state.frame_arena.offset + size > FRAME_ARENA_CAPACITY) { 81 | log_message(LOG_ERROR, "Frame arena overflow: Failed to allocate memory."); 82 | state.running = 0; 83 | return NULL; 84 | } 85 | 86 | void *ptr = &state.frame_arena.data[state.frame_arena.offset]; 87 | state.frame_arena.offset += size; 88 | return ptr; 89 | } 90 | 91 | uint8_t calc_proportional_hovered(uint8_t current_count, uint8_t next_count) { 92 | uint8_t current_hovered = state.navigation.hovered; 93 | 94 | if (current_count <= 1 || next_count <= 1) return 0; 95 | if (current_count == next_count) return current_hovered < next_count ? current_hovered : next_count - 1; 96 | 97 | float ratio = (float)current_hovered / (float)(current_count - 1); 98 | int new_hovered = (int)roundf(ratio * (next_count - 1)); 99 | 100 | if (new_hovered < 0) new_hovered = 0; 101 | if (new_hovered >= next_count) new_hovered = next_count - 1; 102 | 103 | return (uint8_t)new_hovered; 104 | } 105 | 106 | void move_nav_cursor(NavigationDirection direction) { 107 | const NavigationLayout *layout = 108 | state.overlay == OVERLAY_NONE ? &stage_nav_layouts[state.stage] : &overlay_nav_layouts[state.overlay]; 109 | if (layout->row_count == 0 || (layout->row_count == 1 && layout->rows[0].count == 1)) return; 110 | 111 | NavigationCursor *cursor = &state.navigation.cursor; 112 | NavigationSection initial_section = get_current_section(); 113 | 114 | int8_t d_row = direction == NAVIGATION_UP ? -1 : direction == NAVIGATION_DOWN ? 1 : 0; 115 | int8_t d_col = direction == NAVIGATION_LEFT ? -1 : direction == NAVIGATION_RIGHT ? 1 : 0; 116 | 117 | do { 118 | int new_row = (cursor->row + d_row + layout->row_count) % layout->row_count; 119 | 120 | uint8_t col_count = layout->rows[new_row].count; 121 | if (col_count == 0) { 122 | log_message(LOG_WARNING, "Moved navigation cursor to row with 0 columns."); 123 | return; 124 | } 125 | 126 | uint8_t curr_column = 0; 127 | 128 | do { 129 | int new_col = (cursor->col + d_col + col_count) % col_count; 130 | if (new_col >= col_count) new_col = col_count - 1; 131 | 132 | cursor->row = new_row; 133 | cursor->col = new_col + (d_col == 0 ? curr_column : 0); 134 | curr_column++; 135 | } while (get_nav_section_size(get_current_section()) == 0 && curr_column < col_count); 136 | } while (get_nav_section_size(get_current_section()) == 0 && initial_section != get_current_section()); 137 | 138 | uint8_t initial_section_size = get_nav_section_size(initial_section); 139 | uint8_t new_section_size = get_nav_section_size(get_current_section()); 140 | if (d_row != 0) 141 | state.navigation.hovered = calc_proportional_hovered(initial_section_size, new_section_size); 142 | else if (state.navigation.hovered == 0 && direction == NAVIGATION_LEFT) 143 | state.navigation.hovered = new_section_size - 1; 144 | else 145 | state.navigation.hovered = 0; 146 | } 147 | 148 | NavigationSection get_current_section() { 149 | const NavigationLayout *layout = 150 | state.overlay == OVERLAY_NONE ? &stage_nav_layouts[state.stage] : &overlay_nav_layouts[state.overlay]; 151 | return layout->rows[state.navigation.cursor.row].sections[state.navigation.cursor.col]; 152 | } 153 | 154 | uint8_t get_nav_section_size(NavigationSection section) { 155 | switch (section) { 156 | case NAVIGATION_NONE: 157 | return 0; 158 | case NAVIGATION_MAIN_MENU: 159 | return 3; 160 | case NAVIGATION_SELECT_DECK: 161 | return 15; 162 | case NAVIGATION_SELECT_STAKE: 163 | return 8; 164 | case NAVIGATION_SELECT_BLIND: 165 | return state.game.current_blind->type <= BLIND_BIG ? 2 : 1; 166 | case NAVIGATION_HAND: 167 | return cvector_size(state.game.hand.cards); 168 | case NAVIGATION_SHOP_ITEMS: 169 | return cvector_size(state.game.shop.items); 170 | case NAVIGATION_SHOP_VOUCHER: 171 | return cvector_size(state.game.shop.vouchers) - (cvector_back(state.game.shop.vouchers) == 0 ? 1 : 0); 172 | case NAVIGATION_BOOSTER_PACK: 173 | return cvector_size(state.game.booster_pack.content); 174 | case NAVIGATION_CONSUMABLES: 175 | return cvector_size(state.game.consumables.items); 176 | case NAVIGATION_JOKERS: 177 | return cvector_size(state.game.jokers.cards); 178 | case NAVIGATION_SHOP_BOOSTER_PACKS: 179 | return cvector_size(state.game.shop.booster_packs); 180 | 181 | case NAVIGATION_OVERLAY_MENU: 182 | return 4; 183 | } 184 | } 185 | 186 | uint8_t is_nav_section_horizontal(NavigationSection section) { 187 | if (section == NAVIGATION_OVERLAY_MENU || section == NAVIGATION_SELECT_BLIND) return 0; 188 | 189 | return 1; 190 | } 191 | 192 | void set_nav_hovered(int8_t new_hovered) { 193 | uint8_t max_value = get_nav_section_size(get_current_section()); 194 | 195 | if (new_hovered >= max_value) 196 | new_hovered = max_value - 1; 197 | else if (new_hovered < 0) 198 | new_hovered = 0; 199 | 200 | state.navigation.hovered = new_hovered; 201 | } 202 | 203 | void move_nav_hovered(uint8_t new_position) { 204 | const NavigationSection section = get_current_section(); 205 | uint8_t max_position = get_nav_section_size(section); 206 | 207 | if (new_position >= max_position) return; 208 | 209 | uint8_t *hovered = &state.navigation.hovered; 210 | 211 | switch (section) { 212 | case NAVIGATION_HAND: { 213 | Card temp = state.game.hand.cards[*hovered]; 214 | state.game.hand.cards[*hovered] = state.game.hand.cards[new_position]; 215 | state.game.hand.cards[new_position] = temp; 216 | break; 217 | } 218 | 219 | case NAVIGATION_JOKERS: { 220 | Joker temp = state.game.jokers.cards[*hovered]; 221 | state.game.jokers.cards[*hovered] = state.game.jokers.cards[new_position]; 222 | state.game.jokers.cards[new_position] = temp; 223 | break; 224 | } 225 | 226 | case NAVIGATION_CONSUMABLES: { 227 | Consumable temp = state.game.consumables.items[*hovered]; 228 | state.game.consumables.items[*hovered] = state.game.consumables.items[new_position]; 229 | state.game.consumables.items[new_position] = temp; 230 | break; 231 | } 232 | 233 | default: 234 | return; 235 | } 236 | 237 | *hovered = new_position; 238 | } 239 | 240 | void change_stage(Stage stage) { 241 | state.prev_stage = state.stage; 242 | state.stage = stage; 243 | state.overlay = OVERLAY_NONE; 244 | 245 | state.navigation.hovered = 0; 246 | state.navigation.cursor.col = 0; 247 | state.navigation.cursor.row = stage == STAGE_BOOSTER_PACK ? 2 : stage_nav_layouts[stage].row_count > 1 ? 1 : 0; 248 | 249 | update_render_commands(); 250 | } 251 | 252 | void change_overlay(Overlay overlay) { 253 | if (state.overlay == OVERLAY_NONE && overlay != OVERLAY_NONE) state.prev_navigation = state.navigation; 254 | 255 | state.overlay = overlay; 256 | 257 | if (overlay == OVERLAY_NONE) { 258 | state.navigation = state.prev_navigation; 259 | update_render_commands(); 260 | return; 261 | } 262 | 263 | state.navigation.cursor = (NavigationCursor){0, 0}; 264 | state.navigation.hovered = 0; 265 | 266 | update_render_commands(); 267 | } 268 | 269 | void overlay_menu_button_click() { 270 | switch (state.navigation.hovered) { 271 | case 0: 272 | change_overlay(OVERLAY_NONE); 273 | break; 274 | 275 | case 1: 276 | change_overlay(OVERLAY_POKER_HANDS); 277 | break; 278 | 279 | case 2: { 280 | Deck current_deck = state.game.deck_type; 281 | Stake current_stake = state.game.stake; 282 | game_destroy(); 283 | game_init(current_deck, current_stake); 284 | break; 285 | } 286 | 287 | case 3: 288 | game_destroy(); 289 | change_stage(STAGE_MAIN_MENU); 290 | break; 291 | 292 | default: 293 | return; 294 | } 295 | } 296 | 297 | void main_menu_button_click() { 298 | switch (state.navigation.hovered) { 299 | case 0: 300 | change_stage(STAGE_SELECT_DECK); 301 | break; 302 | case 1: 303 | change_stage(STAGE_CREDITS); 304 | break; 305 | case 2: 306 | state.running = 0; 307 | break; 308 | default: 309 | return; 310 | } 311 | } 312 | 313 | void select_blind_button_click() { 314 | switch (state.navigation.hovered) { 315 | case 0: 316 | select_blind(); 317 | break; 318 | case 1: 319 | skip_blind(); 320 | break; 321 | default: 322 | return; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /game.h: -------------------------------------------------------------------------------- 1 | #ifndef GAME_H 2 | #define GAME_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "content/joker.h" 9 | #include "content/spectral.h" 10 | #include "content/tarot.h" 11 | 12 | typedef enum { 13 | DECK_RED, 14 | DECK_BLUE, 15 | DECK_YELLOW, 16 | DECK_GREEN, 17 | DECK_BLACK, 18 | DECK_MAGIC, 19 | DECK_NEBULA, 20 | DECK_GHOST, 21 | DECK_ABANDONED, 22 | DECK_CHECKERED, 23 | DECK_ZODIAC, 24 | DECK_PAINTED, 25 | DECK_ANAGLYPH, 26 | DECK_PLASMA, 27 | DECK_ERRATIC, 28 | } Deck; 29 | 30 | typedef enum { 31 | STAKE_WHITE, 32 | STAKE_RED, 33 | STAKE_GREEN, 34 | STAKE_BLACK, 35 | STAKE_BLUE, 36 | STAKE_PURPLE, 37 | STAKE_ORANGE, 38 | STAKE_GOLD, 39 | } Stake; 40 | 41 | typedef enum { 42 | TAG_UNCOMMON, 43 | TAG_RARE, 44 | TAG_NEGATIVE, 45 | TAG_FOIL, 46 | TAG_HOLOGRAPHIC, 47 | TAG_POLYCHROME, 48 | TAG_INVESTMENT, 49 | TAG_VOUCHER, 50 | TAG_BOSS, 51 | TAG_STANDARD, 52 | TAG_CHARM, 53 | TAG_METEOR, 54 | TAG_BUFFOON, 55 | TAG_HANDY, 56 | TAG_GARBAGE, 57 | TAG_ETHEREAL, 58 | TAG_COUPON, 59 | TAG_DOUBLE, 60 | TAG_JUGGLE, 61 | TAG_D6, 62 | TAG_TOPUP, 63 | TAG_SPEED, 64 | TAG_ORBITAL, 65 | TAG_ECONOMY, 66 | } Tag; 67 | 68 | typedef enum { SUIT_HEARTS, SUIT_DIAMONDS, SUIT_SPADES, SUIT_CLUBS } Suit; 69 | typedef enum { 70 | RANK_ACE, 71 | RANK_TWO, 72 | RANK_THREE, 73 | RANK_FOUR, 74 | RANK_FIVE, 75 | RANK_SIX, 76 | RANK_SEVEN, 77 | RANK_EIGHT, 78 | RANK_NINE, 79 | RANK_TEN, 80 | RANK_JACK, 81 | RANK_QUEEN, 82 | RANK_KING 83 | } Rank; 84 | 85 | typedef enum { 86 | ENHANCEMENT_NONE, 87 | ENHANCEMENT_BONUS, 88 | ENHANCEMENT_MULT, 89 | ENHANCEMENT_WILD, 90 | ENHANCEMENT_GLASS, 91 | ENHANCEMENT_STEEL, 92 | ENHANCEMENT_STONE, 93 | ENHANCEMENT_GOLD, 94 | ENHANCEMENT_LUCKY 95 | } Enhancement; 96 | 97 | typedef enum { SEAL_NONE, SEAL_GOLD, SEAL_RED, SEAL_BLUE, SEAL_PURPLE } Seal; 98 | 99 | typedef enum { 100 | HAND_FLUSH_FIVE = 1 << 0, 101 | HAND_FLUSH_HOUSE = 1 << 1, 102 | HAND_FIVE_OF_KIND = 1 << 2, 103 | 104 | HAND_STRAIGHT_FLUSH = 1 << 3, 105 | HAND_FOUR_OF_KIND = 1 << 4, 106 | HAND_FULL_HOUSE = 1 << 5, 107 | HAND_FLUSH = 1 << 6, 108 | HAND_STRAIGHT = 1 << 7, 109 | HAND_THREE_OF_KIND = 1 << 8, 110 | HAND_TWO_PAIR = 1 << 9, 111 | HAND_PAIR = 1 << 10, 112 | HAND_HIGH_CARD = 1 << 11 113 | } PokerHand; 114 | 115 | typedef enum { 116 | // Secret planets 117 | PLANET_ERIS, 118 | PLANET_CERES, 119 | PLANET_X, 120 | 121 | PLANET_NEPTUNE, 122 | PLANET_MARS, 123 | PLANET_EARTH, 124 | PLANET_JUPITER, 125 | PLANET_SATURN, 126 | PLANET_VENUS, 127 | PLANET_URANUS, 128 | PLANET_MERCURY, 129 | PLANET_PLUTO, 130 | } Planet; 131 | 132 | typedef struct { 133 | uint8_t level; 134 | uint16_t played; 135 | } PokerHandStats; 136 | 137 | typedef struct { 138 | double mult; 139 | uint32_t chips; 140 | } ScorePair; 141 | 142 | typedef enum { 143 | BLIND_SMALL, 144 | BLIND_BIG, 145 | 146 | // Boss Blinds 147 | BLIND_HOOK, 148 | BLIND_OX, 149 | BLIND_HOUSE, 150 | BLIND_WALL, 151 | BLIND_WHEEL, 152 | BLIND_ARM, 153 | BLIND_CLUB, 154 | BLIND_FISH, 155 | BLIND_PSYCHIC, 156 | BLIND_GOAD, 157 | BLIND_WATER, 158 | BLIND_WINDOW, 159 | BLIND_MANACLE, 160 | BLIND_EYE, 161 | BLIND_MOUTH, 162 | BLIND_PLANT, 163 | BLIND_SERPENT, 164 | BLIND_PILLAR, 165 | BLIND_NEEDLE, 166 | BLIND_HEAD, 167 | BLIND_TOOTH, 168 | BLIND_FLINT, 169 | BLIND_MARK, 170 | 171 | // Finisher Boss Blinds 172 | BLIND_AMBER_ACORN, 173 | BLIND_VERDANT_LEAF, 174 | BLIND_VIOLET_VESSEL, 175 | BLIND_CRIMSON_HEART, 176 | BLIND_CERULEAN_BELL 177 | } BlindType; 178 | 179 | typedef struct { 180 | BlindType type; 181 | uint8_t is_active; 182 | Tag tag; 183 | } Blind; 184 | 185 | struct Card { 186 | Suit suit; 187 | Rank rank; 188 | 189 | Edition edition; 190 | Enhancement enhancement; 191 | Seal seal; 192 | 193 | uint16_t chips; 194 | uint8_t selected; 195 | 196 | uint8_t was_played; 197 | CardStatus status; 198 | 199 | uint8_t trigger_count; 200 | bool is_first_trigger; 201 | }; 202 | typedef struct Card Card; 203 | 204 | typedef struct { 205 | // Max number of cards in structure that can be obtained naturally 206 | // (some bosses/jokers will be able to overflow this value) 207 | uint8_t size; 208 | cvector_vector_type(Card) cards; 209 | } Hand; 210 | 211 | typedef enum { CONSUMABLE_PLANET, CONSUMABLE_TAROT, CONSUMABLE_SPECTRAL } ConsumableType; 212 | 213 | typedef struct { 214 | ConsumableType type; 215 | 216 | union { 217 | Planet planet; 218 | Tarot tarot; 219 | Spectral spectral; 220 | }; 221 | } Consumable; 222 | 223 | typedef struct { 224 | uint8_t was_used; 225 | Consumable consumable; 226 | } FoolLastUsed; 227 | 228 | typedef struct { 229 | int8_t hovered; 230 | uint8_t size; 231 | cvector_vector_type(Consumable) items; 232 | } Consumables; 233 | 234 | typedef struct { 235 | uint8_t size; 236 | cvector_vector_type(Joker) cards; 237 | } JokerHand; 238 | 239 | typedef struct { 240 | uint8_t count; 241 | uint16_t hand_union; 242 | ScorePair score_pair; 243 | Card *scoring_cards[5]; 244 | } SelectedHand; 245 | 246 | typedef enum { 247 | // Base vouchers 248 | VOUCHER_OVERSTOCK = 1u << 0, 249 | VOUCHER_CLEARANCE_SALE = 1u << 1, 250 | VOUCHER_HONE = 1u << 2, 251 | VOUCHER_REROLL_SURPLUS = 1u << 3, 252 | VOUCHER_CRYSTAL_BALL = 1u << 4, 253 | VOUCHER_TELESCOPE = 1u << 5, 254 | VOUCHER_GRABBER = 1u << 6, 255 | VOUCHER_WASTEFUL = 1u << 7, 256 | VOUCHER_TAROT_MERCHANT = 1u << 8, 257 | VOUCHER_PLANET_MERCHANT = 1u << 9, 258 | VOUCHER_SEED_MONEY = 1u << 10, 259 | VOUCHER_BLANK = 1u << 11, 260 | VOUCHER_MAGIC_TRICK = 1u << 12, 261 | VOUCHER_HIEROGLYPH = 1u << 13, 262 | VOUCHER_DIRECTORS_CUT = 1u << 14, 263 | VOUCHER_PAINT_BRUSH = 1u << 15, 264 | 265 | // Upgraded vouchers 266 | VOUCHER_OVERSTOCK_PLUS = 1u << (0 + 16), 267 | VOUCHER_LIQUIDATION = 1u << (1 + 16), 268 | VOUCHER_GLOW_UP = 1u << (2 + 16), 269 | VOUCHER_REROLL_GLUT = 1u << (3 + 16), 270 | VOUCHER_OMEN_GLOBE = 1u << (4 + 16), 271 | VOUCHER_OBSERVATORY = 1u << (5 + 16), 272 | VOUCHER_NACHO_TONG = 1u << (6 + 16), 273 | VOUCHER_RECYCLOMANCY = 1u << (7 + 16), 274 | VOUCHER_TAROT_TYCOON = 1u << (8 + 16), 275 | VOUCHER_PLANET_TYCOON = 1u << (9 + 16), 276 | VOUCHER_MONEY_TREE = 1u << (10 + 16), 277 | VOUCHER_ANTIMATTER = 1u << (11 + 16), 278 | VOUCHER_ILLUSION = 1u << (12 + 16), 279 | VOUCHER_PTEROGLYPH = 1u << (13 + 16), 280 | VOUCHER_RETCON = 1u << (14 + 16), 281 | VOUCHER_PALETTE = 1u << (15 + 16), 282 | } Voucher; 283 | 284 | typedef enum { SHOP_ITEM_CARD, SHOP_ITEM_TAROT, SHOP_ITEM_PLANET, SHOP_ITEM_JOKER, SHOP_ITEM_SPECTRAL } ShopItemType; 285 | 286 | typedef struct { 287 | ShopItemType type; 288 | bool is_free; 289 | union { 290 | Joker joker; 291 | Card card; 292 | Planet planet; 293 | Tarot tarot; 294 | Spectral spectral; 295 | }; 296 | } ShopItem; 297 | 298 | typedef enum { 299 | BOOSTER_PACK_STANDARD, 300 | BOOSTER_PACK_ARCANA, 301 | BOOSTER_PACK_CELESTIAL, 302 | BOOSTER_PACK_BUFFOON, 303 | BOOSTER_PACK_SPECTRAL 304 | } BoosterPackType; 305 | 306 | typedef enum { BOOSTER_PACK_NORMAL, BOOSTER_PACK_JUMBO, BOOSTER_PACK_MEGA } BoosterPackSize; 307 | 308 | typedef struct { 309 | BoosterPackType type; 310 | BoosterPackSize size; 311 | bool is_free; 312 | } BoosterPackItem; 313 | 314 | typedef struct { 315 | uint8_t uses; 316 | BoosterPackItem item; 317 | cvector_vector_type(ShopItem) content; 318 | } BoosterPack; 319 | 320 | typedef struct { 321 | uint8_t size; 322 | uint8_t reroll_count; 323 | cvector_vector_type(Voucher) vouchers; 324 | cvector_vector_type(ShopItem) items; 325 | cvector_vector_type(BoosterPackItem) booster_packs; 326 | } Shop; 327 | 328 | typedef enum { SORTING_BY_RANK, SORTING_BY_SUIT } SortingMode; 329 | 330 | typedef struct { 331 | uint8_t remaining; 332 | uint8_t total; 333 | } UsageState; 334 | 335 | typedef struct { 336 | UsageState hands; 337 | UsageState discards; 338 | uint16_t drawn_cards; 339 | } Stats; 340 | 341 | typedef struct { 342 | Deck deck_type; 343 | Stake stake; 344 | 345 | cvector_vector_type(Card) full_deck; 346 | cvector_vector_type(Card) deck; 347 | 348 | Hand hand; 349 | SelectedHand selected_hand; 350 | 351 | uint32_t vouchers; 352 | JokerHand jokers; 353 | Consumables consumables; 354 | 355 | double score; 356 | uint8_t ante; 357 | uint8_t round; 358 | 359 | cvector_vector_type(Tag) tags; 360 | Blind *current_blind; 361 | Blind blinds[3]; 362 | 363 | UsageState hands; 364 | UsageState discards; 365 | 366 | PokerHandStats poker_hands[12]; 367 | 368 | int16_t money; 369 | Shop shop; 370 | 371 | BoosterPack booster_pack; 372 | 373 | FoolLastUsed fool_last_used; 374 | uint32_t played_poker_hands; 375 | uint32_t defeated_boss_blinds; 376 | uint8_t has_rerolled_boss; 377 | 378 | SortingMode sorting_mode; 379 | Stats stats; 380 | } Game; 381 | 382 | void game_init(Deck deck, Stake stake); 383 | void game_destroy(); 384 | void generate_deck(); 385 | void apply_deck_settings(); 386 | 387 | uint8_t compare_cards(Card *a, Card *b); 388 | Card create_card(Suit suit, Rank rank, Edition edition, Enhancement enchacement, Seal seal); 389 | void shuffle_deck(); 390 | void draw_card(); 391 | void play_hand(); 392 | void discard_hand(); 393 | void fill_hand(); 394 | void sort_hand(); 395 | 396 | void toggle_card_select(uint8_t index); 397 | void force_card_select(uint8_t index); 398 | void deselect_all_cards(); 399 | void remove_selected_cards(); 400 | void replace_selected_cards(); 401 | void discard_card(uint8_t index); 402 | 403 | uint8_t is_face_card(Card *card); 404 | uint8_t is_suit(Card *card, Suit suit); 405 | uint8_t is_poker_hand_unknown(); 406 | bool is_planet_card_locked(Planet planet); 407 | bool filter_locked_planet_cards(uint8_t planet); 408 | 409 | uint16_t evaluate_hand(); 410 | uint8_t does_poker_hand_contain(uint16_t hand_union, PokerHand expected); 411 | PokerHand get_poker_hand(uint16_t hand_union); 412 | void update_scoring_hand(); 413 | static void apply_scoring_enhancement(Enhancement enhancement); 414 | static void apply_scoring_edition(Edition edition); 415 | 416 | PokerHandStats *get_poker_hand_stats(uint16_t hand_union); 417 | ScorePair get_poker_hand_base_score(uint16_t hand_union); 418 | ScorePair get_planet_card_base_score(uint16_t hand_union); 419 | ScorePair get_poker_hand_total_score(uint16_t hand_union); 420 | double get_ante_base_score(uint8_t ante); 421 | double get_required_score(uint8_t ante, BlindType blind_type); 422 | 423 | uint8_t get_blind_money(BlindType blind_type); 424 | uint8_t get_hands_money(); 425 | uint8_t get_discards_money(); 426 | uint8_t get_interest_money(); 427 | uint8_t get_investment_tag_money(); 428 | 429 | void cash_out(); 430 | 431 | uint8_t use_consumable(Consumable *consumable); 432 | uint8_t add_item_to_player(ShopItem *item); 433 | uint8_t get_shop_item_price(ShopItem *item); 434 | uint8_t get_voucher_price(Voucher voucher); 435 | void add_voucher_to_player(Voucher voucher); 436 | uint8_t get_booster_pack_price(BoosterPackItem *booster_pack); 437 | uint8_t get_booster_pack_items_count(BoosterPackItem *booster_pack); 438 | uint8_t get_shop_item_sell_price(ShopItem *item); 439 | void buy_item(bool should_use); 440 | void sell_shop_item(); 441 | void open_booster_pack(BoosterPackItem *booster_pack); 442 | void select_booster_pack_item(); 443 | void skip_booster_pack(); 444 | void fill_shop_items(); 445 | uint8_t get_reroll_price(); 446 | void reroll_shop_items(); 447 | void restock_shop(); 448 | void exit_shop(); 449 | 450 | void select_blind(); 451 | void skip_blind(); 452 | Tag roll_tag(); 453 | void trigger_immediate_tags(); 454 | 455 | PokerHand get_most_played_poker_hand(); 456 | 457 | uint8_t get_blind_min_ante(BlindType blind); 458 | void roll_boss_blind(); 459 | void trigger_reroll_boss_voucher(); 460 | void enable_boss_blind(); 461 | void disable_boss_blind(); 462 | 463 | #endif 464 | -------------------------------------------------------------------------------- /content/tarot.c: -------------------------------------------------------------------------------- 1 | #include "tarot.h" 2 | 3 | #include "../random.h" 4 | #include "../state.h" 5 | #include "cvector.h" 6 | 7 | const char *get_tarot_card_name(Tarot tarot) { 8 | switch (tarot) { 9 | case TAROT_FOOL: 10 | return "The Fool (0)"; 11 | case TAROT_MAGICIAN: 12 | return "The Magician (I)"; 13 | case TAROT_HIGH_PRIESTESS: 14 | return "The High Priestess (II)"; 15 | case TAROT_EMPRESS: 16 | return "The Empress (III)"; 17 | case TAROT_EMPEROR: 18 | return "The Emperor (IV)"; 19 | case TAROT_HIEROPHANT: 20 | return "The Hierophant (V)"; 21 | case TAROT_LOVERS: 22 | return "The Lovers (VI)"; 23 | case TAROT_CHARIOT: 24 | return "The Chariot (VII)"; 25 | case TAROT_JUSTICE: 26 | return "Justice (VIII)"; 27 | case TAROT_HERMIT: 28 | return "The Hermit (IX)"; 29 | case TAROT_WHEEL_OF_FORTUNE: 30 | return "The Wheel of Fortune (X)"; 31 | case TAROT_STRENGTH: 32 | return "Strength (XI)"; 33 | case TAROT_HANGED_MAN: 34 | return "The Hanged Man (XII)"; 35 | case TAROT_DEATH: 36 | return "Death (XIII)"; 37 | case TAROT_TEMPERANCE: 38 | return "Temperance (XIV)"; 39 | case TAROT_DEVIL: 40 | return "The Devil (XV)"; 41 | case TAROT_TOWER: 42 | return "The Tower (XVI)"; 43 | case TAROT_STAR: 44 | return "The Star (XVII)"; 45 | case TAROT_MOON: 46 | return "The Moon (XVIII)"; 47 | case TAROT_SUN: 48 | return "The Sun (XIX)"; 49 | case TAROT_JUDGEMENT: 50 | return "Judgement (XX)"; 51 | case TAROT_WORLD: 52 | return "The World (XXI)"; 53 | } 54 | } 55 | 56 | const char *get_tarot_card_description(Tarot tarot) { 57 | switch (tarot) { 58 | case TAROT_FOOL: 59 | return "Creates the last Tarot or Planet card used during this run (The Fool excluded)"; 60 | case TAROT_MAGICIAN: 61 | return "Enhances 2 selected cards to Lucky Cards"; 62 | case TAROT_HIGH_PRIESTESS: 63 | return "Creates up to 2 random Planet cards (Must have room)"; 64 | case TAROT_EMPRESS: 65 | return "Enhances 2 selected cards to Mult Cards"; 66 | case TAROT_EMPEROR: 67 | return "Creates up to 2 random Tarot cards (Must have room)"; 68 | case TAROT_HIEROPHANT: 69 | return "Enhances 2 selected cards to Bonus Cards"; 70 | case TAROT_LOVERS: 71 | return "Enhances 1 selected card into a Wild Card"; 72 | case TAROT_CHARIOT: 73 | return "Enhances 1 selected card into a Steel Card"; 74 | case TAROT_JUSTICE: 75 | return "Enhances 1 selected card into a Glass Card"; 76 | case TAROT_HERMIT: 77 | return "Doubles money (Max of $20)"; 78 | case TAROT_WHEEL_OF_FORTUNE: 79 | return "1 in 4 chance to add Foil, Holographic, or Polychrome edition to a random Joker"; 80 | case TAROT_STRENGTH: 81 | return "Increases rank of up to 2 selected cards by 1"; 82 | case TAROT_HANGED_MAN: 83 | return "Destroys up to 2 selected cards"; 84 | case TAROT_DEATH: 85 | return "Select 2 cards, convert the left card into the right card (Move to rearrange)"; 86 | case TAROT_TEMPERANCE: 87 | return "Gives the total sell value of all current Jokers (Max of $50)"; 88 | case TAROT_DEVIL: 89 | return "Enhances 1 selected card into a Gold Card"; 90 | case TAROT_TOWER: 91 | return "Enhances 1 selected card into a Stone Card"; 92 | case TAROT_STAR: 93 | return "Converts up to 3 selected cards to Diamonds"; 94 | case TAROT_MOON: 95 | return "Converts up to 3 selected cards to Clubs"; 96 | case TAROT_SUN: 97 | return "Converts up to 3 selected cards to Hearts"; 98 | case TAROT_JUDGEMENT: 99 | return "Creates a random Joker card (Must have room)"; 100 | case TAROT_WORLD: 101 | return "Converts up to 3 selected cards to Spades"; 102 | } 103 | } 104 | 105 | uint8_t get_tarot_max_selected(Tarot tarot) { 106 | switch (tarot) { 107 | case TAROT_FOOL: 108 | case TAROT_HIGH_PRIESTESS: 109 | case TAROT_EMPEROR: 110 | case TAROT_HERMIT: 111 | case TAROT_WHEEL_OF_FORTUNE: 112 | case TAROT_TEMPERANCE: 113 | case TAROT_JUDGEMENT: 114 | return 0; 115 | 116 | case TAROT_LOVERS: 117 | case TAROT_CHARIOT: 118 | case TAROT_JUSTICE: 119 | case TAROT_DEVIL: 120 | case TAROT_TOWER: 121 | return 1; 122 | 123 | case TAROT_MAGICIAN: 124 | case TAROT_EMPRESS: 125 | case TAROT_HIEROPHANT: 126 | case TAROT_STRENGTH: 127 | case TAROT_HANGED_MAN: 128 | case TAROT_DEATH: 129 | return 2; 130 | 131 | case TAROT_STAR: 132 | case TAROT_MOON: 133 | case TAROT_SUN: 134 | case TAROT_WORLD: 135 | return 3; 136 | } 137 | } 138 | 139 | void tarot_create_consumable(ConsumableType type) { 140 | uint8_t available_space = state.game.consumables.size - cvector_size(state.game.consumables.items); 141 | for (uint8_t i = 0; i < available_space; i++) { 142 | Consumable consumable = {.type = type}; 143 | if (type == CONSUMABLE_PLANET) 144 | consumable.planet = random_filtered_range_pick(0, 11, filter_locked_planet_cards); 145 | else if (type == CONSUMABLE_TAROT) 146 | consumable.tarot = random_max_value(21); 147 | 148 | cvector_push_back(state.game.consumables.items, consumable); 149 | } 150 | } 151 | 152 | void tarot_change_enhancement(Card **selected_cards, uint8_t selected_count, Enhancement new_enhancement) { 153 | for (uint8_t i = 0; i < selected_count; i++) { 154 | cvector_for_each(state.game.full_deck, Card, card) { 155 | if (compare_cards(selected_cards[i], card)) { 156 | card->enhancement = new_enhancement; 157 | card->was_played = 0; 158 | selected_cards[i]->enhancement = new_enhancement; 159 | selected_cards[i]->was_played = 0; 160 | break; 161 | } 162 | } 163 | } 164 | } 165 | 166 | void tarot_change_suit(Card **selected_cards, uint8_t selected_count, Suit new_suit) { 167 | for (uint8_t i = 0; i < selected_count; i++) { 168 | cvector_for_each(state.game.full_deck, Card, card) { 169 | if (compare_cards(selected_cards[i], card)) { 170 | card->suit = new_suit; 171 | card->was_played = 0; 172 | selected_cards[i]->suit = new_suit; 173 | selected_cards[i]->was_played = 0; 174 | break; 175 | } 176 | } 177 | } 178 | } 179 | 180 | bool filter_non_edition_jokers(uint8_t i) { return state.game.jokers.cards[i].edition == EDITION_BASE; } 181 | 182 | uint8_t use_tarot_card(Tarot tarot) { 183 | Card *selected_cards[3] = {0}; 184 | uint8_t selected_count = 0; 185 | 186 | cvector_for_each(state.game.hand.cards, Card, card) { 187 | if (card->selected == 0) continue; 188 | 189 | selected_cards[selected_count] = card; 190 | selected_count++; 191 | if (selected_count > 3) return 0; 192 | } 193 | 194 | uint8_t max_selected_count = get_tarot_max_selected(tarot); 195 | if ((selected_count == 0 && max_selected_count != 0) || 196 | (max_selected_count != 0 && selected_count > max_selected_count) || 197 | (tarot == TAROT_DEATH && selected_count != max_selected_count)) 198 | return 0; 199 | 200 | switch (tarot) { 201 | case TAROT_FOOL: 202 | if (cvector_size(state.game.consumables.items) >= state.game.consumables.size || 203 | state.game.fool_last_used.was_used == 0 || 204 | (state.game.fool_last_used.consumable.type == CONSUMABLE_TAROT && 205 | state.game.fool_last_used.consumable.tarot == TAROT_FOOL)) 206 | return 0; 207 | cvector_push_back(state.game.consumables.items, state.game.fool_last_used.consumable); 208 | break; 209 | case TAROT_HERMIT: 210 | if (state.game.money > 0) state.game.money += state.game.money > 20 ? 20 : state.game.money; 211 | break; 212 | case TAROT_WHEEL_OF_FORTUNE: { 213 | int16_t joker_index = random_filtered_vector_pick(state.game.jokers.cards, filter_non_edition_jokers); 214 | if (joker_index == -1) return 0; 215 | if (!random_chance(1, 4)) break; 216 | 217 | // Foil, Holographic, Polychrome 218 | uint16_t edition_weights[] = {50, 35, 15}; 219 | state.game.jokers.cards[joker_index].edition = random_weighted(edition_weights, 3) + 1; 220 | break; 221 | } 222 | case TAROT_STRENGTH: 223 | for (uint8_t i = 0; i < selected_count; i++) { 224 | cvector_for_each(state.game.full_deck, Card, card) { 225 | if (compare_cards(selected_cards[i], card)) { 226 | card->rank = (card->rank + 1) % 13; 227 | selected_cards[i]->rank = card->rank; 228 | break; 229 | } 230 | } 231 | } 232 | break; 233 | case TAROT_HANGED_MAN: 234 | for (uint8_t i = 0; i < selected_count; i++) { 235 | for (size_t j = 0; j < cvector_size(state.game.full_deck); j++) { 236 | if (compare_cards(selected_cards[i], &state.game.full_deck[j])) { 237 | cvector_erase(state.game.full_deck, j); 238 | break; 239 | } 240 | } 241 | 242 | for (uint8_t j = 0; j < cvector_size(state.game.hand.cards); j++) { 243 | if (compare_cards(selected_cards[i], &state.game.hand.cards[j])) { 244 | cvector_erase(state.game.hand.cards, j); 245 | break; 246 | } 247 | } 248 | } 249 | break; 250 | case TAROT_DEATH: 251 | cvector_for_each(state.game.full_deck, Card, card) { 252 | if (compare_cards(selected_cards[0], card)) { 253 | uint8_t is_force_selected = selected_cards[0]->selected == 2; 254 | *(selected_cards[0]) = *(selected_cards[1]); 255 | if (is_force_selected) selected_cards[0]->selected = 2; 256 | *card = *(selected_cards[1]); 257 | card->selected = 0; 258 | break; 259 | } 260 | } 261 | break; 262 | case TAROT_TEMPERANCE: { 263 | uint8_t total = 0; 264 | cvector_for_each(state.game.jokers.cards, Joker, joker) { 265 | total += get_shop_item_sell_price(&(ShopItem){.type = SHOP_ITEM_JOKER, .joker = *joker}); 266 | 267 | if (total >= 50) { 268 | total = 50; 269 | break; 270 | } 271 | } 272 | 273 | state.game.money += total; 274 | break; 275 | } 276 | case TAROT_JUDGEMENT: 277 | if (cvector_size(state.game.jokers.cards) >= state.game.jokers.size) break; 278 | cvector_push_back(state.game.jokers.cards, random_available_joker()); 279 | break; 280 | 281 | case TAROT_HIGH_PRIESTESS: 282 | tarot_create_consumable(CONSUMABLE_PLANET); 283 | break; 284 | case TAROT_EMPEROR: 285 | tarot_create_consumable(CONSUMABLE_TAROT); 286 | break; 287 | 288 | case TAROT_LOVERS: 289 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_WILD); 290 | break; 291 | case TAROT_CHARIOT: 292 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_STEEL); 293 | break; 294 | case TAROT_JUSTICE: 295 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_GLASS); 296 | break; 297 | case TAROT_DEVIL: 298 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_GOLD); 299 | break; 300 | case TAROT_TOWER: 301 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_STONE); 302 | break; 303 | case TAROT_MAGICIAN: 304 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_LUCKY); 305 | break; 306 | case TAROT_EMPRESS: 307 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_MULT); 308 | break; 309 | case TAROT_HIEROPHANT: 310 | tarot_change_enhancement(selected_cards, selected_count, ENHANCEMENT_BONUS); 311 | break; 312 | 313 | case TAROT_STAR: 314 | tarot_change_suit(selected_cards, selected_count, SUIT_DIAMONDS); 315 | break; 316 | case TAROT_MOON: 317 | tarot_change_suit(selected_cards, selected_count, SUIT_CLUBS); 318 | break; 319 | case TAROT_SUN: 320 | tarot_change_suit(selected_cards, selected_count, SUIT_HEARTS); 321 | break; 322 | case TAROT_WORLD: 323 | tarot_change_suit(selected_cards, selected_count, SUIT_SPADES); 324 | break; 325 | } 326 | 327 | if (max_selected_count != 0) deselect_all_cards(); 328 | if (state.stage == STAGE_GAME && state.game.current_blind->type > BLIND_BIG) { 329 | disable_boss_blind(); 330 | enable_boss_blind(); 331 | } 332 | return 1; 333 | } 334 | -------------------------------------------------------------------------------- /system.c: -------------------------------------------------------------------------------- 1 | #include "system.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "game.h" 13 | #include "gfx.h" 14 | #include "state.h" 15 | 16 | static Vertex vertex_buffer[RENDER_BATCH_SIZE]; 17 | static RenderBatch render_batch = {.count = 0}; 18 | 19 | void draw_rectangle(Rect *rect, uint32_t color) { draw_texture(NULL, &(Rect){0, 0, 0, 0}, rect, color, 0); } 20 | 21 | void draw_texture(Texture *texture, Rect *src, Rect *dst, uint32_t color, float angle) { 22 | uint8_t is_angled = (angle != 0); 23 | 24 | if (render_batch.texture != texture || render_batch.is_angled != is_angled || 25 | render_batch.count + 6 > RENDER_BATCH_SIZE) 26 | flush_render_batch(); 27 | 28 | render_batch.texture = texture; 29 | render_batch.is_angled = is_angled; 30 | 31 | if (is_angled) { 32 | float cx = dst->x + dst->w / 2.0f; 33 | float cy = dst->y + dst->h / 2.0f; 34 | 35 | float rad = angle * (M_PI / 180.0f); 36 | float cos_a = cosf(rad); 37 | float sin_a = sinf(rad); 38 | 39 | // Corners of the rectangle before rotation (relative to center) 40 | float dx[4] = {-dst->w / 2.0f, dst->w / 2.0f, dst->w / 2.0f, -dst->w / 2.0f}; 41 | float dy[4] = {-dst->h / 2.0f, -dst->h / 2.0f, dst->h / 2.0f, dst->h / 2.0f}; 42 | 43 | float u[4] = {src->x, src->x + src->w, src->x + src->w, src->x}; 44 | float v[4] = {src->y, src->y, src->y + src->h, src->y + src->h}; 45 | 46 | static const uint8_t idx[6] = {0, 1, 2, 0, 2, 3}; 47 | for (int i = 0; i < 6; ++i) { 48 | int j = idx[i]; 49 | Vertex vertex = {.x = cos_a * dx[j] - sin_a * dy[j] + cx, 50 | .y = sin_a * dx[j] + cos_a * dy[j] + cy, 51 | .z = 0.0f, 52 | .u = u[j], 53 | .v = v[j], 54 | .color = color}; 55 | render_batch.vertices[render_batch.count++] = vertex; 56 | } 57 | return; 58 | } 59 | 60 | Vertex vertex = {.u = src->x, .v = src->y, .color = color, .x = dst->x, .y = dst->y, .z = 0.0f}; 61 | render_batch.vertices[render_batch.count++] = vertex; 62 | 63 | vertex.u += src->w; 64 | vertex.v += src->h; 65 | vertex.x += dst->w; 66 | vertex.y += dst->h; 67 | render_batch.vertices[render_batch.count++] = vertex; 68 | } 69 | 70 | void flush_render_batch() { 71 | if (render_batch.count == 0) return; 72 | Texture *texture = render_batch.texture; 73 | 74 | if (texture != NULL) { 75 | sceGuTexMode(GU_PSM_8888, 0, 0, GU_FALSE); 76 | sceGuTexImage(0, texture->width, texture->height, texture->width, texture->data); 77 | 78 | if (texture == state.bg) { 79 | sceGuTexFilter(GU_LINEAR, GU_LINEAR); 80 | sceGuTexWrap(GU_REPEAT, GU_REPEAT); 81 | } else { 82 | sceGuTexFilter(GU_NEAREST, GU_NEAREST); 83 | } 84 | 85 | sceGuTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA); 86 | sceGuEnable(GU_TEXTURE_2D); 87 | } 88 | 89 | // Copy from RAM buffer to GPU memory 90 | Vertex *gpu_vertices = (Vertex *)sceGuGetMemory(render_batch.count * sizeof(Vertex)); 91 | memcpy(gpu_vertices, render_batch.vertices, render_batch.count * sizeof(Vertex)); 92 | 93 | sceGuEnable(GU_BLEND); 94 | sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); 95 | 96 | sceGuDrawArray(render_batch.is_angled == 0 ? GU_SPRITES : GU_TRIANGLES, 97 | GU_COLOR_8888 | GU_TEXTURE_32BITF | GU_VERTEX_32BITF | GU_TRANSFORM_2D, render_batch.count, 0, 98 | gpu_vertices); 99 | 100 | sceGuDisable(GU_BLEND); 101 | if (texture != NULL) sceGuDisable(GU_TEXTURE_2D); 102 | 103 | render_batch.count = 0; 104 | } 105 | 106 | Texture *load_texture(const char *filename) { 107 | Texture *texture = (Texture *)calloc(1, sizeof(Texture)); 108 | 109 | texture->data = (uint32_t *)stbi_load(filename, &(texture->width), &(texture->height), NULL, STBI_rgb_alpha); 110 | 111 | sceKernelDcacheWritebackInvalidateAll(); 112 | 113 | return texture; 114 | } 115 | 116 | Texture *init_texture(uint32_t width, uint32_t height) { 117 | Texture *texture = (Texture *)calloc(1, sizeof(Texture)); 118 | 119 | texture->width = width; 120 | texture->height = height; 121 | texture->data = guGetStaticVramTexture(width, height, GU_PSM_8888); 122 | 123 | sceKernelDcacheWritebackInvalidateAll(); 124 | 125 | return texture; 126 | } 127 | 128 | uint8_t button_pressed(unsigned int button) { 129 | if ((state.controls.data.Buttons & button) && (state.controls.state & button) == 0) { 130 | return 1; 131 | } 132 | 133 | return 0; 134 | } 135 | 136 | Vector2 draw_text_len(const char *text, uint32_t len, const Vector2 *pos, uint32_t color) { 137 | Rect dst = {.x = pos->x, .y = pos->y, .w = 6, .h = 10}; 138 | Rect src = {.x = 0, .y = 0, .w = 6, .h = 10}; 139 | 140 | uint8_t xOffset = 0; 141 | uint8_t yOffest = 0; 142 | 143 | for (; len > 0 && *text; len--) { 144 | if (*text == ' ') { 145 | dst.x += CHAR_WIDTH; 146 | text++; 147 | continue; 148 | } 149 | 150 | xOffset = 0; 151 | yOffest = 0; 152 | 153 | if (*text >= 'A' && *text <= 'Z') { 154 | xOffset = *text - 'A'; 155 | } else if (*text >= 'a' && *text <= 'z') { 156 | xOffset = *text - 'a'; 157 | yOffest = 2; 158 | } else if (*text >= '0' && *text <= '9') { 159 | xOffset = *text - '0'; 160 | yOffest = 4; 161 | } else if (*text >= '!' && *text <= '/') { 162 | xOffset = *text - '!'; 163 | yOffest = 5; 164 | } else if (*text >= ':' && *text <= '@') { 165 | xOffset = *text - ':'; 166 | yOffest = 7; 167 | } else { 168 | switch (*text) { 169 | case '[': 170 | xOffset = 0; 171 | break; 172 | case ']': 173 | xOffset = 1; 174 | break; 175 | case '{': 176 | xOffset = 2; 177 | break; 178 | case '}': 179 | xOffset = 3; 180 | break; 181 | } 182 | yOffest = 8; 183 | } 184 | 185 | src.x = (xOffset % 13) * CHAR_WIDTH; 186 | src.y = ((uint8_t)(xOffset / 13) + yOffest) * CHAR_HEIGHT; 187 | 188 | draw_texture(state.font, &src, &dst, color, 0); 189 | dst.x += CHAR_WIDTH; 190 | text++; 191 | } 192 | 193 | return (Vector2){.x = dst.x, .y = dst.y}; 194 | } 195 | 196 | Vector2 draw_text(const char *text, const Vector2 *pos, uint32_t color) { 197 | return draw_text_len(text, strlen(text), pos, color); 198 | } 199 | 200 | uint8_t handle_navigation_controls() { 201 | NavigationSection section = get_current_section(); 202 | uint8_t hovered = state.navigation.hovered; 203 | uint8_t section_size = get_nav_section_size(section); 204 | uint8_t is_horizontal = is_nav_section_horizontal(section); 205 | 206 | if (button_pressed(PSP_CTRL_RIGHT)) { 207 | if (is_horizontal && hovered < section_size - 1) 208 | set_nav_hovered(hovered + 1); 209 | else 210 | move_nav_cursor(NAVIGATION_RIGHT); 211 | 212 | return 1; 213 | } else if (button_pressed(PSP_CTRL_LEFT)) { 214 | if (is_horizontal && hovered > 0) 215 | set_nav_hovered(hovered - 1); 216 | else 217 | move_nav_cursor(NAVIGATION_LEFT); 218 | 219 | return 1; 220 | } else if (button_pressed(PSP_CTRL_UP)) { 221 | if (!is_horizontal && hovered > 0) 222 | set_nav_hovered(hovered - 1); 223 | else 224 | move_nav_cursor(NAVIGATION_UP); 225 | 226 | return 1; 227 | } else if (button_pressed(PSP_CTRL_DOWN)) { 228 | if (!is_horizontal && hovered < section_size - 1) 229 | set_nav_hovered(hovered + 1); 230 | else 231 | move_nav_cursor(NAVIGATION_DOWN); 232 | 233 | return 1; 234 | } else if (button_pressed(PSP_CTRL_LTRIGGER)) { 235 | move_nav_hovered(hovered - 1); 236 | return 1; 237 | } else if (button_pressed(PSP_CTRL_RTRIGGER)) { 238 | move_nav_hovered(hovered + 1); 239 | return 1; 240 | } else if (state.stage != STAGE_MAIN_MENU && state.stage != STAGE_SELECT_DECK && button_pressed(PSP_CTRL_START)) { 241 | change_overlay(state.overlay == OVERLAY_NONE ? OVERLAY_MENU : OVERLAY_NONE); 242 | return 1; 243 | } 244 | 245 | if (section == NAVIGATION_CONSUMABLES) { 246 | if (button_pressed(PSP_CTRL_CROSS)) { 247 | use_consumable(NULL); 248 | return 1; 249 | } else if (button_pressed(PSP_CTRL_TRIANGLE)) { 250 | sell_shop_item(); 251 | return 1; 252 | } 253 | } 254 | 255 | if (section == NAVIGATION_JOKERS) { 256 | if (button_pressed(PSP_CTRL_TRIANGLE)) { 257 | sell_shop_item(); 258 | return 1; 259 | } 260 | } 261 | 262 | if (section == NAVIGATION_SELECT_STAKE) { 263 | if (button_pressed(PSP_CTRL_CROSS)) { 264 | game_init(state.prev_navigation.hovered, state.navigation.hovered); 265 | return 1; 266 | } else if (button_pressed(PSP_CTRL_CIRCLE)) { 267 | change_overlay(OVERLAY_NONE); 268 | return 1; 269 | } 270 | } 271 | 272 | if (section == NAVIGATION_OVERLAY_MENU) { 273 | if (button_pressed(PSP_CTRL_CROSS)) { 274 | overlay_menu_button_click(); 275 | return 1; 276 | } 277 | } 278 | 279 | if (state.overlay != OVERLAY_NONE && state.overlay != OVERLAY_MENU && state.overlay != OVERLAY_SELECT_STAKE) { 280 | if (button_pressed(PSP_CTRL_CIRCLE)) { 281 | change_overlay(OVERLAY_MENU); 282 | return 1; 283 | } 284 | } 285 | 286 | return 0; 287 | } 288 | 289 | void handle_controls() { 290 | Controls *controls = &state.controls; 291 | sceCtrlReadBufferPositive(&controls->data, 1); 292 | 293 | if (handle_navigation_controls() == 1 || state.overlay != OVERLAY_NONE) { 294 | state.controls.state = controls->data.Buttons; 295 | update_render_commands(); 296 | return; 297 | } 298 | 299 | switch (state.stage) { 300 | case STAGE_MAIN_MENU: 301 | if (button_pressed(PSP_CTRL_CROSS)) main_menu_button_click(); 302 | break; 303 | 304 | case STAGE_SELECT_DECK: 305 | if (button_pressed(PSP_CTRL_CROSS)) 306 | change_overlay(OVERLAY_SELECT_STAKE); 307 | else if (button_pressed(PSP_CTRL_CIRCLE)) 308 | change_stage(STAGE_MAIN_MENU); 309 | break; 310 | 311 | case STAGE_CREDITS: 312 | if (button_pressed(PSP_CTRL_CIRCLE)) change_stage(STAGE_MAIN_MENU); 313 | break; 314 | 315 | case STAGE_GAME: 316 | if (button_pressed(PSP_CTRL_CROSS)) { 317 | toggle_card_select(state.navigation.hovered); 318 | } else if (button_pressed(PSP_CTRL_SQUARE)) { 319 | play_hand(); 320 | } else if (button_pressed(PSP_CTRL_CIRCLE)) { 321 | deselect_all_cards(); 322 | } else if (button_pressed(PSP_CTRL_TRIANGLE)) { 323 | discard_hand(); 324 | } else if (button_pressed(PSP_CTRL_SELECT)) { 325 | state.game.sorting_mode = state.game.sorting_mode == SORTING_BY_SUIT ? SORTING_BY_RANK : SORTING_BY_SUIT; 326 | sort_hand(); 327 | } 328 | 329 | break; 330 | 331 | case STAGE_CASH_OUT: 332 | if (button_pressed(PSP_CTRL_CROSS)) cash_out(); 333 | break; 334 | 335 | case STAGE_SELECT_BLIND: 336 | if (button_pressed(PSP_CTRL_CROSS)) 337 | select_blind_button_click(); 338 | else if (button_pressed(PSP_CTRL_SQUARE)) 339 | select_blind(); 340 | else if (button_pressed(PSP_CTRL_TRIANGLE)) 341 | skip_blind(); 342 | else if (button_pressed(PSP_CTRL_SELECT)) 343 | trigger_reroll_boss_voucher(); 344 | break; 345 | 346 | case STAGE_SHOP: 347 | if (button_pressed(PSP_CTRL_CIRCLE)) 348 | exit_shop(); 349 | else if (button_pressed(PSP_CTRL_CROSS)) 350 | buy_item(false); 351 | else if (button_pressed(PSP_CTRL_SQUARE)) 352 | buy_item(true); 353 | else if (button_pressed(PSP_CTRL_SELECT)) 354 | reroll_shop_items(); 355 | break; 356 | 357 | case STAGE_BOOSTER_PACK: 358 | if (button_pressed(PSP_CTRL_CIRCLE)) { 359 | skip_booster_pack(); 360 | } else if (button_pressed(PSP_CTRL_CROSS)) { 361 | if (get_current_section() == NAVIGATION_HAND) 362 | toggle_card_select(state.navigation.hovered); 363 | else 364 | select_booster_pack_item(); 365 | } 366 | break; 367 | 368 | case STAGE_GAME_OVER: 369 | if (button_pressed(PSP_CTRL_CROSS)) { 370 | Deck current_deck = state.game.deck_type; 371 | Stake current_stake = state.game.stake; 372 | game_destroy(); 373 | game_init(current_deck, current_stake); 374 | } 375 | break; 376 | } 377 | 378 | controls->state = controls->data.Buttons; 379 | update_render_commands(); 380 | } 381 | 382 | void init_gu(char list[]) { 383 | void *fbp0 = guGetStaticVramBuffer(BUFFER_WIDTH, BUFFER_HEIGHT, GU_PSM_8888); 384 | void *fbp1 = guGetStaticVramBuffer(BUFFER_WIDTH, BUFFER_HEIGHT, GU_PSM_8888); 385 | 386 | sceGuInit(); 387 | 388 | sceGuStart(GU_DIRECT, list); 389 | sceGuDrawBuffer(GU_PSM_8888, fbp0, BUFFER_WIDTH); 390 | sceGuDispBuffer(SCREEN_WIDTH, SCREEN_HEIGHT, fbp1, BUFFER_WIDTH); 391 | 392 | sceGuDepthBuffer(fbp0, 0); 393 | sceGuDisable(GU_DEPTH_TEST); 394 | 395 | sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2)); 396 | sceGuViewport(2048, 2048, SCREEN_WIDTH, SCREEN_HEIGHT); 397 | sceGuEnable(GU_SCISSOR_TEST); 398 | sceGuScissor(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); 399 | 400 | render_batch.vertices = vertex_buffer; 401 | 402 | sceGuFinish(); 403 | sceGuDisplay(GU_TRUE); 404 | } 405 | 406 | void end_gu() { 407 | sceGuDisplay(GU_FALSE); 408 | sceGuTerm(); 409 | } 410 | 411 | void start_frame(char list[]) { 412 | sceGuStart(GU_DIRECT, list); 413 | sceGuClearColor(0xFF000000); 414 | sceGuClear(GU_COLOR_BUFFER_BIT); 415 | 416 | render_batch.count = 0; 417 | } 418 | 419 | void end_frame() { 420 | flush_render_batch(); 421 | 422 | sceGuFinish(); 423 | sceGuSync(0, 0); 424 | sceDisplayWaitVblankStart(); 425 | sceGuSwapBuffers(); 426 | } 427 | 428 | int exit_callback(int arg1, int arg2, void *common) { 429 | state.running = 0; 430 | return 0; 431 | } 432 | 433 | int callback_thread(SceSize args, void *argp) { 434 | int cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL); 435 | sceKernelRegisterExitCallback(cbid); 436 | sceKernelSleepThreadCB(); 437 | return 0; 438 | } 439 | 440 | int setup_callbacks() { 441 | int thid = sceKernelCreateThread("update_thread", callback_thread, 0x11, 0xFA0, 0, 0); 442 | if (thid >= 0) sceKernelStartThread(thid, 0, 0); 443 | return thid; 444 | } 445 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /text.c: -------------------------------------------------------------------------------- 1 | #include "text.h" 2 | 3 | #include "game.h" 4 | #include "state.h" 5 | 6 | char *get_poker_hand_name(uint16_t hand_union) { 7 | switch (get_poker_hand(hand_union)) { 8 | case HAND_FLUSH_FIVE: 9 | return "Flush Five"; 10 | case HAND_FLUSH_HOUSE: 11 | return "Flush House"; 12 | case HAND_FIVE_OF_KIND: 13 | return "Five of Kind"; 14 | case HAND_STRAIGHT_FLUSH: 15 | return "Straight Flush"; 16 | case HAND_FOUR_OF_KIND: 17 | return "Four of Kind"; 18 | case HAND_FULL_HOUSE: 19 | return "Full House"; 20 | case HAND_FLUSH: 21 | return "Flush"; 22 | case HAND_STRAIGHT: 23 | return "Straight"; 24 | case HAND_THREE_OF_KIND: 25 | return "Three of Kind"; 26 | case HAND_TWO_PAIR: 27 | return "Two Pair"; 28 | case HAND_PAIR: 29 | return "Pair"; 30 | case HAND_HIGH_CARD: 31 | return "High Card"; 32 | } 33 | } 34 | 35 | char *get_planet_card_name(Planet planet) { 36 | switch (planet) { 37 | case PLANET_ERIS: 38 | return "Eris"; 39 | case PLANET_CERES: 40 | return "Ceres"; 41 | case PLANET_X: 42 | return "Planet X"; 43 | case PLANET_NEPTUNE: 44 | return "Neptune"; 45 | case PLANET_MARS: 46 | return "Mars"; 47 | case PLANET_EARTH: 48 | return "Earth"; 49 | case PLANET_JUPITER: 50 | return "Jupiter"; 51 | case PLANET_SATURN: 52 | return "Saturn"; 53 | case PLANET_VENUS: 54 | return "Venus"; 55 | case PLANET_URANUS: 56 | return "Uranus"; 57 | case PLANET_MERCURY: 58 | return "Mercury"; 59 | case PLANET_PLUTO: 60 | return "Pluto"; 61 | } 62 | } 63 | 64 | char *get_card_suit_name(Suit suit) { 65 | switch (suit) { 66 | case SUIT_HEARTS: 67 | return "Hearts"; 68 | case SUIT_DIAMONDS: 69 | return "Diamonds"; 70 | case SUIT_SPADES: 71 | return "Spades"; 72 | case SUIT_CLUBS: 73 | return "Clubs"; 74 | } 75 | } 76 | 77 | char *get_card_rank_name(Rank rank) { 78 | switch (rank) { 79 | case RANK_ACE: 80 | return "Ace"; 81 | case RANK_TWO: 82 | return "2"; 83 | case RANK_THREE: 84 | return "3"; 85 | case RANK_FOUR: 86 | return "4"; 87 | case RANK_FIVE: 88 | return "5"; 89 | case RANK_SIX: 90 | return "6"; 91 | case RANK_SEVEN: 92 | return "7"; 93 | case RANK_EIGHT: 94 | return "8"; 95 | case RANK_NINE: 96 | return "9"; 97 | case RANK_TEN: 98 | return "10"; 99 | case RANK_JACK: 100 | return "Jack"; 101 | case RANK_QUEEN: 102 | return "Queen"; 103 | case RANK_KING: 104 | return "King"; 105 | } 106 | } 107 | 108 | Clay_String get_full_card_name(Suit suit, Rank rank) { 109 | Clay_String card_name; 110 | append_clay_string(&card_name, "%s of %s", get_card_rank_name(rank), get_card_suit_name(suit)); 111 | 112 | return card_name; 113 | } 114 | 115 | char *get_booster_pack_size_name(BoosterPackSize size) { 116 | switch (size) { 117 | case BOOSTER_PACK_NORMAL: 118 | return ""; 119 | case BOOSTER_PACK_JUMBO: 120 | return "Jumbo"; 121 | case BOOSTER_PACK_MEGA: 122 | return "Mega"; 123 | } 124 | } 125 | 126 | char *get_booster_pack_type_name(BoosterPackType type) { 127 | switch (type) { 128 | case BOOSTER_PACK_STANDARD: 129 | return "Standard"; 130 | case BOOSTER_PACK_BUFFOON: 131 | return "Buffoon"; 132 | case BOOSTER_PACK_CELESTIAL: 133 | return "Celestial"; 134 | case BOOSTER_PACK_ARCANA: 135 | return "Arcana"; 136 | case BOOSTER_PACK_SPECTRAL: 137 | return "Spectral"; 138 | } 139 | } 140 | 141 | char *get_booster_pack_description_suffix(BoosterPackType type) { 142 | switch (type) { 143 | case BOOSTER_PACK_STANDARD: 144 | return "Playing cards to add to your deck"; 145 | case BOOSTER_PACK_BUFFOON: 146 | return "Joker cards"; 147 | case BOOSTER_PACK_CELESTIAL: 148 | return "Planet cards to be used immediately"; 149 | case BOOSTER_PACK_ARCANA: 150 | return "Tarot cards to be used immediately"; 151 | case BOOSTER_PACK_SPECTRAL: 152 | return "Spectral cards to be used immediately"; 153 | } 154 | } 155 | 156 | Clay_String get_full_booster_pack_name(BoosterPackSize size, BoosterPackType type) { 157 | Clay_String booster_pack_name; 158 | append_clay_string(&booster_pack_name, "%s%s%s Pack", get_booster_pack_size_name(size), 159 | size == BOOSTER_PACK_NORMAL ? "" : " ", get_booster_pack_type_name(type)); 160 | 161 | return booster_pack_name; 162 | } 163 | 164 | char *get_voucher_name(Voucher voucher) { 165 | switch (voucher) { 166 | case VOUCHER_OVERSTOCK: 167 | return "Overstock"; 168 | case VOUCHER_CLEARANCE_SALE: 169 | return "Clearance Sale"; 170 | case VOUCHER_HONE: 171 | return "Hone"; 172 | case VOUCHER_REROLL_SURPLUS: 173 | return "Reroll Surplus"; 174 | case VOUCHER_CRYSTAL_BALL: 175 | return "Crystal Ball"; 176 | case VOUCHER_TELESCOPE: 177 | return "Telescope"; 178 | case VOUCHER_GRABBER: 179 | return "Grabber"; 180 | case VOUCHER_WASTEFUL: 181 | return "Wasteful"; 182 | case VOUCHER_TAROT_MERCHANT: 183 | return "Tarot Merchant"; 184 | case VOUCHER_PLANET_MERCHANT: 185 | return "Planet Merchant"; 186 | case VOUCHER_SEED_MONEY: 187 | return "Seed Money"; 188 | case VOUCHER_BLANK: 189 | return "Blank"; 190 | case VOUCHER_MAGIC_TRICK: 191 | return "Magic Trick"; 192 | case VOUCHER_HIEROGLYPH: 193 | return "Hieroglyph"; 194 | case VOUCHER_DIRECTORS_CUT: 195 | return "Director's Cut"; 196 | case VOUCHER_PAINT_BRUSH: 197 | return "Paint Brush"; 198 | 199 | case VOUCHER_OVERSTOCK_PLUS: 200 | return "Overstock Plus"; 201 | case VOUCHER_LIQUIDATION: 202 | return "Liquidation"; 203 | case VOUCHER_GLOW_UP: 204 | return "Glow Up"; 205 | case VOUCHER_REROLL_GLUT: 206 | return "Reroll Glut"; 207 | case VOUCHER_OMEN_GLOBE: 208 | return "Omen Globe"; 209 | case VOUCHER_OBSERVATORY: 210 | return "Observatory"; 211 | case VOUCHER_NACHO_TONG: 212 | return "Nacho Tong"; 213 | case VOUCHER_RECYCLOMANCY: 214 | return "Recyclomancy"; 215 | case VOUCHER_TAROT_TYCOON: 216 | return "Tarot Tycoon"; 217 | case VOUCHER_PLANET_TYCOON: 218 | return "Planet Tycoon"; 219 | case VOUCHER_MONEY_TREE: 220 | return "Money Tree"; 221 | case VOUCHER_ANTIMATTER: 222 | return "Antimatter"; 223 | case VOUCHER_ILLUSION: 224 | return "Illusion"; 225 | case VOUCHER_PTEROGLYPH: 226 | return "Pteroglyph"; 227 | case VOUCHER_RETCON: 228 | return "Retcon"; 229 | case VOUCHER_PALETTE: 230 | return "Palette"; 231 | } 232 | } 233 | 234 | char *get_voucher_description(Voucher voucher) { 235 | switch (voucher) { 236 | case VOUCHER_OVERSTOCK: 237 | return "+1 card slot available in shop (to 3 slots)"; 238 | case VOUCHER_CLEARANCE_SALE: 239 | return "All cards and packs in shop are 25% off"; 240 | case VOUCHER_HONE: 241 | return "Foil, Holographic, and Polychrome cards appear 2x more often"; 242 | case VOUCHER_REROLL_SURPLUS: 243 | return "Rerolls cost $2 less"; 244 | case VOUCHER_CRYSTAL_BALL: 245 | return "+1 consumable slot"; 246 | case VOUCHER_TELESCOPE: 247 | return "Celestial Packs always contain the Planet card for your most played poker hand"; 248 | case VOUCHER_GRABBER: 249 | return "Permanently gain +1 hand per round"; 250 | case VOUCHER_WASTEFUL: 251 | return "Permanently gain +1 discard each round"; 252 | case VOUCHER_TAROT_MERCHANT: 253 | return "Tarot cards appear 2X more frequently in the shop"; 254 | case VOUCHER_PLANET_MERCHANT: 255 | return "Planet cards appear 2X more frequently in the shop"; 256 | case VOUCHER_SEED_MONEY: 257 | return "Raise the cap on interest earned in each round to $10"; 258 | case VOUCHER_BLANK: 259 | return "Does nothing?"; 260 | case VOUCHER_MAGIC_TRICK: 261 | return "Playing cards can be purchased from the shop"; 262 | case VOUCHER_HIEROGLYPH: 263 | return "-1 Ante, -1 hand each round"; 264 | case VOUCHER_DIRECTORS_CUT: 265 | return "Reroll Boss Blind 1 time per Ante, $10 per roll"; 266 | case VOUCHER_PAINT_BRUSH: 267 | return "+1 Hand Size"; 268 | 269 | case VOUCHER_OVERSTOCK_PLUS: 270 | return "+1 card slot available in shop (to 4 slots)"; 271 | case VOUCHER_LIQUIDATION: 272 | return "All cards and packs in shop are 50% off"; 273 | case VOUCHER_GLOW_UP: 274 | return "Foil, Holographic, and Polychrome cards appear 4x more often"; 275 | case VOUCHER_REROLL_GLUT: 276 | return "Rerolls cost an additional $2 less"; 277 | case VOUCHER_OMEN_GLOBE: 278 | return "Spectral cards may appear in any of the Arcana Packs"; 279 | case VOUCHER_OBSERVATORY: 280 | return "Planet cards in your consumable area give X1.5 Mult for their specified poker hand"; 281 | case VOUCHER_NACHO_TONG: 282 | return "Permanently gain an additional +1 hand per round"; 283 | case VOUCHER_RECYCLOMANCY: 284 | return "Permanently gain an additional +1 discard each round"; 285 | case VOUCHER_TAROT_TYCOON: 286 | return "Tarot cards appear 4X more frequently in the shop"; 287 | case VOUCHER_PLANET_TYCOON: 288 | return "Planet cards appear 4X more frequently in the shop"; 289 | case VOUCHER_MONEY_TREE: 290 | return "Raise the cap on interest earned in each round to $20"; 291 | case VOUCHER_ANTIMATTER: 292 | return "+1 Joker slot"; 293 | case VOUCHER_ILLUSION: 294 | return "Playing cards in shop may have an Enhancement, Edition, and/or a Seal"; 295 | case VOUCHER_PTEROGLYPH: 296 | return "-1 Ante, -1 discard each round"; 297 | case VOUCHER_RETCON: 298 | return "Reroll Boss Blind unlimited times, $10 per roll"; 299 | case VOUCHER_PALETTE: 300 | return "+1 Hand Size again"; 301 | } 302 | } 303 | 304 | char *get_deck_name(Deck deck) { 305 | switch (deck) { 306 | case DECK_RED: 307 | return "Red Deck"; 308 | case DECK_BLUE: 309 | return "Blue Deck"; 310 | case DECK_YELLOW: 311 | return "Yellow Deck"; 312 | case DECK_GREEN: 313 | return "Green Deck"; 314 | case DECK_BLACK: 315 | return "Black Deck"; 316 | case DECK_MAGIC: 317 | return "Magic Deck"; 318 | case DECK_NEBULA: 319 | return "Nebula Deck"; 320 | case DECK_GHOST: 321 | return "Ghost Deck"; 322 | case DECK_ABANDONED: 323 | return "Abandoned Deck"; 324 | case DECK_CHECKERED: 325 | return "Checkered Deck"; 326 | case DECK_ZODIAC: 327 | return "Zodiac Deck"; 328 | case DECK_PAINTED: 329 | return "Painted Deck"; 330 | case DECK_ANAGLYPH: 331 | return "Anaglyph Deck"; 332 | case DECK_PLASMA: 333 | return "Plasma Deck"; 334 | case DECK_ERRATIC: 335 | return "Erratic Deck"; 336 | } 337 | } 338 | 339 | char *get_deck_description(Deck deck) { 340 | switch (deck) { 341 | case DECK_RED: 342 | return "+1 discard every round"; 343 | case DECK_BLUE: 344 | return "+1 hand every round"; 345 | case DECK_YELLOW: 346 | return "Start with extra 10$"; 347 | case DECK_GREEN: 348 | return "At end of each Round: $2 per remaining Hand, $1 per remaining Discard, Earn no Interest"; 349 | case DECK_BLACK: 350 | return "+1 Joker slot, -1 hand every round"; 351 | case DECK_MAGIC: 352 | return "Start run with the Crystal Ball voucher and 2 copies of The Fool"; 353 | case DECK_NEBULA: 354 | return "Start run with the Telescope voucher and -1 consumable slot"; 355 | case DECK_GHOST: 356 | return "Spectral cards may appear in the shop, start with a Hex card"; 357 | case DECK_ABANDONED: 358 | return "Start run with no Face Cards in your deck"; 359 | case DECK_CHECKERED: 360 | return "Start run with 26 Spades and 26 Hearts in deck"; 361 | case DECK_ZODIAC: 362 | return "Start run with Tarot Merchant, Planet Merchant, and Overstock"; 363 | case DECK_PAINTED: 364 | return "+2 hand size, -1 Joker slot"; 365 | case DECK_ANAGLYPH: 366 | return "After defeating each Boss Blind, gain a Double Tag"; 367 | case DECK_PLASMA: 368 | return "Balance Chips and Mult when calculating score for played hand, X2 base Blind size"; 369 | case DECK_ERRATIC: 370 | return "All Ranks and Suits in deck are randomized"; 371 | } 372 | } 373 | 374 | char *get_stake_name(Stake stake) { 375 | switch (stake) { 376 | case STAKE_WHITE: 377 | return "White Stake"; 378 | case STAKE_RED: 379 | return "Red Stake"; 380 | case STAKE_GREEN: 381 | return "Green Stake"; 382 | case STAKE_BLACK: 383 | return "Black Stake NOT IMPLEMENTED"; 384 | case STAKE_BLUE: 385 | return "Blue Stake"; 386 | case STAKE_PURPLE: 387 | return "Purple Stake"; 388 | case STAKE_ORANGE: 389 | return "Orange Stake NOT IMPLEMENTED"; 390 | case STAKE_GOLD: 391 | return "Gold Stake NOT IMPLEMENTED"; 392 | } 393 | } 394 | 395 | char *get_stake_description(Stake stake) { 396 | switch (stake) { 397 | case STAKE_WHITE: 398 | return "Base difficulty"; 399 | case STAKE_RED: 400 | return "Small Blind gives no reward money"; 401 | case STAKE_GREEN: 402 | return "Required score scales faster for each Ante"; 403 | case STAKE_BLACK: 404 | // TODO Implement when stickers will be added 405 | return "30% chance for Jokers in shops or booster packs to have an Eternal sticker (Can't be sold or destroyed)"; 406 | case STAKE_BLUE: 407 | return "-1 Discard"; 408 | case STAKE_PURPLE: 409 | return "Required score scales even faster for each Ante"; 410 | case STAKE_ORANGE: 411 | // TODO Implement when stickers will be added 412 | return "30% chance for Jokers in shops or booster packs to have a Perishable sticker (Debuffed after 5 rounds)"; 413 | case STAKE_GOLD: 414 | // TODO Implement when stickers will be added 415 | return "30% chance for Jokers in shops or booster packs to have a Rental sticker (Costs $3 per round, can be " 416 | "bought for $1)"; 417 | } 418 | } 419 | 420 | char *get_blind_name(BlindType blind_type) { 421 | switch (blind_type) { 422 | case BLIND_SMALL: 423 | return "Small Blind"; 424 | case BLIND_BIG: 425 | return "Big Blind"; 426 | 427 | case BLIND_HOOK: 428 | return "The Hook"; 429 | case BLIND_OX: 430 | return "The Ox"; 431 | case BLIND_HOUSE: 432 | return "The House"; 433 | case BLIND_WALL: 434 | return "The Wall"; 435 | case BLIND_WHEEL: 436 | return "The Wheel"; 437 | case BLIND_ARM: 438 | return "The Arm"; 439 | case BLIND_CLUB: 440 | return "The Club"; 441 | case BLIND_FISH: 442 | return "The Fish"; 443 | case BLIND_PSYCHIC: 444 | return "The Psychic"; 445 | case BLIND_GOAD: 446 | return "The Goad"; 447 | case BLIND_WATER: 448 | return "The Water"; 449 | case BLIND_WINDOW: 450 | return "The Window"; 451 | case BLIND_MANACLE: 452 | return "The Manacle"; 453 | case BLIND_EYE: 454 | return "The Eye"; 455 | case BLIND_MOUTH: 456 | return "The Mouth"; 457 | case BLIND_PLANT: 458 | return "The Plant"; 459 | case BLIND_SERPENT: 460 | return "The Serpent"; 461 | case BLIND_PILLAR: 462 | return "The Pillar"; 463 | case BLIND_NEEDLE: 464 | return "The Needle"; 465 | case BLIND_HEAD: 466 | return "The Head"; 467 | case BLIND_TOOTH: 468 | return "The Tooth"; 469 | case BLIND_FLINT: 470 | return "The Flint"; 471 | case BLIND_MARK: 472 | return "The Mark"; 473 | 474 | case BLIND_AMBER_ACORN: 475 | return "Amber Acorn"; 476 | case BLIND_VERDANT_LEAF: 477 | return "Verdant Leaf"; 478 | case BLIND_VIOLET_VESSEL: 479 | return "Violet Vessel"; 480 | case BLIND_CRIMSON_HEART: 481 | return "Crimson Heart"; 482 | case BLIND_CERULEAN_BELL: 483 | return "Cerulean Bell"; 484 | } 485 | } 486 | 487 | char *get_blind_description(BlindType blind_type) { 488 | switch (blind_type) { 489 | case BLIND_SMALL: 490 | case BLIND_BIG: 491 | return "No special effects"; 492 | 493 | case BLIND_HOOK: 494 | return "Discards 2 random cards held in hand after every played hand"; 495 | case BLIND_OX: 496 | return "Playing your most played hand this run sets money to $0"; 497 | case BLIND_HOUSE: 498 | return "First hand is drawn face down"; 499 | case BLIND_WALL: 500 | return "Extra large blind"; 501 | case BLIND_WHEEL: 502 | return "1 in 7 cards get drawn face down during that round"; 503 | case BLIND_ARM: 504 | return "Decrease level of played poker hand by 1"; 505 | case BLIND_CLUB: 506 | return "All Club cards are debuffed"; 507 | case BLIND_FISH: 508 | return "Cards drawn face down after each hand played"; 509 | case BLIND_PSYCHIC: 510 | return "Must play 5 cards (not all cards need to score)"; 511 | case BLIND_GOAD: 512 | return "All Spade cards are debuffed"; 513 | case BLIND_WATER: 514 | return "Start with 0 discards"; 515 | case BLIND_WINDOW: 516 | return "All Diamond cards are debuffed"; 517 | case BLIND_MANACLE: 518 | return "-1 Hand Size"; 519 | case BLIND_EYE: 520 | return "No repeat hand types this round"; 521 | case BLIND_MOUTH: 522 | return "Only one hand type can be played this round"; 523 | case BLIND_PLANT: 524 | return "All face cards are debuffed"; 525 | case BLIND_SERPENT: 526 | return "After Play or Discard always draw 3 cards (ignores hand size)"; 527 | case BLIND_PILLAR: 528 | return "Cards played previously this Ante are debuffed"; 529 | case BLIND_NEEDLE: 530 | return "Play only 1 hand"; 531 | case BLIND_HEAD: 532 | return "All Heart cards are debuffed"; 533 | case BLIND_TOOTH: 534 | return "Lose $1 per card played"; 535 | case BLIND_FLINT: 536 | return "Base Chips and Mult for played poker hands are halved for the entire round"; 537 | case BLIND_MARK: 538 | return "All face cards are drawn face down"; 539 | 540 | case BLIND_AMBER_ACORN: 541 | return "Flips and shuffles all Joker cards"; 542 | case BLIND_VERDANT_LEAF: 543 | return "All cards debuffed until 1 Joker sold"; 544 | case BLIND_VIOLET_VESSEL: 545 | return "Very large blind"; 546 | case BLIND_CRIMSON_HEART: 547 | return "One random Joker disabled every hand (changes every hand)"; 548 | case BLIND_CERULEAN_BELL: 549 | return "Forces 1 card to always be selected"; 550 | } 551 | } 552 | 553 | char *get_tag_name(Tag tag) { 554 | switch (tag) { 555 | case TAG_UNCOMMON: 556 | // TODO Implement properly when uncommon jokers and rng utilities will be added 557 | return "Uncommon PARTIALLY IMPLEMENTED"; 558 | case TAG_RARE: 559 | // TODO Implement properly when rare jokers and rng utilities will be added 560 | return "Rare PARTIALLY IMPLEMENTED"; 561 | case TAG_NEGATIVE: 562 | return "Negative"; 563 | case TAG_FOIL: 564 | return "Foil"; 565 | case TAG_HOLOGRAPHIC: 566 | return "Holographic"; 567 | case TAG_POLYCHROME: 568 | return "Polychrome"; 569 | case TAG_INVESTMENT: 570 | return "Investment"; 571 | case TAG_VOUCHER: 572 | return "Voucher"; 573 | case TAG_BOSS: 574 | return "Boss"; 575 | case TAG_STANDARD: 576 | return "Standard"; 577 | case TAG_CHARM: 578 | return "Charm"; 579 | case TAG_METEOR: 580 | return "Meteor"; 581 | case TAG_BUFFOON: 582 | return "Buffoon"; 583 | case TAG_HANDY: 584 | return "Handy"; 585 | case TAG_GARBAGE: 586 | return "Garbage"; 587 | case TAG_ETHEREAL: 588 | return "Ethereal"; 589 | case TAG_COUPON: 590 | return "Coupon"; 591 | case TAG_DOUBLE: 592 | return "Double"; 593 | case TAG_JUGGLE: 594 | return "Juggle"; 595 | case TAG_D6: 596 | return "D6"; 597 | case TAG_TOPUP: 598 | return "Top-up"; 599 | case TAG_SPEED: 600 | return "Speed"; 601 | case TAG_ORBITAL: 602 | return "Orbital"; 603 | case TAG_ECONOMY: 604 | return "Economy"; 605 | } 606 | } 607 | 608 | char *get_tag_description(Tag tag) { 609 | switch (tag) { 610 | case TAG_UNCOMMON: 611 | return "The next shop will have a free Uncommon Joker."; 612 | case TAG_RARE: 613 | return "The next shop will have a free Rare Joker."; 614 | case TAG_NEGATIVE: 615 | return "The next base edition Joker you find in a shop becomes Negative (+1 joker slot) and free."; 616 | case TAG_FOIL: 617 | return "The next base edition Joker you find in a shop becomes Foil (+50 chips) and free."; 618 | case TAG_HOLOGRAPHIC: 619 | return "The next base edition Joker you find in a shop becomes Holographic (+10 mult) and free."; 620 | case TAG_POLYCHROME: 621 | return "The next base edition Joker you find in a shop becomes Polychrome (X1.5 mult) and free"; 622 | case TAG_INVESTMENT: 623 | return "Gain $25 after defeating the next Boss Blind."; 624 | case TAG_VOUCHER: 625 | return "Adds a Voucher to the next Shop."; 626 | case TAG_BOSS: 627 | return "Re-rolls the next Boss Blind."; 628 | case TAG_STANDARD: 629 | return "Immediately open a free Mega Standard Pack."; 630 | case TAG_CHARM: 631 | return "Immediately open a free Mega Arcana Pack."; 632 | case TAG_METEOR: 633 | return "Immediately open a free Mega Celestial Pack."; 634 | case TAG_BUFFOON: 635 | return "Immediately open a free Mega Buffoon Pack."; 636 | case TAG_HANDY: 637 | return "Gain $1 for each hand played this run."; 638 | case TAG_GARBAGE: 639 | return "Gain $1 for each unused discard this run."; 640 | case TAG_ETHEREAL: 641 | return "Immediately open a free Spectral Pack."; 642 | case TAG_COUPON: 643 | return "In the next shop, initial Jokers, Consumables, Cards and Booster Packs are free ($0)."; 644 | case TAG_DOUBLE: 645 | return "Gives a copy of the next Tag selected (excluding Double Tags)."; 646 | case TAG_JUGGLE: 647 | return "+3 Hand Size for the next round only."; 648 | case TAG_D6: 649 | return "In the next Shop, Rerolls start at $0."; 650 | case TAG_TOPUP: 651 | return "Create up to 2 Common Jokers (if you have space)."; 652 | case TAG_SPEED: 653 | return "Gives $5 for each Blind you've skipped this run."; 654 | case TAG_ORBITAL: 655 | return "Upgrades specified random Poker Hand by three levels."; 656 | case TAG_ECONOMY: 657 | return "Doubles your money (adds a maximum of $40)."; 658 | } 659 | } 660 | -------------------------------------------------------------------------------- /lib/cvector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Evan Teran 3 | * 4 | * License: The MIT License (MIT) 5 | */ 6 | 7 | #ifndef CVECTOR_H_ 8 | #define CVECTOR_H_ 9 | 10 | /* cvector heap implemented using C library malloc() */ 11 | 12 | /* in case C library malloc() needs extra protection, 13 | * allow these defines to be overridden. 14 | */ 15 | #ifndef cvector_clib_free 16 | #include /* for free */ 17 | #define cvector_clib_free free 18 | #endif 19 | #ifndef cvector_clib_malloc 20 | #include /* for malloc */ 21 | #define cvector_clib_malloc malloc 22 | #endif 23 | #ifndef cvector_clib_calloc 24 | #include /* for calloc */ 25 | #define cvector_clib_calloc calloc 26 | #endif 27 | #ifndef cvector_clib_realloc 28 | #include /* for realloc */ 29 | #define cvector_clib_realloc realloc 30 | #endif 31 | #ifndef cvector_clib_assert 32 | #include /* for assert */ 33 | #define cvector_clib_assert assert 34 | #endif 35 | #ifndef cvector_clib_memcpy 36 | #include /* for memcpy */ 37 | #define cvector_clib_memcpy memcpy 38 | #endif 39 | #ifndef cvector_clib_memmove 40 | #include /* for memmove */ 41 | #define cvector_clib_memmove memmove 42 | #endif 43 | #include /* for size_t, ptrdiff_t */ 44 | 45 | /* NOTE: Similar to C's qsort and bsearch, you will receive a T* 46 | * for a vector of Ts. This means that you cannot use `free` directly 47 | * as a destructor. Instead if you have for example a cvector_vector_type(int *) 48 | * you will need to supply a function which casts `elem_ptr` to an `int**` 49 | * and then does a free on what that pointer points to: 50 | * 51 | * ex: 52 | * 53 | * void free_int(void *p) { free(*(int **)p); } 54 | */ 55 | typedef void (*cvector_elem_destructor_t)(void *elem_ptr); 56 | 57 | typedef struct cvector_metadata_t { 58 | size_t size; 59 | size_t capacity; 60 | cvector_elem_destructor_t elem_destructor; 61 | } cvector_metadata_t; 62 | 63 | /** 64 | * @brief cvector_vector_type - The vector type used in this library 65 | */ 66 | #define cvector_vector_type(type) type * 67 | 68 | /** 69 | * @brief cvector - Syntactic sugar to retrieve a vector type 70 | * 71 | * @param type The type of vector to act on. 72 | */ 73 | #define cvector(type) cvector_vector_type(type) 74 | 75 | /* 76 | * @brief cvector_iterator - The iterator type used for cvector 77 | */ 78 | #define cvector_iterator(type) cvector_vector_type(type) 79 | 80 | /** 81 | * @brief cvector_vec_to_base - For internal use, converts a vector pointer to a 82 | * metadata pointer 83 | * @param vec - the vector 84 | * @return the metadata pointer of the vector 85 | * @internal 86 | */ 87 | #define cvector_vec_to_base(vec) (&((cvector_metadata_t *)(vec))[-1]) 88 | 89 | /** 90 | * @brief cvector_base_to_vec - For internal use, converts a metadata pointer to 91 | * a vector pointer 92 | * @param ptr - pointer to the metadata 93 | * @return the vector 94 | * @internal 95 | */ 96 | #define cvector_base_to_vec(ptr) ((void *)&((cvector_metadata_t *)(ptr))[1]) 97 | 98 | /** 99 | * @brief cvector_capacity - gets the current capacity of the vector 100 | * @param vec - the vector 101 | * @return the capacity as a size_t 102 | */ 103 | #define cvector_capacity(vec) ((vec) ? cvector_vec_to_base(vec)->capacity : (size_t)0) 104 | 105 | /** 106 | * @brief cvector_size - gets the current size of the vector 107 | * @param vec - the vector 108 | * @return the size as a size_t 109 | */ 110 | #define cvector_size(vec) ((vec) ? cvector_vec_to_base(vec)->size : (size_t)0) 111 | 112 | /** 113 | * @brief cvector_elem_destructor - get the element destructor function used 114 | * to clean up elements 115 | * @param vec - the vector 116 | * @return the function pointer as cvector_elem_destructor_t 117 | */ 118 | #define cvector_elem_destructor(vec) ((vec) ? cvector_vec_to_base(vec)->elem_destructor : NULL) 119 | 120 | /** 121 | * @brief cvector_empty - returns non-zero if the vector is empty 122 | * @param vec - the vector 123 | * @return non-zero if empty, zero if non-empty 124 | */ 125 | #define cvector_empty(vec) (cvector_size(vec) == 0) 126 | 127 | /** 128 | * @brief cvector_reserve - Requests that the vector capacity be at least enough 129 | * to contain n elements. If n is greater than the current vector capacity, the 130 | * function causes the container to reallocate its storage increasing its 131 | * capacity to n (or greater). 132 | * @param vec - the vector 133 | * @param n - Minimum capacity for the vector. 134 | * @return void 135 | */ 136 | #define cvector_reserve(vec, n) \ 137 | do { \ 138 | size_t cv_cap__ = cvector_capacity(vec); \ 139 | if (cv_cap__ < (n)) { \ 140 | cvector_grow((vec), (n)); \ 141 | } \ 142 | } while (0) 143 | 144 | /* 145 | * @brief cvector_init - Initialize a vector. The vector must be NULL for this 146 | * to do anything. 147 | * @param vec - the vector 148 | * @param capacity - vector capacity to reserve 149 | * @param elem_destructor_fn - element destructor function 150 | * @return void 151 | */ 152 | #define cvector_init(vec, capacity, elem_destructor_fn) \ 153 | do { \ 154 | if (!(vec)) { \ 155 | cvector_reserve((vec), capacity); \ 156 | cvector_set_elem_destructor((vec), (elem_destructor_fn)); \ 157 | } \ 158 | } while (0) 159 | 160 | /** 161 | * @brief cvector_erase - removes the element at index i from the vector 162 | * @param vec - the vector 163 | * @param i - index of element to remove 164 | * @return void 165 | */ 166 | #define cvector_erase(vec, i) \ 167 | do { \ 168 | if (vec) { \ 169 | const size_t cv_sz__ = cvector_size(vec); \ 170 | if ((i) < cv_sz__) { \ 171 | cvector_elem_destructor_t elem_destructor__ = cvector_elem_destructor(vec); \ 172 | if (elem_destructor__) { \ 173 | elem_destructor__(&(vec)[i]); \ 174 | } \ 175 | cvector_set_size((vec), cv_sz__ - 1); \ 176 | cvector_clib_memmove((vec) + (i), (vec) + (i) + 1, sizeof(*(vec)) * (cv_sz__ - 1 - (i))); \ 177 | } \ 178 | } \ 179 | } while (0) 180 | 181 | /** 182 | * @brief cvector_clear - erase all of the elements in the vector 183 | * @param vec - the vector 184 | * @return void 185 | */ 186 | #define cvector_clear(vec) \ 187 | do { \ 188 | if (vec) { \ 189 | cvector_elem_destructor_t elem_destructor__ = cvector_elem_destructor(vec); \ 190 | if (elem_destructor__) { \ 191 | size_t i__; \ 192 | for (i__ = 0; i__ < cvector_size(vec); ++i__) { \ 193 | elem_destructor__(&(vec)[i__]); \ 194 | } \ 195 | } \ 196 | cvector_set_size(vec, 0); \ 197 | } \ 198 | } while (0) 199 | 200 | /** 201 | * @brief cvector_free - frees all memory associated with the vector 202 | * @param vec - the vector 203 | * @return void 204 | */ 205 | #define cvector_free(vec) \ 206 | do { \ 207 | if (vec) { \ 208 | void *p1__ = cvector_vec_to_base(vec); \ 209 | cvector_elem_destructor_t elem_destructor__ = cvector_elem_destructor(vec); \ 210 | if (elem_destructor__) { \ 211 | size_t i__; \ 212 | for (i__ = 0; i__ < cvector_size(vec); ++i__) { \ 213 | elem_destructor__(&(vec)[i__]); \ 214 | } \ 215 | } \ 216 | cvector_clib_free(p1__); \ 217 | } \ 218 | } while (0) 219 | 220 | /** 221 | * @brief cvector_begin - returns an iterator to first element of the vector 222 | * @param vec - the vector 223 | * @return a pointer to the first element (or NULL) 224 | */ 225 | #define cvector_begin(vec) (vec) 226 | 227 | /** 228 | * @brief cvector_end - returns an iterator to one past the last element of the 229 | * vector 230 | * @param vec - the vector 231 | * @return a pointer to one past the last element (or NULL) 232 | */ 233 | #define cvector_end(vec) ((vec) ? &((vec)[cvector_size(vec)]) : NULL) 234 | 235 | /* user request to use logarithmic growth algorithm */ 236 | #ifdef CVECTOR_LOGARITHMIC_GROWTH 237 | 238 | /** 239 | * @brief cvector_compute_next_grow - returns an the computed size in next 240 | * vector grow size is increased by multiplication of 2 241 | * @param size - current size 242 | * @return size after next vector grow 243 | */ 244 | #define cvector_compute_next_grow(size) ((size) ? ((size) << 1) : 1) 245 | 246 | #else 247 | 248 | /** 249 | * @brief cvector_compute_next_grow - returns an the computed size in next 250 | * vector grow size is increased by 1 251 | * @param size - current size 252 | * @return size after next vector grow 253 | */ 254 | #define cvector_compute_next_grow(size) ((size) + 1) 255 | 256 | #endif /* CVECTOR_LOGARITHMIC_GROWTH */ 257 | 258 | /** 259 | * @brief cvector_push_back - adds an element to the end of the vector 260 | * @param vec - the vector 261 | * @param value - the value to add 262 | * @return void 263 | */ 264 | #define cvector_push_back(vec, value) \ 265 | do { \ 266 | size_t cv_cap__ = cvector_capacity(vec); \ 267 | if (cv_cap__ <= cvector_size(vec)) { \ 268 | cvector_grow((vec), cvector_compute_next_grow(cv_cap__)); \ 269 | } \ 270 | (vec)[cvector_size(vec)] = (value); \ 271 | cvector_set_size((vec), cvector_size(vec) + 1); \ 272 | } while (0) 273 | 274 | /** 275 | * @brief cvector_insert - insert element at position pos to the vector 276 | * @param vec - the vector 277 | * @param pos - position in the vector where the new elements are inserted. 278 | * @param val - value to be copied (or moved) to the inserted elements. 279 | * @return void 280 | */ 281 | #define cvector_insert(vec, pos, val) \ 282 | do { \ 283 | size_t cv_cap__ = cvector_capacity(vec); \ 284 | if (cv_cap__ <= cvector_size(vec)) { \ 285 | cvector_grow((vec), cvector_compute_next_grow(cv_cap__)); \ 286 | } \ 287 | if ((pos) < cvector_size(vec)) { \ 288 | cvector_clib_memmove((vec) + (pos) + 1, (vec) + (pos), sizeof(*(vec)) * ((cvector_size(vec)) - (pos))); \ 289 | } \ 290 | (vec)[(pos)] = (val); \ 291 | cvector_set_size((vec), cvector_size(vec) + 1); \ 292 | } while (0) 293 | 294 | /** 295 | * @brief cvector_pop_back - removes the last element from the vector 296 | * @param vec - the vector 297 | * @return void 298 | */ 299 | #define cvector_pop_back(vec) \ 300 | do { \ 301 | cvector_elem_destructor_t elem_destructor__ = cvector_elem_destructor(vec); \ 302 | if (elem_destructor__) { \ 303 | elem_destructor__(&(vec)[cvector_size(vec) - 1]); \ 304 | } \ 305 | cvector_set_size((vec), cvector_size(vec) - 1); \ 306 | } while (0) 307 | 308 | /** 309 | * @brief cvector_copy - copy a vector 310 | * @param from - the original vector 311 | * @param to - destination to which the function copy to 312 | * @return void 313 | */ 314 | #define cvector_copy(from, to) \ 315 | do { \ 316 | if ((from)) { \ 317 | cvector_grow(to, cvector_size(from)); \ 318 | cvector_set_size(to, cvector_size(from)); \ 319 | cvector_clib_memcpy((to), (from), cvector_size(from) * sizeof(*(from))); \ 320 | } \ 321 | } while (0) 322 | 323 | /** 324 | * @brief cvector_swap - exchanges the content of the vector by the content of 325 | * another vector of the same type 326 | * @param vec - the original vector 327 | * @param other - the other vector to swap content with 328 | * @param type - the type of both vectors 329 | * @return void 330 | */ 331 | #define cvector_swap(vec, other, type) \ 332 | do { \ 333 | if (vec && other) { \ 334 | cvector_vector_type(type) cv_swap__ = vec; \ 335 | vec = other; \ 336 | other = cv_swap__; \ 337 | } \ 338 | } while (0) 339 | 340 | /** 341 | * @brief cvector_set_capacity - For internal use, sets the capacity variable of 342 | * the vector 343 | * @param vec - the vector 344 | * @param size - the new capacity to set 345 | * @return void 346 | * @internal 347 | */ 348 | #define cvector_set_capacity(vec, size) \ 349 | do { \ 350 | if (vec) { \ 351 | cvector_vec_to_base(vec)->capacity = (size); \ 352 | } \ 353 | } while (0) 354 | 355 | /** 356 | * @brief cvector_set_size - For internal use, sets the size variable of the 357 | * vector 358 | * @param vec - the vector 359 | * @param size - the new capacity to set 360 | * @return void 361 | * @internal 362 | */ 363 | #define cvector_set_size(vec, _size) \ 364 | do { \ 365 | if (vec) { \ 366 | cvector_vec_to_base(vec)->size = (_size); \ 367 | } \ 368 | } while (0) 369 | 370 | /** 371 | * @brief cvector_set_elem_destructor - set the element destructor function 372 | * used to clean up removed elements. The vector must NOT be NULL for this to do 373 | * anything. 374 | * @param vec - the vector 375 | * @param elem_destructor_fn - function pointer of type 376 | * cvector_elem_destructor_t used to destroy elements 377 | * @return void 378 | */ 379 | #define cvector_set_elem_destructor(vec, elem_destructor_fn) \ 380 | do { \ 381 | if (vec) { \ 382 | cvector_vec_to_base(vec)->elem_destructor = (elem_destructor_fn); \ 383 | } \ 384 | } while (0) 385 | 386 | /** 387 | * @brief cvector_grow - For internal use, ensures that the vector is at least 388 | * elements big 389 | * @param vec - the vector 390 | * @param count - the new capacity to set 391 | * @return void 392 | * @internal 393 | */ 394 | #define cvector_grow(vec, count) \ 395 | do { \ 396 | const size_t cv_sz__ = (count) * sizeof(*(vec)) + sizeof(cvector_metadata_t); \ 397 | if (vec) { \ 398 | void *cv_p1__ = cvector_vec_to_base(vec); \ 399 | void *cv_p2__ = cvector_clib_realloc(cv_p1__, cv_sz__); \ 400 | cvector_clib_assert(cv_p2__); \ 401 | (vec) = cvector_base_to_vec(cv_p2__); \ 402 | } else { \ 403 | void *cv_p__ = cvector_clib_malloc(cv_sz__); \ 404 | cvector_clib_assert(cv_p__); \ 405 | (vec) = cvector_base_to_vec(cv_p__); \ 406 | cvector_set_size((vec), 0); \ 407 | cvector_set_elem_destructor((vec), NULL); \ 408 | } \ 409 | cvector_set_capacity((vec), (count)); \ 410 | } while (0) 411 | 412 | /** 413 | * @brief cvector_shrink_to_fit - requests the container to reduce its capacity 414 | * to fit its size 415 | * @param vec - the vector 416 | * @return void 417 | */ 418 | #define cvector_shrink_to_fit(vec) \ 419 | do { \ 420 | if (vec) { \ 421 | const size_t cv_sz___ = cvector_size(vec); \ 422 | cvector_grow(vec, cv_sz___); \ 423 | } \ 424 | } while (0) 425 | 426 | /** 427 | * @brief cvector_at - returns a reference to the element at position n in the 428 | * vector. 429 | * @param vec - the vector 430 | * @param n - position of an element in the vector. 431 | * @return the element at the specified position in the vector. 432 | */ 433 | #define cvector_at(vec, n) ((vec)[(cvector_clib_assert((vec) && (ptrdiff_t)(n) < (ptrdiff_t)cvector_size(vec)), (n))]) 434 | 435 | /** 436 | * @brief cvector_front - returns a reference to the first element in the 437 | * vector. Unlike member cvector_begin, which returns an iterator to this same 438 | * element, this function returns a direct reference. 439 | * @return a reference to the first element in the vector container. 440 | */ 441 | #define cvector_front(vec) (cvector_at(vec, 0)) 442 | 443 | /** 444 | * @brief cvector_back - returns a reference to the last element in the 445 | * vector.Unlike member cvector_end, which returns an iterator just past this 446 | * element, this function returns a direct reference. 447 | * @return a reference to the last element in the vector. 448 | */ 449 | #define cvector_back(vec) (cvector_at(vec, cvector_size(vec) - 1)) 450 | 451 | /** 452 | * @brief cvector_resize - resizes the container to contain count elements. 453 | * @param vec - the vector 454 | * @param count - new size of the vector 455 | * @param value - the value to initialize new elements with 456 | * @return void 457 | */ 458 | #define cvector_resize(vec, count, value) \ 459 | do { \ 460 | if (vec) { \ 461 | size_t cv_sz_count__ = (size_t)(count); \ 462 | size_t cv_sz__ = cvector_vec_to_base(vec)->size; \ 463 | if (cv_sz_count__ > cv_sz__) { \ 464 | cvector_reserve((vec), cv_sz_count__); \ 465 | cvector_set_size((vec), cv_sz_count__); \ 466 | do { \ 467 | (vec)[cv_sz__++] = (value); \ 468 | } while (cv_sz__ < cv_sz_count__); \ 469 | } else { \ 470 | while (cv_sz_count__ < cv_sz__--) { \ 471 | cvector_pop_back(vec); \ 472 | } \ 473 | } \ 474 | } \ 475 | } while (0) 476 | 477 | /** 478 | * BELOW CODE IS NOT PART OF THE ORIGINAL LIBRARY 479 | * 480 | * It was written by myself 481 | */ 482 | 483 | /** 484 | * @brief cvector_for_each - loops over each element inside vector. 485 | * @param vec - the vector 486 | * @param type - type of vector element 487 | * @param it - iterator name 488 | * @return void 489 | */ 490 | #define cvector_for_each(vec, type, it) \ 491 | for (cvector_iterator(type) it = cvector_begin(vec); it != cvector_end(vec); it++) 492 | 493 | /** 494 | * @brief cvector_destroy - frees all memory associated with the vector and sets vec pointer to NULL 495 | * @param vec - the vector 496 | * @return void 497 | */ 498 | #define cvector_destroy(vec) \ 499 | cvector_free(vec); \ 500 | vec = NULL; 501 | 502 | #endif /* CVECTOR_H_ */ 503 | -------------------------------------------------------------------------------- /gfx.c: -------------------------------------------------------------------------------- 1 | #include "gfx.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "content/joker.h" 10 | #include "game.h" 11 | #include "renderer.h" 12 | #include "state.h" 13 | #include "system.h" 14 | #include "text.h" 15 | #include "utils.h" 16 | 17 | void update_render_commands() { 18 | Clay_BeginLayout(); 19 | 20 | CLAY({.id = CLAY_ID("Container"), .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}}}) { 21 | if (state.stage != STAGE_GAME_OVER && state.stage >= STAGE_GAME) render_sidebar(); 22 | 23 | CLAY({.id = CLAY_ID("Content"), 24 | .layout = { 25 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 26 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 27 | .padding = {.top = 8, .left = 8, .right = 8, .bottom = 0}, 28 | }}) { 29 | if (state.stage != STAGE_GAME_OVER && state.stage >= STAGE_GAME) render_topbar(); 30 | 31 | switch (state.stage) { 32 | case STAGE_MAIN_MENU: 33 | render_main_menu(); 34 | break; 35 | case STAGE_SELECT_DECK: 36 | render_select_deck(); 37 | break; 38 | case STAGE_CREDITS: 39 | render_credits(); 40 | break; 41 | 42 | case STAGE_GAME: 43 | render_hand(); 44 | break; 45 | case STAGE_CASH_OUT: 46 | render_cash_out(); 47 | break; 48 | case STAGE_SHOP: 49 | render_shop(); 50 | break; 51 | case STAGE_SELECT_BLIND: 52 | render_select_blind(); 53 | break; 54 | case STAGE_GAME_OVER: 55 | render_game_over(); 56 | break; 57 | case STAGE_BOOSTER_PACK: 58 | render_booster_pack_content(); 59 | break; 60 | } 61 | } 62 | } 63 | 64 | switch (state.overlay) { 65 | case OVERLAY_NONE: 66 | break; 67 | case OVERLAY_MENU: 68 | render_overlay_menu(); 69 | break; 70 | case OVERLAY_SELECT_STAKE: 71 | render_overlay_select_stake(); 72 | break; 73 | case OVERLAY_POKER_HANDS: 74 | render_overlay_poker_hands(); 75 | break; 76 | } 77 | 78 | state.render_commands = Clay_EndLayout(); 79 | } 80 | 81 | const Clay_String main_menu_buttons[] = {CLAY_STRING("Play"), CLAY_STRING("Credits"), CLAY_STRING("Quit")}; 82 | 83 | void render_main_menu() { 84 | CLAY({.layout = { 85 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 86 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 87 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 88 | .padding = CLAY_PADDING_ALL(16), 89 | }}) { 90 | CLAY({.layout = { 91 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 92 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_BOTTOM}, 93 | }}) { 94 | CLAY({.layout = {.sizing = {CLAY_SIZING_FIXED(256), CLAY_SIZING_FIXED(64)}}, 95 | .image = {.imageData = state.logo}}) {} 96 | } 97 | 98 | CLAY({.id = CLAY_ID("MainMenuButtons"), 99 | .layout = { 100 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 101 | .childGap = 4, 102 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_BOTTOM}, 103 | }}) { 104 | for (uint8_t i = 0; i < sizeof(main_menu_buttons) / sizeof(main_menu_buttons[0]); i++) { 105 | CLAY({.id = CLAY_IDI_LOCAL("Button", i + 1), 106 | .backgroundColor = state.navigation.hovered == i ? COLOR_CHIPS : COLOR_MULT, 107 | .layout = {.padding = CLAY_PADDING_ALL(4)}}) { 108 | CLAY_TEXT(main_menu_buttons[i], WHITE_TEXT_CONFIG); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | void render_select_deck() { 116 | CLAY({.layout = { 117 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 118 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 119 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 120 | .childGap = 8, 121 | }}) { 122 | CLAY({.layout = {.sizing = {CLAY_SIZING_FIXED(256), CLAY_SIZING_FIXED(64)}}, .image = {.imageData = state.logo}}) {} 123 | 124 | CLAY({.layout = {.sizing = {CLAY_SIZING_PERCENT(0.5), CLAY_SIZING_FIT(0)}, 125 | .padding = CLAY_PADDING_ALL(8), 126 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 127 | .childGap = 8}, 128 | .backgroundColor = COLOR_CARD_BG}) { 129 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, .childGap = 4}}) { 130 | CustomElementData *deck_icon = frame_arena_allocate(sizeof(CustomElementData)); 131 | *deck_icon = (CustomElementData){.type = CUSTOM_ELEMENT_DECK, .deck = state.navigation.hovered}; 132 | CLAY({.custom = deck_icon, 133 | .layout = {.sizing = {CLAY_SIZING_FIXED(CARD_WIDTH), CLAY_SIZING_FIXED(CARD_HEIGHT)}}}) {} 134 | 135 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 136 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 137 | .childGap = 4}}) { 138 | Deck deck = get_current_section() == NAVIGATION_SELECT_DECK ? state.navigation.hovered 139 | : state.prev_navigation.hovered; 140 | Clay_String deck_name; 141 | append_clay_string(&deck_name, "%s", get_deck_name(deck)); 142 | 143 | Clay_String deck_description; 144 | append_clay_string(&deck_description, "%s", get_deck_description(deck)); 145 | 146 | CLAY_TEXT(deck_name, WHITE_TEXT_CONFIG); 147 | CLAY_TEXT(deck_description, WHITE_TEXT_CONFIG); 148 | } 149 | } 150 | } 151 | } 152 | } 153 | 154 | void render_credits() { 155 | CLAY({.layout = { 156 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 157 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 158 | }}) { 159 | CLAY({.layout = {.sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_FIT(0)}, 160 | .padding = CLAY_PADDING_ALL(16), 161 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 162 | .childGap = 8}, 163 | .backgroundColor = COLOR_CARD_BG}) { 164 | CLAY({.layout = {.padding = {.bottom = 8}}}) { 165 | CLAY_TEXT(CLAY_STRING("Following software libraries and assets are utilized in the creation of this game:"), 166 | WHITE_TEXT_CONFIG); 167 | } 168 | 169 | CLAY_TEXT(CLAY_STRING("c-vector by Evan Teran is licensed under MIT"), WHITE_TEXT_CONFIG); 170 | CLAY_TEXT(CLAY_STRING("Clay by Nic Barker is licensed under zlib"), WHITE_TEXT_CONFIG); 171 | CLAY_TEXT(CLAY_STRING("Poker cards asset pack by IvoryRed is licensed under CC-BY-4.0 / Changed placement of " 172 | "sprites, removed backgrounds, added custom elements"), 173 | WHITE_TEXT_CONFIG); 174 | CLAY_TEXT(CLAY_STRING("Pixel Bitmap Fonts by frostyfreeze is licensed under CC0"), WHITE_TEXT_CONFIG); 175 | CLAY_TEXT(CLAY_STRING("Logo by Grzybson4 is licensed under CC-BY-4.0"), WHITE_TEXT_CONFIG); 176 | } 177 | } 178 | } 179 | 180 | void render_atlas_sprite(Texture *atlas, Vector2 *sprite_index, Rect *dst) { 181 | float angle = 3.0f * sinf(state.time * 0.75f - dst->x / SCREEN_WIDTH * M_PI * 3); 182 | Rect src = {.x = sprite_index->x * CARD_WIDTH, .y = sprite_index->y * CARD_HEIGHT, .w = CARD_WIDTH, .h = CARD_HEIGHT}; 183 | 184 | draw_texture(atlas, &src, dst, 0xFFFFFFFF, angle); 185 | } 186 | 187 | void render_card_atlas_sprite(Vector2 *sprite_index, Rect *dst) { 188 | render_atlas_sprite(state.cards_atlas, sprite_index, dst); 189 | } 190 | 191 | void render_edition(Edition edition, Rect *dst) { 192 | Vector2 src = {.x = 5 + edition - 1, .y = 3}; 193 | if (edition != EDITION_BASE) render_card_atlas_sprite(&src, dst); 194 | } 195 | 196 | void render_card(Card *card, Rect *dst) { 197 | if (card->status & CARD_STATUS_FACE_DOWN) { 198 | render_card_atlas_sprite(&(Vector2){3, 7}, dst); 199 | return; 200 | } 201 | if (card->status & CARD_STATUS_DEBUFFED) { 202 | render_card_atlas_sprite(&(Vector2){3, 1}, dst); 203 | return; 204 | } 205 | 206 | Vector2 background = {.x = 9, .y = 7}; 207 | if (card->enhancement != ENHANCEMENT_NONE) { 208 | uint8_t enhancement_offset = card->enhancement - 1; 209 | background.x = 5 + enhancement_offset % 4; 210 | background.y = 5 + 2 * floorf(enhancement_offset / 4.0f); 211 | } 212 | render_card_atlas_sprite(&background, dst); 213 | 214 | Vector2 face = {.x = card->rank % 10, .y = 2 * card->suit + floorf(card->rank / 10.0f)}; 215 | if (card->enhancement != ENHANCEMENT_STONE) render_card_atlas_sprite(&face, dst); 216 | 217 | render_edition(card->edition, dst); 218 | 219 | Vector2 seal = {.x = 5 + card->seal - 1, .y = 1}; 220 | if (card->seal != SEAL_NONE) render_card_atlas_sprite(&seal, dst); 221 | } 222 | 223 | void render_joker(Joker *joker, Rect *dst) { 224 | if (joker->status & CARD_STATUS_FACE_DOWN) { 225 | render_card_atlas_sprite(&(Vector2){3, 7}, dst); 226 | return; 227 | } 228 | if (joker->status & CARD_STATUS_DEBUFFED) { 229 | render_card_atlas_sprite(&(Vector2){3, 1}, dst); 230 | return; 231 | } 232 | 233 | // Lowest Joker ID is 1 234 | uint8_t index = (joker->id - 1) % 80; 235 | Vector2 sprite = {.x = index % 10, .y = floorf(index / 10.0f)}; 236 | Texture *atlas = joker->id <= 80 ? state.jokers_atlas1 : state.jokers_atlas2; 237 | 238 | render_atlas_sprite(atlas, &sprite, dst); 239 | render_edition(joker->edition, dst); 240 | } 241 | 242 | void render_consumable(Consumable *consumable, Rect *dst) { 243 | Vector2 index = {.x = 4, .y = 0}; 244 | switch (consumable->type) { 245 | case CONSUMABLE_PLANET: 246 | index.y = 5; 247 | break; 248 | case CONSUMABLE_TAROT: 249 | index.y = 1; 250 | break; 251 | 252 | case CONSUMABLE_SPECTRAL: 253 | index.y = 7; 254 | break; 255 | } 256 | 257 | render_card_atlas_sprite(&index, dst); 258 | } 259 | 260 | void render_voucher(Voucher voucher, Rect *dst) { render_card_atlas_sprite(&(Vector2){9, 7}, dst); } 261 | 262 | void render_booster_pack(BoosterPackItem *booster_pack, Rect *dst) { 263 | render_card_atlas_sprite(&(Vector2){.x = 4, .y = 3}, dst); 264 | } 265 | 266 | void render_deck(Deck deck, Rect *dst) { render_card_atlas_sprite(&(Vector2){.x = 3, .y = 3}, dst); } 267 | 268 | void render_tooltip(Clay_String *title, Clay_String *description, float y_offset, 269 | Clay_FloatingAttachPoints *attach_points, Clay_String *other_description) { 270 | CLAY({.id = CLAY_ID("Tooltip"), 271 | .floating = { 272 | .attachTo = CLAY_ATTACH_TO_PARENT, 273 | .zIndex = 10, 274 | .offset = {.y = y_offset}, 275 | .attachPoints = *attach_points, 276 | }}) { 277 | CLAY({.backgroundColor = COLOR_CARD_BG, 278 | .layout = { 279 | .padding = CLAY_PADDING_ALL(4), 280 | .childGap = 2, 281 | .sizing = {CLAY_SIZING_GROW(0, 100), CLAY_SIZING_GROW(0)}, 282 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 283 | }}) { 284 | CLAY_TEXT(*title, WHITE_TEXT_CONFIG); 285 | CLAY_TEXT(*description, WHITE_TEXT_CONFIG); 286 | 287 | if (other_description) CLAY_TEXT(*other_description, CLAY_TEXT_CONFIG({.textColor = COLOR_CARD_LIGHT_BG})); 288 | } 289 | } 290 | } 291 | 292 | void render_spread_items(NavigationSection section, Clay_String parent_id) { 293 | uint8_t item_count = get_nav_section_size(section); 294 | if (item_count == 0) return; 295 | size_t items_width = item_count * CARD_WIDTH; 296 | 297 | for (uint8_t i = 0; i < item_count; i++) { 298 | float parent_width = Clay_GetElementData(CLAY_SID(parent_id)).boundingBox.width - 2 * SECTION_PADDING; 299 | CustomElementData *element = frame_arena_allocate(sizeof(CustomElementData)); 300 | *element = create_spread_item_element(section, i); 301 | 302 | float x_offset = 0; 303 | if (item_count == 1) 304 | x_offset = (float)(parent_width - CARD_WIDTH) / 2; 305 | else if (items_width <= parent_width) 306 | x_offset = (float)(parent_width - items_width) / (item_count + 1) * (i + 1) + CARD_WIDTH * i; 307 | else 308 | x_offset = i * (float)(parent_width - CARD_WIDTH) / (item_count - 1); 309 | 310 | x_offset += SECTION_PADDING; 311 | 312 | float y_offset = 0; 313 | if (section == NAVIGATION_HAND && state.game.hand.cards[i].selected > 0) y_offset = -40; 314 | 315 | uint8_t is_hovered = state.navigation.hovered == i && get_current_section() == section; 316 | 317 | CLAY({.id = CLAY_SIDI(parent_id, i + 1), 318 | .floating = { 319 | .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, 320 | .parentId = CLAY_SID(parent_id).id, 321 | .offset = {.x = x_offset, .y = y_offset}, 322 | .zIndex = is_hovered ? 2 : 1, 323 | .attachPoints = {.parent = CLAY_ATTACH_POINT_LEFT_CENTER, .element = CLAY_ATTACH_POINT_LEFT_CENTER}, 324 | }}) { 325 | float scale = is_hovered ? 1.2 : 1; 326 | 327 | CLAY({.custom = {.customData = element}, 328 | .layout = { 329 | .sizing = {CLAY_SIZING_FIXED(scale * CARD_WIDTH), CLAY_SIZING_FIXED(scale * CARD_HEIGHT)}, 330 | }}) { 331 | uint8_t is_shop = section == NAVIGATION_SHOP_ITEMS || section == NAVIGATION_SHOP_BOOSTER_PACKS || 332 | section == NAVIGATION_SHOP_VOUCHER; 333 | if (element->type == CUSTOM_ELEMENT_CARD && element->card.status & CARD_STATUS_FACE_DOWN || 334 | element->type == CUSTOM_ELEMENT_JOKER && element->joker.status & CARD_STATUS_FACE_DOWN) 335 | continue; 336 | 337 | if (is_hovered) { 338 | Clay_String name; 339 | Clay_String description; 340 | get_nav_item_tooltip_content(&name, &description, section); 341 | 342 | float y_offset = -4; 343 | Clay_FloatingAttachPoints attach_points = {.parent = CLAY_ATTACH_POINT_CENTER_TOP, 344 | .element = CLAY_ATTACH_POINT_CENTER_BOTTOM}; 345 | 346 | if (section == NAVIGATION_JOKERS || section == NAVIGATION_CONSUMABLES) { 347 | y_offset *= -1; 348 | attach_points.parent = CLAY_ATTACH_POINT_CENTER_BOTTOM; 349 | attach_points.element = CLAY_ATTACH_POINT_CENTER_TOP; 350 | } 351 | 352 | Clay_String *other_description = NULL; 353 | if (element->type == CUSTOM_ELEMENT_JOKER && element->joker.get_scaling_description) { 354 | Clay_String scaling_description; 355 | other_description = &scaling_description; 356 | element->joker.get_scaling_description(&element->joker, other_description); 357 | } 358 | 359 | render_tooltip(&name, &description, y_offset, &attach_points, other_description); 360 | } 361 | 362 | if (!is_shop) continue; 363 | 364 | CLAY({.id = CLAY_ID_LOCAL("Price"), 365 | .floating = {.attachTo = CLAY_ATTACH_TO_PARENT, 366 | .offset = {.y = CHAR_HEIGHT}, 367 | .zIndex = is_hovered ? 10 : 1, 368 | .attachPoints = {.parent = CLAY_ATTACH_POINT_CENTER_TOP, 369 | .element = CLAY_ATTACH_POINT_CENTER_BOTTOM}}}) { 370 | CLAY({.backgroundColor = COLOR_CARD_BG, 371 | .border = {.color = COLOR_MONEY, .width = CLAY_BORDER_ALL(1)}, 372 | .layout = { 373 | .padding = CLAY_PADDING_ALL(4), 374 | }}) { 375 | Clay_String price; 376 | append_clay_string(&price, "$%d", get_item_price(section, i)); 377 | CLAY_TEXT(price, WHITE_TEXT_CONFIG); 378 | } 379 | } 380 | } 381 | } 382 | } 383 | } 384 | 385 | void render_hand() { 386 | const Hand *hand = &state.game.hand; 387 | 388 | CLAY({.id = CLAY_ID("Game"), 389 | .layout = { 390 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 391 | .padding = {.bottom = 8}, 392 | .childAlignment = {CLAY_ALIGN_X_LEFT, CLAY_ALIGN_Y_BOTTOM}, 393 | }}) { 394 | CLAY({.id = CLAY_ID_LOCAL("Bottom"), 395 | .layout = { 396 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT + 2 * SECTION_PADDING)}, 397 | .childGap = 8, 398 | .layoutDirection = CLAY_LEFT_TO_RIGHT, 399 | }}) { 400 | CLAY({.id = CLAY_ID("Hand"), 401 | .backgroundColor = COLOR_SECTION_BG, 402 | .layout = { 403 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 404 | }}) {} 405 | 406 | CLAY({ 407 | .id = CLAY_ID_LOCAL("Deck"), 408 | .layout = {.sizing = {CLAY_SIZING_FIXED(CARD_WIDTH), CLAY_SIZING_GROW(0)}, 409 | .childAlignment = {CLAY_ALIGN_X_RIGHT, CLAY_ALIGN_Y_BOTTOM}}, 410 | }) { 411 | Clay_String deck; 412 | append_clay_string(&deck, "Deck %zu/%zu", cvector_size(state.game.deck), cvector_size(state.game.full_deck)); 413 | 414 | CLAY_TEXT(deck, CLAY_TEXT_CONFIG({.textColor = COLOR_WHITE, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); 415 | } 416 | } 417 | } 418 | 419 | render_spread_items(NAVIGATION_HAND, CLAY_STRING("Hand")); 420 | } 421 | 422 | void render_topbar() { 423 | CLAY({.id = CLAY_ID("Topbar"), 424 | .layout = { 425 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT + 2 * SECTION_PADDING)}, 426 | .childGap = 4, 427 | }}) { 428 | CLAY({.id = CLAY_ID("Jokers"), 429 | .backgroundColor = COLOR_SECTION_BG, 430 | .layout = { 431 | .sizing = {CLAY_SIZING_PERCENT(0.7), CLAY_SIZING_GROW(0)}, 432 | .childGap = 8, 433 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 434 | }}) { 435 | CLAY({.id = CLAY_ID_LOCAL("Size"), 436 | .floating = { 437 | .attachTo = CLAY_ATTACH_TO_PARENT, 438 | .attachPoints = {.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM, .element = CLAY_ATTACH_POINT_LEFT_TOP}, 439 | }}) { 440 | Clay_String size; 441 | append_clay_string(&size, "%d/%d", cvector_size(state.game.jokers.cards), state.game.jokers.size); 442 | 443 | CLAY_TEXT(size, WHITE_TEXT_CONFIG); 444 | } 445 | } 446 | 447 | CLAY({.id = CLAY_ID("Consumables"), 448 | .backgroundColor = COLOR_SECTION_BG, 449 | .layout = { 450 | .sizing = {CLAY_SIZING_PERCENT(0.3), CLAY_SIZING_GROW(0)}, 451 | .childGap = 8, 452 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 453 | }}) { 454 | CLAY({.id = CLAY_ID_LOCAL("Size"), 455 | .floating = { 456 | .attachTo = CLAY_ATTACH_TO_PARENT, 457 | .attachPoints = {.parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM, .element = CLAY_ATTACH_POINT_RIGHT_TOP}, 458 | }}) { 459 | Clay_String size; 460 | append_clay_string(&size, "%d/%d", cvector_size(state.game.consumables.items), state.game.consumables.size); 461 | 462 | CLAY_TEXT(size, WHITE_TEXT_CONFIG); 463 | } 464 | } 465 | } 466 | 467 | render_spread_items(NAVIGATION_JOKERS, CLAY_STRING("Jokers")); 468 | render_spread_items(NAVIGATION_CONSUMABLES, CLAY_STRING("Consumables")); 469 | } 470 | 471 | const Clay_ElementDeclaration sidebar_block_config = { 472 | .backgroundColor = COLOR_CARD_LIGHT_BG, 473 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 474 | .padding = {.top = SIDEBAR_GAP, .left = SIDEBAR_GAP, .right = SIDEBAR_GAP, .bottom = SIDEBAR_GAP}, 475 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 476 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}}; 477 | 478 | void render_sidebar() { 479 | CLAY({.id = CLAY_ID("Sidebar"), 480 | .layout = {.sizing = {CLAY_SIZING_FIXED(SIDEBAR_WIDTH), CLAY_SIZING_GROW(0)}, 481 | .padding = CLAY_PADDING_ALL(SIDEBAR_GAP), 482 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 483 | .childGap = SIDEBAR_GAP}, 484 | .backgroundColor = COLOR_CARD_BG}) { 485 | CLAY({.id = CLAY_ID("Stage"), 486 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 487 | .padding = CLAY_PADDING_ALL(SIDEBAR_GAP), 488 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}, 489 | .backgroundColor = COLOR_MONEY}) { 490 | Clay_String stage; 491 | if (state.stage == STAGE_GAME) 492 | append_clay_string(&stage, "%s", get_blind_name(state.game.current_blind->type)); 493 | else if (state.stage == STAGE_SELECT_BLIND) 494 | append_clay_string(&stage, "Choose your next Blind"); 495 | else 496 | append_clay_string(&stage, "SHOP"); 497 | 498 | CLAY_TEXT(stage, CLAY_TEXT_CONFIG({.textAlignment = CLAY_TEXT_ALIGN_CENTER, .textColor = COLOR_WHITE})); 499 | } 500 | 501 | if (state.stage == STAGE_GAME) { 502 | CLAY(sidebar_block_config) { 503 | CLAY_TEXT(CLAY_STRING("Score at least:"), 504 | CLAY_TEXT_CONFIG({.textColor = COLOR_WHITE, .wrapMode = CLAY_TEXT_WRAP_NONE})); 505 | Clay_String required_score; 506 | append_clay_string(&required_score, "%.0lf", 507 | get_required_score(state.game.ante, state.game.current_blind->type)); 508 | 509 | CLAY_TEXT(required_score, CLAY_TEXT_CONFIG({.textColor = {255, 63, 52, 255}})); 510 | } 511 | } 512 | 513 | CLAY({.id = CLAY_ID("Score"), 514 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 515 | .padding = CLAY_PADDING_ALL(SIDEBAR_GAP), 516 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 517 | .childGap = SIDEBAR_GAP}, 518 | .backgroundColor = sidebar_block_config.backgroundColor}) { 519 | CLAY({.id = CLAY_ID_LOCAL("RoundScore"), 520 | .layout = {.sizing = {CLAY_SIZING_FIXED(5 * CHAR_WIDTH), CLAY_SIZING_GROW(0)}, 521 | .childAlignment = {CLAY_ALIGN_X_LEFT, CLAY_ALIGN_Y_CENTER}}}) { 522 | CLAY_TEXT(CLAY_STRING("Round score"), WHITE_TEXT_CONFIG); 523 | } 524 | 525 | CLAY({.id = CLAY_ID_LOCAL("ScoreValue"), 526 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 527 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}}) { 528 | Clay_String score; 529 | append_clay_string(&score, "%.0lf", state.game.score); 530 | 531 | CLAY_TEXT(score, CLAY_TEXT_CONFIG({.textColor = {255, 63, 52, 255}})); 532 | } 533 | } 534 | 535 | Clay_ElementDeclaration sidebar_block_gap = sidebar_block_config; 536 | sidebar_block_gap.layout.childGap = SIDEBAR_GAP; 537 | 538 | CLAY(sidebar_block_gap) { 539 | Clay_String hand; 540 | if (state.game.selected_hand.count != 0) 541 | append_clay_string(&hand, "%s (%d)", get_poker_hand_name(state.game.selected_hand.hand_union), 542 | get_poker_hand_stats(state.game.selected_hand.hand_union)->level + 1); 543 | CLAY_TEXT(state.game.selected_hand.count == 0 ? CLAY_STRING(" ") 544 | : is_poker_hand_unknown() ? CLAY_STRING("???") 545 | : hand, 546 | CLAY_TEXT_CONFIG({.textColor = COLOR_WHITE, .wrapMode = CLAY_TEXT_WRAP_NONE})); 547 | 548 | CLAY({.id = CLAY_ID_LOCAL("Score"), 549 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 550 | .childGap = SIDEBAR_GAP, 551 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}}) { 552 | CLAY({.id = CLAY_ID_LOCAL("Chips"), 553 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 554 | .padding = CLAY_PADDING_ALL(SIDEBAR_GAP), 555 | .childAlignment = {CLAY_ALIGN_X_RIGHT, CLAY_ALIGN_Y_CENTER}}, 556 | .backgroundColor = COLOR_CHIPS}) { 557 | Clay_String chips; 558 | append_clay_string(&chips, "%d", state.game.selected_hand.score_pair.chips); 559 | 560 | CLAY_TEXT(state.game.selected_hand.count == 0 ? CLAY_STRING(" ") 561 | : is_poker_hand_unknown() ? CLAY_STRING("?") 562 | : chips, 563 | WHITE_TEXT_CONFIG); 564 | } 565 | 566 | CLAY_TEXT(CLAY_STRING("x"), WHITE_TEXT_CONFIG); 567 | 568 | CLAY({.id = CLAY_ID_LOCAL("Mult"), 569 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 570 | .padding = CLAY_PADDING_ALL(SIDEBAR_GAP), 571 | .childAlignment = {CLAY_ALIGN_X_LEFT, CLAY_ALIGN_Y_CENTER}}, 572 | .backgroundColor = COLOR_MULT}) { 573 | Clay_String mult; 574 | append_clay_string(&mult, "%0.lf", state.game.selected_hand.score_pair.mult); 575 | 576 | CLAY_TEXT(state.game.selected_hand.count == 0 ? CLAY_STRING(" ") 577 | : is_poker_hand_unknown() ? CLAY_STRING("?") 578 | : mult, 579 | WHITE_TEXT_CONFIG); 580 | } 581 | } 582 | } 583 | 584 | CLAY(sidebar_block_config) { 585 | CLAY_TEXT(CLAY_STRING("Hands"), WHITE_TEXT_CONFIG); 586 | 587 | Clay_String hands; 588 | append_clay_string(&hands, "%d", state.game.hands.remaining); 589 | CLAY_TEXT(hands, WHITE_TEXT_CONFIG); 590 | } 591 | 592 | CLAY(sidebar_block_config) { 593 | CLAY_TEXT(CLAY_STRING("Discards"), WHITE_TEXT_CONFIG); 594 | 595 | Clay_String discards; 596 | append_clay_string(&discards, "%d", state.game.discards.remaining); 597 | CLAY_TEXT(discards, WHITE_TEXT_CONFIG); 598 | } 599 | 600 | CLAY(sidebar_block_config) { 601 | CLAY_TEXT(CLAY_STRING("Money"), WHITE_TEXT_CONFIG); 602 | 603 | Clay_String money; 604 | append_clay_string(&money, "$%d", state.game.money); 605 | CLAY_TEXT(money, WHITE_TEXT_CONFIG); 606 | } 607 | 608 | CLAY({.id = CLAY_ID("RoundInfo"), 609 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, .childGap = SIDEBAR_GAP}}) { 610 | CLAY(sidebar_block_config) { 611 | CLAY_TEXT(CLAY_STRING("Ante"), WHITE_TEXT_CONFIG); 612 | 613 | Clay_String ante; 614 | append_clay_string(&ante, "%d/8", state.game.ante); 615 | CLAY_TEXT(ante, WHITE_TEXT_CONFIG); 616 | } 617 | 618 | CLAY(sidebar_block_config) { 619 | CLAY_TEXT(CLAY_STRING("Round"), WHITE_TEXT_CONFIG); 620 | 621 | Clay_String round; 622 | append_clay_string(&round, "%d", state.game.round); 623 | CLAY_TEXT(round, WHITE_TEXT_CONFIG); 624 | } 625 | } 626 | }; 627 | } 628 | 629 | Clay_ElementDeclaration card_element_config(Clay_ElementId id) { 630 | return (Clay_ElementDeclaration){.id = id, 631 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 632 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_BOTTOM}, 633 | .padding = {.top = 16, .left = 16, .right = 16, .bottom = 0}}}; 634 | }; 635 | 636 | Clay_ElementDeclaration card_content_config() { 637 | return (Clay_ElementDeclaration){.id = CLAY_ID_LOCAL("Content"), 638 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 639 | .padding = CLAY_PADDING_ALL(16), 640 | .childGap = 8, 641 | .layoutDirection = CLAY_TOP_TO_BOTTOM}, 642 | .backgroundColor = COLOR_CARD_BG}; 643 | } 644 | 645 | void render_shop() { 646 | CLAY(card_element_config(CLAY_ID("Shop"))) { 647 | CLAY(card_content_config()) { 648 | CLAY({.id = CLAY_ID("ShopTop"), 649 | .layout = { 650 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}, 651 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 652 | .childGap = 8, 653 | }}) { 654 | CLAY({ 655 | .id = CLAY_ID("RerollButton"), 656 | .backgroundColor = COLOR_MONEY, 657 | .layout = {.padding = CLAY_PADDING_ALL(4)}, 658 | }) { 659 | Clay_String reroll_text; 660 | append_clay_string(&reroll_text, "Reroll $%d", get_reroll_price()); 661 | CLAY_TEXT(reroll_text, WHITE_TEXT_CONFIG); 662 | } 663 | 664 | CLAY({.id = CLAY_ID("ShopItems"), 665 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}}}) {} 666 | render_spread_items(NAVIGATION_SHOP_ITEMS, CLAY_STRING("ShopItems")); 667 | } 668 | 669 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}}}) {} 670 | 671 | CLAY({.id = CLAY_ID("ShopBottom"), 672 | .layout = { 673 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}, 674 | }}) { 675 | CLAY({.id = CLAY_ID("ShopVoucher"), 676 | .layout = {.sizing = CLAY_SIZING_PERCENT(0.3), CLAY_SIZING_FIXED(CARD_HEIGHT)}}) {} 677 | render_spread_items(NAVIGATION_SHOP_VOUCHER, CLAY_STRING("ShopVoucher")); 678 | 679 | CLAY({.id = CLAY_ID("ShopBoosterPacks"), 680 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}}}) {} 681 | render_spread_items(NAVIGATION_SHOP_BOOSTER_PACKS, CLAY_STRING("ShopBoosterPacks")); 682 | } 683 | } 684 | } 685 | } 686 | 687 | void render_booster_pack_content() { 688 | CLAY({.id = CLAY_ID("BoosterPack"), 689 | .layout = { 690 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 691 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 692 | .childGap = 8, 693 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_BOTTOM}, 694 | }}) { 695 | if (state.game.booster_pack.item.type == BOOSTER_PACK_ARCANA || 696 | state.game.booster_pack.item.type == BOOSTER_PACK_SPECTRAL) { 697 | CLAY({.id = CLAY_ID("BoosterPackHand"), 698 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}}}) {} 699 | render_spread_items(NAVIGATION_HAND, CLAY_STRING("BoosterPackHand")); 700 | } 701 | 702 | CLAY({.id = CLAY_ID("BoosterPackItems"), 703 | .layout = { 704 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(CARD_HEIGHT)}, 705 | }}) {} 706 | 707 | CLAY({.id = CLAY_ID_LOCAL("Name"), 708 | .layout = {.sizing = CLAY_SIZING_FIT(0), CLAY_SIZING_FIT(0), .padding = CLAY_PADDING_ALL(4)}, 709 | .backgroundColor = COLOR_CARD_BG}) { 710 | Clay_String name = 711 | get_full_booster_pack_name(state.game.booster_pack.item.size, state.game.booster_pack.item.type); 712 | CLAY_TEXT(name, WHITE_TEXT_CONFIG); 713 | } 714 | } 715 | 716 | render_spread_items(NAVIGATION_BOOSTER_PACK, CLAY_STRING("BoosterPackItems")); 717 | } 718 | 719 | void render_cash_out() { 720 | CLAY(card_element_config(CLAY_ID("Cashout"))) { 721 | CLAY(card_content_config()) { 722 | uint8_t interest = get_interest_money(); 723 | uint8_t hands = get_hands_money(); 724 | uint8_t discards = get_discards_money(); 725 | uint8_t blind = get_blind_money(state.game.current_blind->type); 726 | uint8_t investment_tag = get_investment_tag_money(); 727 | 728 | CLAY({.layout = { 729 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 730 | .childAlignment = {CLAY_ALIGN_X_CENTER}, 731 | }}) { 732 | Clay_String cash_out_text; 733 | append_clay_string(&cash_out_text, "Cash Out: $%d", interest + hands + discards + blind + investment_tag); 734 | CLAY_TEXT(cash_out_text, CLAY_TEXT_CONFIG({.textColor = COLOR_MONEY})); 735 | } 736 | 737 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(1)}}, .backgroundColor = COLOR_WHITE}) {} 738 | 739 | CLAY({.layout = {.layoutDirection = CLAY_TOP_TO_BOTTOM}}) { 740 | if (blind != 0) { 741 | Clay_String blind_money; 742 | append_clay_string(&blind_money, "Blind: $%d", blind); 743 | CLAY_TEXT(blind_money, WHITE_TEXT_CONFIG); 744 | } 745 | 746 | if (hands != 0) { 747 | Clay_String hands_money; 748 | append_clay_string(&hands_money, "Hands: $%d", hands); 749 | CLAY_TEXT(hands_money, WHITE_TEXT_CONFIG); 750 | } 751 | 752 | if (discards != 0) { 753 | Clay_String discards_money; 754 | append_clay_string(&discards_money, "Discards: $%d", discards); 755 | CLAY_TEXT(discards_money, WHITE_TEXT_CONFIG); 756 | } 757 | 758 | if (interest != 0) { 759 | Clay_String interest_money; 760 | append_clay_string(&interest_money, "Interest: $%d", interest); 761 | CLAY_TEXT(interest_money, WHITE_TEXT_CONFIG); 762 | } 763 | 764 | if (investment_tag != 0) { 765 | Clay_String investment_tag_money; 766 | append_clay_string(&investment_tag_money, "Investment Tag: $%d", investment_tag); 767 | CLAY_TEXT(investment_tag_money, WHITE_TEXT_CONFIG); 768 | } 769 | } 770 | } 771 | } 772 | } 773 | 774 | void render_blind_element(uint8_t blind_index) { 775 | Blind *blind = &state.game.blinds[blind_index]; 776 | uint8_t is_current_blind = blind == state.game.current_blind; 777 | uint8_t is_current_section = get_current_section() == NAVIGATION_SELECT_BLIND; 778 | 779 | CLAY({.id = CLAY_IDI_LOCAL("Blind", blind_index), 780 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_PERCENT(is_current_blind ? 1.0f : 0.85f)}, 781 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_TOP}, 782 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 783 | .childGap = 4, 784 | .padding = CLAY_PADDING_ALL(8)}, 785 | .backgroundColor = COLOR_CARD_BG}) { 786 | uint8_t is_select_button_hovered = is_current_section && state.navigation.hovered == 0; 787 | 788 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0)}, 789 | .padding = {.top = 4, .bottom = 4}, 790 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}, 791 | .backgroundColor = 792 | is_current_blind ? is_select_button_hovered ? COLOR_CHIPS : COLOR_MONEY : COLOR_CARD_LIGHT_BG}) { 793 | CLAY_TEXT(is_current_blind ? CLAY_STRING("Select") 794 | : !blind->is_active ? CLAY_STRING("Skipped") 795 | : blind < state.game.current_blind ? CLAY_STRING("Defeated") 796 | : CLAY_STRING("Upcoming"), 797 | WHITE_TEXT_CONFIG); 798 | 799 | if (is_current_blind && is_select_button_hovered && state.game.current_blind->type > BLIND_BIG) { 800 | Clay_String blind_name; 801 | append_clay_string(&blind_name, "%s", get_blind_name(state.game.current_blind->type)); 802 | 803 | Clay_String blind_description; 804 | append_clay_string(&blind_description, "%s", get_blind_description(state.game.current_blind->type)); 805 | 806 | render_tooltip(&blind_name, &blind_description, -4, 807 | &(Clay_FloatingAttachPoints){.parent = CLAY_ATTACH_POINT_CENTER_TOP, 808 | .element = CLAY_ATTACH_POINT_CENTER_BOTTOM}, 809 | NULL); 810 | } 811 | } 812 | 813 | Clay_String blind_name; 814 | append_clay_string(&blind_name, "%s", get_blind_name(blind->type)); 815 | CLAY_TEXT(blind_name, WHITE_TEXT_CONFIG); 816 | 817 | Clay_String score; 818 | append_clay_string(&score, "Score at least:\n%.0lf", get_required_score(state.game.ante, blind->type)); 819 | CLAY_TEXT(score, WHITE_TEXT_CONFIG); 820 | 821 | Clay_String money; 822 | append_clay_string(&money, "Reward: $%d", get_blind_money(blind->type)); 823 | CLAY_TEXT(money, WHITE_TEXT_CONFIG); 824 | 825 | if (blind->type > BLIND_BIG) continue; 826 | 827 | Clay_String tag_name; 828 | append_clay_string(&tag_name, "%s", get_tag_name(blind->tag)); 829 | Clay_String tag_description; 830 | append_clay_string(&tag_description, "%s", get_tag_description(blind->tag)); 831 | 832 | uint8_t is_skip_button_hovered = is_current_blind && is_current_section && state.navigation.hovered == 1; 833 | CLAY_TEXT(CLAY_STRING("or"), WHITE_TEXT_CONFIG); 834 | CLAY({.layout = {.sizing = {CLAY_SIZING_GROW(0)}, 835 | .padding = {.top = 4, .bottom = 4}, 836 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}, 837 | .backgroundColor = is_skip_button_hovered ? COLOR_CHIPS : COLOR_MULT}) { 838 | CLAY_TEXT(CLAY_STRING("Skip Blind"), WHITE_TEXT_CONFIG); 839 | 840 | if (is_skip_button_hovered) 841 | render_tooltip(&tag_name, &tag_description, -4, 842 | &(Clay_FloatingAttachPoints){.parent = CLAY_ATTACH_POINT_CENTER_TOP, 843 | .element = CLAY_ATTACH_POINT_CENTER_BOTTOM}, 844 | NULL); 845 | } 846 | 847 | CLAY_TEXT(tag_name, WHITE_TEXT_CONFIG); 848 | } 849 | } 850 | 851 | void render_select_blind() { 852 | CLAY({.id = CLAY_ID("SelectBlind"), 853 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 854 | .childGap = 16, 855 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_BOTTOM}, 856 | .padding = {.top = 16}}}) { 857 | render_blind_element(0); 858 | render_blind_element(1); 859 | render_blind_element(2); 860 | } 861 | } 862 | 863 | void render_game_over() { CLAY_TEXT(CLAY_STRING("You've lost:("), WHITE_TEXT_CONFIG); } 864 | 865 | const Clay_ElementDeclaration overlay_bg_config = 866 | (Clay_ElementDeclaration){.floating = {.zIndex = 10, .attachTo = CLAY_ATTACH_TO_ROOT}, 867 | .layout = 868 | { 869 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 870 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 871 | }, 872 | .backgroundColor = {0, 0, 0, 150}}; 873 | 874 | const Clay_String overlay_menu_buttons[] = {CLAY_STRING("Continue"), CLAY_STRING("Poker hands"), CLAY_STRING("Restart"), 875 | CLAY_STRING("Go to main menu")}; 876 | 877 | void render_overlay_menu() { 878 | CLAY(overlay_bg_config) { 879 | CLAY({.layout = { 880 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_FIT(0)}, 881 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 882 | .childGap = 4, 883 | }}) { 884 | for (uint8_t i = 0; i < sizeof(overlay_menu_buttons) / sizeof(overlay_menu_buttons[0]); i++) { 885 | CLAY({.id = CLAY_IDI_LOCAL("Button", i + 1), 886 | .backgroundColor = state.navigation.hovered == i ? COLOR_CHIPS : COLOR_MULT, 887 | .layout = { 888 | .childAlignment = {CLAY_ALIGN_X_CENTER}, 889 | .padding = CLAY_PADDING_ALL(4), 890 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 891 | }}) { 892 | CLAY_TEXT(overlay_menu_buttons[i], WHITE_TEXT_CONFIG); 893 | } 894 | } 895 | } 896 | } 897 | } 898 | 899 | void render_overlay_select_stake() { 900 | CLAY(overlay_bg_config) { 901 | CLAY({.layout = {.sizing = {CLAY_SIZING_PERCENT(0.5), CLAY_SIZING_FIXED(64)}, 902 | .padding = CLAY_PADDING_ALL(8), 903 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 904 | .childGap = 4}, 905 | .backgroundColor = COLOR_CARD_BG_ALPHA(200)}) { 906 | Clay_String stake_name; 907 | append_clay_string(&stake_name, "%s", get_stake_name(state.navigation.hovered)); 908 | 909 | Clay_String stake_description; 910 | append_clay_string(&stake_description, "%s", get_stake_description(state.navigation.hovered)); 911 | 912 | CLAY_TEXT(stake_name, WHITE_TEXT_CONFIG); 913 | CLAY_TEXT(stake_description, WHITE_TEXT_CONFIG); 914 | } 915 | } 916 | } 917 | 918 | void render_overlay_poker_hands() { 919 | CLAY(overlay_bg_config) { 920 | CLAY({.id = CLAY_ID_LOCAL("Wrapper"), 921 | .backgroundColor = COLOR_CARD_BG_ALPHA(200), 922 | .layout = { 923 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 924 | .padding = CLAY_PADDING_ALL(4), 925 | .childGap = 4, 926 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 927 | }}) { 928 | for (uint8_t i = 0; i < 12; i++) { 929 | CLAY({.id = CLAY_IDI_LOCAL("PokerHand", i + 1), 930 | .backgroundColor = {255, 255, 255, 30}, 931 | .layout = { 932 | .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0)}, 933 | .childGap = 4, 934 | }}) { 935 | CLAY({.id = CLAY_ID_LOCAL("Level"), 936 | .backgroundColor = COLOR_WHITE, 937 | .layout = {.padding = CLAY_PADDING_ALL(2), 938 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 939 | .sizing = {CLAY_SIZING_FIXED(7 * CHAR_WIDTH), CLAY_SIZING_FIT(0)}}}) { 940 | Clay_String level; 941 | append_clay_string(&level, "lvl.%d", state.game.poker_hands[i].level + 1); 942 | CLAY_TEXT(level, CLAY_TEXT_CONFIG({.textColor = COLOR_BLACK})); 943 | } 944 | 945 | CLAY({.id = CLAY_ID_LOCAL("Name"), 946 | .layout = {.sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, 947 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}}) { 948 | const char *name_chars = get_poker_hand_name(1 << i); 949 | Clay_String name = {.chars = name_chars, .length = strlen(name_chars)}; 950 | CLAY_TEXT(name, WHITE_TEXT_CONFIG); 951 | } 952 | 953 | CLAY({.id = CLAY_ID_LOCAL("Score"), 954 | .backgroundColor = COLOR_CARD_LIGHT_BG, 955 | .layout = { 956 | .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, 957 | .padding = {.left = 2, .right = 2}, 958 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, 959 | }}) { 960 | ScorePair score = get_poker_hand_total_score(1 << i); 961 | 962 | CLAY({.id = CLAY_ID_LOCAL("Chips"), 963 | .backgroundColor = COLOR_CHIPS, 964 | .layout = { 965 | .sizing = {CLAY_SIZING_FIXED(4 * CHAR_WIDTH + 4)}, 966 | .childAlignment = {CLAY_ALIGN_X_RIGHT, CLAY_ALIGN_Y_CENTER}, 967 | .padding = {.left = 2, .right = 2}, 968 | }}) { 969 | Clay_String chips; 970 | append_clay_string(&chips, "%d", score.chips); 971 | CLAY_TEXT(chips, WHITE_TEXT_CONFIG); 972 | } 973 | 974 | CLAY({.layout = { 975 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, 976 | .padding = {.left = 2, .right = 2}, 977 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 978 | }}) { 979 | CLAY_TEXT(CLAY_STRING("x"), WHITE_TEXT_CONFIG); 980 | } 981 | 982 | CLAY({.id = CLAY_ID_LOCAL("Mult"), 983 | .backgroundColor = COLOR_MULT, 984 | .layout = { 985 | .sizing = {CLAY_SIZING_FIXED(4 * CHAR_WIDTH + 4)}, 986 | .childAlignment = {CLAY_ALIGN_X_LEFT, CLAY_ALIGN_Y_CENTER}, 987 | .padding = {.left = 2, .right = 2}, 988 | }}) { 989 | Clay_String mult; 990 | append_clay_string(&mult, "%.0lf", score.mult); 991 | CLAY_TEXT(mult, WHITE_TEXT_CONFIG); 992 | } 993 | } 994 | 995 | CLAY({.id = CLAY_ID_LOCAL("Played"), 996 | .layout = {.childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 997 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}}}) { 998 | CLAY_TEXT(CLAY_STRING("#"), WHITE_TEXT_CONFIG); 999 | 1000 | CLAY({.layout = {.padding = {.right = 2, .left = 2}, 1001 | .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, 1002 | .sizing = {CLAY_SIZING_FIXED(3 * CHAR_WIDTH), CLAY_SIZING_GROW(0)}}}) { 1003 | Clay_String played; 1004 | append_clay_string(&played, "%d", state.game.poker_hands[i].played); 1005 | CLAY_TEXT(played, CLAY_TEXT_CONFIG({.textColor = COLOR_MONEY})); 1006 | } 1007 | } 1008 | } 1009 | } 1010 | } 1011 | } 1012 | } 1013 | 1014 | static const int8_t grad2[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, 1}, {1, -1}, {-1, -1}}; 1015 | uint8_t PERLIN_PERM[BG_PERIOD]; 1016 | 1017 | float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } 1018 | float lerp(float a, float b, float t) { return a + t * (b - a); } 1019 | float grad_dot(int hash, float dx, float dy) { 1020 | int8_t *g = (int8_t *)grad2[hash & 7]; 1021 | return g[0] * dx + g[1] * dy; 1022 | } 1023 | 1024 | float perlin(float x, float y) { 1025 | int xi = (int)floorf(x) % BG_PERIOD; 1026 | int yi = (int)floorf(y) % BG_PERIOD; 1027 | int xi1 = (xi + 1) % BG_PERIOD; 1028 | int yi1 = (yi + 1) % BG_PERIOD; 1029 | 1030 | float tx = x - floorf(x); 1031 | float ty = y - floorf(y); 1032 | 1033 | float u = fade(tx); 1034 | float v = fade(ty); 1035 | 1036 | int aa = PERLIN_PERM[(PERLIN_PERM[xi] + yi) % BG_PERIOD]; 1037 | int ab = PERLIN_PERM[(PERLIN_PERM[xi] + yi1) % BG_PERIOD]; 1038 | int ba = PERLIN_PERM[(PERLIN_PERM[xi1] + yi) % BG_PERIOD]; 1039 | int bb = PERLIN_PERM[(PERLIN_PERM[xi1] + yi1) % BG_PERIOD]; 1040 | 1041 | float a = grad_dot(aa, tx, ty); 1042 | float b = grad_dot(ba, tx - 1, ty); 1043 | float c = grad_dot(ab, tx, ty - 1); 1044 | float d = grad_dot(bb, tx - 1, ty - 1); 1045 | 1046 | float x1 = lerp(a, b, u); 1047 | float x2 = lerp(c, d, u); 1048 | return lerp(x1, x2, v); 1049 | } 1050 | 1051 | void init_background() { 1052 | state.bg = init_texture(BG_NOISE_SIZE, BG_NOISE_SIZE); 1053 | 1054 | for (int i = 0; i < BG_PERIOD; i++) PERLIN_PERM[i] = i; 1055 | for (int i = BG_PERIOD - 1; i > 0; i--) { 1056 | int j = rand() % (i + 1); 1057 | uint8_t temp = PERLIN_PERM[i]; 1058 | PERLIN_PERM[i] = PERLIN_PERM[j]; 1059 | PERLIN_PERM[j] = temp; 1060 | } 1061 | 1062 | float scale = 16.0f; 1063 | 1064 | for (int y = 0; y < BG_NOISE_SIZE; y++) { 1065 | for (int x = 0; x < BG_NOISE_SIZE; x++) { 1066 | float fx = (float)x / BG_NOISE_SIZE * BG_PERIOD / scale; 1067 | float fy = (float)y / BG_NOISE_SIZE * BG_PERIOD / scale; 1068 | 1069 | float n = perlin(fx, fy); 1070 | n = (n + 1.0f) * 0.5f; 1071 | if (n < 0.0f) n = 0.0f; 1072 | if (n > 1.0f) n = 1.0f; 1073 | 1074 | float subtle = 0.3f + n * 0.4f; 1075 | 1076 | uint8_t r = (uint8_t)(subtle * 52); 1077 | uint8_t g = (uint8_t)(subtle * 130); 1078 | uint8_t b = (uint8_t)(subtle * 255); 1079 | 1080 | state.bg->data[y * BG_NOISE_SIZE + x] = RGB(r, g, b); 1081 | } 1082 | } 1083 | } 1084 | 1085 | void render_background() { 1086 | float x_offest = state.time * SCREEN_WIDTH * 0.0078125f; 1087 | float y_offest = state.time * SCREEN_HEIGHT * 0.0078125f; 1088 | 1089 | draw_texture(state.bg, &(Rect){.x = x_offest, .y = y_offest, .w = BG_TEXTURE_WIDTH, .h = BG_TEXTURE_HEIGHT}, 1090 | &(Rect){.x = 0, .y = 0, .w = SCREEN_WIDTH, .h = SCREEN_HEIGHT}, RGB(255, 255, 255), 0); 1091 | } 1092 | --------------------------------------------------------------------------------