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 "soundbank.h"
17#include "timer.h"
18#include "util.h"
19
20#include <tonc.h>
21
22#define RED_BTN_MAIN_COLOR_PAL_IDX 6
23#define BLUE_BTN_MAIN_COLOR_PAL_IDX 7
24#define REUSE_SEED_BTN_OUTLINE_PAL_IDX 8
25#define NEW_RUN_BTN_OUTLINE_PAL_IDX 9
26#define MAIN_MENU_BTN_OUTLINE_PAL_IDX 10
27#define ENDLESS_BTN_OUTLINE_PAL_IDX 11
28
29enum EndCondition
30{
31 END_CONDITION_NONE,
32 END_CONDITION_WIN,
33 END_CONDITION_LOSS
34};
35
36// clang-format off
37static const Rect GAME_OVER_ANIM_RECT = { 3, 5, 26, 31};
38
39static const Rect GAME_OVER_TEXT_TILES_SRC_RECT_1 = { 27, 24, 31, 25};
40static const Rect GAME_OVER_TEXT_TILES_SRC_RECT_2 = { 27, 26, 31, 27};
41static const BG_POINT GAME_OVER_TEXT_TILES_DEST_POS_1 = { 10, 21};
42static const BG_POINT GAME_OVER_TEXT_TILES_DEST_POS_2 = { 15, 21};
43
44static const Rect DEFEATED_BY_TEXT_SRC_RECT = { 28, 30, 31, 31};
45static const BG_POINT DEFEATED_BY_TEXT_DEST_POS = { 22, 24};
46static const BG_POINT DEFEATED_BY_TOKEN_INIT_POS = {180, 207};
47
48static const Rect REUSE_SEED_BTN_OFF_SRC_RECT = { 28, 28, 29, 29};
49static const Rect REUSE_SEED_BTN_ON_SRC_RECT = { 30, 28, 31, 29};
50static const BG_POINT REUSE_SEED_BTN_DEST_POS = { 4, 11};
51
52static const Rect BEST_HAND_SCORE_RECT = {126, 64, 166, 72};
53static const Rect MOST_PLAYED_HAND_RECT = {112, 80, 168, 88};
54static const BG_POINT SEED_VALUE_POS = {112, 96};
55
56static const BG_POINT NEW_RUN_BTN_TEXT_POS = { 48, 112};
57static const BG_POINT MAIN_MENU_BTN_TEXT_POS = {120, 112};
58// clang-format on
59
60static const u32 GAME_OVER_ANIM_FRAMES = 17;
61static const mm_byte GAME_OVER_SFX_VOL = 178; // 70% of MM_SFX_FULL_VOLUME
62static const mm_word GAME_OVER_WHOOSH_RATE = 922; // 90% of MM_BASE_PITCH_RATE
63
64// Selection Grid
65
66enum GameOverRows
67{
68 GAME_OVER_SEED_ROW,
69 GAME_OVER_RUN_MENU_ROW,
70 GAME_OVER_ROW_MAX
71};
72
73#define GAME_OVER_SEED_BTN_COL 0
74#define GAME_OVER_NEW_RUN_BTN_COL 0
75#define GAME_OVER_MAIN_MENU_BTN_COL 1
76
77static int game_over_get_row_size1(void)
78{
79 return 1;
80}
81static int game_over_get_row_size2(void)
82{
83 return 2;
84}
85
86static void game_over_on_key_transit(SelectionGrid* selection_grid, Selection* selection);
87static bool game_over_on_selection_changed(
88 SelectionGrid* selection_grid,
89 int row_idx,
90 const Selection* prev_selection,
91 const Selection* new_selection
92);
93
94static const SelectionGridRow game_over_selection_rows[] = {
95 {GAME_OVER_SEED_ROW, game_over_get_row_size1, game_over_on_selection_changed, game_over_on_key_transit, {.wrap = false}},
96 {GAME_OVER_RUN_MENU_ROW, game_over_get_row_size2, game_over_on_selection_changed, game_over_on_key_transit, {.wrap = false}}
97};
98
99static const Selection GAME_OVER_INIT_SEL = {0, GAME_OVER_RUN_MENU_ROW};
100static SelectionGrid game_over_selection_grid = {
101 game_over_selection_rows,
102 GAME_OVER_ROW_MAX,
103 GAME_OVER_INIT_SEL
104};
105
106// Buttons
107
108static void reuse_seed_on_pressed(void);
109static void new_run_on_pressed(void);
110static void main_menu_on_pressed(void);
111static Button* game_over_get_button_from_sel(const Selection* selection);
112
113// clang-format off
114static Button game_over_buttons[GAME_OVER_ROW_MAX][2] = {
115 // GAME_OVER_SEED_ROW
116 {
117 { REUSE_SEED_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, reuse_seed_on_pressed, NULL }
118 },
119 // GAME_OVER_RUN_MENU_ROW
120 {
121 { NEW_RUN_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, new_run_on_pressed, NULL },
122 { MAIN_MENU_BTN_OUTLINE_PAL_IDX, RED_BTN_MAIN_COLOR_PAL_IDX, main_menu_on_pressed, NULL }
123 }
124};
125// clang-format on
126
127// Internal Variables
128
129static enum EndCondition s_end_condition = END_CONDITION_NONE;
130static u32 s_timer = TM_ZERO;
131static bool s_reuse_seed = false;
132static bool s_continue_run = false;
133
134static void game_over_common_init(enum EndCondition init_condition)
135{
136 s_end_condition = init_condition;
137 s_timer = TM_ZERO;
138 s_reuse_seed = false;
139 s_continue_run = false;
140
141 // Hide all Joker sprites
142 JokerObject* joker_object = NULL;
143 ListItr itr = list_itr_create(get_jokers_list());
144 while ((joker_object = list_itr_next(&itr)))
145 {
146 obj_hide(joker_object->sprite->obj);
147 }
148
149 // Destroy any preexisting cards in the deck, which will be present if we restart a run
150 Card* card = NULL;
151 while (get_deck_top() >= 0)
152 {
153 card = deck_pop();
154 card_destroy(&card);
155 }
156
157 // Clears the round end menu
158 toggle_windows(false, false);
159 GRIT_CPY(pal_bg_mem, background_game_over_gfxPal);
160 GRIT_CPY(&tile_mem[MAIN_BG_CBB], background_game_over_gfxTiles);
161 GRIT_CPY(&se_mem[MAIN_BG_SBB], background_game_over_gfxMap);
162
163 // Move blind token offscreen at initial position before moving it up
165 g_game_vars.playing_blind_token,
166 DEFEATED_BY_TOKEN_INIT_POS.x,
167 DEFEATED_BY_TOKEN_INIT_POS.y
168 );
169
170 // Highlight New Run button
171 game_over_selection_grid.selection = GAME_OVER_INIT_SEL;
172 button_set_highlight(&game_over_buttons[GAME_OVER_SEED_ROW][GAME_OVER_SEED_BTN_COL], false);
174 &game_over_buttons[GAME_OVER_RUN_MENU_ROW][GAME_OVER_NEW_RUN_BTN_COL],
175 true
176 );
178 &game_over_buttons[GAME_OVER_RUN_MENU_ROW][GAME_OVER_MAIN_MENU_BTN_COL],
179 false
180 );
181}
182
184{
186
187 game_over_common_init(END_CONDITION_WIN);
188 affine_background_set_color(TEXT_CLR_BLUE);
189}
190
192{
193 play_sfx(SFX_NEGATIVE_LOSE, MM_BASE_PITCH_RATE / 2, GAME_OVER_SFX_VOL);
194 play_sfx(SFX_WHOOSH2_LOSE, GAME_OVER_WHOOSH_RATE, GAME_OVER_SFX_VOL);
195
197
198 game_over_common_init(END_CONDITION_LOSS);
199 affine_background_set_color(TEXT_CLR_RED);
200
201 // Change top text to "GAME OVER"
202 main_bg_se_copy_rect(GAME_OVER_TEXT_TILES_SRC_RECT_1, GAME_OVER_TEXT_TILES_DEST_POS_1);
203 main_bg_se_copy_rect(GAME_OVER_TEXT_TILES_SRC_RECT_2, GAME_OVER_TEXT_TILES_DEST_POS_2);
204
205 // Change blind frame text
206 main_bg_se_copy_rect(DEFEATED_BY_TEXT_SRC_RECT, DEFEATED_BY_TEXT_DEST_POS);
207}
208
210{
211 s_timer++;
212
213 // Need to clear text here or the number of cards remaining in deck stays for some reason
214 if (s_timer == 1)
215 tte_erase_screen();
216
217 // Move whole panel and Blind Token sprite up by a tile each frame
218 if (s_timer < GAME_OVER_ANIM_FRAMES)
219 {
220 main_bg_se_move_rect_1_tile_vert(GAME_OVER_ANIM_RECT, SCREEN_UP);
221
223 g_game_vars.playing_blind_token,
224 g_game_vars.playing_blind_token->pos.x,
225 g_game_vars.playing_blind_token->pos.y - TILE_SIZE
226 );
227 }
228
229 // Print values and buttons text
230 else if (s_timer == GAME_OVER_ANIM_FRAMES)
231 {
232 char best_hand_str[UINT_MAX_DIGITS + 1];
234 g_game_vars.best_hand_score,
235 rect_width(&BEST_HAND_SCORE_RECT) / TTE_CHAR_SIZE,
236 best_hand_str
237 );
238 Rect best_hand_rect = BEST_HAND_SCORE_RECT;
239 update_text_rect_to_center_str(&best_hand_rect, best_hand_str, SCREEN_LEFT);
240 tte_printf(
241 "#{P:%d,%d; cx:0x%X000}%s",
242 best_hand_rect.left,
243 best_hand_rect.top,
244 TTE_RED_PB,
245 best_hand_str
246 );
247
248 // Most played hand
249 enum HandType most_played_hand = HIGH_CARD;
250 for (enum HandType hand_type = PAIR; hand_type <= HAND_TYPE_MAX; hand_type++)
251 {
252 if (g_game_vars.nb_played_hands[hand_type - 1] >
253 g_game_vars.nb_played_hands[most_played_hand - 1])
254 most_played_hand = hand_type;
255 }
256
257 const char* hand_name_str = get_hand_type_name(most_played_hand);
258 Rect hand_type_rect = MOST_PLAYED_HAND_RECT;
259 update_text_rect_to_center_str(&hand_type_rect, hand_name_str, SCREEN_LEFT);
260 tte_printf(
261 "#{P:%d,%d; cx:0x%X000}%s",
262 hand_type_rect.left,
263 hand_type_rect.top,
264 TTE_WHITE_PB,
265 hand_name_str
266 );
267
268 // Run's seed
269 char seed_str[BASE36_MAX_DIGITS + 1] = {'\0'};
270 u32_to_base36(g_game_vars.rng_info.seed, seed_str);
271 tte_printf(
272 "#{P:%d,%d; cx:0x%X000}%s",
273 SEED_VALUE_POS.x,
274 SEED_VALUE_POS.y,
275 TTE_WHITE_PB,
276 seed_str
277 );
278
279 // Buttons' text
280 tte_printf(
281 "#{P:%d,%d; cx:0x%X000}New Run",
282 NEW_RUN_BTN_TEXT_POS.x,
283 NEW_RUN_BTN_TEXT_POS.y,
284 TTE_WHITE_PB
285 );
286 tte_printf(
287 "#{P:%d,%d; cx:0x%X000}Main Menu",
288 MAIN_MENU_BTN_TEXT_POS.x,
289 MAIN_MENU_BTN_TEXT_POS.y,
290 TTE_WHITE_PB
291 );
292 }
293
294 if (s_timer >= GAME_OVER_ANIM_FRAMES)
295 selection_grid_process_input(&game_over_selection_grid);
296}
297
299{
300 toggle_windows(false, false);
301
303 s_end_condition = END_CONDITION_NONE;
304
305 u32 previous_seed = g_game_vars.rng_info.seed;
306
307 if (!s_continue_run)
308 game_reset();
309
310 if (s_reuse_seed)
311 rng_set_seed(previous_seed);
312 else
313 g_game_vars.rng_info.seed = UNDEFINED;
314}
315
316// SelectionGrid Implementation
317
318static Button* game_over_get_button_from_sel(const Selection* selection)
319{
320 return &game_over_buttons[selection->y][selection->x];
321}
322
323static void game_over_on_key_transit(SelectionGrid* selection_grid, Selection* selection)
324{
325 if (key_hit(SELECT_CARD))
326 button_press(&game_over_buttons[selection->y][selection->x]);
327}
328
329static bool game_over_on_selection_changed(
330 SelectionGrid* selection_grid,
331 int row_idx,
332 const Selection* prev_selection,
333 const Selection* new_selection
334)
335{
336 if (row_idx == prev_selection->y)
337 button_set_highlight(game_over_get_button_from_sel(prev_selection), false);
338
339 if (row_idx == new_selection->y)
340 button_set_highlight(game_over_get_button_from_sel(new_selection), true);
341
342 return true;
343}
344
345// Buttons
346
347static void reuse_seed_on_pressed(void)
348{
349 s_reuse_seed = !s_reuse_seed;
351 s_reuse_seed ? REUSE_SEED_BTN_ON_SRC_RECT : REUSE_SEED_BTN_OFF_SRC_RECT,
352 REUSE_SEED_BTN_DEST_POS
353 );
354}
355
356static void new_run_on_pressed(void)
357{
358 game_change_state(GAME_STATE_RUN_SETUP);
359}
360
361static void main_menu_on_pressed(void)
362{
363 game_change_state(GAME_STATE_MAIN_MENU);
364}
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:191
void game_over_on_update(void)
Game Over screen state update.
Definition game_over.c:209
void game_over_on_exit(void)
Game Over screen cleanup, common to both losing and winning.
Definition game_over.c:298
void game_win_on_init(void)
Game Over screen state initialization when winning.
Definition game_over.c:183
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.
INLINE void sprite_position(Sprite *sprite, int x, int y)
Set sprite position. Inlined for efficiency.
Definition sprite.h:400
A button representation.
Definition button.h:31
Definition card.h:50
An iterator into a list.
Definition list.h:91
u32 seed
Definition random.h:43
The core selection grid struct, represents the grid itself and its state.
POINT pos
Sprite position on screen in pixels.
Definition sprite.h:43
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