diff --git a/README.md b/README.md
index 1e8f8686..59b4fde0 100644
--- a/README.md
+++ b/README.md
@@ -1,55 +1,78 @@
-# Balatro GBA
-This is an attempt to recreate the game 'Balatro' as accurately as possible, including all of the visual effects that make Balatro feel satisfying to play.
+# Balatro-GBA
-This port is strictly limited in content to a minimal version of Balatro and will not recreate the full game.
-### **Disclaimer: This project is NOT endorsed by or affiliated with Playstack or LocalThunk**
-#### This is a non-profit community fan project solely aimed to recreate a minimal version of Balatro on the Game Boy Advance as a tribute to the full Balatro and is not meant to infringe or draw sales away from the full game's release or any of the established works by Playstack and LocalThunk.
+[](https://github.com/cellos51/balatro-gba/actions)
+[](https://github.com/cellos51/balatro-gba/issues)
+[](https://github.com/cellos51/balatro-gba/pulls)
+
+This is an attempt to recreate the game **'Balatro'** as accurately as possible, including all of the visual effects that make Balatro feel satisfying to play.
+This **tech-demo/proof of concept** is strictly limited in content to a minimal version of Balatro and will **NOT** recreate the full game. **This version is intended for people who already own and know how the official full game works.** Please refer to the Balatro Wiki if you need help understanding certain mechanics or abilities.
+
+
+
+
+
+### Disclaimer: This project is NOT endorsed by or affiliated with Playstack or LocalThunk
+#### This is a non-profit community fan project solely aimed to recreate a minimal version of Balatro on the Game Boy Advance as a tribute to the full Balatro and is not intended to infringe or draw sales away from the full game's release or any of the established works by Playstack and LocalThunk.
#### All rights are reserved to their respective holders.
-This version is meant for people who already own and know the official full game.
-Buy the official full version
+### Please buy the official full version from these sources below:
+[](https://store.steampowered.com/app/2379780/Balatro/)
+[](https://play.google.com/store/apps/details?id=com.playstack.balatro.android)
+[](https://apps.apple.com/us/app/balatro/id6502453075)
+[](https://www.nintendo.com/us/store/products/balatro-switch/)
+[](https://store.playstation.com/en-us/concept/10010334)
+[](https://www.xbox.com/en-US/games/store/balatro/9PK087LNGJC5)
+[](https://www.humblebundle.com/store/balatro?srsltid=AfmBOoqS2De8T4kizzWxJS1pbvQosJ_bYCl4qvC6LA1YLPAh4sZ8vJqO)
-[Balatro on Steam](https://store.steampowered.com/app/2379780/Balatro/)
+
+
-[Balatro on Google Play](https://play.google.com/store/apps/details?id=com.playstack.balatro.android)
-[Balatro on Apple App Store](https://apps.apple.com/us/app/balatro/id6502453075)
+### Controls:
+(A: Pick Card/Make Selections)
-[Balatro on Switch](https://www.nintendo.com/us/store/products/balatro-switch/)
+(B: Deselect All Cards)
-[Balatro on PlayStation Store](https://store.playstation.com/en-us/concept/10010334)
+(L: Sell Joker)
-[Balatro on Xbox](https://www.xbox.com/games/store/balatro/9PK087LNGJC5)
-
-
+(R: Sort Suit/Rank)
+(D-Pad: Navigation)
# **Build Instructions:**
## **-Windows-**
1. Install `devkitPro` from https://github.com/devkitPro/installer by downloading the latest release.
-2. Execute it and follow the installation instructions, you only need to install the tools for the GBA development so ensure you have it selected and install it to the default directory.
+
+2. Open it and follow the installation instructions, you only need to install the tools for the GBA development so ensure you have it selected and install it to the default directory.
+
3. Go to the Start Menu and search for `MSys2` and open it.
+
4. Install `Git` by typing this command: `pacman -S git` if you don't have it already installed.
+
5. Navigate to where you want to clone the project such as your desktop or Documents folder.
The easiest way to properly `cd` to the directory is to type `cd` and drag and drop the folder into the terminal window and press Enter.
-Example: `cd '/home/user/OneDrive/Documents/balatro-gba` or something like this.
-6. Clone the project with `git clone --recursive https://github.com/cellos51/balatro-gba.git` in the terminal window.
+- Example: `cd /home/user/OneDrive/Desktop/balatro-gba` or something like this.
+
+6. Clone the project with `git clone https://github.com/cellos51/balatro-gba.git` in the terminal window.
+
7. Use `cd` and drag the newly generated folder into the window to add the file path and press Enter.
+
8. Type `make` into the window and press Enter to start building the game.
+
9. After it completes, navigate through the `build` directory in the project folder and look for `balatro-gba.gba` and load it on an emulator or flashcart.
-## **-Linux-**
-[No Instructions Available]
+## **Common Issues:**
+#### 1. **When I drag the folder into the window and press enter, it doesn't work!**
+- After dragging the folder, nake sure to click back into the window and press enter
-### **Common Issues:**
-1. **When I drag the folder into the window and press enter, it doesn't work!**
--After dragging the folder, press the right or left arrow key until the text is no longer highlighted and then press enter.
-2. **When I run `make` it errors out and won't compile!**
--Move the project folder to your desktop and then `cd` to it by dragging the folder into the terminal window after typing `cd` first.
-3. **I can't find the build folder!**
--Look for a folder called `build` inside the project folder and then find `balatro-gba.gba`.
-4. **The Game won't start!**
--Try a different emulator or if you are using original hardware, make sure the rom is not corrupted and try a different flashcart or SD Card. If this does not work, open an issue on the Github page because a recent commit may have broke the game.
-5. **It says I don't have `Git` or `Make` installed!**
--Use `pacman -S git` or `pacman -S make` although it should already be installed if you followed the instructions correctly.
+#### 2. **When I run `make` it errors out and won't compile!**
+- Move the project folder to your desktop and then `cd` to it by dragging the folder into the terminal window after typing `cd` first.
+#### 3. **I can't find the compiled rom!**
+- Look for a folder called `build` inside the project folder and then find `balatro-gba.gba`.
+
+#### 4. **The Game won't start!**
+- Try a different emulator or if you are using original hardware, make sure the rom is not corrupted and try a different flashcart or SD Card. If this does not work, open an issue on the Github page because a recent commit may have broke the game.
+
+#### 5. **It says I don't have `Git` or `Make` installed!**
+- Use `pacman -S git` or `pacman -S make` although it should already be installed if you followed the instructions correctly.
diff --git a/example.gif b/example.gif
index b83c0e15..e2d598d0 100644
Binary files a/example.gif and b/example.gif differ
diff --git a/graphics/background_gfx.png b/graphics/background_gfx.png
index 15315a0f..798282f4 100644
Binary files a/graphics/background_gfx.png and b/graphics/background_gfx.png differ
diff --git a/graphics/joker_gfx0.png b/graphics/joker_gfx0.png
index dcc73ab8..bb69932e 100644
Binary files a/graphics/joker_gfx0.png and b/graphics/joker_gfx0.png differ
diff --git a/graphics/joker_gfx11.png b/graphics/joker_gfx11.png
index 07b537c4..f67828ff 100644
Binary files a/graphics/joker_gfx11.png and b/graphics/joker_gfx11.png differ
diff --git a/graphics/joker_gfx12.grit b/graphics/joker_gfx12.grit
new file mode 100644
index 00000000..a8ac3f6e
--- /dev/null
+++ b/graphics/joker_gfx12.grit
@@ -0,0 +1 @@
+-gB4 -Mw4 -Mh4 -m! -pn 16
\ No newline at end of file
diff --git a/graphics/joker_gfx12.png b/graphics/joker_gfx12.png
new file mode 100644
index 00000000..c36e740b
Binary files /dev/null and b/graphics/joker_gfx12.png differ
diff --git a/graphics/joker_gfx13.grit b/graphics/joker_gfx13.grit
new file mode 100644
index 00000000..a8ac3f6e
--- /dev/null
+++ b/graphics/joker_gfx13.grit
@@ -0,0 +1 @@
+-gB4 -Mw4 -Mh4 -m! -pn 16
\ No newline at end of file
diff --git a/graphics/joker_gfx13.png b/graphics/joker_gfx13.png
new file mode 100644
index 00000000..ce7f8fef
Binary files /dev/null and b/graphics/joker_gfx13.png differ
diff --git a/graphics/joker_gfx14.grit b/graphics/joker_gfx14.grit
new file mode 100644
index 00000000..a8ac3f6e
--- /dev/null
+++ b/graphics/joker_gfx14.grit
@@ -0,0 +1 @@
+-gB4 -Mw4 -Mh4 -m! -pn 16
\ No newline at end of file
diff --git a/graphics/joker_gfx14.png b/graphics/joker_gfx14.png
new file mode 100644
index 00000000..e1b6bcd6
Binary files /dev/null and b/graphics/joker_gfx14.png differ
diff --git a/graphics/joker_gfx3.png b/graphics/joker_gfx3.png
index 959c457b..9e84c1e1 100644
Binary files a/graphics/joker_gfx3.png and b/graphics/joker_gfx3.png differ
diff --git a/graphics/joker_gfx5.png b/graphics/joker_gfx5.png
index 70c21b6a..a3dbac82 100644
Binary files a/graphics/joker_gfx5.png and b/graphics/joker_gfx5.png differ
diff --git a/include/card.h b/include/card.h
index 2b003b63..e403b332 100644
--- a/include/card.h
+++ b/include/card.h
@@ -35,6 +35,8 @@
#define NUM_RANKS 13
#define RANK_OFFSET 2 // Because the first rank is 2 and ranks start at 0
+#define IMPOSSIBLY_HIGH_CARD_VALUE 100
+
// Card types
typedef struct
{
diff --git a/include/game.h b/include/game.h
index 55e87306..ed65d3d1 100644
--- a/include/game.h
+++ b/include/game.h
@@ -23,6 +23,7 @@
#define PEEK_DECK KEY_L // Not implemented
#define SORT_HAND KEY_R
#define PAUSE_GAME KEY_START // Not implemented
+#define SELL_KEY KEY_L
enum GameState
{
@@ -31,6 +32,7 @@ enum GameState
GAME_SHOP,
GAME_BLIND_SELECT,
GAME_LOSE,
+ GAME_WIN
};
enum HandState
@@ -74,6 +76,10 @@ enum HandType
void game_init();
void game_update();
+// Forward declaration
+struct List;
+typedef struct List List;
+
// Utility functions for other files
typedef struct CardObject CardObject; // forward declaration, actually declared in card.h
typedef struct JokerObject JokerObject;
@@ -82,9 +88,10 @@ int get_hand_top(void);
int hand_get_size(void);
CardObject** get_played_array(void);
int get_played_top(void);
-JokerObject** get_jokers(void);
-int get_jokers_top(void);
+List* get_jokers(void);
+int get_deck_top(void);
int get_num_discards_remaining(void);
+int get_money(void);
-#endif // GAME_H
\ No newline at end of file
+#endif // GAME_H
diff --git a/include/graphic_utils.h b/include/graphic_utils.h
index 2e80828c..1df8e49c 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
@@ -34,45 +49,55 @@
#define SE_COL_LEN 32
// Since y direction goes from the top of the screen to the bottom
-#define SE_UP -1
-#define SE_DOWN 1
+#define SCREEN_UP -1
+#define SCREEN_DOWN 1
+#define SCREEN_LEFT -1
+#define SCREEN_RIGHT 1
-#define OVERFLOW_LEFT -1
-#define OVERFLOW_RIGHT 1
+#define SE_UP SCREEN_UP
+#define SE_DOWN SCREEN_DOWN
+
+#define OVERFLOW_LEFT SCREEN_LEFT
+#define OVERFLOW_RIGHT SCREEN_RIGHT
// Tile size in pixels, both height and width as tiles are square
#define TILE_SIZE 8
+#define EFFECT_TEXT_SEPARATION_AMOUNT 32; // If we need to show multiple effects at once
// 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.
*/
@@ -90,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/include/joker.h b/include/joker.h
index c9598839..3ee358bf 100644
--- a/include/joker.h
+++ b/include/joker.h
@@ -78,6 +78,7 @@ void joker_destroy(Joker **joker);
// Unique effects like "Four Fingers" or "Credit Card" will be hard coded into game.c with a conditional check for the joker ID from the players owned jokers
// game.c should probably be restructured so most of the variables in it are moved to some sort of global variable header file so they can be easily accessed and modified for the jokers
JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card);
+int joker_get_sell_value(const Joker* joker);
JokerObject *joker_object_new(Joker *joker);
void joker_object_destroy(JokerObject **joker_object);
diff --git a/include/joker_gfx.h b/include/joker_gfx.h
index 75cfbbc9..bcb1ad26 100644
--- a/include/joker_gfx.h
+++ b/include/joker_gfx.h
@@ -13,5 +13,8 @@
#include "joker_gfx9.h"
#include "joker_gfx10.h"
#include "joker_gfx11.h"
+#include "joker_gfx12.h"
+#include "joker_gfx13.h"
+#include "joker_gfx14.h"
#endif
\ No newline at end of file
diff --git a/include/list.h b/include/list.h
index 64341f71..fd1c3669 100644
--- a/include/list.h
+++ b/include/list.h
@@ -3,23 +3,23 @@
#include
-/* Defining an int list for simplicity
- * If another list is needed, a generic void*
- * list should be defined....
- */
-typedef struct IntList
+typedef struct List
{
- int* _array;
+ void** _array;
int size;
int allocated_size;
-} IntList;
+} List;
-IntList *int_list_new(int init_size);
-void int_list_destroy(IntList **list);
-bool int_list_append(IntList *list, int value);
-bool int_list_remove_by_idx(IntList *list, int index);
-int int_list_get(IntList *list, int index);
-int int_list_get_size(IntList *list);
-bool int_list_remove_by_value(IntList *list, int value);
+List *list_new(int init_size);
+void list_destroy(List **list);
+bool list_append(List *list, void *value);
+bool list_remove_by_idx(List *list, int index);
+void* list_get(List *list, int index);
+int list_get_size(List *list);
+bool list_remove_by_value(List *list, void *value);
+
+bool int_list_append(List *list, intptr_t value);
+intptr_t int_list_get(List *list, int index);
+bool int_list_remove_by_value(List *list, intptr_t value);
#endif
diff --git a/include/selection_grid.h b/include/selection_grid.h
new file mode 100644
index 00000000..9ddd2bfb
--- /dev/null
+++ b/include/selection_grid.h
@@ -0,0 +1,41 @@
+#ifndef SELECTION_GRID_H
+#define SELECTION_GRID_H
+#include
+
+typedef POINT Selection;
+
+struct SelectionGridRow;
+struct SelectionGrid;
+typedef struct SelectionGridRow SelectionGridRow;
+typedef struct SelectionGrid SelectionGrid;
+
+// Called whenever there is a change in the selection cursor
+// row_idx is the index of the row whose function is invoked - can be used to identify whether it is the previous or new selection row.
+typedef void (*RowOnSelectionChangedFunc)(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection);
+typedef int (*RowGetSizeFunc)();
+// Called for any non-directional key hit
+// The key will not be passed, the function will have to check key_hit() etc. for the key it wants to check
+typedef void (*RowOnKeyHitFunc)(SelectionGrid* selection_grid, Selection* selection);
+
+struct SelectionGridRow
+{
+ int row_idx;
+ RowGetSizeFunc get_size;
+ RowOnSelectionChangedFunc on_selection_changed;
+ RowOnKeyHitFunc on_key_hit;
+};
+
+struct SelectionGrid
+{
+ SelectionGridRow* rows;
+ const int num_rows;
+ Selection selection;
+ };
+
+
+void selection_grid_process_input(SelectionGrid* selection_grid);
+
+void selection_grid_move_selection_horz(SelectionGrid* selection_grid, int direction_tribool);
+void selection_grid_move_selection_vert(SelectionGrid* selection_grid, int direction_tribool);
+
+#endif
\ No newline at end of file
diff --git a/include/sprite.h b/include/sprite.h
index b4d6bca5..b3f27b83 100644
--- a/include/sprite.h
+++ b/include/sprite.h
@@ -27,6 +27,8 @@ typedef struct
FIXED rotation;
FIXED vrotation;
bool selected;
+ bool focused;
+
} SpriteObject;
// Sprite methods
@@ -58,6 +60,7 @@ void sprite_object_shake(SpriteObject* sprite_object, mm_word sound_id);
void sprite_object_set_selected(SpriteObject* sprite_object, bool selected);
bool sprite_object_is_selected(SpriteObject* sprite_object);
Sprite* sprite_object_get_sprite(SpriteObject* sprite_object);
-
+void sprite_object_set_focus(SpriteObject* sprite_object, bool focus);
+bool sprite_object_is_focused(SpriteObject* sprite_object);
#endif // SPRITE_H
\ No newline at end of file
diff --git a/source/game.c b/source/game.c
index 83ef5255..ce165ca0 100644
--- a/source/game.c
+++ b/source/game.c
@@ -15,6 +15,7 @@
#include "graphic_utils.h"
#include "tonc_video.h"
#include "audio_utils.h"
+#include "selection_grid.h"
#include "background_gfx.h"
#include "background_shop_gfx.h"
@@ -75,10 +76,11 @@ static int selection_y = 0;
static bool sort_by_suit = false;
-// Stacks
-static JokerObject *jokers[MAX_JOKERS_HELD_SIZE] = {NULL};
-static int jokers_top = -1;
+static List *jokers = NULL;
+static List *discarded_jokers = NULL;
+static List* jokers_available_to_shop; // List of joker IDs
+// Stacks
static CardObject *played[MAX_SELECTION_SIZE] = {NULL};
static int played_top = -1;
@@ -91,19 +93,6 @@ static int deck_top = -1;
static Card *discard_pile[MAX_DECK_SIZE] = {NULL};
static int discard_top = -1;
-// Joker stack
-static inline void joker_push(JokerObject *joker)
-{
- if (jokers_top >= MAX_JOKERS_HELD_SIZE - 1) return;
- jokers[++jokers_top] = joker;
-}
-
-static inline JokerObject *joker_pop()
-{
- if (jokers_top < 0) return NULL;
- return jokers[jokers_top--];
-}
-
// Played stack
static inline void played_push(CardObject *card_object)
{
@@ -165,15 +154,24 @@ int get_played_top(void) {
return played_top;
}
-JokerObject **get_jokers(void) {
+List *get_jokers(void) {
return jokers;
}
-int get_jokers_top(void) {
- return jokers_top;
+
+
+int get_deck_top(void) {
+ return deck_top;
+}
+
+int get_num_discards_remaining(void) {
+ return discards;
+}
+
+int get_money(void) {
+ return money;
}
-IntList* jokers_available_to_shop; // List of joker IDs
// Consts
@@ -188,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?
@@ -207,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
@@ -238,22 +239,29 @@ 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};
+
+#define ITEM_SHOP_Y 71 // TODO: Needs to be a rect?
//TODO: Properly define and use
#define MENU_POP_OUT_ANIM_FRAMES 20
+#define GAME_OVER_ANIM_FRAMES 15
#define SCORED_CARD_TEXT_Y 48
+#define HIGHLIGHT_COLOR 0xFFFF
+
#define PITCH_STEP_DISCARD_SFX (-64)
#define PITCH_STEP_DRAW_SFX 24
#define PITCH_STEP_UNDISCARD_SFX 2*PITCH_STEP_DRAW_SFX
// Naming the stage where cards return from the discard pile to the deck "undiscard"
-int get_num_discards_remaining(void) {
- return discards;
-}
-
// General functions
void set_seed(int seed)
{
@@ -515,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);
@@ -631,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)
@@ -660,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)
@@ -675,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)
@@ -711,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()
@@ -921,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);
@@ -967,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)
@@ -988,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;
@@ -995,18 +1029,18 @@ 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);
game_state = new_game_state;
}
-void jokers_avialable_to_shop_init()
+void jokers_available_to_shop_init()
{
int num_defined_jokers = get_joker_registry_size();
- jokers_available_to_shop = int_list_new(num_defined_jokers);
- for (int i = 0; i < num_defined_jokers; i++)
+ jokers_available_to_shop = list_new(num_defined_jokers);
+ for (intptr_t i = 0; i < num_defined_jokers; i++)
{
// Add all joker IDs sequentially
int_list_append(jokers_available_to_shop, i);
@@ -1015,7 +1049,14 @@ void jokers_avialable_to_shop_init()
void game_init()
{
- jokers_avialable_to_shop_init();
+ jokers_available_to_shop_init();
+
+ // Initialize jokers list
+ if (jokers) list_destroy(&jokers);
+ jokers = list_new(MAX_JOKERS_HELD_SIZE);
+
+ if (discarded_jokers != NULL) list_destroy(&discarded_jokers);
+ discarded_jokers = list_new(MAX_JOKERS_HELD_SIZE);
hands = max_hands;
discards = max_discards;
@@ -1040,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
@@ -1051,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()
@@ -1128,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);
}
}
}
@@ -1228,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)
@@ -1266,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);
@@ -1309,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)
@@ -1548,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;
}
@@ -1610,9 +1698,10 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
if (*played_selections > 0)
{
- for (int k = 0; k <= jokers_top; k++)
+ for (int k = 0; k < list_get_size(jokers); k++)
{
- if (joker_object_score(jokers[k], played[*played_selections - 1]->card, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
+ JokerObject *joker = list_get(jokers, k);
+ if (joker_object_score(joker, played[*played_selections - 1]->card, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
display_chips(chips);
display_mult(mult);
@@ -1628,11 +1717,12 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
scored_cards = j + 1; // Count the number of cards that have been scored
if (scored_cards > *played_selections)
{
- for (int k = 0; k <= jokers_top; k++)
+ for (int k = 0; k < list_get_size(jokers); k++)
{
- if (jokers[k] != NULL)
+ JokerObject *joker = list_get(jokers, k);
+ if (joker != NULL)
{
- jokers[k]->joker->processed = false; // Reset the joker's processed state for the next score
+ joker->joker->processed = false; // Reset the joker's processed state for the next score
}
}
@@ -1659,9 +1749,10 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
{
tte_erase_rect_wrapper(PLAYED_CARDS_SCORES_RECT);
- for (int k = 0; k <= jokers_top; k++) // Independent joker scoring loop
+ for (int k = 0; k <= list_get_size(jokers); k++) // Independent joker scoring loop
{
- if (joker_object_score(jokers[k], NULL, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
+ JokerObject *joker = list_get(jokers, k);
+ if (joker_object_score(joker, NULL, &chips, &mult, NULL, &money, NULL)) // NULLs aren't implemented yet
{
display_chips(chips);
display_mult(mult);
@@ -1671,11 +1762,12 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
}
}
- for (int k = 0; k <= jokers_top; k++)
+ for (int k = 0; k <= list_get_size(jokers); k++)
{
- if (jokers[k] != NULL)
+ JokerObject *joker = list_get(jokers, k);
+ if (joker != NULL)
{
- jokers[k]->joker->processed = false; // Reset the joker's processed state for the next round
+ joker->joker->processed = false; // Reset the joker's processed state for the next round
}
}
@@ -1732,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
{
@@ -1770,34 +1857,6 @@ static void played_cards_update_loop(bool* discarded_card, int* played_selection
}
}
-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(108);
- FIXED hand_y = int2fx(10);
-
- for (int i = jokers_top; i >= 0; i--)
- {
- jokers[i]->sprite_object->tx = hand_x - int2fx(spacing_lut[jokers_top][i]);
- jokers[i]->sprite_object->ty = hand_y;
-
- if (joker_object_is_selected(jokers[i]))
- {
- jokers[i]->sprite_object->ty -= int2fx(10);
- }
-
- joker_object_update(jokers[i]);
- }
-}
-
static void game_playing_ui_text_update()
{
static int last_hand_size = 0;
@@ -1807,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();
@@ -1837,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)
{
@@ -1923,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)
{
@@ -1968,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))
{
@@ -2050,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
{
@@ -2061,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
}
}
@@ -2087,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
{
@@ -2137,44 +2189,77 @@ void game_round_end()
}
}
-static void game_shop_create_items(JokerObject *shop_jokers[])
+// Shop
+static List *shop_jokers = NULL;
+#define REROLL_BASE_COST 5 // Base cost for rerolling the shop items
+static int reroll_cost = REROLL_BASE_COST;
+
+#define NEXT_ROUND_BTN_SEL_X 0
+
+#define NEXT_ROUND_BTN_FRAME_PAL_IDX 5
+#define NEXT_ROUND_BTN_PAL_IDX 16
+#define REROLL_BTN_FRAME_PAL_IDX 7
+#define REROLL_BTN_PAL_IDX 3
+
+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:0x%X000}$%d", x, y, TTE_YELLOW_PB, price);
+}
+
+void erase_price_under_sprite_object(SpriteObject *sprite_object)
+{
+ Rect price_rect;
+ price_rect.left = fx2int(sprite_object->tx);
+ price_rect.top = fx2int(sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE;
+ // TODO: Should probably extract the size from the sprite
+
+ price_rect.right = price_rect.left + TILE_SIZE * 3;
+ price_rect.bottom = price_rect.top + 2*TILE_SIZE; // Taking 2 tiles down so the highlighted case is also covered
+ tte_erase_rect_wrapper(price_rect);
+}
+
+static void game_shop_create_items()
{
tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT);
- if (int_list_get_size(jokers_available_to_shop) == 0)
+ if (list_get_size(jokers_available_to_shop) == 0)
{
// No jokers to create
return;
}
+ shop_jokers = list_new(MAX_SHOP_JOKERS);
+
for (int i = 0; i < MAX_SHOP_JOKERS; i++)
{
// TODO: weight the random choice by joker rarity
- int joker_idx = random() % int_list_get_size(jokers_available_to_shop);
- int joker_id = int_list_get(jokers_available_to_shop, joker_idx);
- int_list_remove_by_idx(jokers_available_to_shop, joker_idx);
+ int joker_idx = random() % list_get_size(jokers_available_to_shop);
+ intptr_t joker_id = int_list_get(jokers_available_to_shop, joker_idx);
+ list_remove_by_idx(jokers_available_to_shop, joker_idx);
- shop_jokers[i] = joker_object_new(joker_new(joker_id));
- shop_jokers[i]->sprite_object->x = int2fx(120 + i * 32);
- shop_jokers[i]->sprite_object->y = int2fx(160);
- shop_jokers[i]->sprite_object->tx = shop_jokers[i]->sprite_object->x;
- shop_jokers[i]->sprite_object->ty = int2fx(71);
+ JokerObject *joker_object = joker_object_new(joker_new(joker_id));
- int x = fx2int(shop_jokers[i]->sprite_object->tx) + TILE_SIZE - (get_digits_even(shop_jokers[i]->joker->value) - 1) * TILE_SIZE;
- int y = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + TILE_SIZE;
- tte_printf("#{P:%d,%d; cx:0xC000}$%d", x, y, shop_jokers[i]->joker->value);
+ joker_object->sprite_object->x = int2fx(120 + i * CARD_SPRITE_SIZE);
+ joker_object->sprite_object->y = int2fx(160);
+ joker_object->sprite_object->tx = joker_object->sprite_object->x;
+ joker_object->sprite_object->ty = int2fx(ITEM_SHOP_Y);
- sprite_position(joker_object_get_sprite(shop_jokers[i]), fx2int(shop_jokers[i]->sprite_object->x), fx2int(shop_jokers[i]->sprite_object->y));
+ print_price_under_sprite_object(joker_object->sprite_object, joker_object->joker->value);
+
+ sprite_position(joker_object_get_sprite(joker_object), fx2int(joker_object->sprite_object->x), fx2int(joker_object->sprite_object->y));
+ list_append(shop_jokers, joker_object);
}
}
// Intro sequence (menu and shop icon coming into frame)
-static void game_shop_intro(JokerObject* shop_jokers[])
+static void game_shop_intro()
{
main_bg_se_copy_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_UP);
if (timer == 1)
{
- game_shop_create_items(shop_jokers);
+ game_shop_create_items();
}
if (timer >= 7) // Shift the shop icon
@@ -2201,153 +2286,247 @@ static void game_shop_intro(JokerObject* shop_jokers[])
}
}
-static void game_shop_reroll(JokerObject** shop_jokers, int *reroll_cost)
+static void game_shop_reroll(int *reroll_cost)
{
money -= *reroll_cost;
display_money(money); // Update the money display
- for (int i = 0; i < MAX_SHOP_JOKERS; i++)
+ for (int i = 0; i < list_get_size(shop_jokers); i++)
{
- if (shop_jokers[i] != NULL)
+ JokerObject *joker_object = list_get(shop_jokers, i);
+ if (joker_object != NULL)
{
- int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
- joker_object_destroy(&shop_jokers[i]); // Destroy the joker object if it exists
+ int_list_append(jokers_available_to_shop, joker_object->joker->id);
+ joker_object_destroy(&joker_object); // Destroy the joker object if it exists
}
}
- game_shop_create_items(shop_jokers);
+ list_destroy(&shop_jokers);
+
+ game_shop_create_items();
- for (int i = 0; i < MAX_SHOP_JOKERS; i++)
+ for (int i = 0; i < list_get_size(shop_jokers); i++)
{
- if (shop_jokers[i] != NULL)
+ JokerObject *joker_object = list_get(shop_jokers, i);
+ if (joker_object != NULL)
{
- shop_jokers[i]->sprite_object->y = shop_jokers[i]->sprite_object->ty; // Set the y position to the target position
- joker_object_shake(shop_jokers[i], UNDEFINED); // Give the joker a little wiggle animation
+ joker_object->sprite_object->y = joker_object->sprite_object->ty; // Set the y position to the target position
+ joker_object_shake(joker_object, UNDEFINED); // Give the joker a little wiggle animation
}
}
(*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);
}
-// Shop menu input and selection
-static void game_shop_process_user_input(JokerObject** shop_jokers)
+static int jokers_sel_row_get_size()
{
- static const int REROLL_BASE_COST = 5; // Base cost for rerolling the shop items
- static int reroll_cost = REROLL_BASE_COST;
+ return list_get_size(jokers);
+}
- // temp variables for future implementation
- const ushort max_items_top = MAX_SHOP_JOKERS;
- const ushort max_items_bottom = 0;
+static void jokers_sel_row_on_selection_changed(SelectionGrid *selection_grid,
+ int row_idx,
+ const Selection *prev_selection,
+ const Selection *new_selection)
+{
+ if (prev_selection->y == row_idx)
+ {
+ JokerObject* joker_object = list_get(jokers, prev_selection->x);
+ erase_price_under_sprite_object(joker_object->sprite_object);
+ sprite_object_set_focus(joker_object->sprite_object, false);
+ }
+ if (new_selection->y == row_idx)
+ {
+ JokerObject* joker_object = list_get(jokers, new_selection->x);
+ sprite_object_set_focus(joker_object->sprite_object, true);
+ print_price_under_sprite_object(joker_object->sprite_object, joker_get_sell_value(joker_object->joker));
+ }
+}
+
+void joker_start_discard_animation(JokerObject *joker_object)
+{
+ joker_object->sprite_object->tx = int2fx(JOKER_DISCARD_TARGET.x);
+ joker_object->sprite_object->ty = int2fx(JOKER_DISCARD_TARGET.y);
+ list_append(discarded_jokers, joker_object);
+}
+
+void game_sell_joker(int joker_idx)
+{
+ if (joker_idx < 0 || joker_idx > list_get_size(jokers))
+ return;
+
+ JokerObject *joker_object = list_get(jokers, joker_idx);
+ money += joker_get_sell_value(joker_object->joker);
+ display_money(money);
+ erase_price_under_sprite_object(joker_object->sprite_object);
+
+ list_remove_by_idx(jokers, joker_idx);
+ int_list_append(jokers_available_to_shop, (intptr_t)joker_object->joker->id);
+
+ joker_start_discard_animation(joker_object);
+}
+
+static void jokers_sel_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection)
+{
+ if (!key_hit(SELL_KEY))
+ return;
+
+ game_sell_joker(selection->x);
+ // Move the selection away from the jokers so it doesn't point to an invalid place
+ selection_grid_move_selection_vert(selection_grid, SCREEN_DOWN);
+}
+
+// Shop input
+static int shop_top_row_get_size()
+{
+ return list_get_size(shop_jokers) + 1; // + 1 to account for next round button
+}
+
+void add_joker(JokerObject *joker_object)
+{
+ list_append(jokers, joker_object);
+ joker_object->sprite_object->ty = int2fx(HELD_JOKERS_POS.y);
+}
+
+static void game_shop_buy_joker(int shop_joker_idx)
+{
+ JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx);
+
+ money -= joker_object->joker->value; // Deduct the money spent on the joker
+ display_money(money); // Update the money display
+ erase_price_under_sprite_object(joker_object->sprite_object);
+ sprite_object_set_focus(joker_object->sprite_object, false);
+
+ add_joker(joker_object);
+ list_remove_by_idx(shop_jokers, shop_joker_idx); // Remove the joker from the shop
+}
+
+static void shop_top_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection)
+{
+ if (!key_hit(SELECT_CARD))
+ return;
+
+ if (selection->x == NEXT_ROUND_BTN_SEL_X)
+ {
+ // Go to next blind selection game state
+ state = 2; // Go to the outro sequence state
+ timer = 0; // Reset the timer
+ reroll_cost = REROLL_BASE_COST;
+
+ memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], &pal_bg_mem[6], 1);
+
+ // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1);
+ // This changes the color of the button to a dark red.
+ // However, it shares a palette with the shop icon, so it will change the color of the shop icon as well.
+ // And I don't care enough to fix it right now.
+ }
+ else
+ {
+ int shop_joker_idx = selection->x - 1; // - 1 to account for next round button
+ JokerObject *joker_object = list_get(shop_jokers, shop_joker_idx);
+ if (joker_object == NULL
+ || list_get_size(jokers) >= MAX_JOKERS_HELD_SIZE
+ || money < joker_object->joker->value)
+ {
+ return;
+ }
+
+ game_shop_buy_joker(shop_joker_idx);
+
+ // In Balatro the selection actually stays on the purchased joker it's easier to just move it left
+ selection_grid_move_selection_horz(selection_grid, -1);
+ }
+}
+
+
+
+static void shop_top_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx,
+ const Selection* prev_selection,
+ const Selection* new_selection)
+{
+ // The selection grid system only guarantees that the new selection is within bounds
+ if (prev_selection->y == row_idx && prev_selection->x >= 0 && prev_selection->x < shop_top_row_get_size())
+ {
+ if (prev_selection->x == NEXT_ROUND_BTN_SEL_X)
+ {
+ // Remove next round button highlight
+ memcpy16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], &pal_bg_mem[NEXT_ROUND_BTN_PAL_IDX], 1);
+ }
+ else
+ {
+ JokerObject *joker = list_get(shop_jokers, prev_selection->x - 1);
+ sprite_object_set_focus(joker->sprite_object, false);
+ // -1 to account for next round button
+ }
+ }
+
+ if (new_selection->y == row_idx)
+ {
+ if (new_selection->x == NEXT_ROUND_BTN_SEL_X)
+ {
+ // Highlight next round button
+ memset16(&pal_bg_mem[NEXT_ROUND_BTN_FRAME_PAL_IDX], HIGHLIGHT_COLOR, 1);
+ }
+ else
+ {
+ JokerObject *joker = list_get(shop_jokers, new_selection->x - 1);
+ sprite_object_set_focus(joker->sprite_object, true);
+ // -1 to account for next round button
+ }
+ }
+}
+
+static int shop_reroll_row_get_size()
+{
+ return 1; // Only the reroll button
+}
+
+static void shop_reroll_row_on_selection_changed(SelectionGrid* selection_grid, int row_idx, const Selection* prev_selection, const Selection* new_selection)
+{
+ if (row_idx == prev_selection->y)
+ {
+ // Remove highlight
+ memcpy16(&pal_bg_mem[REROLL_BTN_FRAME_PAL_IDX], &pal_bg_mem[REROLL_BTN_PAL_IDX], 1);
+ }
+ else if (row_idx == new_selection->y)
+ {
+ memset16(&pal_bg_mem[REROLL_BTN_FRAME_PAL_IDX], HIGHLIGHT_COLOR, 1);
+ }
+}
+
+static void shop_reroll_row_on_key_hit(SelectionGrid* selection_grid, Selection* selection)
+{
+ if (money >= reroll_cost)
+ {
+ game_shop_reroll(&reroll_cost);
+ }
+}
+
+SelectionGridRow shop_selection_rows[] =
+{
+ {0, jokers_sel_row_get_size, jokers_sel_row_on_selection_changed, jokers_sel_row_on_key_hit},
+ {1, shop_top_row_get_size, shop_top_row_on_selection_changed, shop_top_row_on_key_hit},
+ {2, shop_reroll_row_get_size, shop_reroll_row_on_selection_changed, shop_reroll_row_on_key_hit}
+};
+
+static const Selection SHOP_INIT_SEL = {-1, 1};
+
+SelectionGrid shop_selection_grid = {shop_selection_rows, NUM_ELEM_IN_ARR(shop_selection_rows), SHOP_INIT_SEL};
+
+// Shop menu input and selection
+static void game_shop_process_user_input()
+{
if (timer == 1)
{
- tte_printf("#{P:%d,%d; cx:0xF000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, reroll_cost);
+ // The selection grid is initialized outside of bounds and moved
+ // 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:0x%X000}$%d", SHOP_REROLL_RECT.left, SHOP_REROLL_RECT.top, TTE_WHITE_PB, reroll_cost);
}
// Shop input logic
- if (key_hit(KEY_UP))
- {
- selection_y = 0;
-
- if (selection_x > max_items_top)
- {
- selection_x = max_items_top;
- }
- }
- else if (key_hit(KEY_DOWN))
- {
- selection_y = 1;
-
- if (selection_x > max_items_bottom)
- {
- selection_x = max_items_bottom;
- }
- }
- else if (key_hit(KEY_LEFT))
- {
- if (selection_x > 0)
- {
- selection_x--;
- }
- }
- else if (key_hit(KEY_RIGHT))
- {
- // TODO: Don't allow cursor on sold items
- if (selection_y == 0 && selection_x < max_items_top)
- {
- selection_x++;
- }
- else if (selection_y == 1 && selection_x < max_items_bottom)
- {
- selection_x++;
- }
- }
-
- memcpy16(&pal_bg_mem[7], &pal_bg_mem[3], 1);
- memcpy16(&pal_bg_mem[5], &pal_bg_mem[16], 1);
-
- // Shop selection logic
- if (selection_x == 0 && selection_y == 0) // Next round button
- {
- memset16(&pal_bg_mem[5], 0xFFFF, 1);
-
- if (key_hit(SELECT_CARD))
- {
- // Go to next blind selection game state
- state = 2; // Go to the outro sequence state
- timer = 0; // Reset the timer
- reroll_cost = REROLL_BASE_COST;
-
- memcpy16(&pal_bg_mem[5], &pal_bg_mem[6], 1);
-
- // memcpy16(&pal_bg_mem[16], &pal_bg_mem[6], 1);
- // This changes the color of the button to a dark red.
- // However, it shares a palette with the shop icon, so it will change the color of the shop icon as well.
- // And I don't care enough to fix it right now.
- }
- }
- else if (selection_x == 0 && selection_y == 1) // Reroll button
- {
- memset16(&pal_bg_mem[7], 0xFFFF, 1);
-
- if (key_hit(SELECT_CARD) && money >= reroll_cost)
- {
- game_shop_reroll(shop_jokers, &reroll_cost);
- }
- }
-
- for (int i = 0; i < MAX_SHOP_JOKERS; i++) // Item selection logic
- {
- if (shop_jokers[i] != NULL)
- {
- if (i == selection_x - 1 && selection_y == 0)
- {
- shop_jokers[i]->sprite_object->ty = int2fx(61);
-
- if (key_hit(SELECT_CARD) && jokers_top < MAX_JOKERS_HELD_SIZE - 1 && money >= shop_jokers[i]->joker->value)
- {
- joker_push(shop_jokers[i]);
- money -= shop_jokers[i]->joker->value; // Deduct the money spent on the joker
- display_money(money); // Update the money display
-
- Rect joker_price_rect;
- joker_price_rect.left = fx2int(shop_jokers[i]->sprite_object->tx);
- // 2*TILE_SIZE because the item is highlighted and raised by 1 additional tile
- joker_price_rect.top = fx2int(shop_jokers[i]->sprite_object->ty) + CARD_SPRITE_SIZE + 2*TILE_SIZE;
- joker_price_rect.right = joker_price_rect.left + TILE_SIZE * 3;
- joker_price_rect.bottom = joker_price_rect.top + TILE_SIZE;
-
- tte_erase_rect_wrapper(joker_price_rect);
-
- shop_jokers[i] = NULL; // Remove the joker from the shop
- }
- }
- else
- {
- shop_jokers[i]->sprite_object->ty = int2fx(71);
- }
- }
- }
+ selection_grid_process_input(&shop_selection_grid);
}
static void game_shop_lights_anim_frame()
@@ -2375,20 +2554,66 @@ static void game_shop_lights_anim_frame()
memcpy16(&pal_bg_mem[8], &shifted_palette[3], 1);
}
+// Outro sequence (menu and shop icon going out of frame)
+static void game_shop_outro()
+{
+ // Shift the shop panel
+ main_bg_se_move_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_DOWN);
+
+ main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP);
+
+ if (timer == 1)
+ {
+ tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text
+
+ for (int i = 0; i < list_get_size(shop_jokers); i++)
+ {
+ JokerObject *joker_object = list_get(shop_jokers, i);
+ if (joker_object != NULL)
+ {
+ joker_object->sprite_object->ty = int2fx(160);
+ }
+ }
+
+ int y = 6;
+ memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0006, 1);
+ memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0007, 2);
+ memset16(&se_mem[MAIN_BG_SBB][3 + 32 * (y - 1)], 0x0008, 1);
+ memset16(&se_mem[MAIN_BG_SBB][4 + 32 * (y - 1)], 0x0009, 4);
+ memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1);
+ memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1);
+ }
+ else if (timer == 2)
+ {
+ int y = 5;
+ memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1);
+ memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7);
+ memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1);
+ }
+
+ if (timer >= MENU_POP_OUT_ANIM_FRAMES)
+ {
+ state = 3; // Go to the next state
+ timer = 0; // Reset the timer
+ }
+}
void game_shop()
{
change_background(BG_ID_SHOP);
- static JokerObject *shop_jokers[MAX_SHOP_JOKERS] = {NULL};
-
- for (int i = 0; i < MAX_SHOP_JOKERS; i++)
+ if (shop_jokers != NULL)
{
- if (shop_jokers[i] != NULL)
+ for (int i = 0; i < list_get_size(shop_jokers); i++)
{
- joker_object_update(shop_jokers[i]);
+ JokerObject *joker_object = list_get(shop_jokers, i);
+ if (joker_object != NULL)
+ {
+ joker_object_update(joker_object);
+ }
}
}
+
if (timer % 20 == 0)
{
@@ -2399,73 +2624,35 @@ void game_shop()
{
case 0:
{
- game_shop_intro(shop_jokers);
+ game_shop_intro();
break;
}
case 1:
{
- game_shop_process_user_input(shop_jokers);
+ game_shop_process_user_input();
break;
}
- case 2: // Outro sequence (menu and shop icon going out of frame)
+ case 2:
{
- // Shift the shop panel
- main_bg_se_move_rect_1_tile_vert(POP_MENU_ANIM_RECT, SE_DOWN);
-
- main_bg_se_copy_rect_1_tile_vert(TOP_LEFT_PANEL_ANIM_RECT, SE_UP);
-
- if (timer == 1)
- {
- tte_erase_rect_wrapper(SHOP_PRICES_TEXT_RECT); // Erase the shop prices text
-
- for (int i = 0; i < MAX_SHOP_JOKERS; i++)
- {
- if (shop_jokers[i] != NULL)
- {
- shop_jokers[i]->sprite_object->ty = int2fx(160);
- }
- }
-
- int y = 6;
- memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0006, 1);
- memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0007, 2);
- memset16(&se_mem[MAIN_BG_SBB][3 + 32 * (y - 1)], 0x0008, 1);
- memset16(&se_mem[MAIN_BG_SBB][4 + 32 * (y - 1)], 0x0009, 4);
- memset16(&se_mem[MAIN_BG_SBB][7 + 32 * (y - 1)], 0x000A, 1);
- memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0406, 1);
- }
- else if (timer == 2)
- {
- int y = 5;
- memset16(&se_mem[MAIN_BG_SBB][32 * (y - 1)], 0x0001, 1);
- memset16(&se_mem[MAIN_BG_SBB][1 + 32 * (y - 1)], 0x0002, 7);
- memset16(&se_mem[MAIN_BG_SBB][8 + 32 * (y - 1)], 0x0401, 1);
- }
-
- if (timer >= MENU_POP_OUT_ANIM_FRAMES)
- {
- state = 3; // Go to the next state
- timer = 0; // Reset the timer
- }
-
+ game_shop_outro();
break;
}
default:
state = 0; // Reset the state
- selection_x = 0; // Reset the selection
- selection_y = 0; // Reset the selection
-
- for (int i = 0; i < MAX_SHOP_JOKERS; i++)
+ for (int i = 0; i < list_get_size(shop_jokers); i++)
{
- if (shop_jokers[i] != NULL)
+ JokerObject* joker_object = list_get(shop_jokers, i);
+ if (joker_object != NULL)
{
- // Make the joker available back to shop
- int_list_append(jokers_available_to_shop, shop_jokers[i]->joker->id);
+ // Make the joker available back to shop
+ int_list_append(jokers_available_to_shop, (intptr_t)joker_object->joker->id);
}
- joker_object_destroy(&shop_jokers[i]); // Destroy the joker objects
+ joker_object_destroy(&joker_object); // Destroy the joker objects
}
+ list_destroy(&shop_jokers);
+
increment_blind(BLIND_DEFEATED); // TODO: Move to game_round_end()?
game_set_state(GAME_BLIND_SELECT); // If we reach here, we should go to the blind select state
@@ -2473,8 +2660,6 @@ void game_shop()
}
}
-
-
void game_blind_select()
{
switch (state) // I'm only using magic numbers here for the sake of simplicity since it's just sequential, but you can replace them with named constants or enums if it makes it clearer
@@ -2639,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++;
@@ -2652,7 +2915,7 @@ void game_update()
}
}
- held_jokers_update_loop();
+ jokers_update_loop();
switch (game_state)
{
@@ -2669,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 b568442a..2e76aaea 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/joker.c b/source/joker.c
index 6b300618..d8665ea0 100644
--- a/source/joker.c
+++ b/source/joker.c
@@ -130,6 +130,9 @@ void joker_init()
joker_gfxTiles[9] = joker_gfx9Tiles;
joker_gfxTiles[10] = joker_gfx10Tiles;
joker_gfxTiles[11] = joker_gfx11Tiles;
+ joker_gfxTiles[12] = joker_gfx12Tiles;
+ joker_gfxTiles[13] = joker_gfx13Tiles;
+ joker_gfxTiles[14] = joker_gfx14Tiles;
joker_gfxPal[0] = joker_gfx0Pal;
joker_gfxPal[1] = joker_gfx1Pal;
@@ -143,6 +146,9 @@ void joker_init()
joker_gfxPal[9] = joker_gfx9Pal;
joker_gfxPal[10] = joker_gfx10Pal;
joker_gfxPal[11] = joker_gfx11Pal;
+ joker_gfxPal[12] = joker_gfx12Pal;
+ joker_gfxPal[13] = joker_gfx13Pal;
+ joker_gfxPal[14] = joker_gfx14Pal;
for (int i = 0; i < num_spritesheets; i++)
{
@@ -181,6 +187,16 @@ JokerEffect joker_get_score_effect(Joker *joker, Card *scored_card)
return jinfo->effect(joker, scored_card);
}
+int joker_get_sell_value(const Joker* joker)
+{
+ if (joker == NULL)
+ {
+ return UNDEFINED;
+ }
+
+ return joker->value/2;
+}
+
// JokerObject methods
JokerObject *joker_object_new(Joker *joker)
{
@@ -320,7 +336,6 @@ bool joker_object_score(JokerObject *joker_object, Card* scored_card, int *chips
return false;
}
-
void joker_object_set_selected(JokerObject* joker_object, bool selected)
{
if (joker_object == NULL)
diff --git a/source/joker_effects.c b/source/joker_effects.c
index bab45126..1382ea2b 100644
--- a/source/joker_effects.c
+++ b/source/joker_effects.c
@@ -2,6 +2,7 @@
#include "joker.h"
#include "util.h"
#include "hand_analysis.h"
+#include "list.h"
#include
static JokerEffect default_joker_effect(Joker *joker, Card *scored_card) {
@@ -191,16 +192,19 @@ static JokerEffect joker_stencil_effect(Joker *joker, Card *scored_card) {
if (scored_card != NULL)
return effect; // if card != null, we are not at the end-phase of scoring yet
+ List* jokers = get_jokers();
+
// +1 xmult per empty joker slot...
- int num_jokers = get_jokers_top() + 1;
+ int num_jokers = list_get_size(jokers);
effect.xmult = (MAX_JOKERS_HELD_SIZE) - num_jokers;
// ...and also each stencil_joker adds +1 xmult
- JokerObject** jokers = get_jokers();
+
for (int i = 0; i < num_jokers; i++ )
{
- if (jokers[i]->joker->id == JOKER_STENCIL_ID)
+ JokerObject* joker_object = list_get(jokers, i);
+ if (joker_object->joker->id == JOKER_STENCIL_ID)
effect.xmult++;
}
@@ -290,30 +294,227 @@ static JokerEffect blackboard_joker_effect(Joker *joker, Card *scored_card) {
return effect;
}
+static JokerEffect blue_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card != NULL)
+ return effect; // if card != null, we are not at the end-phase of scoring yet
+
+ effect.chips = (get_deck_top() + 1) * 2;
+
+ return effect;
+}
+
+// Using __attribute__((unused)) for jokers with no sprites yet to avoid warning
+// Remove the attribute once they have sprites
+__attribute__((unused))
+static JokerEffect raised_fist_joker_effect(Joker *joker, Card *scored_card)
+{
+ JokerEffect effect = {0};
+ if (scored_card != NULL)
+ return effect; // if card != null, we are not at the end-phase of scoring yet
+
+ // Find the lowest rank card in hand
+ // Aces are always considered high value, even in an ace-low straight
+ u8 lowest_value = IMPOSSIBLY_HIGH_CARD_VALUE;
+ CardObject** hand = get_hand_array();
+ int hand_size = hand_get_size();
+ for (int i = 0; i < hand_size; i++ )
+ {
+ u8 value = card_get_value(hand[i]->card);
+ if (lowest_value > value)
+ lowest_value = value;
+ }
+
+ if (lowest_value != IMPOSSIBLY_HIGH_CARD_VALUE)
+ effect.mult = lowest_value * 2;
+
+ return effect;
+}
+
+__attribute__((unused))
+static JokerEffect reserved_parking_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card != NULL)
+ return effect; // if card != null, we are not at the end-phase of scoring yet
+
+ CardObject** hand = get_hand_array();
+ int hand_size = hand_get_size();
+ for (int i = 0; i < hand_size; i++ )
+ {
+ switch (hand[i]->card->rank) {
+ case KING: case QUEEN: case JACK:
+ if (random() % 2 == 0)
+ effect.money += 1;
+ default:
+ break;
+ }
+ }
+
+ return effect;
+};
+
+static JokerEffect business_card_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ switch (scored_card->rank) {
+ case KING: case QUEEN: case JACK:
+ if (random() % 2 == 0)
+ effect.money = 2;
+ default:
+ break;
+ }
+
+ return effect;
+}
+
+static JokerEffect scholar_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ if (scored_card->rank == ACE) {
+ effect.chips = 20;
+ effect.mult = 4;
+ }
+
+ return effect;
+}
+
+static JokerEffect scary_face_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ switch (scored_card->rank) {
+ case KING: case QUEEN: case JACK:
+ effect.chips = 30;
+ default:
+ break;
+ }
+
+ return effect;
+}
+
+ __attribute__((unused))
+static JokerEffect abstract_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card != NULL)
+ return effect; // if card != null, we are not at the end-phase of scoring yet
+
+ // +1 xmult per occupied joker slot
+ int num_jokers = list_get_size(get_jokers());
+ effect.mult = num_jokers * 3;
+
+ return effect;
+}
+
+__attribute__((unused))
+static JokerEffect bull_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card != NULL)
+ return effect; // if card != null, we are not at the end-phase of scoring yet
+
+ effect.chips = get_money() * 2;
+
+ return effect;
+}
+
+static JokerEffect smiley_face_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ switch (scored_card->rank) {
+ case KING: case QUEEN: case JACK:
+ effect.mult = 5;
+ default:
+ break;
+ }
+
+ return effect;
+}
+
+static JokerEffect even_steven_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ if (card_get_value(scored_card) % 2 == 0) {
+ switch (scored_card->rank) {
+ case KING: case QUEEN: case JACK:
+ break;
+ default:
+ effect.mult = 4;
+ }
+ }
+
+ return effect;
+}
+
+static JokerEffect odd_todd_joker_effect(Joker *joker, Card *scored_card) {
+ JokerEffect effect = {0};
+ if (scored_card == NULL)
+ return effect;
+
+ if (card_get_value(scored_card) % 2 == 1) // todo test ace
+ effect.chips = 31;
+
+ return effect;
+}
+
+/* The index of a joker in the registry matches its ID.
+ * The joker sprites are matched by ID so the position in the registry
+ * determines the joker's sprite.
+ * Each consecutive NUM_JOKERS_PER_SPRITESHEET (defined in joker.c) jokers
+ * share a spritesheet and thus a color palette.
+ * To make better use of color palettes jokers may be rearranged here
+ * (and put together in the matching spritesheet) to share a color palette.
+ * Otherwise the order is similar to the wiki.
+ */
const JokerInfo joker_registry[] = {
{ COMMON_JOKER, 2, default_joker_effect }, // DEFAULT_JOKER_ID = 0
- { COMMON_JOKER, 5, greedy_joker_effect }, // GREEDY_JOKER_ID = 1
- { COMMON_JOKER, 5, lusty_joker_effect }, // etc...
- { COMMON_JOKER, 5, wrathful_joker_effect },
- { COMMON_JOKER, 5, gluttonous_joker_effect },
- { COMMON_JOKER, 3, jolly_joker_effect },
- { COMMON_JOKER, 4, zany_joker_effect },
- { COMMON_JOKER, 4, mad_joker_effect },
- { COMMON_JOKER, 4, crazy_joker_effect },
- { COMMON_JOKER, 4, droll_joker_effect },
- { COMMON_JOKER, 3, sly_joker_effect },
- { COMMON_JOKER, 4, wily_joker_effect },
- { COMMON_JOKER, 4, clever_joker_effect },
- { COMMON_JOKER, 4, devious_joker_effect },
- { COMMON_JOKER, 4, crafty_joker_effect },
- { COMMON_JOKER, 5, half_joker_effect },
- { UNCOMMON_JOKER, 8, joker_stencil_effect },
- { COMMON_JOKER, 5, banner_joker_effect },
- { COMMON_JOKER, 4, walkie_talkie_joker_effect },
- { UNCOMMON_JOKER, 8, fibonnaci_joker_effect },
- { UNCOMMON_JOKER, 6, blackboard_joker_effect },
- { COMMON_JOKER, 5, mystic_summit_joker_effect },
- { COMMON_JOKER, 4, misprint_joker_effect },
+ { COMMON_JOKER, 5, greedy_joker_effect }, // GREEDY_JOKER_ID = 1
+ { COMMON_JOKER, 5, lusty_joker_effect }, // etc... 2
+ { COMMON_JOKER, 5, wrathful_joker_effect }, // 3
+ { COMMON_JOKER, 5, gluttonous_joker_effect }, // 4
+ { COMMON_JOKER, 3, jolly_joker_effect }, // 5
+ { COMMON_JOKER, 4, zany_joker_effect }, // 6
+ { COMMON_JOKER, 4, mad_joker_effect }, // 7
+ { COMMON_JOKER, 4, crazy_joker_effect }, // 8
+ { COMMON_JOKER, 4, droll_joker_effect }, // 9
+ { COMMON_JOKER, 3, sly_joker_effect }, // 10
+ { COMMON_JOKER, 4, wily_joker_effect }, // 11
+ { COMMON_JOKER, 4, clever_joker_effect }, // 12
+ { COMMON_JOKER, 4, devious_joker_effect }, // 13
+ { COMMON_JOKER, 4, crafty_joker_effect }, // 14
+ { COMMON_JOKER, 5, half_joker_effect }, // 15
+ { UNCOMMON_JOKER, 8, joker_stencil_effect }, // 16
+ { COMMON_JOKER, 5, banner_joker_effect }, // 17
+ { COMMON_JOKER, 4, walkie_talkie_joker_effect }, // 18
+ { UNCOMMON_JOKER, 8, fibonnaci_joker_effect }, // 19
+ { UNCOMMON_JOKER, 6, blackboard_joker_effect }, // 20
+ { COMMON_JOKER, 5, mystic_summit_joker_effect }, // 21
+ { COMMON_JOKER, 4, misprint_joker_effect }, // 22
+ { COMMON_JOKER, 4, even_steven_joker_effect }, // 23
+ { COMMON_JOKER, 5, blue_joker_effect }, // 24
+ { COMMON_JOKER, 4, odd_todd_joker_effect }, // 25
+ { COMMON_JOKER, 4, scholar_joker_effect }, // 26
+ { COMMON_JOKER, 4, business_card_joker_effect }, // 27
+ // Business card should be paired with Shortcut for palette optimization when it's added
+ { COMMON_JOKER, 4, scary_face_joker_effect }, // 28
+ { COMMON_JOKER, 4, smiley_face_joker_effect }, // 29
+
+ // The following jokers don't have sprites yet,
+ // uncomment them when their sprites are added.
+#if 0
+ { COMMON_JOKER, 5, raised_fist_joker_effect },
+ { COMMON_JOKER, 6, reserved_parking_joker_effect },
+
+ { COMMON_JOKER, 4, abstract_joker_effect },
+ { UNCOMMON_JOKER, 6, bull_joker_effect},
+#endif
};
static const size_t joker_registry_size = NUM_ELEM_IN_ARR(joker_registry);
diff --git a/source/int_list.c b/source/list.c
similarity index 58%
rename from source/int_list.c
rename to source/list.c
index 0b65f3d9..c57324a5 100644
--- a/source/int_list.c
+++ b/source/list.c
@@ -1,12 +1,13 @@
#include
#include
+#include
#include "list.h"
#include "util.h"
-IntList *int_list_new(int init_size) {
- IntList *list = (IntList *)malloc(sizeof(IntList));
+List *list_new(int init_size) {
+ List *list = (List *)malloc(sizeof(List));
if (list == NULL) return NULL;
- list->_array = (int *)malloc(sizeof(int) * init_size);
+ list->_array = (void **)malloc(sizeof(void*) * init_size);
if (!list->_array)
{
free(list);
@@ -17,7 +18,7 @@ IntList *int_list_new(int init_size) {
return list;
}
-void int_list_destroy(IntList **list) {
+void list_destroy(List **list) {
if (list == NULL || *list == NULL)
return;
{
@@ -28,22 +29,28 @@ void int_list_destroy(IntList **list) {
*list = NULL;
}
-bool int_list_append(IntList *list, int value)
+bool int_list_append(List *list, intptr_t value)
+{
+ return list_append(list, (void*)value);
+}
+
+bool list_append(List *list, void *value)
{
if (list->size >= list->allocated_size)
{
int new_size = list->allocated_size * 2;
- int *new_arr = (int *)realloc(list->_array, sizeof(int) * new_size);
+ void **new_arr = (void **)realloc(list->_array, sizeof(void*) * new_size);
if (new_arr == NULL)
return false;
list->_array = new_arr;
list->allocated_size = new_size;
}
+
list->_array[list->size++] = value;
return true;
}
-bool int_list_remove_by_idx(IntList *list, int index) {
+bool list_remove_by_idx(List *list, int index) {
if (index < 0 || index >= list->size)
return false;
for (int i = index; i < list->size - 1; ++i)
@@ -54,27 +61,37 @@ bool int_list_remove_by_idx(IntList *list, int index) {
return true;
}
-bool int_list_remove_by_value(IntList *list, int value)
+bool list_remove_by_value(List *list, void* value)
{
for (int i = 0; i < list->size; i++)
{
if (list->_array[i] == value)
{
- return int_list_remove_by_idx(list, i);
+ return list_remove_by_idx(list, i);
}
}
return false;
}
-int int_list_get(IntList *list, int index)
+bool int_list_remove_by_value(List *list, intptr_t value)
+{
+ return list_remove_by_value(list, (void*)value);
+}
+
+void* list_get(List *list, int index)
{
if (index < 0 || index >= list->size)
- return 0;
+ return NULL;
return list->_array[index];
}
-int int_list_get_size(IntList *list)
+intptr_t int_list_get(List *list, int index)
+{
+ return (intptr_t)list_get(list, index);
+}
+
+int list_get_size(List *list)
{
if (list == NULL)
{
diff --git a/source/main.c b/source/main.c
index 34c38f9b..0d9a5967 100644
--- a/source/main.c
+++ b/source/main.c
@@ -29,15 +29,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
diff --git a/source/selection_grid.c b/source/selection_grid.c
new file mode 100644
index 00000000..1cb1012c
--- /dev/null
+++ b/source/selection_grid.c
@@ -0,0 +1,85 @@
+#include "selection_grid.h"
+
+
+static void selection_grid_process_directional_input(SelectionGrid *selection_grid)
+{
+ int horz_tri_input = bit_tribool(key_hit(KEY_ANY), KI_RIGHT, KI_LEFT);
+
+ if (horz_tri_input != 0)
+ {
+ selection_grid_move_selection_horz(selection_grid, horz_tri_input);
+ /* Avoid handling both vertical and horizontal input at the same time,
+ * it creates all sorts of difficult edge cases.
+ */
+ return;
+ }
+
+ int vert_tri_input = bit_tribool(key_hit(KEY_ANY), KI_DOWN, KI_UP);
+
+
+ if (vert_tri_input != 0)
+ {
+ selection_grid_move_selection_vert(selection_grid, vert_tri_input);
+ }
+
+}
+
+void selection_grid_move_selection_horz(SelectionGrid* selection_grid,
+ int direction_tribool)
+{
+ Selection new_selection = selection_grid->selection;
+ new_selection.x += direction_tribool;
+ if( selection_grid->selection.y >= 0
+ && selection_grid->selection.y < selection_grid->num_rows
+ && new_selection.x >= 0
+ && new_selection.x < selection_grid->rows[new_selection.y].get_size())
+ {
+ selection_grid->rows[new_selection.y].on_selection_changed(selection_grid, new_selection.y, &selection_grid->selection, &new_selection);
+ selection_grid->selection = new_selection;
+ }
+}
+
+void selection_grid_move_selection_vert(SelectionGrid *selection_grid, int direction_tribool)
+{
+ Selection selection = selection_grid->selection;
+ Selection new_selection = selection;
+ new_selection.y += direction_tribool;
+
+ if (new_selection.y >= 0
+ && new_selection.y < selection_grid->num_rows)
+ {
+ int new_row_size = selection_grid->rows[new_selection.y].get_size();
+ if (new_row_size <= 0)
+ return;
+
+ if (selection.x >= new_row_size)
+ {
+ // TODO: Maintain relative horizontal position
+ // Clip selection to row size
+ new_selection.x = new_row_size - 1;
+ }
+ if (selection.y >= 0 && selection.y < selection_grid->num_rows)
+ {
+ selection_grid->rows[selection.y].on_selection_changed(selection_grid, selection.y, &selection, &new_selection);
+ }
+ selection_grid->rows[new_selection.y].on_selection_changed(selection_grid, new_selection.y, &selection, &new_selection);
+ selection_grid->selection = new_selection;
+ }
+}
+
+void selection_grid_process_input(SelectionGrid *selection_grid)
+{
+ if (selection_grid == NULL || selection_grid->rows == NULL)
+ return;
+
+ selection_grid_process_directional_input(selection_grid);
+
+ u32 non_directional_key = KEY_ANY & ~KEY_DIR;
+ if (key_hit(non_directional_key))
+ {
+ Selection* selection = &selection_grid->selection; // To make the next line shorter and more readable
+ selection_grid->rows[selection->y].on_key_hit(selection_grid, selection);
+ }
+}
+
+
diff --git a/source/sprite.c b/source/sprite.c
index df1e917c..e69f2a28 100644
--- a/source/sprite.c
+++ b/source/sprite.c
@@ -1,6 +1,7 @@
#include "sprite.h"
#include "util.h"
#include "audio_utils.h"
+#include "soundbank.h"
#include
#include
@@ -8,6 +9,7 @@
#define MAX_SPRITES 128
#define MAX_AFFINES 32
+#define SPRITE_FOCUS_RAISE_PX 10
OBJ_ATTR obj_buffer[MAX_SPRITES];
OBJ_AFFINE *obj_aff_buffer = (OBJ_AFFINE*)obj_buffer;
@@ -117,6 +119,7 @@ SpriteObject* sprite_object_new()
sprite_object->sprite = NULL;
sprite_object_reset_transform(sprite_object);
sprite_object->selected = false;
+ sprite_object->focused = false;
return sprite_object;
}
@@ -239,3 +242,20 @@ Sprite* sprite_object_get_sprite(SpriteObject* sprite_object)
return NULL;
return sprite_object->sprite;
}
+
+void sprite_object_set_focus(SpriteObject* sprite_object, bool focus)
+{
+ if (sprite_object->focused == focus)
+ {
+ return;
+ }
+ sprite_object->focused = focus;
+
+ play_sfx(SFX_CARD_FOCUS , MM_BASE_PITCH_RATE + rand() % 512);
+ sprite_object->ty = sprite_object->ty + int2fx((focus ? -1 : 1) * SPRITE_FOCUS_RAISE_PX);
+}
+
+bool sprite_object_is_focused(SpriteObject* sprite_object)
+{
+ return sprite_object->focused;
+}