diff --git a/graphics/background_gfx.png b/graphics/background_gfx.png index 15315a0..798282f 100644 Binary files a/graphics/background_gfx.png and b/graphics/background_gfx.png differ diff --git a/include/game.h b/include/game.h index 01c7d3d..ed65d3d 100644 --- a/include/game.h +++ b/include/game.h @@ -32,6 +32,7 @@ enum GameState GAME_SHOP, GAME_BLIND_SELECT, GAME_LOSE, + GAME_WIN }; enum HandState diff --git a/include/graphic_utils.h b/include/graphic_utils.h index 2f32db5..1df8e49 100644 --- a/include/graphic_utils.h +++ b/include/graphic_utils.h @@ -1,6 +1,8 @@ #ifndef GRAPHIC_UTILS_H #define GRAPHIC_UTILS_H +#include + /* This file contains general utils and wrappers that relate to * graphics/video/vram and generally displaying things on the screen. * Mostly wrappers and defines for using tonc. @@ -24,6 +26,19 @@ #define PAL_ROW_LEN 16 #define NUM_PALETTES 16 +#define TTE_BIT_UNPACK_OFFSET 14 +#define TTE_BIT_ON_CLR_IDX TTE_BIT_UNPACK_OFFSET + 1 + +#define TTE_YELLOW_PB 12 // 0xC +#define TTE_BLUE_PB 13 // 0xD +#define TTE_RED_PB 14 // 0xE +#define TTE_WHITE_PB 15 // 0xF + +#define TEXT_CLR_YELLOW RGB15(31, 20, 0) // 0x029F +#define TEXT_CLR_BLUE RGB15(0, 18, 31) // 0x7E40 +#define TEXT_CLR_RED RGB15(31, 9, 8) // 0x213F +#define TEXT_CLR_WHITE CLR_WHITE + /* Dimensions for a screenblock. * A 1024 size screenblock is arranged in a grid of 32x32 screen entries * Interestingly since each block is 8x8 pixels, the 240x160 GBA screen @@ -52,33 +67,37 @@ // By default TTE characters occupy a single tile #define TTE_CHAR_SIZE TILE_SIZE -typedef struct -{ - int left; - int top; - int right; - int bottom; -} Rect; +// When making this, missed that it already exists in tonc_math.h +typedef RECT Rect; -/* Gets the screenblock tile for the given coordinates (x, y). +/* Gets the screenblock entry for the given coordinates (x, y). * x and y are in number of tiles. - * Returns the screenblock tile. + * Returns the screenblock entry. */ -u16 main_bg_se_get_tile(BG_POINT pos); +SE main_bg_se_get_se(BG_POINT pos); INLINE int rect_width(const Rect* rect) { - /* Extra parens to avoid issues in case compiler turns INLINE into macro - * Not sure if necessary, could be just paranoia - */ - return (((rect)->right) - ((rect)->left) + 1); + /* Extra parens to avoid issues in case compiler turns INLINE into macro + * Not sure if necessary, could be just paranoia + */ + return (((rect)->right) - ((rect)->left) + 1); } INLINE int rect_height(const Rect* rect) { - return (((rect)->bottom) - ((rect)->top) + 1); + return (((rect)->bottom) - ((rect)->top) + 1); } +/* Copies an SE rect vertically in direction by a single tile. + * bg_sbb is the SBB of the background in which to move the rect + * direction must be either SE_UP or SE_DOWN. + * se_rect dimensions are in number of tiles. + * + * NOTE: This does not work with TTE_SBB, probably because it's 4BPP... + */ +void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction); + /* Clears a rect in the main background. * The se_rect dimensions need to be in number of tiles. */ @@ -96,11 +115,21 @@ void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction); */ void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos); -/* Copies a tile to a rect in the main background. +/* Copies a screen entry to a rect in the main background. * se_rect dimensions are in number of tiles. * The tile is copied to the top left corner of the rect. */ -void main_bg_se_copy_tile_to_rect(u16 tile, Rect se_rect); +void main_bg_se_fill_rect_with_se(SE tile, Rect se_rect); + +/* Copies a 3x3 rect into se_rect_dest, the 3x3 rect is stretched to fill se_rect_dest. + * The corners are copied, the sides are stretched, and the center is filled. + * The parameter se_rect_src_3x3_top_left points to the top left corner of the source + * 3x3 rect. + * Dest rect sides can be of length 2, then the sides are not copied, only the corners. + * But dest rect sides must be at least 2. + */ +void main_bg_se_copy_expand_3x3_rect(Rect se_rect_dest, BG_POINT se_rect_src_3x3_top_left); + /* Moves a rect in the main background vertically in direction by a single tile. * Note that tiles in the previous location will be transparent (0x000) * so maybe copy would be a better choice if you don't want to delete things diff --git a/source/game.c b/source/game.c index dd19796..ce165ca 100644 --- a/source/game.c +++ b/source/game.c @@ -186,7 +186,7 @@ static const Rect POP_MENU_ANIM_RECT = {9, 7, 24, 31 }; // This is because when popping, the target position is blank so we just animate // the whole rect so we don't have to track its position -static const Rect SINGLE_BLIND_SELECT_RECT = { 9, 7, 13, 32 }; +static const Rect SINGLE_BLIND_SELECT_RECT = {9, 7, 13, 32 }; static const Rect HAND_BG_RECT_SELECTING = {9, 11, 24, 17 }; // TODO: Currently unused, remove? @@ -205,6 +205,9 @@ static const Rect TOP_LEFT_PANEL_ANIM_RECT = {0, 0, 8, 4 }; static const BG_POINT TOP_LEFT_BLIND_TITLE_POINT = {0, 21, }; static const Rect BIG_BLIND_TITLE_SRC_RECT = {0, 26, 8, 26 }; static const Rect BOSS_BLIND_TITLE_SRC_RECT = {0, 27, 8, 27 }; +static const BG_POINT GAME_OVER_SRC_RECT_3X3_POS = {25, 29}; +static const Rect GAME_OVER_DIALOG_DEST_RECT= {11, 21, 23, 26}; +static const Rect GAME_OVER_ANIM_RECT = {11, 8, 23, 26}; // Rects for TTE (in pixels) static const Rect HAND_SIZE_RECT = {128, 128, 152, 160 }; // Seems to include both SELECT and PLAYING @@ -236,7 +239,10 @@ static const Rect ROUND_END_BLIND_REWARD_RECT = { 168, 96, UNDEFINED, UNDEF static const Rect ROUND_END_NUM_HANDS_RECT = {88, 116, UNDEFINED, UNDEFINED }; static const Rect HAND_REWARD_RECT = {168, UNDEFINED, UNDEFINED, UNDEFINED }; static const Rect CASHOUT_RECT = {88, 72, UNDEFINED, UNDEFINED }; -static const Rect SHOP_REROLL_RECT = {88, 96, UNDEFINED, UNDEFINED }; +static const Rect SHOP_REROLL_RECT = {88, 96, UNDEFINED, UNDEFINED }; +static const Rect GAME_LOSE_MSG_TEXT_RECT = {104, 72, UNDEFINED, UNDEFINED}; +// 1 character to the right oF GAME_LOSE +static const Rect GAME_WIN_MSG_TEXT_RECT = {112, 72, UNDEFINED, UNDEFINED}; static const BG_POINT HELD_JOKERS_POS = {108, 10}; static const BG_POINT JOKER_DISCARD_TARGET = {240, 30}; @@ -245,6 +251,7 @@ static const BG_POINT JOKER_DISCARD_TARGET = {240, 30}; //TODO: Properly define and use #define MENU_POP_OUT_ANIM_FRAMES 20 +#define GAME_OVER_ANIM_FRAMES 15 #define SCORED_CARD_TEXT_Y 48 @@ -516,7 +523,7 @@ void change_background(int id) obj_unhide(blind_select_tokens[BIG_BLIND]->obj, 0); obj_unhide(blind_select_tokens[BOSS_BLIND]->obj, 0); - const int default_y = 89 + (TILE_SIZE * 12); // Default y position for the blind select tokens. 8 is the size of a tile and 12 is the amound of tiles the background is shifted down by + const int default_y = 89 + (TILE_SIZE * 12); // Default y position for the blind select tokens. 12 is the amound of tiles the background is shifted down by sprite_position(blind_select_tokens[SMALL_BLIND], 80, default_y); sprite_position(blind_select_tokens[BIG_BLIND], 120, default_y); sprite_position(blind_select_tokens[BOSS_BLIND], 160, default_y); @@ -632,7 +639,7 @@ void display_temp_score(int value) { int x_offset = 40 - get_digits_even(value) * TILE_SIZE; tte_erase_rect_wrapper(TEMP_SCORE_RECT); - tte_printf("#{P:%d,%d; cx:0xF000}%d", x_offset, TEMP_SCORE_RECT.top, value); + tte_printf("#{P:%d,%d; cx:0x%X000}%d", x_offset, TEMP_SCORE_RECT.top, TTE_WHITE_PB, value); } void display_score(int value) @@ -661,14 +668,14 @@ void display_score(int value) int rect_width = SCORE_RECT.right - SCORE_RECT.left; int x_offset = SCORE_RECT.left + (rect_width - text_width) / 2; - tte_printf("#{P:%d,48; cx:0xF000}%d%c", x_offset, display_value, score_suffix); + tte_printf("#{P:%d,48; cx:0x%X000}%d%c", x_offset, TTE_WHITE_PB, display_value, score_suffix); } void display_money(int value) { int x_offset = 32 - get_digits_odd(value) * TILE_SIZE; tte_erase_rect_wrapper(MONEY_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0xC000}$%d", x_offset, MONEY_TEXT_RECT.top, value); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x_offset, MONEY_TEXT_RECT.top, TTE_YELLOW_PB, value); } void display_chips(int value) @@ -676,19 +683,19 @@ void display_chips(int value) Rect chips_text_rect = CHIPS_TEXT_RECT; tte_erase_rect_wrapper(CHIPS_TEXT_RECT); update_text_rect_to_right_align_num(&chips_text_rect, value, OVERFLOW_LEFT); - tte_printf("#{P:%d,%d; cx:0xF000;}%d", chips_text_rect.left, chips_text_rect.top, value); + tte_printf("#{P:%d,%d; cx:0x%X000;}%d", chips_text_rect.left, chips_text_rect.top, TTE_WHITE_PB, value); } void display_mult(int value) { tte_erase_rect_wrapper(MULT_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0xF000;}%d", MULT_TEXT_RECT.left, MULT_TEXT_RECT.top, value); // Mult + tte_printf("#{P:%d,%d; cx:0x%X000;}%d", MULT_TEXT_RECT.left, MULT_TEXT_RECT.top, TTE_WHITE_PB, value); // Mult } void display_round(int value) { //tte_erase_rect_wrapper(ROUND_TEXT_RECT); - tte_printf("#{P:%d,%d; cx:0xC000}%d", ROUND_TEXT_RECT.left, ROUND_TEXT_RECT.top, round); + tte_printf("#{P:%d,%d; cx:0x%X000}%d", ROUND_TEXT_RECT.left, ROUND_TEXT_RECT.top, TTE_YELLOW_PB, round); } void display_ante(int value) @@ -712,7 +719,7 @@ static void print_hand_type(const char* hand_type_str) { if (hand_type_str == NULL) return; // NULL-checking paranoia - tte_printf("#{P:%d,%d; cx:0xF000}%s", HAND_TYPE_RECT.left, HAND_TYPE_RECT.top, hand_type_str); + tte_printf("#{P:%d,%d; cx:0x%X000}%s", HAND_TYPE_RECT.left, HAND_TYPE_RECT.top, TTE_WHITE_PB, hand_type_str); } void set_hand() @@ -922,7 +929,7 @@ void increment_blind(enum BlindState increment_reason) } } -void game_round_init() +static void game_round_init() { // once we have a main menu, move the seed set there. this is only here now because there isn't a way to set the seed to a psudorandom value unless there is something to wait on set_seed(rng_seed); @@ -968,13 +975,35 @@ void game_round_init() blind_req_text_rect.left -= TILE_SIZE; // Move left by one character width to make room for 'k' } - tte_printf("#{P:%d,%d; cx:0xE000}%d%c", blind_req_text_rect.left, blind_req_text_rect.top, blind_requirement, score_suffix); // Blind requirement - tte_printf("#{P:%d,%d; cx:0xC000}$%d", BLIND_REWARD_RECT.left, BLIND_REWARD_RECT.top, blind_get_reward(current_blind)); // Blind reward + tte_printf("#{P:%d,%d; cx:0x%X000}%d%c", blind_req_text_rect.left, blind_req_text_rect.top, TTE_RED_PB, blind_requirement, score_suffix); // Blind requirement + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", BLIND_REWARD_RECT.left, BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_get_reward(current_blind)); // Blind reward deck_shuffle(); // Shuffle the deck at the start of the round } -void init_game_state(enum GameState game_state_to_init) +static void game_over_init() +{ + // Clears the round end menu + main_bg_se_clear_rect(POP_MENU_ANIM_RECT); + main_bg_se_copy_expand_3x3_rect(GAME_OVER_DIALOG_DEST_RECT, GAME_OVER_SRC_RECT_3X3_POS); +} + +static void game_lose_init() +{ + game_over_init(); + // Using the text color to match the "Game Over" text + affine_background_set_color(TEXT_CLR_RED); + +} + +static void game_win_init() +{ + game_over_init(); + // Using the text color to match the "You Win" text + affine_background_set_color(TEXT_CLR_BLUE); +} + +static void init_game_state(enum GameState game_state_to_init) { // Switch written out, add init for states as needed switch (game_state_to_init) @@ -989,6 +1018,10 @@ void init_game_state(enum GameState game_state_to_init) case GAME_BLIND_SELECT: break; case GAME_LOSE: + game_lose_init(); + break; + case GAME_WIN: + game_win_init(); break; default: break; @@ -996,7 +1029,7 @@ void init_game_state(enum GameState game_state_to_init) } // Game functions -void game_set_state(enum GameState new_game_state) +static void game_set_state(enum GameState new_game_state) { timer = 0; // Reset the timer init_game_state(new_game_state); @@ -1048,7 +1081,7 @@ void game_init() change_background(BG_ID_BLIND_SELECT); - tte_printf("#{P:%d,%d; cx:0xF000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, deck_get_size(), deck_get_max_size()); // Deck size/max size + tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, TTE_WHITE_PB, deck_get_size(), deck_get_max_size()); // Deck size/max size display_round(round); // Set the round display display_score(score); // Set the score display @@ -1059,10 +1092,9 @@ void game_init() display_hands(hands); // Hand display_discards(discards); // Discard - //tte_printf("#{P:24,120; cx:0xC000}$%d", money); // Money display_money(money); // Set the money display - tte_printf("#{P:%d,%d; cx:0xC000}%d#{cx:0xF000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, ante, MAX_ANTE); // Ante + tte_printf("#{P:%d,%d; cx:0x%X000}%d#{cx:0x%X000}/%d", ANTE_TEXT_RECT.left, ANTE_TEXT_RECT.top, TTE_YELLOW_PB, ante, TTE_WHITE_PB, MAX_ANTE); // Ante } static void game_playing_process_hand_select_input() @@ -1136,7 +1168,7 @@ static void game_playing_process_hand_select_input() selection_y = 0; display_hands(--discards); set_hand(); - tte_printf("#{P:%d,%d; cx:0xE000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, discards); + tte_printf("#{P:%d,%d; cx:0x%X000}%d", DISCARDS_TEXT_RECT.left, DISCARDS_TEXT_RECT.top, TTE_RED_PB, discards); } } } @@ -1236,6 +1268,37 @@ static void game_playing_process_card_draw() } } +static bool game_round_is_over() +{ + return hands == 0 || score >= blind_get_requirement(current_blind, ante); +} + +static void game_playing_handle_round_over() +{ + enum GameState next_state = GAME_ROUND_END; + + if (score >= blind_get_requirement(current_blind, ante)) + { + if (current_blind == BOSS_BLIND) + { + if (ante < MAX_ANTE) + { + display_ante(++ante); + } + else + { + next_state = GAME_WIN; + } + } + } + else if (hands == 0) + { + next_state = GAME_LOSE; + } + + game_set_state(next_state); +} + static void game_playing_discarded_cards_loop() { // Discarded cards loop (mainly for shuffling) @@ -1274,26 +1337,97 @@ static void game_playing_discarded_cards_loop() if (discard_top == -1 && discarded_card_object == NULL) // If there are no more discarded cards, stop shuffling { - game_set_state(GAME_ROUND_END); // Set the game state back to playing + // After HAND_SHUFFLING the round is over + game_playing_handle_round_over(); } } } +static const int HAND_SPACING_LUT[MAX_HAND_SIZE] = { 28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7 }; // This is a stupid way to do this but I don't care + +void card_in_hand_loop_handle_discard_and_shuffling(int card_idx, bool* discarded_card, FIXED* hand_x, FIXED* hand_y, bool* sound_played, bool* break_loop) +{ + if (hand_state != HAND_DISCARD && hand_state != HAND_SHUFFLING) + { + // Assumes hand_state is one of these + return; + } + + *break_loop = false; + if (card_object_is_selected(hand[card_idx]) || hand_state == HAND_SHUFFLING) + { + if (!*discarded_card) + { + *hand_x = int2fx(240); + *hand_y = int2fx(70); + + if (!*sound_played) + { + play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn * PITCH_STEP_DISCARD_SFX); + *sound_played = true; + } + + if (hand[card_idx]->sprite_object->x >= *hand_x) + { + discard_push(hand[card_idx]->card); + card_object_destroy(&hand[card_idx]); + sort_cards(); + + hand_top--; + cards_drawn++; // This technically isn't drawing cards, I'm just reusing the variable + *sound_played = false; + timer = 0; + + *hand_y = hand[card_idx]->sprite_object->y; + *hand_x = hand[card_idx]->sprite_object->x; + } + + *discarded_card = true; + } + else + { + if (hand_state == HAND_DISCARD) + { + *hand_y -= int2fx(15); // Don't raise the card if we're mass discarding, it looks stupid. + } + else // hand_state == HAND_SHUFFLING + { + *hand_y += int2fx(24); + } + *hand_x = *hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + } + } + else + { + *hand_x = *hand_x + (int2fx(card_idx) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; + } + + if (card_idx == 0 && *discarded_card == false && timer % FRAMES(10) == 0) + { + // This is never reached in the case of HAND_SHUFFLING. // Not sure why but that's how it's supposed to be. + hand_state = HAND_DRAW; + *sound_played = false; + cards_drawn = 0; + hand_selections = 0; + timer = 0; + *break_loop = true; + return; + }; +} + static void cards_in_hand_update_loop(bool* discarded_card, int* played_selections, bool* sound_played) { for (int i = hand_top + 1; i >= 0; i--) // Start from the end of the hand and work backwards because that's how Balatro does it { if (hand[i] != NULL) { - const int spacing_lut[MAX_HAND_SIZE] = { 28, 28, 28, 28, 27, 21, 18, 15, 13, 12, 10, 9, 9, 8, 8, 7 }; // This is a stupid way to do this but I don't care - FIXED hand_x = int2fx(120); FIXED hand_y = int2fx(90); switch (hand_state) { case HAND_DRAW: - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; + hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; break; case HAND_SELECT: bool is_focused = (i == selection_x && selection_y == 0); @@ -1317,72 +1451,18 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio hand[i]->sprite_object->vy = 0; } - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; // TODO: Change this later to reference a 2D LUT of positions + hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; // TODO: Change this later to reference a 2D LUT of positions break; case HAND_SHUFFLING: /* FALL THROUGH */ case HAND_DISCARD: // TODO: Add sound - if (card_object_is_selected(hand[i]) || hand_state == HAND_SHUFFLING) - { - if (!*discarded_card) - { - hand_x = int2fx(240); - hand_y = int2fx(70); - - if (!*sound_played) - { - play_sfx(SFX_CARD_DRAW, MM_BASE_PITCH_RATE + cards_drawn*PITCH_STEP_DISCARD_SFX); - *sound_played = true; - } - - if (hand[i]->sprite_object->x >= hand_x) - { - discard_push(hand[i]->card); - card_object_destroy(&hand[i]); - sort_cards(); - - hand_top--; - cards_drawn++; // This technically isn't drawing cards, I'm just reusing the variable - *sound_played = false; - timer = 0; - - hand_y = hand[i]->sprite_object->y; - hand_x = hand[i]->sprite_object->x; - } - - *discarded_card = true; - } - else - { - if (hand_state == HAND_DISCARD) - { - hand_y -= int2fx(15); // Don't raise the card if we're mass discarding, it looks stupid. - } - else - { - hand_y += int2fx(24); - } - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; - } - } - else - { - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; - } - - if (i == 0 && *discarded_card == false && timer % FRAMES(10) == 0) - { - hand_state = HAND_DRAW; - *sound_played = false; - cards_drawn = 0; - hand_selections = 0; - timer = 0; - break; - } + bool break_loop; + card_in_hand_loop_handle_discard_and_shuffling(i, discarded_card, &hand_x, &hand_y, sound_played, &break_loop); + if (break_loop) break; break; case HAND_PLAY: - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; + hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; hand_y += int2fx(24); if (card_object_is_selected(hand[i]) && *discarded_card == false && timer % FRAMES(10) == 0) @@ -1556,7 +1636,7 @@ static void cards_in_hand_update_loop(bool* discarded_card, int* played_selectio break; case HAND_PLAYING: // Don't need to do anything here, just wait for the player to select cards - hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -spacing_lut[hand_top]; + hand_x = hand_x + (int2fx(i) - int2fx(hand_top) / 2) * -HAND_SPACING_LUT[hand_top]; hand_y += int2fx(24); break; } @@ -1744,14 +1824,9 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection if (i == played_top) { - if (score >= blind_get_requirement(current_blind, ante)) + if (game_round_is_over()) { hand_state = HAND_SHUFFLING; - - if (current_blind == BOSS_BLIND) - { - display_ante(++ante); - } } else { @@ -1782,55 +1857,6 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection } } -static void discarded_jokers_update_loop() -{ - if (discarded_jokers == NULL) - return; - - // Iterating backwards because of removal within loop - for (int i = list_get_size(discarded_jokers) - 1; i >= 0; i--) - { - JokerObject* joker_object = list_get(discarded_jokers, i); - joker_object_update(joker_object); - if (joker_object->sprite_object->x == joker_object->sprite_object->tx - && joker_object->sprite_object->y == joker_object->sprite_object->ty) - { - list_remove_by_idx(discarded_jokers, i); - joker_object_destroy(&joker_object); - } - } - -} - -static void held_jokers_update_loop() -{ - const int spacing_lut[MAX_JOKERS_HELD_SIZE][MAX_JOKERS_HELD_SIZE] = - { - {0, 0, 0, 0, 0}, - {13, -13, 0, 0, 0}, - {26, 0, -26, 0, 0}, - {39, 13, -13, -39, 0}, - {40, 20, 0, -20, -40} - }; - - FIXED hand_x = int2fx(HELD_JOKERS_POS.x); - - int jokers_top = list_get_size(jokers) - 1; - for (int i = jokers_top; i >= 0; i--) - { - JokerObject *joker = list_get(jokers, i); - joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i]); - - joker_object_update(joker); - } -} - -static void jokers_update_loop() -{ - held_jokers_update_loop(); - discarded_jokers_update_loop(); -} - static void game_playing_ui_text_update() { static int last_hand_size = 0; @@ -1840,14 +1866,14 @@ static void game_playing_ui_text_update() { if (background == BG_ID_CARD_SELECTING) { - tte_printf("#{P:%d,%d; cx:0xF000}%d/%d", HAND_SIZE_RECT_SELECT.left, HAND_SIZE_RECT_SELECT.top, hand_get_size(), hand_get_max_size()); // Hand size/max size + tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", HAND_SIZE_RECT_SELECT.left, HAND_SIZE_RECT_SELECT.top, TTE_WHITE_PB, hand_get_size(), hand_get_max_size()); // Hand size/max size } else if (background == BG_ID_CARD_PLAYING) { - tte_printf("#{P:%d,%d; cx:0xF000}%d/%d", HAND_SIZE_RECT_PLAYING.left, HAND_SIZE_RECT_PLAYING.top, hand_get_size(), hand_get_max_size()); // Hand size/max size + tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", HAND_SIZE_RECT_PLAYING.left, HAND_SIZE_RECT_PLAYING.top, TTE_WHITE_PB, hand_get_size(), hand_get_max_size()); // Hand size/max size } - tte_printf("#{P:%d,%d; cx:0xF000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, deck_get_size(), deck_get_max_size()); // Deck size/max size + tte_printf("#{P:%d,%d; cx:0x%X000}%d/%d", DECK_SIZE_RECT.left, DECK_SIZE_RECT.top, TTE_WHITE_PB, deck_get_size(), deck_get_max_size()); // Deck size/max size last_hand_size = hand_get_size(); last_deck_size = deck_get_size(); @@ -1870,13 +1896,6 @@ static void game_round_end_cashout() void game_playing() { - // TODO: Blind rect sliding into view animation... - if (hand_state == HAND_SELECT && hands == 0) - { - // TODO: Check if it's safe to change to game_set_state() without side effects - game_state = GAME_LOSE; - } - // Background logic (thissss might be moved to the card'ssss logic later. I'm a sssssnake) if (hand_state == HAND_DRAW || hand_state == HAND_DISCARD || hand_state == HAND_SELECT) { @@ -1956,7 +1975,7 @@ void game_round_end() int blind_req = blind_get_requirement(current_blind, current_ante); update_text_rect_to_right_align_num(&blind_req_rect, blind_req, OVERFLOW_RIGHT); - tte_printf("#{P:%d,%d; cx:0xE000}%d", blind_req_rect.left, blind_req_rect.top, blind_req); + tte_printf("#{P:%d,%d; cx:0x%X000}%d", blind_req_rect.left, blind_req_rect.top, TTE_RED_PB, blind_req); if (timer == 1) { @@ -2001,8 +2020,8 @@ void game_round_end() if (blind_reward > 0) { blind_reward--; - tte_printf("#{P:%d,%d; cx:0xC000}$%d", BLIND_REWARD_RECT.left , BLIND_REWARD_RECT.top, blind_reward); - tte_printf("#{P:%d,%d; cx:0xC000}$%d", ROUND_END_BLIND_REWARD_RECT.left, ROUND_END_BLIND_REWARD_RECT.top, blind_get_reward(current_blind) - blind_reward); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", BLIND_REWARD_RECT.left , BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_reward); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", ROUND_END_BLIND_REWARD_RECT.left, ROUND_END_BLIND_REWARD_RECT.top, TTE_YELLOW_PB, blind_get_reward(current_blind) - blind_reward); } else if (timer > FRAMES(20)) { @@ -2083,7 +2102,7 @@ void game_round_end() int x = (8 + timer) * TILE_SIZE; int y = (13) * TILE_SIZE; - tte_printf("#{P:%d,%d; cx:0xF000}.", x, y); + tte_printf("#{P:%d,%d; cx:0x%X000}.", x, y, TTE_WHITE_PB); } else if (timer >= 30 && hand_reward > 0) // Wait an additional 15 frames since the last sequenced action { @@ -2094,13 +2113,13 @@ void game_round_end() single_line_rect.bottom = single_line_rect.top + 1; main_bg_se_copy_rect_1_tile_vert(single_line_rect, SE_DOWN); - tte_printf("#{P:%d,%d; cx:0xD000}%d #{cx:0xF000}Hands", ROUND_END_NUM_HANDS_RECT.left, ROUND_END_NUM_HANDS_RECT.top, hand_reward); // Print the hand reward + tte_printf("#{P:%d,%d; cx:0x%X000}%d #{cx:0x%X000}Hands", ROUND_END_NUM_HANDS_RECT.left, ROUND_END_NUM_HANDS_RECT.top, TTE_BLUE_PB, hand_reward, TTE_WHITE_PB); // Print the hand reward } else if (timer > 45 && timer % FRAMES(20) == 0) // After 15 frames, every 20 frames, increment the hand reward text until the hand reward variable is depleted { int y = (13 + hand_y) * TILE_SIZE; hand_reward--; - tte_printf("#{P:%d, %d; cx:0xC000}$%d", HAND_REWARD_RECT.left, y, hands - hand_reward); // Print the hand reward + tte_printf("#{P:%d, %d; cx:0x%X000}$%d", HAND_REWARD_RECT.left, y, TTE_YELLOW_PB, hands - hand_reward); // Print the hand reward } } @@ -2120,17 +2139,17 @@ void game_round_end() Rect top_rect = {11, 8, 22, 8}; BG_POINT top_point = {6, 29}; - main_bg_se_copy_tile_to_rect(main_bg_se_get_tile(top_point), top_rect); + main_bg_se_fill_rect_with_se(main_bg_se_get_se(top_point), top_rect); Rect middle_rect = {11, 9, 22, 9}; BG_POINT middle_point = {6, 30}; - main_bg_se_copy_tile_to_rect(main_bg_se_get_tile(middle_point), middle_rect); + main_bg_se_fill_rect_with_se(main_bg_se_get_se(middle_point), middle_rect); Rect bottom_rect = {11, 10, 22, 10}; BG_POINT bottom_point = {6, 31}; - main_bg_se_copy_tile_to_rect(main_bg_se_get_tile(bottom_point), bottom_rect); + main_bg_se_fill_rect_with_se(main_bg_se_get_se(bottom_point), bottom_rect); - tte_printf("#{P:%d, %d; cx:0xF000}Cash Out: $%d", CASHOUT_RECT.left, CASHOUT_RECT.top, hands + blind_get_reward(current_blind)); // Print the cash out amount + tte_printf("#{P:%d, %d; cx:0x%X000}Cash Out: $%d", CASHOUT_RECT.left, CASHOUT_RECT.top, TTE_WHITE_PB, hands + blind_get_reward(current_blind)); // Print the cash out amount } else if (timer > FRAMES(40) && key_hit(SELECT_CARD)) // Wait until the player presses A to cash out { @@ -2186,7 +2205,7 @@ void print_price_under_sprite_object(SpriteObject *sprite_object, int price) { int x = fx2int(sprite_object->tx) + TILE_SIZE - (get_digits_even(price) - 1) * TILE_SIZE; int y = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE; // TODO: Should probably extract the sprite size - tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, price); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", x, y, TTE_YELLOW_PB, price); } void erase_price_under_sprite_object(SpriteObject *sprite_object) @@ -2296,7 +2315,7 @@ static void game_shop_reroll(int *reroll_cost) } (*reroll_cost)++; - tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, *reroll_cost); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, TTE_WHITE_PB, *reroll_cost); } static int jokers_sel_row_get_size() @@ -2503,7 +2522,7 @@ static void game_shop_process_user_input() // to trigger the selection change so the initial selection is visible shop_selection_grid.selection = SHOP_INIT_SEL; selection_grid_move_selection_horz(&shop_selection_grid, 1); - tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost); + tte_printf("#{P:%d,%d; cx:0x%X000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, TTE_WHITE_PB, reroll_cost); } // Shop input logic @@ -2805,6 +2824,84 @@ void game_blind_select() } } +static void discarded_jokers_update_loop() +{ + if (discarded_jokers == NULL) + return; + + // Iterating backwards because of removal within loop + for (int i = list_get_size(discarded_jokers) - 1; i >= 0; i--) + { + JokerObject* joker_object = list_get(discarded_jokers, i); + joker_object_update(joker_object); + if (joker_object->sprite_object->x == joker_object->sprite_object->tx + && joker_object->sprite_object->y == joker_object->sprite_object->ty) + { + list_remove_by_idx(discarded_jokers, i); + joker_object_destroy(&joker_object); + } + } + +} + +static void held_jokers_update_loop() +{ + const int spacing_lut[MAX_JOKERS_HELD_SIZE][MAX_JOKERS_HELD_SIZE] = + { + {0, 0, 0, 0, 0}, + {13, -13, 0, 0, 0}, + {26, 0, -26, 0, 0}, + {39, 13, -13, -39, 0}, + {40, 20, 0, -20, -40} + }; + + FIXED hand_x = int2fx(HELD_JOKERS_POS.x); + + int jokers_top = list_get_size(jokers) - 1; + for (int i = jokers_top; i >= 0; i--) + { + JokerObject *joker = list_get(jokers, i); + joker->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i]); + + joker_object_update(joker); + } +} + +static void jokers_update_loop() +{ + held_jokers_update_loop(); + discarded_jokers_update_loop(); +} + +static void game_over_anim_frame() +{ + main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SE_UP); +} + +static void game_lose() +{ + if (timer < GAME_OVER_ANIM_FRAMES) + { + game_over_anim_frame(); + } + else if (timer == GAME_OVER_ANIM_FRAMES) + { + tte_printf("#{P:%d,%d; cx:0x%X000}GAME OVER", GAME_LOSE_MSG_TEXT_RECT.left, GAME_LOSE_MSG_TEXT_RECT.top, TTE_RED_PB); + } +} + +static void game_win() +{ + if (timer < GAME_OVER_ANIM_FRAMES) + { + game_over_anim_frame(); + } + else if (timer == GAME_OVER_ANIM_FRAMES) + { + tte_printf("#{P:%d,%d; cx:0x%X000}YOU WIN", GAME_WIN_MSG_TEXT_RECT.left, GAME_WIN_MSG_TEXT_RECT.top, TTE_BLUE_PB); + } +} + void game_update() { timer++; @@ -2835,7 +2932,10 @@ void game_update() game_blind_select(); break; case GAME_LOSE: - // Handle lose logic here + game_lose(); + break; + case GAME_WIN: + game_win(); break; } } \ No newline at end of file diff --git a/source/graphic_utils.c b/source/graphic_utils.c index b568442..2e76aae 100644 --- a/source/graphic_utils.c +++ b/source/graphic_utils.c @@ -24,7 +24,7 @@ static void clip_se_rect_to_screenblock(Rect* rect) clip_se_rect_to_bounding_rect(rect, &FULL_SCREENBLOCK_RECT); } -u16 main_bg_se_get_tile(BG_POINT pos) +SE main_bg_se_get_se(BG_POINT pos) { return se_mat[MAIN_BG_SBB][pos.y][pos.x]; } @@ -59,11 +59,10 @@ void main_bg_se_clear_rect(Rect se_rect) } } - // Internal static function to merge implementation of move/copy functions. -static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, int direction, bool move) +static void bg_se_copy_or_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction, bool move) { - if (se_rect.left > se_rect.right + if (se_rect.left > se_rect.right || (direction != SE_UP && direction != SE_DOWN)) { return; @@ -77,17 +76,32 @@ static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, int direction for (int y = start; y != end - direction; y -= direction) { - memcpy16(&(se_mat[MAIN_BG_SBB][y + direction][se_rect.left]), - &se_mat[MAIN_BG_SBB][y][se_rect.left], + memcpy16(&(se_mat[bg_sbb][y + direction][se_rect.left]), + &se_mat[bg_sbb][y][se_rect.left], rect_width(&se_rect)); } if (move) { - memset16(&se_mat[MAIN_BG_SBB][end][se_rect.left], 0x0000, rect_width(&se_rect)); + memset16(&se_mat[bg_sbb][end][se_rect.left], 0x0000, rect_width(&se_rect)); } } +static void main_bg_se_copy_or_move_rect_1_tile_vert(Rect se_rect, int direction, bool move) +{ + bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, move); +} + +void bg_se_copy_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction) +{ + bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, false); +} + +void bg_se_move_rect_1_tile_vert(u16 bg_sbb, Rect se_rect, int direction) +{ + bg_se_copy_or_move_rect_1_tile_vert(MAIN_BG_SBB, se_rect, direction, true); +} + void main_bg_se_copy_rect_1_tile_vert(Rect se_rect, int direction) { main_bg_se_copy_or_move_rect_1_tile_vert(se_rect, direction, false); @@ -108,20 +122,18 @@ void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos) int width = rect_width(&se_rect); int height = rect_height(&se_rect); - u16 tile_map[height][width]; + SE tile_map[height][width]; // Copy the rect to the tile map for (int sy = 0; sy < height; sy++) { - for (int sx = 0; sx < width; sx++) - { - BG_POINT pt; - pt.x = se_rect.left + sx; - pt.y = se_rect.top + sy; - tile_map[sy][sx] = main_bg_se_get_tile(pt); - } + memcpy16(&tile_map[sy][0], + &se_mat[MAIN_BG_SBB][se_rect.top + sy][se_rect.left], + width); } - + + // TODO: Avoid overflow + // Copy the tilemap to the new rect position for (int sy = 0; sy < height; sy++) { memcpy16(&se_mat[MAIN_BG_SBB][pos.y + sy][pos.x], @@ -130,7 +142,7 @@ void main_bg_se_copy_rect(Rect se_rect, BG_POINT pos) } } -void main_bg_se_copy_tile_to_rect(u16 tile, Rect se_rect) +void main_bg_se_fill_rect_with_se(SE se, Rect se_rect) { if (se_rect.left > se_rect.right || se_rect.top > se_rect.bottom) return; @@ -143,7 +155,78 @@ void main_bg_se_copy_tile_to_rect(u16 tile, Rect se_rect) for (int sy = 0; sy < height; sy++) { - memset16(&se_mat[MAIN_BG_SBB][se_rect.top + sy][se_rect.left], tile, width); + memset16(&se_mat[MAIN_BG_SBB][se_rect.top + sy][se_rect.left], se, width); + } +} + +// Helper: Copy the corners of a 3x3 tile block +static void main_bg_se_expand_3x3_copy_corners(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width, int dest_rect_height) +{ + SE top_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y][src_top_left_pnt->x]; + se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left] = top_left_se; + + SE top_right_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y][src_top_left_pnt->x + 2]; + se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left + dest_rect_width - 1] = top_right_se; + + SE bottom_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x]; + se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1][se_dest_rect->left] = bottom_left_se; + + SE bottom_right_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x + 2]; + se_mat[MAIN_BG_SBB][se_dest_rect->top + dest_rect_height - 1][se_dest_rect->left + dest_rect_width - 1] = bottom_right_se; +} + +// Helper: Copy the top and bottom sides of a 3x3 tile block +static void main_bg_se_expand_3x3_copy_top_bottom(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width) +{ + if (dest_rect_width > 2) + { + SE top_middle_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y][src_top_left_pnt->x + 1]; + SE bottom_middle_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 2][src_top_left_pnt->x + 1]; + memset16(&se_mat[MAIN_BG_SBB][se_dest_rect->top][se_dest_rect->left + 1], top_middle_se, dest_rect_width - 2); + memset16(&se_mat[MAIN_BG_SBB][se_dest_rect->bottom][se_dest_rect->left + 1], bottom_middle_se, dest_rect_width - 2); + } +} + +// Helper: Copy the left and right sides of a 3x3 tile block +static void main_bg_se_expand_3x3_copy_left_right(const Rect* se_dest_rect, const BG_POINT* src_top_left_pnt, int dest_rect_width, int dest_rect_height) +{ + SE middle_left_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 1][src_top_left_pnt->x]; + SE middle_right_se = se_mat[MAIN_BG_SBB][src_top_left_pnt->y + 1][src_top_left_pnt->x + 2]; + for (int y = 1; y < dest_rect_height - 1; y++) + { + se_mat[MAIN_BG_SBB][se_dest_rect->top + y][se_dest_rect->left] = middle_left_se; + se_mat[MAIN_BG_SBB][se_dest_rect->top + y][se_dest_rect->left + dest_rect_width - 1] = middle_right_se; + } +} + +void main_bg_se_copy_expand_3x3_rect(Rect se_dest_rect, BG_POINT src_top_left_pnt) +{ + clip_se_rect_to_screenblock(&se_dest_rect); + + int dest_rect_width = rect_width(&se_dest_rect); + int dest_rect_height = rect_height(&se_dest_rect); + + // Verify the dest rect is at least 2x2 + if (dest_rect_width < 2 || dest_rect_height < 2) + { + return; + } + + // Copy the corners + main_bg_se_expand_3x3_copy_corners(&se_dest_rect, &src_top_left_pnt, dest_rect_width, dest_rect_height); + + // Copy top and bottom sides + main_bg_se_expand_3x3_copy_top_bottom(&se_dest_rect, &src_top_left_pnt, dest_rect_width); + + // Copy left and right sides + main_bg_se_expand_3x3_copy_left_right(&se_dest_rect, &src_top_left_pnt, dest_rect_width, dest_rect_height); + + // Fill the center if needed + if (dest_rect_width > 2 && dest_rect_height > 2) + { + SE middle_fill_se = se_mat[MAIN_BG_SBB][src_top_left_pnt.y + 1][src_top_left_pnt.x + 1]; + Rect dest_inner_fill_rect = {se_dest_rect.left + 1, se_dest_rect.top + 1, se_dest_rect.right - 1, se_dest_rect.bottom - 1}; + main_bg_se_fill_rect_with_se(middle_fill_se, dest_inner_fill_rect); } } diff --git a/source/main.c b/source/main.c index 269fc09..066d9a0 100644 --- a/source/main.c +++ b/source/main.c @@ -28,15 +28,15 @@ void init() mmStart(MOD_MAIN_THEME, MM_PLAY_LOOP); // Initialize text engine - tte_init_se(0, BG_CBB(TTE_CBB) | BG_SBB(TTE_SBB), 0, CLR_WHITE, 14, NULL, NULL); + tte_init_se(0, BG_CBB(TTE_CBB) | BG_SBB(TTE_SBB), 0, CLR_WHITE, TTE_BIT_UNPACK_OFFSET, NULL, NULL); tte_erase_screen(); tte_init_con(); // TTE palette setup - pal_bg_bank[12][15] = 0x029F; // Yellow. honestly fuck libtonc because i cannot figure out how you're supposed to select a color from the palette index so i'm doing it like this - pal_bg_bank[13][15] = 0x7E40; // Blue - pal_bg_bank[14][15] = 0x213F; // Red - pal_bg_bank[15][15] = CLR_WHITE; + pal_bg_bank[TTE_YELLOW_PB][TTE_BIT_ON_CLR_IDX] = TEXT_CLR_YELLOW; + pal_bg_bank[TTE_BLUE_PB][TTE_BIT_ON_CLR_IDX] = TEXT_CLR_BLUE; + pal_bg_bank[TTE_RED_PB][TTE_BIT_ON_CLR_IDX] = TEXT_CLR_RED; + pal_bg_bank[TTE_WHITE_PB][TTE_BIT_ON_CLR_IDX] = TEXT_CLR_WHITE; // Set up the video mode // BG0 is the TTE text layer