GBAlatro
A Demake of Balatro for the GBA
Loading...
Searching...
No Matches
game_over.c
1#include "game/game_over.h"
2
3#include "affine_background.h"
4#include "audio_utils.h"
5#include "background_game_over_gfx.h"
6#include "button.h"
7#include "card.h"
8#include "game.h"
9#include "graphic_utils.h"
10#include "hand.h"
11#include "joker.h"
12#include "layout.h"
13#include "list.h"
14#include "random.h"
15#include "selection_grid.h"
16#include "skip_tag.h"
17#include "soundbank.h"
18#include "timer.h"
19#include "util.h"
20
21#include <tonc.h>
22
23#define RED_BTN_MAIN_COLOR_PAL_IDX 6
24#define BLUE_BTN_MAIN_COLOR_PAL_IDX 7
25#define REUSE_SEED_BTN_OUTLINE_PAL_IDX 8
26#define NEW_RUN_BTN_OUTLINE_PAL_IDX 9
27#define MAIN_MENU_BTN_OUTLINE_PAL_IDX 10
28#define ENDLESS_BTN_OUTLINE_PAL_IDX 11
29
30enum EndCondition
31{
32 END_CONDITION_NONE,
33 END_CONDITION_WIN,
34 END_CONDITION_LOSS
35};
36
37// clang-format off
38static const Rect GAME_OVER_ANIM_RECT = { 3, 5, 26, 31};
39
40static const Rect GAME_OVER_TEXT_TILES_SRC_RECT_1 = { 27, 24, 31, 25};
41static const Rect GAME_OVER_TEXT_TILES_SRC_RECT_2 = { 27, 26, 31, 27};
42static const BG_POINT GAME_OVER_TEXT_TILES_DEST_POS_1 = { 10, 21};
43static const BG_POINT GAME_OVER_TEXT_TILES_DEST_POS_2 = { 15, 21};
44
45static const Rect DEFEATED_BY_TEXT_SRC_RECT = { 28, 30, 31, 31};
46static const BG_POINT DEFEATED_BY_TEXT_DEST_POS = { 22, 24};
47static const BG_POINT DEFEATED_BY_TOKEN_INIT_POS = {180, 207};
48
49static const Rect REUSE_SEED_BTN_OFF_SRC_RECT = { 28, 28, 29, 29};
50static const Rect REUSE_SEED_BTN_ON_SRC_RECT = { 30, 28, 31, 29};
51static const BG_POINT REUSE_SEED_BTN_DEST_POS = { 4, 11};
52
53static const Rect BEST_HAND_SCORE_RECT = {126, 64, 166, 72};
54static const Rect MOST_PLAYED_HAND_RECT = {112, 80, 168, 88};
55static const BG_POINT SEED_VALUE_POS = {112, 96};
56
57static const BG_POINT NEW_RUN_BTN_TEXT_POS = { 48, 112};
58static const BG_POINT MAIN_MENU_BTN_TEXT_POS = {120, 112};
59// clang-format on
60
61static const u32 GAME_OVER_ANIM_FRAMES = 17;
62static const mm_byte GAME_OVER_SFX_VOL = 178; // 70% of MM_SFX_FULL_VOLUME
63static const mm_word GAME_OVER_WHOOSH_RATE = 922; // 90% of MM_BASE_PITCH_RATE
64
65// Selection Grid
66
67enum GameOverRows
68{
69 GAME_OVER_SEED_ROW,
70 GAME_OVER_RUN_MENU_ROW,
71 GAME_OVER_ROW_MAX
72};
73
74#define GAME_OVER_SEED_BTN_COL 0
75#define GAME_OVER_NEW_RUN_BTN_COL 0
76#define GAME_OVER_MAIN_MENU_BTN_COL 1
77
78static int game_over_get_row_size1(void)
79{
80 return 1;
81}
82static int game_over_get_row_size2(void)
83{
84 return 2;
85}
86
87static void game_over_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
88static bool game_over_on_selection_changed(
89 SelectionGrid* selection_grid,
90 int row_idx,
91 const Selection* prev_selection,
92 const Selection* new_selection
93);
94
95static const SelectionGridRow game_over_selection_rows[] = {
96 {GAME_OVER_SEED_ROW, game_over_get_row_size1, game_over_on_selection_changed, game_over_on_key_transit, {.wrap = false}},
97 {GAME_OVER_RUN_MENU_ROW, game_over_get_row_size2, game_over_on_selection_changed, game_over_on_key_transit, {.wrap = false}}
98};
99
100static const Selection GAME_OVER_INIT_SEL = {0, GAME_OVER_RUN_MENU_ROW};
101static SelectionGrid game_over_selection_grid = {
102 game_over_selection_rows,
103 GAME_OVER_ROW_MAX,
104 GAME_OVER_INIT_SEL
105};
106
107// Buttons
108
109static void reuse_seed_on_pressed(void);
110static void new_run_on_pressed(void);
111static void main_menu_on_pressed(void);
112static Button* game_over_get_button_from_sel(const Selection* selection);
113
114// clang-format off
115static Button game_over_buttons[GAME_OVER_ROW_MAX][2] = {
116 // GAME_OVER_SEED_ROW
117 {
118 { REUSE_SEED_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, reuse_seed_on_pressed, NULL }
119 },
120 // GAME_OVER_RUN_MENU_ROW
121 {
122 { NEW_RUN_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, new_run_on_pressed, NULL },
123 { MAIN_MENU_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, main_menu_on_pressed, NULL }
124 }
125};
126// clang-format on
127
128// Internal Variables
129
130static enum EndCondition s_end_condition = END_CONDITION_NONE;
131static u32 s_timer = TM_ZERO;
132static bool s_reuse_seed = false;
133static bool s_continue_run = false;
134
135static void game_over_common_init(enum EndCondition init_condition)
136{
137 s_end_condition = init_condition;
138 s_timer = TM_ZERO;
139 s_reuse_seed = false;
140 s_continue_run = false;
141
142 // Hide all Joker sprites
143 JokerObject* joker_object = NULL;
144 ListItr itr = list_itr_create(get_jokers_list());
145 while ((joker_object = list_itr_next(&itr)))
146 {
147 sprite_object_hide((SpriteObject*)(joker_object));
148 }
149
150 // Destroy any preexisting cards in the deck, which will be present if we restart a run
151 Card* card = NULL;
152 while (get_deck_top() >= 0)
153 {
154 card = deck_pop();
155 card_destroy(&card);
156 }
157
159
160 // Clears the round end menu
161 toggle_windows(false, false);
162 GRIT_CPY(pal_bg_mem, background_game_over_gfxPal);
163 GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_game_over_gfxTiles);
164 GRIT_CPY(&se_mem[MAIN_BG_SBB], background_game_over_gfxMap);
165
166 // Move blind token offscreen at initial position before moving it up
168 g_game_vars.playing_blind_token,
169 DEFEATED_BY_TOKEN_INIT_POS.x,
170 DEFEATED_BY_TOKEN_INIT_POS.y
171 );
172
173 // Highlight New Run button
174 game_over_selection_grid.selection = GAME_OVER_INIT_SEL;
175 button_set_highlight(&game_over_buttons[GAME_OVER_SEED_ROW][GAME_OVER_SEED_BTN_COL], false);
177 &game_over_buttons[GAME_OVER_RUN_MENU_ROW][GAME_OVER_NEW_RUN_BTN_COL],
178 true
179 );
181 &game_over_buttons[GAME_OVER_RUN_MENU_ROW][GAME_OVER_MAIN_MENU_BTN_COL],
182 false
183 );
184}
185
187{
189
190 game_over_common_init(END_CONDITION_WIN);
191 affine_background_set_color(TEXT_CLR_BLUE);
192}
193
195{
196 play_sfx(SFX_NEGATIVE_LOSE, MM_BASE_PITCH_RATE / 2, GAME_OVER_SFX_VOL);
197 play_sfx(SFX_WHOOSH2_LOSE, GAME_OVER_WHOOSH_RATE, GAME_OVER_SFX_VOL);
198
200
201 game_over_common_init(END_CONDITION_LOSS);
202 affine_background_set_color(TEXT_CLR_RED);
203
204 // Change top text to "GAME OVER"
205 main_bg_se_copy_rect(GAME_OVER_TEXT_TILES_SRC_RECT_1, GAME_OVER_TEXT_TILES_DEST_POS_1);
206 main_bg_se_copy_rect(GAME_OVER_TEXT_TILES_SRC_RECT_2, GAME_OVER_TEXT_TILES_DEST_POS_2);
207
208 // Change blind frame text
209 main_bg_se_copy_rect(DEFEATED_BY_TEXT_SRC_RECT, DEFEATED_BY_TEXT_DEST_POS);
210}
211
213{
214 s_timer++;
215
216 // Need to clear text here or the number of cards remaining in deck stays for some reason
217 if (s_timer == 1)
218 tte_erase_screen();
219
220 // Move whole panel and Blind Token sprite up by a tile each frame
221 if (s_timer < GAME_OVER_ANIM_FRAMES)
222 {
223 main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SCREEN_UP);
224
226 g_game_vars.playing_blind_token,
227 g_game_vars.playing_blind_token->pos.x,
228 g_game_vars.playing_blind_token->pos.y - TILE_SIZE
229 );
230 }
231
232 // Print values and buttons text
233 else if (s_timer == GAME_OVER_ANIM_FRAMES)
234 {
235 char best_hand_str[UINT_MAX_DIGITS + 1];
237 g_game_vars.best_hand_score,
238 rect_width(&BEST_HAND_SCORE_RECT) / TTE_CHAR_SIZE,
239 best_hand_str
240 );
241 Rect best_hand_rect = BEST_HAND_SCORE_RECT;
242 update_text_rect_to_center_str(&best_hand_rect, best_hand_str, SCREEN_LEFT);
243 tte_printf(
244 "#{P:%d,%d; cx:0x%X000}%s",
245 best_hand_rect.left,
246 best_hand_rect.top,
247 TTE_RED_PB,
248 best_hand_str
249 );
250
251 // Most played hand
252 enum HandType most_played_hand = HIGH_CARD;
253 for (enum HandType hand_type = PAIR; hand_type <= HAND_TYPE_MAX; hand_type++)
254 {
255 if (g_game_vars.nb_played_hands[hand_type - 1] >
256 g_game_vars.nb_played_hands[most_played_hand - 1])
257 most_played_hand = hand_type;
258 }
259
260 const char* hand_name_str = get_hand_type_name(most_played_hand);
261 Rect hand_type_rect = MOST_PLAYED_HAND_RECT;
262 update_text_rect_to_center_str(&hand_type_rect, hand_name_str, SCREEN_LEFT);
263 tte_printf(
264 "#{P:%d,%d; cx:0x%X000}%s",
265 hand_type_rect.left,
266 hand_type_rect.top,
267 TTE_WHITE_PB,
268 hand_name_str
269 );
270
271 // Run's seed
272 char seed_str[BASE36_MAX_DIGITS + 1] = {'\0'};
273 u32_to_base36(g_game_vars.rng_info.seed, seed_str);
274 tte_printf(
275 "#{P:%d,%d; cx:0x%X000}%s",
276 SEED_VALUE_POS.x,
277 SEED_VALUE_POS.y,
278 TTE_WHITE_PB,
279 seed_str
280 );
281
282 // Buttons' text
283 tte_printf(
284 "#{P:%d,%d; cx:0x%X000}New Run",
285 NEW_RUN_BTN_TEXT_POS.x,
286 NEW_RUN_BTN_TEXT_POS.y,
287 TTE_WHITE_PB
288 );
289 tte_printf(
290 "#{P:%d,%d; cx:0x%X000}Main Menu",
291 MAIN_MENU_BTN_TEXT_POS.x,
292 MAIN_MENU_BTN_TEXT_POS.y,
293 TTE_WHITE_PB
294 );
295 }
296
297 if (s_timer >= GAME_OVER_ANIM_FRAMES)
298 selection_grid_process_input(&game_over_selection_grid);
299}
300
302{
303 toggle_windows(false, false);
304
306 s_end_condition = END_CONDITION_NONE;
307
308 u32 previous_seed = g_game_vars.rng_info.seed;
309
310 if (!s_continue_run)
311 game_reset();
312
313 if (s_reuse_seed)
314 rng_set_seed(previous_seed);
315 else
316 g_game_vars.rng_info.seed = UNDEFINED;
317}
318
319// SelectionGrid Implementation
320
321static Button* game_over_get_button_from_sel(const Selection* selection)
322{
323 return &game_over_buttons[selection->y][selection->x];
324}
325
326static void game_over_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
327{
328 if (key_hit(SELECT_CARD))
329 button_press(&game_over_buttons[selection->y][selection->x]);
330}
331
332static bool game_over_on_selection_changed(
333 SelectionGrid* selection_grid,
334 int row_idx,
335 const Selection* prev_selection,
336 const Selection* new_selection
337)
338{
339 if (row_idx == prev_selection->y)
340 button_set_highlight(game_over_get_button_from_sel(prev_selection), false);
341
342 if (row_idx == new_selection->y)
343 button_set_highlight(game_over_get_button_from_sel(new_selection), true);
344
345 return true;
346}
347
348// Buttons
349
350static void reuse_seed_on_pressed(void)
351{
352 s_reuse_seed = !s_reuse_seed;
354 s_reuse_seed ? REUSE_SEED_BTN_ON_SRC_RECT : REUSE_SEED_BTN_OFF_SRC_RECT,
355 REUSE_SEED_BTN_DEST_POS
356 );
357}
358
359static void new_run_on_pressed(void)
360{
361 game_change_state(GAME_STATE_RUN_SETUP);
362}
363
364static void main_menu_on_pressed(void)
365{
366 game_change_state(GAME_STATE_MAIN_MENU);
367}
Utilities for affine background on GBA.
void affine_background_set_color(COLOR color)
Update the affine background color.
Utilities for using maxmod to play sound effects.
void play_regular_music(void)
Play music at a normal pitch and tempo.
void play_lose_music(void)
Play music at a low pitch and slow tempo (lose screen)
#define MM_BASE_PITCH_RATE
The default pitch rate for the sound effect played.
Definition audio_utils.h:37
void play_sfx(mm_word id, mm_word rate, mm_byte volume)
Play a sound effect, wrapper for mmEffectEx() See https://maxmod.org/ref/functions/mm_sound_effect....
#define SFX_DEFAULT_VOLUME
Default volume for sound effects.
Definition audio_utils.h:43
A button structure containing the common button functionalities Including highlight and pressing func...
void button_press(Button *button)
Execute a button press, by calling the button's on_pressed function. This checks the button's can_be_...
Definition button.c:18
void button_set_highlight(Button *button, bool highlight)
Set the button highlight on or off.
Definition button.c:8
Game Over screen state functions.
void game_lose_on_init(void)
Game Over screen state initialization when losing.
Definition game_over.c:194
void game_over_on_update(void)
Game Over screen state update.
Definition game_over.c:212
void game_over_on_exit(void)
Game Over screen cleanup, common to both losing and winning.
Definition game_over.c:301
void game_win_on_init(void)
Game Over screen state initialization when winning.
Definition game_over.c:186
Graphic utility functions.
void main_bg_se_move_rect_1_tile_vert(Rect se_rect, enum ScreenVertDir direction)
Moves a rect in the main background vertically in direction by a single tile.
void main_bg_se_copy_rect(Rect se_rect, BG_POINT dest_pos)
Copies a rect in the main background from se_rect to the position (x, y).
void toggle_windows(bool win0, bool win1)
Toggles the visibility of the window layers.
void update_text_rect_to_center_str(Rect *rect, const char *str, enum ScreenHorzDir bias_direction)
Updates a rect so a string is centered within it.
#define TILE_SIZE
Tile size in pixels, both height and width as tiles are square.
#define TTE_CHAR_SIZE
By default TTE characters occupy a single tile.
INLINE int rect_width(const Rect *rect)
Get the width of a rectangle.
Functions relative to manipulating and analyzing the contents of the Hand, a.k.a. the Cards we hold a...
Functions relative to the handling of Jokers.
Header of shared rects and points needed for ui.
A doubly-linked list.
void * list_itr_next(ListItr *itr)
Definition list.c:281
ListItr list_itr_create(List *list)
Definition list.c:257
Common functions to handle RNG manipulation. Using this interface has three goals:
void rng_set_seed(u32 seed)
Set the rng seed to the chosen value, and reset the step counter to 0. The seed will be capped at MAX...
Definition random.c:33
An implementation for a selection grid that handles directional selection.
void selection_grid_process_input(SelectionGrid *selection_grid)
Processes user input for the selection grid.
Data structures and functions related to the handling of Skip Tags.
void remove_all_skip_tags(void)
Delete all owned Skip Tags.
Definition skip_tag.c:289
INLINE void sprite_position(Sprite *sprite, int x, int y)
Set sprite position. Inlined for efficiency.
Definition sprite.h:466
void sprite_object_hide(SpriteObject *sprite_object)
Hides the SpriteObject by manipulating ATTR0_HIDE in OAM.
Definition sprite.c:264
A button representation.
Definition button.h:31
Definition card.h:46
An iterator into a list.
Definition list.h:91
u32 seed
Definition random.h:44
The core selection grid struct, represents the grid itself and its state.
A sprite object is a sprite that is focusable and movable in animation.
Definition sprite.h:86
POINT pos
Sprite position on screen in pixels.
Definition sprite.h:68
Timer constants.
Utilities relating around number string representation and protected arithmatic helper functions.
void truncate_uint_to_suffixed_str(uint32_t num, int num_req_chars, char out_str_buff[UINT_MAX_DIGITS+1])
Truncate an unsigned number into a suffixed string representation e.g. 12000 -> "12K" The least signi...
Definition util.c:116
void u32_to_base36(uint32_t n, char b36_str[])
Convert a 32-bit unsigned integer to its base-36 string representation. This will perform 6 divisions...
Definition util.c:272