199 PuzzleDropWindow(
const std::string &path,
int width,
int height,
bool fullscreen,
bool enable_vsync)
202 data_root(asset_root +
"/data"),
203 shader_root(data_root),
204 tetris_data_root(data_root) {
205 std::random_device rd;
208 setFont(data_root +
"/font.ttf", 22);
209 std::array<std::string, LEVEL_GRAPHIC_COUNT> level_graphics{};
210 for (
size_t i = 0; i < level_graphics.size(); ++i) {
211 level_graphics[i] = std::format(
"{}/level{}.png", data_root, i + 1);
213 std::shuffle(level_graphics.begin(), level_graphics.end(), rng);
214 for (
size_t i = 0; i < backgrounds.size(); ++i) {
217 shader_root +
"/sprite.vert.spv",
218 shader_root +
"/puzzle_drop_background.frag.spv");
221 std::format(
"{}/intro1.png", data_root),
222 shader_root +
"/sprite.vert.spv",
223 shader_root +
"/intro.frag.spv");
225 rain_config.
color =
"#2f8dff";
228 matrix_rain = std::make_unique<matrix::Rain>(*
this, std::move(rain_config));
229 try_open_first_gamepad();
231 const uint32_t white_pixel = 0xFFFFFFFFu;
232 preview_border_sprite->updateTexture(&white_pixel, 1, 1);
233 for (std::size_t i = 0; i < preview_block_sprites.size(); ++i) {
238 reset_intro_screen();
242 if (
device != VK_NULL_HANDLE) {
251 cube_model->resize(
this);
253 if (grid_backdrop_model) {
254 grid_backdrop_model->resize(
this);
257 matrix_rain->on_swapchain_recreated(*
this);
262 if (e.type == SDL_EVENT_QUIT) {
267 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
268 if (!open_gamepad(e.gdevice.which)) {
269 try_open_first_gamepad();
274 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
275 if (gamepad !=
nullptr && e.gdevice.which == gamepad_id) {
277 try_open_first_gamepad();
282 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
283 handle_gamepad_button_down(e.gbutton.button);
287 if (e.type != SDL_EVENT_KEY_DOWN || e.key.repeat) {
291 if (intro_active && (e.key.key == SDLK_SPACE || e.key.key == SDLK_RETURN || e.key.key == SDLK_KP_ENTER)) {
334 const auto now = std::chrono::steady_clock::now();
335 const float delta_seconds = std::chrono::duration<float>(now - last_input_update).count();
336 last_input_update = now;
337 try_open_first_gamepad();
338 randomize_wildcard_color();
343 background_time += delta_seconds;
344 const bool *keys = SDL_GetKeyboardState(
nullptr);
345 if (keys !=
nullptr) {
346 handle_view_controls(keys, delta_seconds);
347 handle_piece_controls(keys, delta_seconds);
349 handle_gamepad_input(delta_seconds);
352 if (game_started && !game_over) {
353 if (std::chrono::duration<float>(now - last_fall).count() >=
FALL_SECONDS[difficulty]) {
357 if (std::chrono::duration<float>(now - last_process).count() >= 0.018f) {
364 const SDL_Color primary{255, 244, 223, 255};
365 const SDL_Color secondary{218, 229, 232, 255};
367 printText(
"Press Enter", 24, 54, secondary);
372 print_game_over_text(secondary);
376 printText(std::format(
"Level {} Lines {} Difficulty {}", level, lines, difficulty + 1), 24, 22, primary);
382 draw_game_scene(cmd, image_index, extent);
383 render_intro(cmd, extent);
387 draw_game_scene(cmd, image_index, extent);
391 std::string asset_root;
392 std::string data_root;
393 std::string shader_root;
394 std::string tetris_data_root;
396 std::array<std::array<Cell, BOARD_WIDTH>, BOARD_HEIGHT> board{};
399 std::unique_ptr<mxvk::VKAbstractModel> cube_model{};
400 std::unique_ptr<mxvk::VKAbstractModel> grid_backdrop_model{};
401 std::array<mxvk::VK_Sprite *, LEVEL_GRAPHIC_COUNT> backgrounds{};
402 mxvk::VK_Sprite *intro_sprite =
nullptr;
403 mxvk::VK_Sprite *preview_border_sprite =
nullptr;
404 std::array<mxvk::VK_Sprite *, PREVIEW_BLOCK_TEXTURE_COUNT> preview_block_sprites{};
405 std::unique_ptr<matrix::Rain> matrix_rain{};
406 SDL_Gamepad *gamepad =
nullptr;
407 SDL_JoystickID gamepad_id = 0;
408 std::chrono::steady_clock::time_point last_fall{std::chrono::steady_clock::now()};
409 std::chrono::steady_clock::time_point last_process{std::chrono::steady_clock::now()};
410 std::chrono::steady_clock::time_point last_input_update{std::chrono::steady_clock::now()};
411 float horizontal_move_timer = 0.0f;
412 float soft_drop_timer = 0.0f;
413 float cycle_timer = 0.0f;
414 float gamepad_move_repeat_timer = 0.0f;
415 float gamepad_soft_drop_repeat_timer = 0.0f;
416 float gamepad_cycle_repeat_timer = 0.0f;
417 float gamepad_move_held_seconds = 0.0f;
418 int horizontal_move_direction = 0;
419 int gamepad_move_direction = 0;
420 bool soft_drop_held =
false;
421 bool cycle_held =
false;
422 bool gamepad_soft_drop_held =
false;
423 bool gamepad_cycle_held =
false;
427 glm::vec3 wildcard_color{1.0f, 0.0f, 1.0f};
428 bool intro_active =
true;
430 std::chrono::steady_clock::time_point intro_last_update{std::chrono::steady_clock::now()};
431 std::chrono::steady_clock::time_point intro_start{std::chrono::steady_clock::now()};
432 float background_time = 0.0f;
433 bool game_started =
false;
434 bool game_over =
false;
435 float grid_yaw = -10.0f;
436 float grid_pitch = -8.0f;
437 float camera_distance = 2.32f;
438 static constexpr Sint16 GAMEPAD_DEADZONE = 10000;
439 static constexpr float GAMEPAD_MOVE_INITIAL_DELAY_SECONDS = 0.22f;
440 static constexpr float GAMEPAD_MOVE_REPEAT_SECONDS = 0.12f;
441 static constexpr float GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS = 0.18f;
442 static constexpr float GAMEPAD_SOFT_DROP_REPEAT_SECONDS = 0.08f;
443 static constexpr float GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
444 static constexpr float GAMEPAD_CYCLE_REPEAT_SECONDS = 0.11f;
445 static constexpr float GAMEPAD_STICK_ROTATE_SPEED = 120.0f;
446 static constexpr float GAMEPAD_STICK_PITCH_SPEED = 100.0f;
447 static constexpr float GAMEPAD_STICK_SCALE = 1.0f / 32768.0f;
449 void draw_game_scene(VkCommandBuffer cmd, uint32_t image_index,
const VkExtent2D &extent) {
450 draw_background(cmd, extent);
452 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
453 glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.12f, camera_distance), glm::vec3(0.0f, 0.05f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
454 view = glm::rotate(view, glm::radians(grid_pitch), glm::vec3(1.0f, 0.0f, 0.0f));
455 view = glm::rotate(view, glm::radians(grid_yaw), glm::vec3(0.0f, 1.0f, 0.0f));
457 glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
460 draw_grid_backdrop(cmd, image_index, view, proj);
461 draw_frame(cmd, image_index, view, proj);
464 Cell &cell = board[y][x];
471 draw_cube(cmd, image_index, cell.type, x, y, view, proj);
475 if ((game_started || intro_active) && !game_over) {
476 for (
const Block &block : piece.blocks) {
477 draw_cube(cmd, image_index, block.type, block.x, block.y, view, proj);
481 draw_next_piece_preview(cmd, extent);
484 void randomize_wildcard_color() {
485 std::uniform_int_distribution<int> dist(0, 254);
486 wildcard_color = glm::vec3(
487 static_cast<float>(dist(rng)) / 255.0f,
488 static_cast<float>(dist(rng)) / 255.0f,
489 static_cast<float>(dist(rng)) / 255.0f);
492 void handle_view_controls(
const bool *keys,
float delta_seconds) {
493 constexpr float YAW_SPEED = 115.0f;
494 constexpr float PITCH_SPEED = 90.0f;
496 if (keys[SDL_SCANCODE_A]) {
497 grid_yaw -= YAW_SPEED * delta_seconds;
499 if (keys[SDL_SCANCODE_D]) {
500 grid_yaw += YAW_SPEED * delta_seconds;
502 if (keys[SDL_SCANCODE_W]) {
503 grid_pitch = std::clamp(grid_pitch + PITCH_SPEED * delta_seconds, -70.0f, 70.0f);
505 if (keys[SDL_SCANCODE_S]) {
506 grid_pitch = std::clamp(grid_pitch - PITCH_SPEED * delta_seconds, -70.0f, 70.0f);
508 if (keys[SDL_SCANCODE_PAGEUP]) {
509 camera_distance = std::max(1.35f, camera_distance - ZOOM_SPEED * delta_seconds);
511 if (keys[SDL_SCANCODE_PAGEDOWN]) {
512 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
516 void handle_piece_controls(
const bool *keys,
float delta_seconds) {
517 if (!game_started || game_over) {
518 reset_held_piece_input();
522 const bool left = keys[SDL_SCANCODE_LEFT];
523 const bool right = keys[SDL_SCANCODE_RIGHT];
524 const int direction = (left == right) ? 0 : (left ? -1 : 1);
525 if (direction == 0) {
526 horizontal_move_direction = 0;
527 horizontal_move_timer = 0.0f;
529 constexpr float INITIAL_DELAY_SECONDS = 0.16f;
530 constexpr float REPEAT_SECONDS = 0.065f;
531 if (horizontal_move_direction != direction) {
532 horizontal_move_direction = direction;
533 horizontal_move_timer = -INITIAL_DELAY_SECONDS;
534 move_piece_horizontal(direction);
536 horizontal_move_timer += delta_seconds;
537 while (horizontal_move_timer >= 0.0f) {
538 horizontal_move_timer -= REPEAT_SECONDS;
539 move_piece_horizontal(direction);
544 if (keys[SDL_SCANCODE_DOWN]) {
545 constexpr float SOFT_DROP_REPEAT_SECONDS = 0.045f;
546 if (!soft_drop_held) {
547 soft_drop_held =
true;
548 soft_drop_timer = 0.0f;
550 last_fall = std::chrono::steady_clock::now();
552 soft_drop_timer += delta_seconds;
553 while (soft_drop_timer >= SOFT_DROP_REPEAT_SECONDS) {
554 soft_drop_timer -= SOFT_DROP_REPEAT_SECONDS;
556 last_fall = std::chrono::steady_clock::now();
560 soft_drop_held =
false;
561 soft_drop_timer = 0.0f;
564 if (keys[SDL_SCANCODE_UP]) {
565 constexpr float CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
566 constexpr float CYCLE_REPEAT_SECONDS = 0.11f;
569 cycle_timer = -CYCLE_INITIAL_DELAY_SECONDS;
570 cycle_piece_blocks();
572 cycle_timer += delta_seconds;
573 while (cycle_timer >= 0.0f) {
574 cycle_timer -= CYCLE_REPEAT_SECONDS;
575 cycle_piece_blocks();
584 void handle_gamepad_input(
float delta_seconds) {
585 if (gamepad ==
nullptr || !game_started || game_over) {
586 reset_held_gamepad_input();
590 const Sint16 left_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
591 const Sint16 left_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
592 const Sint16 right_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
593 const Sint16 right_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
595 const bool dpad_left = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
596 const bool dpad_right = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
597 const bool dpad_down = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
598 const bool dpad_up = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP);
600 const int move_direction = dpad_left == dpad_right
601 ? ((left_x < -GAMEPAD_DEADZONE) ? -1 : (left_x > GAMEPAD_DEADZONE) ? 1
603 : (dpad_left ? -1 : 1);
604 if (move_direction == 0) {
605 gamepad_move_direction = 0;
606 gamepad_move_held_seconds = 0.0f;
607 gamepad_move_repeat_timer = 0.0f;
608 }
else if (move_direction != gamepad_move_direction) {
609 gamepad_move_direction = move_direction;
610 gamepad_move_held_seconds = 0.0f;
611 gamepad_move_repeat_timer = 0.0f;
612 move_piece_horizontal(gamepad_move_direction);
614 gamepad_move_held_seconds += delta_seconds;
615 const float threshold = (gamepad_move_held_seconds < GAMEPAD_MOVE_INITIAL_DELAY_SECONDS)
616 ? GAMEPAD_MOVE_INITIAL_DELAY_SECONDS
617 : GAMEPAD_MOVE_REPEAT_SECONDS;
618 gamepad_move_repeat_timer += delta_seconds;
619 if (gamepad_move_repeat_timer >= threshold) {
620 move_piece_horizontal(gamepad_move_direction);
621 gamepad_move_repeat_timer = 0.0f;
625 const bool soft_drop_down = dpad_down || left_y > GAMEPAD_DEADZONE;
626 if (!soft_drop_down) {
627 gamepad_soft_drop_held =
false;
628 gamepad_soft_drop_repeat_timer = 0.0f;
630 const float threshold = gamepad_soft_drop_held ? GAMEPAD_SOFT_DROP_REPEAT_SECONDS : GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS;
631 gamepad_soft_drop_repeat_timer += delta_seconds;
632 if (gamepad_soft_drop_repeat_timer >= threshold) {
634 last_fall = std::chrono::steady_clock::now();
635 gamepad_soft_drop_repeat_timer = 0.0f;
636 gamepad_soft_drop_held =
true;
641 gamepad_cycle_held =
false;
642 gamepad_cycle_repeat_timer = 0.0f;
644 const float threshold = gamepad_cycle_held ? GAMEPAD_CYCLE_REPEAT_SECONDS : GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS;
645 gamepad_cycle_repeat_timer += delta_seconds;
646 if (gamepad_cycle_repeat_timer >= threshold) {
647 cycle_piece_blocks();
648 gamepad_cycle_repeat_timer = 0.0f;
649 gamepad_cycle_held =
true;
653 if (std::abs(right_x) > GAMEPAD_DEADZONE) {
654 grid_yaw +=
static_cast<float>(right_x) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_ROTATE_SPEED * delta_seconds;
656 if (std::abs(right_y) > GAMEPAD_DEADZONE) {
657 grid_pitch = std::clamp(grid_pitch -
static_cast<float>(right_y) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_PITCH_SPEED * delta_seconds,
663 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
664 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
666 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
667 camera_distance = std::max(1.35f, camera_distance - ZOOM_SPEED * delta_seconds);
671 void handle_gamepad_button_down(Uint8 button) {
673 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
680 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
683 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
693 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
695 }
else if (button == SDL_GAMEPAD_BUTTON_WEST) {
697 }
else if (button == SDL_GAMEPAD_BUTTON_EAST) {
699 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
704 void reset_held_piece_input() {
705 horizontal_move_timer = 0.0f;
706 soft_drop_timer = 0.0f;
708 horizontal_move_direction = 0;
709 soft_drop_held =
false;
713 void reset_held_gamepad_input() {
714 gamepad_move_repeat_timer = 0.0f;
715 gamepad_soft_drop_repeat_timer = 0.0f;
716 gamepad_cycle_repeat_timer = 0.0f;
717 gamepad_move_held_seconds = 0.0f;
718 gamepad_move_direction = 0;
719 gamepad_soft_drop_held =
false;
720 gamepad_cycle_held =
false;
723 void move_piece_horizontal(
int direction) {
724 if (!check_piece(piece, direction, 0)) {
734 void cycle_piece_blocks() {
739 if (!game_started || game_over) {
742 while (check_piece(piece, 0, 1)) {
746 last_fall = std::chrono::steady_clock::now();
749 bool open_gamepad(SDL_JoystickID
id) {
750 if (gamepad !=
nullptr && gamepad_id ==
id) {
754 gamepad = SDL_OpenGamepad(
id);
755 if (gamepad ==
nullptr) {
762 void close_gamepad() {
763 if (gamepad !=
nullptr) {
764 SDL_CloseGamepad(gamepad);
770 void try_open_first_gamepad() {
771 if (gamepad !=
nullptr) {
775 SDL_JoystickID *ids = SDL_GetGamepads(&count);
776 if (ids ==
nullptr || count <= 0) {
777 if (ids !=
nullptr) {
782 open_gamepad(ids[0]);
787 reset_held_piece_input();
788 reset_held_gamepad_input();
789 for (
auto &row : board) {
790 for (Cell &cell : row) {
792 cell.clear_value = 0;
793 cell.flash_counter = 0;
799 piece.new_piece(BOARD_WIDTH / 2, 0, rng);
800 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
801 last_fall = std::chrono::steady_clock::now();
802 last_process = last_fall;
806 if (check_piece(piece, 0, 1)) {
812 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
813 if (!check_piece(piece, 0, 0)) {
818 [[nodiscard]]
bool check_piece(
const Piece &test_piece,
int offset_x,
int offset_y)
const {
819 for (
const Block &block : test_piece.blocks) {
820 const int x = block.x + offset_x;
821 const int y = block.y + offset_y;
822 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
834 for (
const Block &block : piece.blocks) {
835 if (block.x < 0 || block.x >= BOARD_WIDTH || block.y < 0 || block.y >= BOARD_HEIGHT) {
838 Cell &cell = board[block.y][block.x];
839 cell.type = block.type;
840 cell.clear_value = 0;
841 cell.flash_counter = 0;
849 if (!game_started || game_over) {
852 Piece test_piece = piece;
853 test_piece.rotate_left();
854 if (check_piece(test_piece, 0, 0)) {
859 void rotate_right() {
860 if (!game_started || game_over) {
863 Piece test_piece = piece;
864 test_piece.rotate_right();
865 if (check_piece(test_piece, 0, 0)) {
866 piece.rotate_right();
871 constexpr std::array<std::array<int, 2>, 4> directions{{
881 for (
const auto &direction : directions) {
882 for (BlockType start : color_starts) {
886 if (check_sequence(x, y, direction[0], direction[1], one, two, three) ||
887 check_sequence(x, y, direction[0], direction[1], three, two, one)) {
888 mark_clear(x, y, direction[0], direction[1]);
899 bool proc_move_down() {
900 for (
int y = BOARD_HEIGHT - 2; y >= 0; --y) {
902 Cell &source = board[y][x];
903 Cell &target = board[y + 1][x];
905 target.type = source.type;
906 target.clear_value = source.clear_value;
907 target.flash_counter = source.flash_counter;
909 source.clear_value = 0;
910 source.flash_counter = 0;
916 bool updated =
false;
917 for (
auto &row : board) {
918 for (Cell &cell : row) {
921 ++cell.flash_counter;
922 if (cell.clear_value > 50) {
924 cell.clear_value = 0;
925 cell.flash_counter = 0;
934 [[nodiscard]]
bool check_sequence(
int x,
int y,
int dx,
int dy, BlockType first, BlockType second, BlockType third)
const {
935 return check_block(x, y, first) && check_block(x + dx, y + dy, second) && check_block(x + dx * 2, y + dy * 2, third);
938 [[nodiscard]]
bool check_block(
int x,
int y, BlockType expected)
const {
939 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
945 void mark_clear(
int x,
int y,
int dx,
int dy) {
946 for (
int i = 0; i < 3; ++i) {
947 Cell &cell = board[y + dy * i][x + dx * i];
949 cell.clear_value = 1;
950 cell.flash_counter = 0;
956 if ((lines % 6) == 0 && level <
static_cast<int>(backgrounds.size())) {
961 void init_cube_model() {
962 cube_model = std::make_unique<mxvk::VKAbstractModel>();
963 cube_model->load(
this,
964 tetris_data_root +
"/cube.mxmod.z",
965 data_root +
"/cube_textures.txt",
968 cube_model->setShaders(
this,
969 shader_root +
"/puzzle_drop_piece.vert.spv",
970 shader_root +
"/puzzle_drop_piece.frag.spv");
972 grid_backdrop_model = std::make_unique<mxvk::VKAbstractModel>();
973 grid_backdrop_model->load(
this,
974 tetris_data_root +
"/cube.mxmod.z",
975 tetris_data_root +
"/manifest_gray.txt",
978 grid_backdrop_model->setShaders(
this,
979 shader_root +
"/puzzle_drop_piece.vert.spv",
980 shader_root +
"/puzzle_drop_piece.frag.spv");
981 grid_backdrop_model->setAlphaBlending(
true);
984 void cleanup_models() {
986 cube_model->cleanup(
this);
989 if (grid_backdrop_model) {
990 grid_backdrop_model->cleanup(
this);
991 grid_backdrop_model.reset();
995 void print_game_over_text(
const SDL_Color &color) {
996 const std::string text = std::format(
"Game Over: Lines cleared: {} [Press Enter to Restart]", lines);
1005 const int screen_width =
static_cast<int>(extent.width);
1006 const int screen_height =
static_cast<int>(extent.height);
1007 const int x = std::max(0, (screen_width - text_width) / 2);
1008 const int y = std::max(0, (screen_height - text_height) / 2);
1012 void reset_intro_screen() {
1013 intro_active =
true;
1015 intro_last_update = std::chrono::steady_clock::now();
1016 intro_start = intro_last_update;
1017 game_started =
false;
1019 matrix_rain->set_opacity(1.0f);
1020 matrix_rain->reset();
1022 reset_held_piece_input();
1023 reset_held_gamepad_input();
1027 intro_fade = std::min(intro_fade, 0.02f);
1028 intro_last_update = std::chrono::steady_clock::now() - std::chrono::milliseconds(INTRO_FADE_INTERVAL_MS);
1031 void finish_intro(
const std::chrono::steady_clock::time_point &now) {
1032 intro_active =
false;
1035 matrix_rain->set_opacity(0.0f);
1037 game_started =
true;
1040 last_input_update = now;
1041 reset_held_piece_input();
1042 reset_held_gamepad_input();
1045 void update_intro(
const std::chrono::steady_clock::time_point &now) {
1046 const auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - intro_last_update).count();
1047 if (elapsed_ms >
static_cast<long long>(INTRO_FADE_INTERVAL_MS)) {
1048 intro_last_update = now;
1051 if (intro_fade <= 0.0f) {
1056 void render_intro(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1058 finish_intro(std::chrono::steady_clock::now());
1062 const float elapsed = std::chrono::duration<float>(std::chrono::steady_clock::now() - intro_start).count();
1063 intro_sprite->
setShaderParams(elapsed, 0.0f, 0.0f, std::clamp(intro_fade, 0.0f, 1.0f));
1064 intro_sprite->
drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1065 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1068 render_matrix_rain(cmd, extent, std::clamp(intro_fade, 0.0f, 1.0f));
1071 void draw_background(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1072 const int index = std::clamp(level - 1, 0,
static_cast<int>(backgrounds.size()) - 1);
1073 mxvk::VK_Sprite *background = backgrounds[index];
1078 background->
drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1079 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1084 void draw_sprite_rect(mxvk::VK_Sprite *sprite, VkCommandBuffer cmd,
const VkExtent2D &extent,
int x,
int y,
int w,
int h) {
1089 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1094 void draw_next_piece_preview(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1099 const int panel_size = std::min({180,
static_cast<int>(
static_cast<float>(extent.width) * 0.22f),
static_cast<int>(
static_cast<float>(extent.height) * 0.30f)});
1100 if (panel_size < 72) {
1104 const int margin = 24;
1105 const int panel_x =
static_cast<int>(extent.width) - panel_size - margin;
1106 const int panel_y = 88;
1107 const int border = 4;
1109 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y, panel_size, border);
1110 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y + panel_size - border, panel_size, border);
1111 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y, border, panel_size);
1112 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x + panel_size - border, panel_y, border, panel_size);
1114 printText(
"Next", panel_x + 12, panel_y - 28, SDL_Color{255, 255, 255, 255});
1116 int min_x = next_piece.blocks[0].x;
1117 int max_x = next_piece.blocks[0].x;
1118 int min_y = next_piece.blocks[0].y;
1119 int max_y = next_piece.blocks[0].y;
1120 for (
const Block &block : next_piece.blocks) {
1121 min_x = std::min(min_x, block.x);
1122 max_x = std::max(max_x, block.x);
1123 min_y = std::min(min_y, block.y);
1124 max_y = std::max(max_y, block.y);
1127 const float inner_padding = 28.0f;
1128 const float inner_size =
static_cast<float>(panel_size) - inner_padding * 2.0f;
1129 const int cells_wide = max_x - min_x + 1;
1130 const int cells_high = max_y - min_y + 1;
1131 const int block_size =
static_cast<int>(std::min(34.0f, inner_size /
static_cast<float>(std::max(cells_wide, cells_high))));
1132 const float piece_w =
static_cast<float>(cells_wide * block_size);
1133 const float piece_h =
static_cast<float>(cells_high * block_size);
1134 const float center_x =
static_cast<float>(panel_x) +
static_cast<float>(panel_size) * 0.5f;
1135 const float center_y =
static_cast<float>(panel_y) +
static_cast<float>(panel_size) * 0.5f;
1136 const float origin_x = center_x - piece_w * 0.5f;
1137 const float origin_y = center_y - piece_h * 0.5f;
1139 for (
const Block &block : next_piece.blocks) {
1141 if (index < 0 || index >=
static_cast<int>(preview_block_sprites.size())) {
1144 mxvk::VK_Sprite *block_sprite = preview_block_sprites[
static_cast<std::size_t
>(index)];
1145 const float x = origin_x +
static_cast<float>(block.x - min_x) *
static_cast<float>(block_size);
1146 const float y = origin_y +
static_cast<float>(block.y - min_y) *
static_cast<float>(block_size);
1147 draw_sprite_rect(block_sprite, cmd, extent,
static_cast<int>(x),
static_cast<int>(y), block_size, block_size);
1151 void render_matrix_rain(VkCommandBuffer cmd,
const VkExtent2D &extent,
float opacity) {
1155 matrix_rain->set_opacity(opacity);
1156 matrix_rain->update_and_render(*
this,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1157 mxvk::VK_Sprite *rain_sprite = matrix_rain->sprite();
1158 if (rain_sprite ==
nullptr) {
1161 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1166 [[nodiscard]] glm::vec3 block_position(
int x,
int y)
const {
1167 const float world_x = (
static_cast<float>(x) - (
static_cast<float>(
BOARD_WIDTH) - 1.0f) * 0.5f) * CUBE_SPACING;
1168 const float world_y = ((
static_cast<float>(
BOARD_HEIGHT) - 1.0f) * 0.5f -
static_cast<float>(y)) * CUBE_SPACING;
1169 return glm::vec3(world_x, world_y, 0.0f);
1172 void draw_grid_backdrop(VkCommandBuffer cmd, uint32_t image_index,
const glm::mat4 &view,
const glm::mat4 &proj) {
1173 if (!grid_backdrop_model) {
1177 mxvk::UniformBufferObject ubo{};
1178 glm::mat4 model(1.0f);
1179 const float width =
static_cast<float>(
BOARD_WIDTH) * CUBE_SPACING;
1180 const float height =
static_cast<float>(
BOARD_HEIGHT) * CUBE_SPACING;
1181 model = glm::translate(model, glm::vec3(0.0f, 0.0f, -CUBE_SCALE * 0.68f));
1182 model = glm::scale(model, glm::vec3(width + CUBE_SPACING * 0.35f, height + CUBE_SPACING * 0.35f, CUBE_SCALE * 0.10f));
1186 ubo.
fx = glm::vec4(0.16f, 0.16f, 0.17f, 0.62f);
1187 grid_backdrop_model->renderWithPushConstants(cmd, image_index, 0, ubo,
false);
1190 [[nodiscard]] glm::mat4 block_matrix(
int x,
int y,
float scale = CUBE_SCALE)
const {
1191 glm::mat4 model(1.0f);
1192 model = glm::translate(model, block_position(x, y));
1193 model = glm::scale(model, glm::vec3(scale));
1197 void draw_cube(VkCommandBuffer cmd,
1198 uint32_t image_index,
1202 const glm::mat4 &view,
1203 const glm::mat4 &proj)
const {
1208 mxvk::UniformBufferObject ubo{};
1209 ubo.
model = block_matrix(x, y);
1215 if (use_wildcard_texture) {
1216 tint = wildcard_color;
1218 ubo.
fx = glm::vec4(tint, use_wildcard_texture ? 2.0f : 1.0f);
1219 cube_model->renderWithPushConstants(cmd, image_index,
static_cast<size_t>(index), ubo,
false);
1222 void draw_frame(VkCommandBuffer cmd, uint32_t image_index,
const glm::mat4 &view,
const glm::mat4 &proj)
const {
1224 draw_frame_cube(cmd, image_index, -1, y, view, proj);
1225 draw_frame_cube(cmd, image_index, BOARD_WIDTH, y, view, proj);
1228 draw_frame_cube(cmd, image_index, x, BOARD_HEIGHT, view, proj);
1232 void draw_frame_cube(VkCommandBuffer cmd,
1233 uint32_t image_index,
1236 const glm::mat4 &view,
1237 const glm::mat4 &proj)
const {
1242 mxvk::UniformBufferObject ubo{};
1243 ubo.
model = block_matrix(x, y, CUBE_SCALE * 0.82f);
1246 ubo.
fx = glm::vec4(0.58f, 0.62f, 0.66f, 1.0f);
1247 cube_model->renderWithPushConstants(cmd, image_index, FRAME_TEXTURE_INDEX, ubo,
false);