diff --git a/assets/icons/close.png b/assets/icons/close.png new file mode 100644 index 0000000..2c5eb06 Binary files /dev/null and b/assets/icons/close.png differ diff --git a/game.c b/game.c index a3477f2..2bffe08 100644 --- a/game.c +++ b/game.c @@ -7,10 +7,7 @@ #include "timer.h" #include "zephr.h" -#define ANIM_SPEED 2.0f - -// TODO: configurable color palette -// TODO: particles when merging tiles +#define ANIM_SPEED 2.5f Game game = {0}; @@ -87,7 +84,7 @@ Color get_color_from_tile(Tile tile) { case 2048: return ColorRGBA(237, 194, 46, 255); default: - return ColorRGBA(205, 193, 180, 255); + return ColorRGBA(237, 194, 46, 255); } } @@ -109,177 +106,10 @@ u8 get_tile_font_size(Tile tile) { case 2048: return 45; default: - return 35; + return 40; } } -/////////////////////////////////// -// -// -// Drawing -// -// -/////////////////////////////////// - - -void draw_bg(void) { - Size window_size = zephr_get_window_size(); - UIConstraints board_con = {0}; - - set_width_constraint(&board_con, window_size.width, UI_CONSTRAINT_FIXED); - set_height_constraint(&board_con, window_size.height, UI_CONSTRAINT_FIXED); - - // bg - draw_quad(&board_con, ColorRGBA(248, 250, 230, 255), 0, ALIGN_TOP_LEFT); - - // 2048 text - UIConstraints text_con = {0}; - - set_y_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); - set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); - - draw_text("2048", 100, text_con, ColorRGBA(34, 234, 82, 155), ALIGN_TOP_CENTER); - - // score - char score[16]; - sprintf(score, "%d", game.score); - - set_parent_constraint(&text_con, NULL); - set_x_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); - set_y_constraint(&text_con, 0.8f, UI_CONSTRAINT_RELATIVE); - draw_text("Score", 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); - - set_x_constraint(&text_con, 0.15f, UI_CONSTRAINT_RELATIVE); - draw_text(score, 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); -} - -void draw_quit_dialog(void) { - UIConstraints dialog_con = {0}; - set_x_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); - set_y_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); - set_width_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); - set_height_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); - draw_quad(&dialog_con, ColorRGBA(0, 0, 0, 200), 0, ALIGN_CENTER); - - set_width_constraint(&dialog_con, 0.4f, UI_CONSTRAINT_RELATIVE); - set_height_constraint(&dialog_con, 0.2f, UI_CONSTRAINT_RELATIVE); - draw_quad(&dialog_con, ColorRGBA(135, 124, 124, 255), 8, ALIGN_CENTER); - - UIConstraints text_con = {0}; - set_parent_constraint(&text_con, &dialog_con); - set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); - set_y_constraint(&text_con, -0.04f, UI_CONSTRAINT_RELATIVE); - set_width_constraint(&text_con, 1, UI_CONSTRAINT_RELATIVE_PIXELS); - draw_text("Are you sure you want to quit?", 36.f, text_con, COLOR_WHITE, ALIGN_CENTER); - - UIConstraints btn_con = {0}; - set_parent_constraint(&btn_con, &dialog_con); - set_width_constraint(&btn_con, 0.1f, UI_CONSTRAINT_RELATIVE); - set_height_constraint(&btn_con, 0.05f, UI_CONSTRAINT_RELATIVE); - set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); - set_x_constraint(&btn_con, -0.07f, UI_CONSTRAINT_RELATIVE); - if (draw_button(&btn_con, ColorRGBA(201, 146, 126, 255), "Yes", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { - game.quit_dialog = false; - zephr_quit(); - } - - set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); - set_x_constraint(&btn_con, 0.07f, UI_CONSTRAINT_RELATIVE); - if (draw_button(&btn_con, ColorRGBA(201, 146, 126, 255), "No", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { - game.quit_dialog = false; - } -} - -void draw_ui(void) { - ButtonState bg_btns_state = game.quit_dialog ? BUTTON_STATE_INACTIVE : BUTTON_STATE_ACTIVE; - UIConstraints btn_con = {0}; - - set_x_constraint(&btn_con, -8, UI_CONSTRAINT_RELATIVE_PIXELS); - set_y_constraint(&btn_con, 8, UI_CONSTRAINT_RELATIVE_PIXELS); - set_width_constraint(&btn_con, 80, UI_CONSTRAINT_RELATIVE_PIXELS); - set_height_constraint(&btn_con, 1, UI_CONSTRAINT_ASPECT_RATIO); - - if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[HELP_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { - printf("help\n"); - } - - set_x_constraint(&btn_con, -8 - btn_con.width - 10, UI_CONSTRAINT_RELATIVE_PIXELS); - if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[SETTINGS_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { - printf("settings\n"); - } - - set_x_constraint(&btn_con, 0, UI_CONSTRAINT_RELATIVE_PIXELS); - set_y_constraint(&btn_con, -0.05f, UI_CONSTRAINT_RELATIVE); - set_width_constraint(&btn_con, 200, UI_CONSTRAINT_RELATIVE_PIXELS); - set_height_constraint(&btn_con, 65, UI_CONSTRAINT_RELATIVE_PIXELS); - if (draw_button(&btn_con, ColorRGBA(201, 172, 126, 255), "New Game", btn_con.height * 0.25f, ALIGN_BOTTOM_CENTER, bg_btns_state)) { - printf("new game\n"); - } - - if (game.quit_dialog) { - draw_quit_dialog(); - } -} - -void draw_board(void) { - // board - UIConstraints board_con = {0}; - set_y_constraint(&board_con, 0.05f, UI_CONSTRAINT_RELATIVE); - set_height_constraint(&board_con, 0.6f, UI_CONSTRAINT_RELATIVE); - set_width_constraint(&board_con, 1, UI_CONSTRAINT_ASPECT_RATIO); - const float board_border_radius = board_con.width * 0.02f; - draw_quad(&board_con, ColorRGBA(140, 154, 147, 255), board_border_radius, ALIGN_CENTER); - - // empty tiles - const float tile_padding = board_con.width * 0.02f; - const float tile_height = board_con.height * 0.225f; - const float tile_board_radius = tile_height * 0.15f; - - UIConstraints empty_tile_con = {0}; - set_parent_constraint(&empty_tile_con, &board_con); - set_height_constraint(&empty_tile_con, tile_height, UI_CONSTRAINT_FIXED); - set_width_constraint(&empty_tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO); - - for (int i = 0; i < 4; i++) { - set_y_constraint(&empty_tile_con, (tile_height + tile_padding) * i + tile_padding, UI_CONSTRAINT_FIXED); - for (int j = 0; j < 4; j++) { - set_x_constraint(&empty_tile_con, (tile_height + tile_padding) * j + tile_padding, UI_CONSTRAINT_FIXED); - draw_quad(&empty_tile_con, ColorRGBA(120, 133, 126, 255), tile_board_radius, ALIGN_TOP_LEFT); - } - } - - // filled tiles - UIConstraints text_con = {0}; - set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); - UIConstraints tile_con = {0}; - set_parent_constraint(&tile_con, &board_con); - set_height_constraint(&tile_con, tile_height, UI_CONSTRAINT_FIXED); - set_width_constraint(&tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO); - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - Tile *tile = &game.board[i][j]; - Color tile_color = get_color_from_tile(*tile); - char value[16]; - sprintf(value, "%d", tile->value); - - set_y_constraint(&tile_con, (f32)((tile_height + tile_padding) * i + tile_padding + game.board[i][j].anim_y_offset_relative * board_con.height), UI_CONSTRAINT_FIXED); - set_x_constraint(&tile_con, (f32)((tile_height + tile_padding) * j + tile_padding + game.board[i][j].anim_x_offset_relative * board_con.height), UI_CONSTRAINT_FIXED); - if (tile->value > 0) { - draw_quad(&tile_con, tile_color, tile_board_radius, ALIGN_TOP_LEFT); - - // value text - set_parent_constraint(&text_con, &tile_con); - set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); - set_y_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); - - draw_text(value, get_tile_font_size(*tile), text_con, mult_color(tile_color, 0.5), ALIGN_CENTER); - } - } - } -} - - /////////////////////////////////// // // @@ -289,7 +119,6 @@ void draw_board(void) { /////////////////////////////////// void move_right(void) { - if (game.animating) return; game.last_move_dir = MOVE_DIR_RIGHT; for (int i = 0; i < 4; i++) { @@ -340,7 +169,6 @@ void move_right(void) { } void move_left(void) { - if (game.animating) return; game.last_move_dir = MOVE_DIR_LEFT; for (int i = 0; i < 4; i++) { @@ -392,7 +220,6 @@ void move_left(void) { } void move_down(void) { - if (game.animating) return; game.last_move_dir = MOVE_DIR_DOWN; for (int i = 2; i >= 0; i--) { @@ -440,7 +267,6 @@ void move_down(void) { } void move_up(void) { - if (game.animating) return; game.last_move_dir = MOVE_DIR_UP; for (int i = 1; i < 4; i++) { @@ -519,6 +345,280 @@ void spawn_random_tile(void) { spawn_new_tile(x, y); } +// TODO: use this +bool gameover(void) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (game.board[y][x].value == 0) { + return false; + } + + for (int i = 0; i < 4; i++) { + u8 new_x = CORE_CLAMP(x + neighbors[i][0], 0, 3); + u8 new_y = CORE_CLAMP(y + neighbors[i][1], 0, 3); + + if ((new_y != y || new_x != x) && game.board[y][x].value == game.board[new_y][new_x].value) { + return false; + } + } + } + } + + return true; +} + +void game_new(void) { + game.score = 0; + game.animating = false; + game.spawning_new_tile = false; + game.quit_dialog = false; + game.help_dialog = false; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + game.board[y][x].value = 0; + game.board[y][x].new_value = 0; + game.board[y][x].merged = false; + game.board[y][x].tiles_to_move = 0; + game.board[y][x].anim_x_offset_relative = 0; + game.board[y][x].anim_y_offset_relative = 0; + } + } + + for (int i = 0; i < 2; i++) { + spawn_random_tile(); + } +} + +void game_attempt_quit(void) { + game.help_dialog = false; + game.quit_dialog = true; +} + +void game_init(void) { + srand(time(NULL)); + + game.icon_textures[HELP_ICON] = load_texture("assets/icons/help.png"); + game.icon_textures[SETTINGS_ICON] = load_texture("assets/icons/settings.png"); + game.icon_textures[CLOSE_ICON] = load_texture("assets/icons/close.png"); + + for (int i = 0; i < 2; i++) { + spawn_random_tile(); + } +} + +/////////////////////////////////// +// +// +// Drawing +// +// +/////////////////////////////////// + + +void draw_bg(void) { + Size window_size = zephr_get_window_size(); + UIConstraints board_con = {0}; + + set_width_constraint(&board_con, window_size.width, UI_CONSTRAINT_FIXED); + set_height_constraint(&board_con, window_size.height, UI_CONSTRAINT_FIXED); + + // bg + draw_quad(&board_con, ColorRGBA(248, 250, 230, 255), 0, ALIGN_TOP_LEFT); + + // 2048 text + UIConstraints text_con = {0}; + + set_y_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); + + draw_text("2048", 100, text_con, ColorRGBA(34, 234, 82, 155), ALIGN_TOP_CENTER); + + // score + char score[16]; + sprintf(score, "%d", game.score); + + set_parent_constraint(&text_con, NULL); + set_x_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_y_constraint(&text_con, 0.8f, UI_CONSTRAINT_RELATIVE); + draw_text("Score", 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); + + set_x_constraint(&text_con, 0.15f, UI_CONSTRAINT_RELATIVE); + draw_text(score, 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); +} + +void draw_quit_dialog(void) { + UIConstraints dialog_con = {0}; + set_x_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + set_width_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + draw_quad(&dialog_con, ColorRGBA(0, 0, 0, 200), 0, ALIGN_CENTER); + + set_width_constraint(&dialog_con, 0.4f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&dialog_con, 0.2f, UI_CONSTRAINT_RELATIVE); + draw_quad(&dialog_con, ColorRGBA(135, 124, 124, 255), 8, ALIGN_CENTER); + + UIConstraints text_con = {0}; + set_parent_constraint(&text_con, &dialog_con); + set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&text_con, -0.04f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&text_con, 1, UI_CONSTRAINT_RELATIVE_PIXELS); + draw_text("Are you sure you want to quit?", 36.f, text_con, COLOR_WHITE, ALIGN_CENTER); + + UIConstraints btn_con = {0}; + set_parent_constraint(&btn_con, &dialog_con); + set_width_constraint(&btn_con, 0.12f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&btn_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); + set_x_constraint(&btn_con, -0.07f, UI_CONSTRAINT_RELATIVE); + if (draw_button(&btn_con, mult_color(COLOR_WHITE, 0.9f), "Yes", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { + zephr_quit(); + } + + set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); + set_x_constraint(&btn_con, 0.07f, UI_CONSTRAINT_RELATIVE); + if (draw_button(&btn_con, mult_color(COLOR_WHITE, 0.9f), "No", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { + game.quit_dialog = false; + } +} + +void draw_help_dialog(void) { + UIConstraints dialog_con = {0}; + set_width_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + set_x_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + draw_quad(&dialog_con, ColorRGBA(0, 0, 0, 200), 0, ALIGN_CENTER); + + UIConstraints content_card_con = {0}; + set_parent_constraint(&content_card_con, &dialog_con); + set_width_constraint(&content_card_con, 0.4f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&content_card_con, 0.3f, UI_CONSTRAINT_RELATIVE); + draw_quad(&content_card_con, ColorRGBA(135, 124, 124, 255), 8, ALIGN_CENTER); + + UIConstraints text_con = {0}; + set_parent_constraint(&text_con, &content_card_con); + set_width_constraint(&text_con, 1, UI_CONSTRAINT_RELATIVE_PIXELS); + set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&text_con, 40, UI_CONSTRAINT_RELATIVE_PIXELS); + draw_text("How to play", 64.f, text_con, COLOR_YELLOW, ALIGN_TOP_CENTER); + + set_y_constraint(&text_con, 30, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&text_con, 0.9f, UI_CONSTRAINT_RELATIVE); + draw_text("Use the arrow keys to move the tiles.\n" + "When two tiles with the same number touch, they\n" + "merge into one!\n\nYour goal is to reach 2048 without filling all the tiles", 30.f, text_con, COLOR_WHITE, ALIGN_CENTER); + + UIConstraints btn_con = {0}; + set_parent_constraint(&btn_con, &content_card_con); + set_width_constraint(&btn_con, 56, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&btn_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + set_y_constraint(&btn_con, -24, UI_CONSTRAINT_RELATIVE_PIXELS); + set_x_constraint(&btn_con, 24, UI_CONSTRAINT_RELATIVE_PIXELS); + if (draw_icon_button(&btn_con, mult_color(COLOR_WHITE, 0.8f), game.icon_textures[CLOSE_ICON], btn_con.height * 0.20f, ALIGN_TOP_RIGHT, BUTTON_STATE_ACTIVE)) { + game.help_dialog = false; + } +} + +void draw_ui(void) { + ButtonState bg_btns_state = game.quit_dialog || game.help_dialog + ? BUTTON_STATE_INACTIVE + : BUTTON_STATE_ACTIVE; + const int icon_btn_width = 80; + const int icon_btn_offset = 8; + const int icon_btns_padding = 10; + + UIConstraints btn_con = {0}; + set_x_constraint(&btn_con, -icon_btn_offset, UI_CONSTRAINT_RELATIVE_PIXELS); + set_y_constraint(&btn_con, icon_btn_offset, UI_CONSTRAINT_RELATIVE_PIXELS); + set_width_constraint(&btn_con, icon_btn_width, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&btn_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[HELP_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { + game.help_dialog = true; + } + + set_x_constraint(&btn_con, -icon_btn_offset - icon_btn_width - icon_btns_padding, UI_CONSTRAINT_RELATIVE_PIXELS); + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[SETTINGS_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { + printf("TODO: settings\n"); + } + + set_x_constraint(&btn_con, 0, UI_CONSTRAINT_RELATIVE_PIXELS); + set_y_constraint(&btn_con, -0.05f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&btn_con, 200, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&btn_con, 65, UI_CONSTRAINT_RELATIVE_PIXELS); + if (draw_button(&btn_con, ColorRGBA(201, 172, 126, 255), "New Game", btn_con.height * 0.25f, ALIGN_BOTTOM_CENTER, bg_btns_state)) { + game_new(); + } + + if (game.help_dialog) { + draw_help_dialog(); + } + + if (game.quit_dialog) { + draw_quit_dialog(); + } +} + +void draw_board(void) { + // board + UIConstraints board_con = {0}; + set_y_constraint(&board_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&board_con, 0.6f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&board_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + const float board_border_radius = board_con.width * 0.02f; + draw_quad(&board_con, ColorRGBA(140, 154, 147, 255), board_border_radius, ALIGN_CENTER); + + // empty tiles + const float tile_padding = board_con.width * 0.02f; + const float tile_height = board_con.height * 0.225f; + const float tile_board_radius = tile_height * 0.15f; + + UIConstraints empty_tile_con = {0}; + set_parent_constraint(&empty_tile_con, &board_con); + set_height_constraint(&empty_tile_con, tile_height, UI_CONSTRAINT_FIXED); + set_width_constraint(&empty_tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + + for (int i = 0; i < 4; i++) { + set_y_constraint(&empty_tile_con, (tile_height + tile_padding) * i + tile_padding, UI_CONSTRAINT_FIXED); + for (int j = 0; j < 4; j++) { + set_x_constraint(&empty_tile_con, (tile_height + tile_padding) * j + tile_padding, UI_CONSTRAINT_FIXED); + draw_quad(&empty_tile_con, ColorRGBA(120, 133, 126, 255), tile_board_radius, ALIGN_TOP_LEFT); + } + } + + // filled tiles + UIConstraints text_con = {0}; + set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); + UIConstraints tile_con = {0}; + set_parent_constraint(&tile_con, &board_con); + set_height_constraint(&tile_con, tile_height, UI_CONSTRAINT_FIXED); + set_width_constraint(&tile_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + Tile *tile = &game.board[i][j]; + Color tile_color = get_color_from_tile(*tile); + char value[16]; + sprintf(value, "%d", tile->value); + + set_y_constraint(&tile_con, (f32)((tile_height + tile_padding) * i + tile_padding + game.board[i][j].anim_y_offset_relative * board_con.height), UI_CONSTRAINT_FIXED); + set_x_constraint(&tile_con, (f32)((tile_height + tile_padding) * j + tile_padding + game.board[i][j].anim_x_offset_relative * board_con.height), UI_CONSTRAINT_FIXED); + if (tile->value > 0) { + draw_quad(&tile_con, tile_color, tile_board_radius, ALIGN_TOP_LEFT); + + // value text + set_parent_constraint(&text_con, &tile_con); + set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); + + draw_text(value, get_tile_font_size(*tile), text_con, mult_color(tile_color, 0.5), ALIGN_CENTER); + } + } + } +} + void update_positions(f64 delta_t) { if (game.animating) { switch (game.last_move_dir) { @@ -651,37 +751,6 @@ void update_positions(f64 delta_t) { } } -bool gameover(void) { - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; x++) { - if (game.board[y][x].value == 0) { - return false; - } - - for (int i = 0; i < 4; i++) { - u8 new_x = CORE_CLAMP(x + neighbors[i][0], 0, 3); - u8 new_y = CORE_CLAMP(y + neighbors[i][1], 0, 3); - - if ((new_y != y || new_x != x) && game.board[y][x].value == game.board[new_y][new_x].value) { - return false; - } - } - } - } - - return true; -} - -void game_init(void) { - srand(time(NULL)); - - game.icon_textures[HELP_ICON] = load_texture("assets/icons/help.png"); - game.icon_textures[SETTINGS_ICON] = load_texture("assets/icons/settings.png"); - - for (int i = 0; i < 2; i++) { - spawn_random_tile(); - } -} /////////////////////////////////// // @@ -693,25 +762,26 @@ void game_init(void) { void handle_keyboard_input(ZephrEvent e) { + bool can_move = !game.quit_dialog && !game.help_dialog && !game.animating; + if (e.key.mods & ZEPHR_KEY_MOD_CTRL && e.key.code == ZEPHR_KEYCODE_Q) { - game.quit_dialog = true; + game_attempt_quit(); } else if (e.key.code == ZEPHR_KEYCODE_ESCAPE) { - if (game.quit_dialog) { - game.quit_dialog = false; - } + game.quit_dialog = false; + game.help_dialog = false; } else if (e.key.code == ZEPHR_KEYCODE_F11) { zephr_toggle_fullscreen(); } else if (e.key.code == ZEPHR_KEYCODE_UP) { - if (!game.quit_dialog) + if (can_move) move_up(); } else if (e.key.code == ZEPHR_KEYCODE_DOWN) { - if (!game.quit_dialog) + if (can_move) move_down(); } else if (e.key.code == ZEPHR_KEYCODE_LEFT) { - if (!game.quit_dialog) + if (can_move) move_left(); } else if (e.key.code == ZEPHR_KEYCODE_RIGHT) { - if (!game.quit_dialog) + if (can_move) move_right(); } } @@ -730,7 +800,7 @@ void game_loop(void) { if (game.quit_dialog) { zephr_quit(); } else { - game.quit_dialog = true; + game_attempt_quit(); } break; case ZEPHR_EVENT_KEY_PRESSED: @@ -746,6 +816,7 @@ void game_loop(void) { last_frame = now; update_positions(delta_t); + draw_bg(); draw_board(); draw_ui(); diff --git a/game.h b/game.h index e9bbe0d..007134c 100644 --- a/game.h +++ b/game.h @@ -13,6 +13,7 @@ typedef enum MoveDir { typedef enum IconTexture { HELP_ICON, SETTINGS_ICON, + CLOSE_ICON, ICON_TEXTURE_COUNT, } IconTexture; @@ -37,6 +38,7 @@ typedef struct Game { TextureId icon_textures[ICON_TEXTURE_COUNT]; bool quit_dialog; + bool help_dialog; } Game; void draw_board(void); diff --git a/text.c b/text.c index 7907df8..f28cc2f 100644 --- a/text.c +++ b/text.c @@ -11,6 +11,7 @@ #include "zephr.h" #define FONT_PIXEL_SIZE 128 +#define LINE_HEIGHT 2.0f Shader font_shader; unsigned int font_vao; @@ -288,6 +289,11 @@ Sizef calculate_text_size(const char *text, int font_size) { } h = CORE_MAX(h, max_bearing_h - ch.bearing.height + ch.size.height); + + if (text[i] == '\n') { + w = 0; + h += max_bearing_h; + } } size.width = (float)w * scale; size.height = (float)h * scale; @@ -337,12 +343,20 @@ GlyphInstanceList get_glyph_instance_list_from_text(const char *text, int font_s // model matrix int c = 0; int x = 0; + int y = 0; while (text[c] != '\0') { Character ch = zephr_ctx->font.characters[(int)text[c]]; // subtract the bearing width of the first character to remove the extra space // at the start of the text and move every char to the left by that width float xpos = (x + (ch.bearing.width - first_char_bearing_w)); - float ypos = (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h)); + float ypos = y + (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h)); + + if (text[c] == '\n') { + x = 0; + y += max_bearing_h + (u32)(36.f * LINE_HEIGHT); + c++; + continue; + } GlyphInstance instance = { .position = (Vec4f){xpos, ypos, ch.size.width, ch.size.height}, diff --git a/text.h b/text.h index ba419e8..3eb4202 100644 --- a/text.h +++ b/text.h @@ -29,6 +29,13 @@ typedef struct GlyphInstanceList { int capacity; } GlyphInstanceList; +// TODO: impl this to align text relative to its box +typedef enum TextAlign { + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} TextAlign; + void new_glyph_instance_list(GlyphInstanceList *list, u32 capacity); int init_fonts(const char *font_path); Sizef calculate_text_size(const char *text, int font_size); diff --git a/ui.c b/ui.c index 9847cb6..fa63945 100644 --- a/ui.c +++ b/ui.c @@ -43,7 +43,6 @@ void set_parent_constraint(UIConstraints *constraints, UIConstraints *parent_con void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type) { switch (type) { - case UI_CONSTRAINT_RELATIVE_PIXELS: case UI_CONSTRAINT_ASPECT_RATIO: // no-op. fallback to FIXED case UI_CONSTRAINT_FIXED: @@ -52,6 +51,9 @@ void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type case UI_CONSTRAINT_RELATIVE: constraints->x = (value * zephr_ctx->window.size.width); break; + case UI_CONSTRAINT_RELATIVE_PIXELS: + constraints->x = zephr_ctx->window.size.width / (float)zephr_ctx->screen_size.width * value; + break; } if (constraints->parent) { @@ -61,7 +63,6 @@ void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type void set_y_constraint(UIConstraints *constraints, float value, UIConstraint type) { switch (type) { - case UI_CONSTRAINT_RELATIVE_PIXELS: case UI_CONSTRAINT_ASPECT_RATIO: // no-op. fallback to FIXED case UI_CONSTRAINT_FIXED: @@ -70,6 +71,9 @@ void set_y_constraint(UIConstraints *constraints, float value, UIConstraint type case UI_CONSTRAINT_RELATIVE: constraints->y = (value * zephr_ctx->window.size.height); break; + case UI_CONSTRAINT_RELATIVE_PIXELS: + constraints->y = zephr_ctx->window.size.height / (float)zephr_ctx->screen_size.height * value; + break; } if (constraints->parent) { diff --git a/zephr.c b/zephr.c index d375153..ce065ab 100644 --- a/zephr.c +++ b/zephr.c @@ -779,6 +779,7 @@ void zephr_init(const char* font_path, const char* icon_path, const char* window zephr_ctx->cursors[ZEPHR_CURSOR_HRESIZE] = XCreateFontCursor(x11_display, XC_sb_h_double_arrow); zephr_ctx->cursors[ZEPHR_CURSOR_VRESIZE] = XCreateFontCursor(x11_display, XC_sb_v_double_arrow); + // non-standard cursors zephr_ctx->cursors[ZEPHR_CURSOR_DISABLED] = XcursorLibraryLoadCursor(x11_display, "crossed_circle"); x11_get_screen_size(&zephr_ctx->screen_size.width, &zephr_ctx->screen_size.height); diff --git a/zephr_math.c b/zephr_math.c index 50c52ea..1b4ceb3 100644 --- a/zephr_math.c +++ b/zephr_math.c @@ -77,9 +77,9 @@ void apply_translation(Matrix4x4 *matrix, Vec2f pos) { Color mult_color(Color color, float amount) { Color result = color; - result.r = CORE_CLAMP((u8)((float)result.r * amount), 0, 255); - result.g = CORE_CLAMP((u8)((float)result.g * amount), 0, 255); - result.b = CORE_CLAMP((u8)((float)result.b * amount), 0, 255); + result.r = CORE_CLAMP((i32)((float)result.r * amount), 0, 255); + result.g = CORE_CLAMP((i32)((float)result.g * amount), 0, 255); + result.b = CORE_CLAMP((i32)((float)result.b * amount), 0, 255); return result; }