195 PuzzleDropWindow(
const std::string &path,
int width,
int height,
bool fullscreen,
bool enable_vsync) :
mxvk::
VK_Window(
"-[ MXVK 3D PuzzleDrop ]-", width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), asset_root((path.empty() || path ==
".") ? std::string(
puzzle_drop_ASSET_DIR) : path), data_root(asset_root +
"/data"), shader_root(data_root), tetris_data_root(data_root) {
196 std::random_device rd;
199 setFont(data_root +
"/font.ttf", 22);
200 std::array<std::string, LEVEL_GRAPHIC_COUNT> level_graphics{};
201 for (
size_t i = 0; i < level_graphics.size(); ++i) {
202 level_graphics[i] = std::format(
"{}/level{}.png", data_root, i + 1);
204 std::shuffle(level_graphics.begin(), level_graphics.end(), rng);
205 for (
size_t i = 0; i < backgrounds.size(); ++i) {
206 backgrounds[i] =
createSprite(level_graphics[i], shader_root +
"/sprite.vert.spv", shader_root +
"/puzzle_drop_background.frag.spv");
208 intro_sprite =
createSprite(std::format(
"{}/intro1.png", data_root), shader_root +
"/sprite.vert.spv", shader_root +
"/intro.frag.spv");
210 rain_config.
color =
"#2f8dff";
213 matrix_rain = std::make_unique<matrix::Rain>(*
this, std::move(rain_config));
214 try_open_first_gamepad();
216 const uint32_t white_pixel = 0xFFFFFFFFu;
217 preview_border_sprite->updateTexture(&white_pixel, 1, 1);
218 for (std::size_t i = 0; i < preview_block_sprites.size(); ++i) {
223 reset_intro_screen();
227 if (
device != VK_NULL_HANDLE) {
236 cube_model->resize(
this);
238 if (grid_backdrop_model) {
239 grid_backdrop_model->resize(
this);
242 matrix_rain->on_swapchain_recreated(*
this);
247 if (e.type == SDL_EVENT_QUIT) {
252 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
253 if (!open_gamepad(e.gdevice.which)) {
254 try_open_first_gamepad();
259 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
260 if (gamepad !=
nullptr && e.gdevice.which == gamepad_id) {
262 try_open_first_gamepad();
267 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
268 handle_gamepad_button_down(e.gbutton.button);
272 if (e.type != SDL_EVENT_KEY_DOWN || e.key.repeat) {
276 if (intro_active && (e.key.key == SDLK_SPACE || e.key.key == SDLK_RETURN || e.key.key == SDLK_KP_ENTER)) {
319 const auto now = std::chrono::steady_clock::now();
320 const float delta_seconds = std::chrono::duration<float>(now - last_input_update).count();
321 last_input_update = now;
322 try_open_first_gamepad();
323 randomize_wildcard_color();
328 background_time += delta_seconds;
329 const bool *keys = SDL_GetKeyboardState(
nullptr);
330 if (keys !=
nullptr) {
331 handle_view_controls(keys, delta_seconds);
332 handle_piece_controls(keys, delta_seconds);
334 handle_gamepad_input(delta_seconds);
337 if (game_started && !game_over) {
338 if (std::chrono::duration<float>(now - last_fall).count() >=
FALL_SECONDS[difficulty]) {
342 if (std::chrono::duration<float>(now - last_process).count() >= 0.018f) {
349 const SDL_Color primary{255, 244, 223, 255};
350 const SDL_Color secondary{218, 229, 232, 255};
352 printText(
"Press Enter", 24, 54, secondary);
357 print_game_over_text(secondary);
361 printText(std::format(
"Level {} Lines {} Difficulty {}", level, lines, difficulty + 1), 24, 22, primary);
367 draw_game_scene(cmd, image_index, extent);
368 render_intro(cmd, extent);
372 draw_game_scene(cmd, image_index, extent);
376 std::string asset_root;
377 std::string data_root;
378 std::string shader_root;
379 std::string tetris_data_root;
381 std::array<std::array<Cell, BOARD_WIDTH>, BOARD_HEIGHT> board{};
384 std::unique_ptr<mxvk::VKAbstractModel> cube_model{};
385 std::unique_ptr<mxvk::VKAbstractModel> grid_backdrop_model{};
386 std::array<mxvk::VK_Sprite *, LEVEL_GRAPHIC_COUNT> backgrounds{};
387 mxvk::VK_Sprite *intro_sprite =
nullptr;
388 mxvk::VK_Sprite *preview_border_sprite =
nullptr;
389 std::array<mxvk::VK_Sprite *, PREVIEW_BLOCK_TEXTURE_COUNT> preview_block_sprites{};
390 std::unique_ptr<matrix::Rain> matrix_rain{};
391 SDL_Gamepad *gamepad =
nullptr;
392 SDL_JoystickID gamepad_id = 0;
393 std::chrono::steady_clock::time_point last_fall{std::chrono::steady_clock::now()};
394 std::chrono::steady_clock::time_point last_process{std::chrono::steady_clock::now()};
395 std::chrono::steady_clock::time_point last_input_update{std::chrono::steady_clock::now()};
396 float horizontal_move_timer = 0.0f;
397 float soft_drop_timer = 0.0f;
398 float cycle_timer = 0.0f;
399 float gamepad_move_repeat_timer = 0.0f;
400 float gamepad_soft_drop_repeat_timer = 0.0f;
401 float gamepad_cycle_repeat_timer = 0.0f;
402 float gamepad_move_held_seconds = 0.0f;
403 int horizontal_move_direction = 0;
404 int gamepad_move_direction = 0;
405 bool soft_drop_held =
false;
406 bool cycle_held =
false;
407 bool gamepad_soft_drop_held =
false;
408 bool gamepad_cycle_held =
false;
412 glm::vec3 wildcard_color{1.0f, 0.0f, 1.0f};
413 bool intro_active =
true;
415 std::chrono::steady_clock::time_point intro_last_update{std::chrono::steady_clock::now()};
416 std::chrono::steady_clock::time_point intro_start{std::chrono::steady_clock::now()};
417 float background_time = 0.0f;
418 bool game_started =
false;
419 bool game_over =
false;
420 float grid_yaw = -10.0f;
421 float grid_pitch = -8.0f;
422 float camera_distance = 2.32f;
423 static constexpr Sint16 GAMEPAD_DEADZONE = 10000;
424 static constexpr float GAMEPAD_MOVE_INITIAL_DELAY_SECONDS = 0.22f;
425 static constexpr float GAMEPAD_MOVE_REPEAT_SECONDS = 0.12f;
426 static constexpr float GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS = 0.18f;
427 static constexpr float GAMEPAD_SOFT_DROP_REPEAT_SECONDS = 0.08f;
428 static constexpr float GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
429 static constexpr float GAMEPAD_CYCLE_REPEAT_SECONDS = 0.11f;
430 static constexpr float GAMEPAD_STICK_ROTATE_SPEED = 120.0f;
431 static constexpr float GAMEPAD_STICK_PITCH_SPEED = 100.0f;
432 static constexpr float GAMEPAD_STICK_SCALE = 1.0f / 32768.0f;
434 void draw_game_scene(VkCommandBuffer cmd, uint32_t image_index,
const VkExtent2D &extent) {
435 draw_background(cmd, extent);
437 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
438 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));
439 view = glm::rotate(view, glm::radians(grid_pitch), glm::vec3(1.0f, 0.0f, 0.0f));
440 view = glm::rotate(view, glm::radians(grid_yaw), glm::vec3(0.0f, 1.0f, 0.0f));
442 glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
445 draw_grid_backdrop(cmd, image_index, view, proj);
446 draw_frame(cmd, image_index, view, proj);
449 Cell &cell = board[y][x];
456 draw_cube(cmd, image_index, cell.type, x, y, view, proj);
460 if ((game_started || intro_active) && !game_over) {
461 for (
const Block &block : piece.blocks) {
462 draw_cube(cmd, image_index, block.type, block.x, block.y, view, proj);
466 draw_next_piece_preview(cmd, extent);
469 void randomize_wildcard_color() {
470 std::uniform_int_distribution<int> dist(0, 254);
471 wildcard_color = glm::vec3(
static_cast<float>(dist(rng)) / 255.0f,
static_cast<float>(dist(rng)) / 255.0f,
static_cast<float>(dist(rng)) / 255.0f);
474 void handle_view_controls(
const bool *keys,
float delta_seconds) {
475 constexpr float YAW_SPEED = 115.0f;
476 constexpr float PITCH_SPEED = 90.0f;
478 if (keys[SDL_SCANCODE_A]) {
479 grid_yaw -= YAW_SPEED * delta_seconds;
481 if (keys[SDL_SCANCODE_D]) {
482 grid_yaw += YAW_SPEED * delta_seconds;
484 if (keys[SDL_SCANCODE_W]) {
485 grid_pitch = std::clamp(grid_pitch + PITCH_SPEED * delta_seconds, -70.0f, 70.0f);
487 if (keys[SDL_SCANCODE_S]) {
488 grid_pitch = std::clamp(grid_pitch - PITCH_SPEED * delta_seconds, -70.0f, 70.0f);
490 if (keys[SDL_SCANCODE_PAGEUP]) {
491 camera_distance = std::max(1.35f, camera_distance - ZOOM_SPEED * delta_seconds);
493 if (keys[SDL_SCANCODE_PAGEDOWN]) {
494 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
498 void handle_piece_controls(
const bool *keys,
float delta_seconds) {
499 if (!game_started || game_over) {
500 reset_held_piece_input();
504 const bool left = keys[SDL_SCANCODE_LEFT];
505 const bool right = keys[SDL_SCANCODE_RIGHT];
506 const int direction = (left == right) ? 0 : (left ? -1 : 1);
507 if (direction == 0) {
508 horizontal_move_direction = 0;
509 horizontal_move_timer = 0.0f;
511 constexpr float INITIAL_DELAY_SECONDS = 0.16f;
512 constexpr float REPEAT_SECONDS = 0.065f;
513 if (horizontal_move_direction != direction) {
514 horizontal_move_direction = direction;
515 horizontal_move_timer = -INITIAL_DELAY_SECONDS;
516 move_piece_horizontal(direction);
518 horizontal_move_timer += delta_seconds;
519 while (horizontal_move_timer >= 0.0f) {
520 horizontal_move_timer -= REPEAT_SECONDS;
521 move_piece_horizontal(direction);
526 if (keys[SDL_SCANCODE_DOWN]) {
527 constexpr float SOFT_DROP_REPEAT_SECONDS = 0.045f;
528 if (!soft_drop_held) {
529 soft_drop_held =
true;
530 soft_drop_timer = 0.0f;
532 last_fall = std::chrono::steady_clock::now();
534 soft_drop_timer += delta_seconds;
535 while (soft_drop_timer >= SOFT_DROP_REPEAT_SECONDS) {
536 soft_drop_timer -= SOFT_DROP_REPEAT_SECONDS;
538 last_fall = std::chrono::steady_clock::now();
542 soft_drop_held =
false;
543 soft_drop_timer = 0.0f;
546 if (keys[SDL_SCANCODE_UP]) {
547 constexpr float CYCLE_INITIAL_DELAY_SECONDS = 0.16f;
548 constexpr float CYCLE_REPEAT_SECONDS = 0.11f;
551 cycle_timer = -CYCLE_INITIAL_DELAY_SECONDS;
552 cycle_piece_blocks();
554 cycle_timer += delta_seconds;
555 while (cycle_timer >= 0.0f) {
556 cycle_timer -= CYCLE_REPEAT_SECONDS;
557 cycle_piece_blocks();
566 void handle_gamepad_input(
float delta_seconds) {
567 if (gamepad ==
nullptr || !game_started || game_over) {
568 reset_held_gamepad_input();
572 const Sint16 left_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
573 const Sint16 left_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
574 const Sint16 right_x = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTX);
575 const Sint16 right_y = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
577 const bool dpad_left = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
578 const bool dpad_right = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
579 const bool dpad_down = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
580 const bool dpad_up = SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_DPAD_UP);
582 const int move_direction = dpad_left == dpad_right ? ((left_x < -GAMEPAD_DEADZONE) ? -1 : (left_x > GAMEPAD_DEADZONE) ? 1 : 0) : (dpad_left ? -1 : 1);
583 if (move_direction == 0) {
584 gamepad_move_direction = 0;
585 gamepad_move_held_seconds = 0.0f;
586 gamepad_move_repeat_timer = 0.0f;
587 }
else if (move_direction != gamepad_move_direction) {
588 gamepad_move_direction = move_direction;
589 gamepad_move_held_seconds = 0.0f;
590 gamepad_move_repeat_timer = 0.0f;
591 move_piece_horizontal(gamepad_move_direction);
593 gamepad_move_held_seconds += delta_seconds;
594 const float threshold = (gamepad_move_held_seconds < GAMEPAD_MOVE_INITIAL_DELAY_SECONDS) ? GAMEPAD_MOVE_INITIAL_DELAY_SECONDS : GAMEPAD_MOVE_REPEAT_SECONDS;
595 gamepad_move_repeat_timer += delta_seconds;
596 if (gamepad_move_repeat_timer >= threshold) {
597 move_piece_horizontal(gamepad_move_direction);
598 gamepad_move_repeat_timer = 0.0f;
602 const bool soft_drop_down = dpad_down || left_y > GAMEPAD_DEADZONE;
603 if (!soft_drop_down) {
604 gamepad_soft_drop_held =
false;
605 gamepad_soft_drop_repeat_timer = 0.0f;
607 const float threshold = gamepad_soft_drop_held ? GAMEPAD_SOFT_DROP_REPEAT_SECONDS : GAMEPAD_SOFT_DROP_INITIAL_DELAY_SECONDS;
608 gamepad_soft_drop_repeat_timer += delta_seconds;
609 if (gamepad_soft_drop_repeat_timer >= threshold) {
611 last_fall = std::chrono::steady_clock::now();
612 gamepad_soft_drop_repeat_timer = 0.0f;
613 gamepad_soft_drop_held =
true;
618 gamepad_cycle_held =
false;
619 gamepad_cycle_repeat_timer = 0.0f;
621 const float threshold = gamepad_cycle_held ? GAMEPAD_CYCLE_REPEAT_SECONDS : GAMEPAD_CYCLE_INITIAL_DELAY_SECONDS;
622 gamepad_cycle_repeat_timer += delta_seconds;
623 if (gamepad_cycle_repeat_timer >= threshold) {
624 cycle_piece_blocks();
625 gamepad_cycle_repeat_timer = 0.0f;
626 gamepad_cycle_held =
true;
630 if (std::abs(right_x) > GAMEPAD_DEADZONE) {
631 grid_yaw +=
static_cast<float>(right_x) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_ROTATE_SPEED * delta_seconds;
633 if (std::abs(right_y) > GAMEPAD_DEADZONE) {
634 grid_pitch = std::clamp(grid_pitch -
static_cast<float>(right_y) * GAMEPAD_STICK_SCALE * GAMEPAD_STICK_PITCH_SPEED * delta_seconds, -70.0f, 70.0f);
638 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER)) {
639 camera_distance = std::min(7.0f, camera_distance + ZOOM_SPEED * delta_seconds);
641 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER)) {
642 camera_distance = std::max(1.35f, camera_distance - ZOOM_SPEED * delta_seconds);
646 void handle_gamepad_button_down(Uint8 button) {
648 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
655 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
658 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
668 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
670 }
else if (button == SDL_GAMEPAD_BUTTON_WEST) {
672 }
else if (button == SDL_GAMEPAD_BUTTON_EAST) {
674 }
else if (button == SDL_GAMEPAD_BUTTON_BACK) {
679 void reset_held_piece_input() {
680 horizontal_move_timer = 0.0f;
681 soft_drop_timer = 0.0f;
683 horizontal_move_direction = 0;
684 soft_drop_held =
false;
688 void reset_held_gamepad_input() {
689 gamepad_move_repeat_timer = 0.0f;
690 gamepad_soft_drop_repeat_timer = 0.0f;
691 gamepad_cycle_repeat_timer = 0.0f;
692 gamepad_move_held_seconds = 0.0f;
693 gamepad_move_direction = 0;
694 gamepad_soft_drop_held =
false;
695 gamepad_cycle_held =
false;
698 void move_piece_horizontal(
int direction) {
699 if (!check_piece(piece, direction, 0)) {
712 if (!game_started || game_over) {
715 while (check_piece(piece, 0, 1)) {
719 last_fall = std::chrono::steady_clock::now();
722 bool open_gamepad(SDL_JoystickID
id) {
723 if (gamepad !=
nullptr && gamepad_id ==
id) {
727 gamepad = SDL_OpenGamepad(
id);
728 if (gamepad ==
nullptr) {
735 void close_gamepad() {
736 if (gamepad !=
nullptr) {
737 SDL_CloseGamepad(gamepad);
743 void try_open_first_gamepad() {
744 if (gamepad !=
nullptr) {
748 SDL_JoystickID *ids = SDL_GetGamepads(&count);
749 if (ids ==
nullptr || count <= 0) {
750 if (ids !=
nullptr) {
755 open_gamepad(ids[0]);
760 reset_held_piece_input();
761 reset_held_gamepad_input();
762 for (
auto &row : board) {
763 for (Cell &cell : row) {
765 cell.clear_value = 0;
766 cell.flash_counter = 0;
772 piece.new_piece(BOARD_WIDTH / 2, 0, rng);
773 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
774 last_fall = std::chrono::steady_clock::now();
775 last_process = last_fall;
779 if (check_piece(piece, 0, 1)) {
785 next_piece.new_piece(BOARD_WIDTH / 2, 0, rng);
786 if (!check_piece(piece, 0, 0)) {
791 [[nodiscard]]
bool check_piece(
const Piece &test_piece,
int offset_x,
int offset_y)
const {
792 for (
const Block &block : test_piece.blocks) {
793 const int x = block.x + offset_x;
794 const int y = block.y + offset_y;
795 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
807 for (
const Block &block : piece.blocks) {
808 if (block.x < 0 || block.x >= BOARD_WIDTH || block.y < 0 || block.y >= BOARD_HEIGHT) {
811 Cell &cell = board[block.y][block.x];
812 cell.type = block.type;
813 cell.clear_value = 0;
814 cell.flash_counter = 0;
822 if (!game_started || game_over) {
825 Piece test_piece = piece;
826 test_piece.rotate_left();
827 if (check_piece(test_piece, 0, 0)) {
832 void rotate_right() {
833 if (!game_started || game_over) {
836 Piece test_piece = piece;
837 test_piece.rotate_right();
838 if (check_piece(test_piece, 0, 0)) {
839 piece.rotate_right();
844 constexpr std::array<std::array<int, 2>, 4> directions{{
854 for (
const auto &direction : directions) {
855 for (BlockType start : color_starts) {
859 if (check_sequence(x, y, direction[0], direction[1], one, two, three) || check_sequence(x, y, direction[0], direction[1], three, two, one)) {
860 mark_clear(x, y, direction[0], direction[1]);
871 bool proc_move_down() {
872 for (
int y = BOARD_HEIGHT - 2; y >= 0; --y) {
874 Cell &source = board[y][x];
875 Cell &target = board[y + 1][x];
877 target.type = source.type;
878 target.clear_value = source.clear_value;
879 target.flash_counter = source.flash_counter;
881 source.clear_value = 0;
882 source.flash_counter = 0;
888 bool updated =
false;
889 for (
auto &row : board) {
890 for (Cell &cell : row) {
893 ++cell.flash_counter;
894 if (cell.clear_value > 50) {
896 cell.clear_value = 0;
897 cell.flash_counter = 0;
906 [[nodiscard]]
bool check_sequence(
int x,
int y,
int dx,
int dy, BlockType first, BlockType second, BlockType third)
const {
return check_block(x, y, first) && check_block(x + dx, y + dy, second) && check_block(x + dx * 2, y + dy * 2, third); }
908 [[nodiscard]]
bool check_block(
int x,
int y, BlockType expected)
const {
909 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) {
915 void mark_clear(
int x,
int y,
int dx,
int dy) {
916 for (
int i = 0; i < 3; ++i) {
917 Cell &cell = board[y + dy * i][x + dx * i];
919 cell.clear_value = 1;
920 cell.flash_counter = 0;
926 if ((lines % 6) == 0 && level <
static_cast<int>(backgrounds.size())) {
931 void init_cube_model() {
932 cube_model = std::make_unique<mxvk::VKAbstractModel>();
933 cube_model->load(
this, tetris_data_root +
"/cube.mxmod.z", data_root +
"/cube_textures.txt", data_root, 1.0f);
934 cube_model->setShaders(
this, shader_root +
"/puzzle_drop_piece.vert.spv", shader_root +
"/puzzle_drop_piece.frag.spv");
936 grid_backdrop_model = std::make_unique<mxvk::VKAbstractModel>();
937 grid_backdrop_model->load(
this, tetris_data_root +
"/cube.mxmod.z", tetris_data_root +
"/manifest_gray.txt", tetris_data_root, 1.0f);
938 grid_backdrop_model->setShaders(
this, shader_root +
"/puzzle_drop_piece.vert.spv", shader_root +
"/puzzle_drop_piece.frag.spv");
939 grid_backdrop_model->setAlphaBlending(
true);
942 void cleanup_models() {
944 cube_model->cleanup(
this);
947 if (grid_backdrop_model) {
948 grid_backdrop_model->cleanup(
this);
949 grid_backdrop_model.reset();
953 void print_game_over_text(
const SDL_Color &color) {
954 const std::string text = std::format(
"Game Over: Lines cleared: {} [Press Enter to Restart]", lines);
963 const int screen_width =
static_cast<int>(extent.width);
964 const int screen_height =
static_cast<int>(extent.height);
965 const int x = std::max(0, (screen_width - text_width) / 2);
966 const int y = std::max(0, (screen_height - text_height) / 2);
970 void reset_intro_screen() {
973 intro_last_update = std::chrono::steady_clock::now();
974 intro_start = intro_last_update;
975 game_started =
false;
977 matrix_rain->set_opacity(1.0f);
978 matrix_rain->reset();
980 reset_held_piece_input();
981 reset_held_gamepad_input();
985 intro_fade = std::min(intro_fade, 0.02f);
986 intro_last_update = std::chrono::steady_clock::now() - std::chrono::milliseconds(INTRO_FADE_INTERVAL_MS);
989 void finish_intro(
const std::chrono::steady_clock::time_point &now) {
990 intro_active =
false;
993 matrix_rain->set_opacity(0.0f);
998 last_input_update = now;
999 reset_held_piece_input();
1000 reset_held_gamepad_input();
1003 void update_intro(
const std::chrono::steady_clock::time_point &now) {
1004 const auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - intro_last_update).count();
1005 if (elapsed_ms >
static_cast<long long>(INTRO_FADE_INTERVAL_MS)) {
1006 intro_last_update = now;
1009 if (intro_fade <= 0.0f) {
1014 void render_intro(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1016 finish_intro(std::chrono::steady_clock::now());
1020 const float elapsed = std::chrono::duration<float>(std::chrono::steady_clock::now() - intro_start).count();
1021 intro_sprite->
setShaderParams(elapsed, 0.0f, 0.0f, std::clamp(intro_fade, 0.0f, 1.0f));
1022 intro_sprite->
drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1023 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1026 render_matrix_rain(cmd, extent, std::clamp(intro_fade, 0.0f, 1.0f));
1029 void draw_background(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1030 const int index = std::clamp(level - 1, 0,
static_cast<int>(backgrounds.size()) - 1);
1031 mxvk::VK_Sprite *background = backgrounds[index];
1036 background->
drawSpriteRect(0, 0,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1037 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1042 void draw_sprite_rect(mxvk::VK_Sprite *sprite, VkCommandBuffer cmd,
const VkExtent2D &extent,
int x,
int y,
int w,
int h) {
1047 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1052 void draw_next_piece_preview(VkCommandBuffer cmd,
const VkExtent2D &extent) {
1057 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)});
1058 if (panel_size < 72) {
1062 const int margin = 24;
1063 const int panel_x =
static_cast<int>(extent.width) - panel_size - margin;
1064 const int panel_y = 88;
1065 const int border = 4;
1067 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y, panel_size, border);
1068 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y + panel_size - border, panel_size, border);
1069 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x, panel_y, border, panel_size);
1070 draw_sprite_rect(preview_border_sprite, cmd, extent, panel_x + panel_size - border, panel_y, border, panel_size);
1072 printText(
"Next", panel_x + 12, panel_y - 28, SDL_Color{255, 255, 255, 255});
1074 int min_x = next_piece.blocks[0].x;
1075 int max_x = next_piece.blocks[0].x;
1076 int min_y = next_piece.blocks[0].y;
1077 int max_y = next_piece.blocks[0].y;
1078 for (
const Block &block : next_piece.blocks) {
1079 min_x = std::min(min_x, block.x);
1080 max_x = std::max(max_x, block.x);
1081 min_y = std::min(min_y, block.y);
1082 max_y = std::max(max_y, block.y);
1085 const float inner_padding = 28.0f;
1086 const float inner_size =
static_cast<float>(panel_size) - inner_padding * 2.0f;
1087 const int cells_wide = max_x - min_x + 1;
1088 const int cells_high = max_y - min_y + 1;
1089 const int block_size =
static_cast<int>(std::min(34.0f, inner_size /
static_cast<float>(std::max(cells_wide, cells_high))));
1090 const float piece_w =
static_cast<float>(cells_wide * block_size);
1091 const float piece_h =
static_cast<float>(cells_high * block_size);
1092 const float center_x =
static_cast<float>(panel_x) +
static_cast<float>(panel_size) * 0.5f;
1093 const float center_y =
static_cast<float>(panel_y) +
static_cast<float>(panel_size) * 0.5f;
1094 const float origin_x = center_x - piece_w * 0.5f;
1095 const float origin_y = center_y - piece_h * 0.5f;
1097 for (
const Block &block : next_piece.blocks) {
1099 if (index < 0 || index >=
static_cast<int>(preview_block_sprites.size())) {
1102 mxvk::VK_Sprite *block_sprite = preview_block_sprites[
static_cast<std::size_t
>(index)];
1103 const float x = origin_x +
static_cast<float>(block.x - min_x) *
static_cast<float>(block_size);
1104 const float y = origin_y +
static_cast<float>(block.y - min_y) *
static_cast<float>(block_size);
1105 draw_sprite_rect(block_sprite, cmd, extent,
static_cast<int>(x),
static_cast<int>(y), block_size, block_size);
1109 void render_matrix_rain(VkCommandBuffer cmd,
const VkExtent2D &extent,
float opacity) {
1113 matrix_rain->set_opacity(opacity);
1114 matrix_rain->update_and_render(*
this,
static_cast<int>(extent.width),
static_cast<int>(extent.height));
1115 mxvk::VK_Sprite *rain_sprite = matrix_rain->sprite();
1116 if (rain_sprite ==
nullptr) {
1119 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
sprite_pipeline);
1124 [[nodiscard]] glm::vec3 block_position(
int x,
int y)
const {
1125 const float world_x = (
static_cast<float>(x) - (
static_cast<float>(
BOARD_WIDTH) - 1.0f) * 0.5f) * CUBE_SPACING;
1126 const float world_y = ((
static_cast<float>(
BOARD_HEIGHT) - 1.0f) * 0.5f -
static_cast<float>(y)) * CUBE_SPACING;
1127 return glm::vec3(world_x, world_y, 0.0f);
1130 void draw_grid_backdrop(VkCommandBuffer cmd, uint32_t image_index,
const glm::mat4 &view,
const glm::mat4 &proj) {
1131 if (!grid_backdrop_model) {
1135 mxvk::UniformBufferObject ubo{};
1136 glm::mat4 model(1.0f);
1137 const float width =
static_cast<float>(
BOARD_WIDTH) * CUBE_SPACING;
1138 const float height =
static_cast<float>(
BOARD_HEIGHT) * CUBE_SPACING;
1139 model = glm::translate(model, glm::vec3(0.0f, 0.0f, -CUBE_SCALE * 0.68f));
1140 model = glm::scale(model, glm::vec3(width + CUBE_SPACING * 0.35f, height + CUBE_SPACING * 0.35f, CUBE_SCALE * 0.10f));
1144 ubo.
fx = glm::vec4(0.16f, 0.16f, 0.17f, 0.62f);
1145 grid_backdrop_model->renderWithPushConstants(cmd, image_index, 0, ubo,
false);
1148 [[nodiscard]] glm::mat4 block_matrix(
int x,
int y,
float scale = CUBE_SCALE)
const {
1149 glm::mat4 model(1.0f);
1150 model = glm::translate(model, block_position(x, y));
1151 model = glm::scale(model, glm::vec3(scale));
1155 void draw_cube(VkCommandBuffer cmd, uint32_t image_index, BlockType type,
int x,
int y,
const glm::mat4 &view,
const glm::mat4 &proj)
const {
1160 mxvk::UniformBufferObject ubo{};
1161 ubo.
model = block_matrix(x, y);
1167 if (use_wildcard_texture) {
1168 tint = wildcard_color;
1170 ubo.
fx = glm::vec4(tint, use_wildcard_texture ? 2.0f : 1.0f);
1171 cube_model->renderWithPushConstants(cmd, image_index,
static_cast<size_t>(index), ubo,
false);
1174 void draw_frame(VkCommandBuffer cmd, uint32_t image_index,
const glm::mat4 &view,
const glm::mat4 &proj)
const {
1176 draw_frame_cube(cmd, image_index, -1, y, view, proj);
1177 draw_frame_cube(cmd, image_index, BOARD_WIDTH, y, view, proj);
1180 draw_frame_cube(cmd, image_index, x, BOARD_HEIGHT, view, proj);
1184 void draw_frame_cube(VkCommandBuffer cmd, uint32_t image_index,
int x,
int y,
const glm::mat4 &view,
const glm::mat4 &proj)
const {
1189 mxvk::UniformBufferObject ubo{};
1190 ubo.
model = block_matrix(x, y, CUBE_SCALE * 0.82f);
1193 ubo.
fx = glm::vec4(0.58f, 0.62f, 0.66f, 1.0f);
1194 cube_model->renderWithPushConstants(cmd, image_index, FRAME_TEXTURE_INDEX, ubo,
false);