MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include "mxvk/argz.hpp"
2#include "mxvk/mxvk.hpp"
4
5#include <SDL3/SDL.h>
6
7#include <algorithm>
8#include <array>
9#include <cmath>
10#include <cstdint>
11#include <cstdlib>
12#include <cstring>
13#include <filesystem>
14#include <format>
15#include <fstream>
16#include <iostream>
17#include <memory>
18#include <random>
19#include <string>
20#include <utility>
21#include <vector>
22
23namespace {
24
25 constexpr int board_rows = 18;
26 constexpr int board_cols = 8;
27 constexpr int piece_height = 3;
28 constexpr int max_scores = 8;
29 constexpr int base_width = 1440;
30 constexpr int base_height = 1080;
31 constexpr int game_base_width = 640;
32 constexpr int game_base_height = 480;
33 constexpr int game_board_start_x = 184;
34 constexpr int game_board_start_y = 78;
35 constexpr int game_block_width = 31;
36 constexpr int game_block_height = 14;
37 constexpr int game_block_spacing = 1;
38 constexpr int game_next_panel_x = 450;
39 constexpr int game_next_panel_y = 180;
40 constexpr int menu_item_count = 4;
41 constexpr int title_screen_time_ms = 1500;
42 constexpr int flash_time_ms = 180;
43 constexpr int lines_per_speedup = 10;
44 constexpr int score_points_per_match = 6;
45 constexpr int max_name_length = 16;
46 constexpr Uint32 joy_repeat_delay_ms = 180;
47 constexpr Sint16 joystick_dead_zone = 16000;
48
57
58 struct Cell {
59 int color = 0;
60 Uint64 flash_until = 0;
61 };
62
63 struct Piece {
64 int x = 3;
65 int y = 0;
66 std::array<int, piece_height> colors{};
67 std::array<int, piece_height> next_colors{};
68 };
69
70 struct ScoreEntry {
71 std::string name;
72 int score = 0;
73 };
74
75 bool hasResolutionArgument(int argc, char **argv) {
76 for (int i = 1; i < argc; ++i) {
77 const std::string arg = argv[i] == nullptr ? std::string{} : std::string(argv[i]);
78 if (arg == "-r" || arg == "-R" || arg == "--resolution") {
79 return true;
80 }
81 }
82 return false;
83 }
84
85 std::filesystem::path resolveAssetRoot(const std::string &path) {
86 if (!path.empty() && path != "." && path != "./") {
87 return std::filesystem::path(path);
88 }
89 return std::filesystem::path(MASTERPIECE_ASSET_DIR);
90 }
91
92 std::filesystem::path resolvePuzzleAssetRoot(const std::filesystem::path &asset_root) {
93 const std::filesystem::path shared_root = asset_root.parent_path() / "puzzle";
94 if (std::filesystem::exists(shared_root / "data" / "gamebg.png")) {
95 return shared_root;
96 }
97 return asset_root;
98 }
99
100 std::filesystem::path scorePath(const std::filesystem::path &asset_root) {
101 return asset_root / "data" / "scores.dat";
102 }
103
104 class HighScores {
105 public:
106 explicit HighScores(std::filesystem::path file_path)
107 : file_path(std::move(file_path)) {
108 load();
109 }
110
111 void add(std::string name, int score) {
112 normalize(name);
113 entries.push_back({std::move(name), score});
114 sortAndTrim();
115 save();
116 }
117
118 [[nodiscard]] bool qualifies(int score) const {
119 if (entries.size() < max_scores) {
120 return true;
121 }
122 return score > entries.back().score;
123 }
124
125 [[nodiscard]] const std::vector<ScoreEntry> &list() const {
126 return entries;
127 }
128
129 private:
130 std::filesystem::path file_path;
131 std::vector<ScoreEntry> entries;
132
133 static void normalize(std::string &name) {
134 std::string cleaned;
135 cleaned.reserve(name.size());
136 for (unsigned char ch : name) {
137 if (ch >= 32 && ch < 127 && ch != ':') {
138 cleaned.push_back(static_cast<char>(ch));
139 }
140 }
141
142 if (cleaned.empty()) {
143 cleaned = "Player";
144 }
145 if (cleaned.size() > max_name_length) {
146 cleaned.resize(max_name_length);
147 }
148 name = std::move(cleaned);
149 }
150
151 void sortAndTrim() {
152 std::sort(entries.begin(), entries.end(), [](const ScoreEntry &a, const ScoreEntry &b) {
153 if (a.score != b.score) {
154 return a.score > b.score;
155 }
156 return a.name < b.name;
157 });
158
159 if (entries.size() > max_scores) {
160 entries.resize(max_scores);
161 }
162 }
163
164 void initDefaults() {
165 entries.clear();
166 for (int i = 0; i < max_scores; ++i) {
167 entries.push_back({"Anonymous", 0});
168 }
169 }
170
171 void load() {
172 entries.clear();
173
174 std::ifstream in(file_path);
175 if (!in.is_open()) {
176 initDefaults();
177 return;
178 }
179
180 std::string line;
181 while (std::getline(in, line)) {
182 const std::size_t sep = line.find(':');
183 if (sep == std::string::npos) {
184 continue;
185 }
186
187 ScoreEntry entry{};
188 entry.name = line.substr(0, sep);
189 entry.score = static_cast<int>(std::strtol(line.substr(sep + 1).c_str(), nullptr, 10));
190 normalize(entry.name);
191 entries.push_back(std::move(entry));
192 }
193
194 if (entries.empty()) {
195 initDefaults();
196 save();
197 return;
198 }
199
200 sortAndTrim();
201 }
202
203 void save() const {
204 std::error_code ec;
205 std::filesystem::create_directories(file_path.parent_path(), ec);
206
207 std::ofstream out(file_path, std::ios::trunc);
208 if (!out.is_open()) {
209 return;
210 }
211
212 for (const ScoreEntry &entry : entries) {
213 out << entry.name << ':' << entry.score << '\n';
214 }
215 }
216 };
217
218 struct Layout {
219 float scale_x = 1.0f;
220 float scale_y = 1.0f;
221 float game_scale = 1.0f;
222 int width = base_width;
223 int height = base_height;
224 int game_x = 0;
225 int game_y = 0;
226 int game_w = base_width;
227 int game_h = base_height;
228 int board_x = 185;
229 int board_y = 95;
230 int cell_w = 32;
231 int cell_h = 16;
232 int next_x = 510;
233 int next_y = 200;
234 int menu_x = 505;
235 int menu_y = 400;
236 int menu_w = 430;
237 int menu_h = 82;
238 int menu_step = 118;
239 };
240
241} // namespace
242
243namespace example {
244
245 class MasterPieceWindow final : public mxvk::VK_Window {
246 public:
247 MasterPieceWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
248 : mxvk::VK_Window("MasterPiece", width, height, fullscreen, MXVK_VALIDATION, enable_vsync),
249 asset_root(resolveAssetRoot(path)),
250 puzzle_asset_root(resolvePuzzleAssetRoot(resolveAssetRoot(path))),
251 high_scores(scorePath(asset_root)) {
252 setClearColor(0.02f, 0.02f, 0.03f, 1.0f);
253
254 const std::string font_path = dataPath("font.ttf");
255 title_font.reset(font_path, 34);
256 ui_font.reset(font_path, 20);
257 setFont(font_path, 20);
258
259 loadSprites();
260 resetGame();
261 setScreen(Screen::Intro);
262 tryOpenFirstGamepad();
263 }
264
266 closeGamepad();
267 }
268
269 void event(SDL_Event &e) override {
270 if (e.type == SDL_EVENT_QUIT) {
271 exit();
272 return;
273 }
274
275 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
276 openGamepad(e.gdevice.which);
277 return;
278 }
279
280 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
281 if (gamepad != nullptr && e.gdevice.which == gamepadId) {
282 closeGamepad();
283 tryOpenFirstGamepad();
284 }
285 return;
286 }
287
288 if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
289 handleControllerButton(e.gbutton.button);
290 return;
291 }
292
293 if (screen == Screen::NameEntry && e.type == SDL_EVENT_TEXT_INPUT) {
294 handleNameText(e.text.text);
295 return;
296 }
297
298 if (e.type != SDL_EVENT_KEY_DOWN) {
299 return;
300 }
301
302 switch (screen) {
303 case Screen::Intro:
304 if (isConfirmKey(e.key.key) || e.key.key == SDLK_ESCAPE) {
305 setScreen(Screen::Menu);
306 }
307 break;
308 case Screen::Menu:
309 handleMenuKey(e.key.key);
310 break;
311 case Screen::Game:
312 handleGameKey(e.key.key);
313 break;
314 case Screen::Scores:
315 case Screen::Credits:
316 if (e.key.key == SDLK_RETURN || e.key.key == SDLK_ESCAPE) {
317 setScreen(Screen::Menu);
318 }
319 break;
321 handleNameKey(e.key.key);
322 break;
323 }
324 }
325
326 void proc() override {
327 const Uint64 now = SDL_GetTicks();
328 layout = computeLayout();
329 pollController(now);
330
331 switch (screen) {
332 case Screen::Intro:
333 if (!drawIntro(now)) {
334 break;
335 }
336 drawMenu();
337 break;
338 case Screen::Menu:
339 drawMenu();
340 break;
341 case Screen::Game:
342 updateGame(now);
343 if (screen == Screen::Game) {
344 drawGame(now);
345 } else if (screen == Screen::Scores) {
346 drawScores(false);
347 } else if (screen == Screen::Credits) {
348 drawCredits();
349 } else if (screen == Screen::NameEntry) {
350 drawScores(true);
351 } else if (screen == Screen::Menu) {
352 drawMenu();
353 }
354 break;
355 case Screen::Scores:
356 drawScores(false);
357 break;
358 case Screen::Credits:
359 drawCredits();
360 break;
362 drawScores(true);
363 break;
364 }
365 }
366
367 private:
368 static constexpr std::array<const char *, 11> block_files{{
369 "block_black.png",
370 "block_yellow.png",
371 "block_orange.png",
372 "block_ltblue.png",
373 "block_dblue.png",
374 "block_purple.png",
375 "block_pink.png",
376 "block_gray.png",
377 "block_red.png",
378 "block_green.png",
379 "block_clear.png",
380 }};
381
382 static constexpr std::array<const char *, menu_item_count> menu_files{{
383 "menu_new_game.png",
384 "menu_high_scores.png",
385 "menu_credits.png",
386 "menu_quit.png",
387 }};
388
389 std::filesystem::path asset_root;
390 std::filesystem::path puzzle_asset_root;
391 HighScores high_scores;
392 Screen screen = Screen::Intro;
393 Layout layout{};
394 std::mt19937 rng{std::random_device{}()};
395 std::array<std::array<Cell, board_cols>, board_rows> board{};
396 Piece piece{};
397 mxvk::Font title_font{};
398 mxvk::Font ui_font{};
399 mxvk::VK_Sprite *background_intro = nullptr;
400 mxvk::VK_Sprite *mxvk_logo = nullptr;
401 mxvk::VK_Sprite *background_menu = nullptr;
402 mxvk::VK_Sprite *background_game = nullptr;
403 mxvk::VK_Sprite *cursor = nullptr;
404 std::array<mxvk::VK_Sprite *, block_files.size()> blocks{};
405 std::array<mxvk::VK_Sprite *, menu_files.size()> menu_items{};
406 mxvk::VK_Sprite *panel = nullptr;
407 mxvk::VK_Sprite *overlay = nullptr;
408 std::string player_name;
409 int menu_selection = 0;
410 int score = 0;
411 int lines = 0;
412 int speed_level = 0;
413 int lines_toward_speedup = 0;
414 int fall_delay_ms = 520;
415 Uint64 intro_start_ms = 0;
416 Uint64 last_update_ms = 0;
417 Uint64 fall_accumulator_ms = 0;
418 Uint32 joy_repeat_left_ms = 0;
419 Uint32 joy_repeat_right_ms = 0;
420 Uint32 joy_repeat_up_ms = 0;
421 Uint32 joy_repeat_down_ms = 0;
422 SDL_Gamepad *gamepad = nullptr;
423 SDL_JoystickID gamepadId = 0;
424 bool paused = false;
425 bool awaiting_name = false;
426 bool score_added = false;
427 bool waiting_for_spawn = false;
428
429 static bool isConfirmKey(SDL_Keycode key) {
430 return key == SDLK_RETURN || key == SDLK_SPACE;
431 }
432
433 static int scaled(int value, float scale) {
434 return std::max(1, static_cast<int>(std::lround(static_cast<float>(value) * scale)));
435 }
436
437 static int scaledPos(int value, float scale) {
438 return static_cast<int>(std::lround(static_cast<float>(value) * scale));
439 }
440
441 static int flashingSpriteIndex(int x, int y, Uint64 now) {
442 const Uint64 tick = now / 18U;
443 const Uint64 mixed = tick + static_cast<Uint64>(x * 37 + y * 101);
444 return static_cast<int>((mixed % 9U) + 1U);
445 }
446
447 [[nodiscard]] Layout computeLayout() const {
448 Layout result{};
449 const VkExtent2D extent = getSwapchainExtent();
450 result.width = extent.width == 0U ? base_width : static_cast<int>(extent.width);
451 result.height = extent.height == 0U ? base_height : static_cast<int>(extent.height);
452 result.scale_x = static_cast<float>(result.width) / static_cast<float>(base_width);
453 result.scale_y = static_cast<float>(result.height) / static_cast<float>(base_height);
454 result.game_scale = std::min(result.scale_x, result.scale_y);
455 result.game_w = scaled(base_width, result.game_scale);
456 result.game_h = scaled(base_height, result.game_scale);
457 result.game_x = (result.width - result.game_w) / 2;
458 result.game_y = (result.height - result.game_h) / 2;
459 result.board_x = result.game_x + scaledPos(185, result.game_scale);
460 result.board_y = result.game_y + scaledPos(95, result.game_scale);
461 result.cell_w = scaled(32, result.game_scale);
462 result.cell_h = scaled(16, result.game_scale);
463 result.next_x = result.game_x + scaledPos(510, result.game_scale);
464 result.next_y = result.game_y + scaledPos(200, result.game_scale);
465 result.menu_x = scaled(505, result.scale_x);
466 result.menu_y = scaled(400, result.scale_y);
467 result.menu_w = scaled(430, result.scale_x);
468 result.menu_h = scaled(82, result.scale_y);
469 result.menu_step = scaled(118, result.scale_y);
470 return result;
471 }
472
473 std::string dataPath(const char *name) const {
474 return (asset_root / "data" / name).string();
475 }
476
477 std::string puzzleDataPath(const char *name) const {
478 const std::filesystem::path shared_path = puzzle_asset_root / "data" / name;
479 if (std::filesystem::exists(shared_path)) {
480 return shared_path.string();
481 }
482 return dataPath(name);
483 }
484
485 mxvk::VK_Sprite *loadPngSprite(const char *name) {
486 return createSprite(dataPath(name));
487 }
488
489 mxvk::VK_Sprite *loadEffectSprite(const char *name) {
490 return createSprite(dataPath(name), "", dataPath("intro.frag.spv"));
491 }
492
493 mxvk::VK_Sprite *makeSolidPixel(std::uint8_t r, std::uint8_t g, std::uint8_t b, std::uint8_t a) {
494 const std::array<std::uint8_t, 4> pixel{r, g, b, a};
495 mxvk::VK_Sprite *sprite = createSprite(1, 1);
496 sprite->updateTexture(pixel.data(), 1, 1, 4);
497 return sprite;
498 }
499
500 void loadSprites() {
501 background_intro = loadEffectSprite("intro.png");
502 background_menu = loadEffectSprite("start.png");
503 background_game = createSprite(puzzleDataPath("gamebg.png"));
504 mxvk_logo = createSprite(puzzleDataPath("mxvk_logo.png"));
505 cursor = loadPngSprite("cursor.png");
506
507 for (std::size_t i = 0; i < block_files.size(); ++i) {
508 blocks[i] = loadPngSprite(block_files[i]);
509 }
510
511 for (std::size_t i = 0; i < menu_files.size(); ++i) {
512 menu_items[i] = loadPngSprite(menu_files[i]);
513 }
514
515 panel = makeSolidPixel(0, 0, 0, 192);
516 overlay = makeSolidPixel(0, 0, 0, 128);
517 }
518
519 void setScreen(Screen next) {
520 if (screen == Screen::NameEntry && next != Screen::NameEntry) {
521 SDL_StopTextInput(window.get());
522 }
523
524 screen = next;
525 awaiting_name = (screen == Screen::NameEntry);
526 if (screen == Screen::NameEntry) {
527 player_name.clear();
528 if (!score_added) {
529 SDL_StartTextInput(window.get());
530 }
531 }
532
533 if (screen == Screen::Intro) {
534 intro_start_ms = SDL_GetTicks();
535 }
536 }
537
538 void resetGame() {
539 for (auto &row : board) {
540 for (Cell &cell : row) {
541 cell = {};
542 }
543 }
544
545 score = 0;
546 lines = 0;
547 speed_level = 0;
548 lines_toward_speedup = 0;
549 fall_delay_ms = 520;
550 last_update_ms = 0;
551 fall_accumulator_ms = 0;
552 paused = false;
553 awaiting_name = false;
554 score_added = false;
555 waiting_for_spawn = false;
556 piece.x = board_cols / 2 - 1;
557 piece.y = 0;
558 piece.next_colors = randomColors();
559 spawnPiece();
560 }
561
562 std::array<int, piece_height> randomColors() {
563 std::uniform_int_distribution<int> dist(1, 9);
564 std::array<int, piece_height> colors{dist(rng), dist(rng), dist(rng)};
565 while (colors[0] == colors[1] && colors[1] == colors[2]) {
566 colors[1] = dist(rng);
567 }
568 return colors;
569 }
570
571 bool canPlacePiece(int x, int y) const {
572 if (x < 0 || x >= board_cols) {
573 return false;
574 }
575
576 for (int i = 0; i < piece_height; ++i) {
577 const int row = y + i;
578 if (row < 0 || row >= board_rows) {
579 return false;
580 }
581 if (board[static_cast<std::size_t>(row)][static_cast<std::size_t>(x)].color != 0) {
582 return false;
583 }
584 }
585 return true;
586 }
587
588 bool movePiece(int dx, int dy) {
589 const int next_x = piece.x + dx;
590 const int next_y = piece.y + dy;
591 if (!canPlacePiece(next_x, next_y)) {
592 return false;
593 }
594
595 piece.x = next_x;
596 piece.y = next_y;
597 return true;
598 }
599
600 void dropPiece() {
601 while (movePiece(0, 1)) {
602 }
603 lockPiece(SDL_GetTicks());
604 }
605
606 void rotatePieceColors(bool forward) {
607 if (forward) {
608 const int temp = piece.colors.back();
609 piece.colors[2] = piece.colors[1];
610 piece.colors[1] = piece.colors[0];
611 piece.colors[0] = temp;
612 } else {
613 const int temp = piece.colors.front();
614 piece.colors[0] = piece.colors[1];
615 piece.colors[1] = piece.colors[2];
616 piece.colors[2] = temp;
617 }
618 }
619
620 void handleControllerButton(Uint8 button) {
621 switch (screen) {
622 case Screen::Intro:
623 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
624 setScreen(Screen::Menu);
625 }
626 break;
627 case Screen::Menu:
628 if (button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
629 menu_selection = (menu_selection + menu_item_count - 1) % menu_item_count;
630 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
631 menu_selection = (menu_selection + 1) % menu_item_count;
632 } else if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
633 handleMenuSelection();
634 } else if (button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
635 exit();
636 }
637 break;
638 case Screen::Game:
639 if (button == SDL_GAMEPAD_BUTTON_BACK) {
640 setScreen(Screen::Menu);
641 } else if (button == SDL_GAMEPAD_BUTTON_START) {
642 paused = !paused;
643 } else if (paused || awaiting_name) {
644 break;
645 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
646 movePiece(-1, 0);
647 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
648 movePiece(1, 0);
649 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
650 if (!movePiece(0, 1)) {
651 lockPiece(SDL_GetTicks());
652 }
653 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_UP || button == SDL_GAMEPAD_BUTTON_SOUTH) {
654 rotatePieceColors(true);
655 } else if (button == SDL_GAMEPAD_BUTTON_EAST) {
656 rotatePieceColors(false);
657 } else if (button == SDL_GAMEPAD_BUTTON_NORTH) {
658 dropPiece();
659 }
660 break;
661 case Screen::Scores:
662 if (awaiting_name) {
663 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
664 commitScore();
665 } else if (button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
666 score_added = true;
667 SDL_StopTextInput(window.get());
668 setScreen(Screen::Scores);
669 }
670 } else if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START ||
671 button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
672 setScreen(Screen::Menu);
673 }
674 break;
675 case Screen::Credits:
676 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START ||
677 button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
678 setScreen(Screen::Menu);
679 }
680 break;
682 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
683 commitScore();
684 } else if (button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
685 score_added = true;
686 SDL_StopTextInput(window.get());
687 setScreen(Screen::Scores);
688 }
689 break;
690 }
691 }
692
693 void handleMenuSelection() {
694 switch (menu_selection) {
695 case 0:
696 resetGame();
697 setScreen(Screen::Game);
698 break;
699 case 1:
700 setScreen(Screen::Scores);
701 break;
702 case 2:
703 setScreen(Screen::Credits);
704 break;
705 case 3:
706 exit();
707 break;
708 default:
709 break;
710 }
711 }
712
713 void handleMenuKey(SDL_Keycode key) {
714 if (key == SDLK_ESCAPE) {
715 exit();
716 return;
717 }
718
719 if (key == SDLK_UP) {
720 menu_selection = (menu_selection + menu_item_count - 1) % menu_item_count;
721 return;
722 }
723
724 if (key == SDLK_DOWN) {
725 menu_selection = (menu_selection + 1) % menu_item_count;
726 return;
727 }
728
729 if (!isConfirmKey(key)) {
730 return;
731 }
732
733 handleMenuSelection();
734 }
735
736 void handleGameKey(SDL_Keycode key) {
737 if (key == SDLK_ESCAPE) {
738 setScreen(Screen::Menu);
739 return;
740 }
741
742 if (key == 'p' || key == 'P') {
743 paused = !paused;
744 return;
745 }
746
747 if (paused || awaiting_name) {
748 return;
749 }
750
751 if (key == SDLK_LEFT) {
752 movePiece(-1, 0);
753 } else if (key == SDLK_RIGHT) {
754 movePiece(1, 0);
755 } else if (key == SDLK_DOWN) {
756 if (!movePiece(0, 1)) {
757 lockPiece(SDL_GetTicks());
758 }
759 } else if (key == 'a' || key == 'A' || key == SDLK_UP) {
760 rotatePieceColors(true);
761 } else if (key == 's' || key == 'S') {
762 rotatePieceColors(false);
763 }
764 }
765
766 void handleNameKey(SDL_Keycode key) {
767 if (key == SDLK_ESCAPE) {
768 score_added = true;
769 SDL_StopTextInput(window.get());
770 setScreen(Screen::Scores);
771 return;
772 }
773
774 if (key == SDLK_BACKSPACE) {
775 if (!player_name.empty()) {
776 player_name.pop_back();
777 }
778 return;
779 }
780
781 if (key == SDLK_RETURN) {
782 commitScore();
783 }
784 }
785
786 void handleNameText(const char *text) {
787 if (text == nullptr) {
788 return;
789 }
790
791 while (*text != '\0' && static_cast<int>(player_name.size()) < max_name_length) {
792 const unsigned char ch = static_cast<unsigned char>(*text++);
793 if (ch >= 32 && ch < 127) {
794 player_name.push_back(static_cast<char>(ch));
795 }
796 }
797 }
798
799 void commitScore() {
800 if (!score_added) {
801 if (player_name.empty()) {
802 player_name = "Player";
803 }
804 high_scores.add(player_name, score);
805 score_added = true;
806 }
807
808 SDL_StopTextInput(window.get());
809 setScreen(Screen::Scores);
810 }
811
812 void spawnPiece() {
813 piece.colors = piece.next_colors;
814 piece.next_colors = randomColors();
815 piece.x = board_cols / 2 - 1;
816 piece.y = 0;
817
818 if (!canPlacePiece(piece.x, piece.y)) {
819 handleGameOver();
820 }
821 }
822
823 bool boardHasFlashCells() const {
824 for (const auto &row : board) {
825 for (const Cell &cell : row) {
826 if (cell.flash_until != 0U) {
827 return true;
828 }
829 }
830 }
831 return false;
832 }
833
834 bool updateFlashState(Uint64 now) {
835 if (!boardHasFlashCells()) {
836 return false;
837 }
838
839 bool expired_any = false;
840 for (auto &row : board) {
841 for (Cell &cell : row) {
842 if (cell.flash_until != 0U && now >= cell.flash_until) {
843 cell = {};
844 expired_any = true;
845 }
846 }
847 }
848
849 if (boardHasFlashCells()) {
850 return true;
851 }
852
853 if (expired_any) {
854 applyGravity();
855 resolveMatches(now);
856 }
857
858 if (!boardHasFlashCells() && waiting_for_spawn) {
859 waiting_for_spawn = false;
860 spawnPiece();
861 }
862
863 return true;
864 }
865
866 void updateGame(Uint64 now) {
867 if (paused || awaiting_name) {
868 return;
869 }
870
871 if (last_update_ms == 0U) {
872 last_update_ms = now;
873 return;
874 }
875
876 if (updateFlashState(now)) {
877 last_update_ms = now;
878 return;
879 }
880
881 if (waiting_for_spawn) {
882 waiting_for_spawn = false;
883 spawnPiece();
884 last_update_ms = now;
885 return;
886 }
887
888 const Uint64 delta = now - last_update_ms;
889 last_update_ms = now;
890 fall_accumulator_ms += delta;
891
892 while (fall_accumulator_ms >= static_cast<Uint64>(fall_delay_ms)) {
893 fall_accumulator_ms -= static_cast<Uint64>(fall_delay_ms);
894 if (!movePiece(0, 1)) {
895 lockPiece(now);
896 break;
897 }
898 }
899 }
900
901 bool resolveMatches(Uint64 now) {
902 std::array<std::array<bool, board_cols>, board_rows> marked{};
903 int matches_found = 0;
904
905 auto mark_run = [&](int x, int y, int dx, int dy) {
906 const int color = board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)].color;
907 int length = 0;
908 int cx = x;
909 int cy = y;
910
911 while (cx >= 0 && cy >= 0 && cx < board_cols && cy < board_rows &&
912 board[static_cast<std::size_t>(cy)][static_cast<std::size_t>(cx)].color == color &&
913 board[static_cast<std::size_t>(cy)][static_cast<std::size_t>(cx)].flash_until == 0U) {
914 ++length;
915 cx += dx;
916 cy += dy;
917 }
918
919 if (length < 3) {
920 return;
921 }
922
923 ++matches_found;
924 cx = x;
925 cy = y;
926 for (int i = 0; i < length; ++i) {
927 marked[static_cast<std::size_t>(cy)][static_cast<std::size_t>(cx)] = true;
928 cx += dx;
929 cy += dy;
930 }
931 };
932
933 for (int y = 0; y < board_rows; ++y) {
934 for (int x = 0; x < board_cols; ++x) {
935 const Cell &cell = board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)];
936 if (cell.color == 0 || cell.flash_until != 0U) {
937 continue;
938 }
939
940 if (x == 0 || board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x - 1)].color != cell.color) {
941 mark_run(x, y, 1, 0);
942 }
943 if (y == 0 || board[static_cast<std::size_t>(y - 1)][static_cast<std::size_t>(x)].color != cell.color) {
944 mark_run(x, y, 0, 1);
945 }
946 if (x == 0 || y == 0 ||
947 board[static_cast<std::size_t>(y - 1)][static_cast<std::size_t>(x - 1)].color != cell.color) {
948 mark_run(x, y, 1, 1);
949 }
950 if (x == board_cols - 1 || y == 0 ||
951 board[static_cast<std::size_t>(y - 1)][static_cast<std::size_t>(x + 1)].color != cell.color) {
952 mark_run(x, y, -1, 1);
953 }
954 }
955 }
956
957 if (matches_found == 0) {
958 return false;
959 }
960
961 for (int y = 0; y < board_rows; ++y) {
962 for (int x = 0; x < board_cols; ++x) {
963 if (marked[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)]) {
964 board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)].flash_until = now + flash_time_ms;
965 }
966 }
967 }
968
969 score += matches_found * score_points_per_match;
970 lines += matches_found;
971 lines_toward_speedup += matches_found;
972 while (lines_toward_speedup >= lines_per_speedup) {
973 lines_toward_speedup -= lines_per_speedup;
974 ++speed_level;
975 fall_delay_ms = std::max(140, 520 - speed_level * 40);
976 }
977
978 return true;
979 }
980
981 void applyGravity() {
982 for (int x = 0; x < board_cols; ++x) {
983 int write_row = board_rows - 1;
984 for (int y = board_rows - 1; y >= 0; --y) {
985 if (board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)].color != 0) {
986 if (write_row != y) {
987 board[static_cast<std::size_t>(write_row)][static_cast<std::size_t>(x)] =
988 board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)];
989 board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)] = {};
990 }
991 --write_row;
992 }
993 }
994
995 for (int y = write_row; y >= 0; --y) {
996 board[static_cast<std::size_t>(y)][static_cast<std::size_t>(x)] = {};
997 }
998 }
999 }
1000
1001 void lockPiece(Uint64 now) {
1002 if (piece.y <= 0) {
1003 handleGameOver();
1004 return;
1005 }
1006
1007 for (int i = 0; i < piece_height; ++i) {
1008 const int row = piece.y + i;
1009 board[static_cast<std::size_t>(row)][static_cast<std::size_t>(piece.x)].color =
1010 std::clamp(piece.colors[static_cast<std::size_t>(i)], 1, 9);
1011 }
1012
1013 if (resolveMatches(now)) {
1014 waiting_for_spawn = true;
1015 return;
1016 }
1017
1018 spawnPiece();
1019 }
1020
1021 void handleGameOver() {
1022 if (high_scores.qualifies(score)) {
1023 score_added = false;
1024 player_name.clear();
1025 setScreen(Screen::NameEntry);
1026 } else {
1027 setScreen(Screen::Scores);
1028 }
1029 }
1030
1031 void drawSprite(mxvk::VK_Sprite *sprite, int x, int y, int w, int h) {
1032 if (sprite != nullptr) {
1033 sprite->drawSpriteRect(x, y, w, h);
1034 }
1035 }
1036
1037 void drawCenteredText(const std::string &text, int y, const SDL_Color &color, const mxvk::Font &font) {
1038 int w = 0;
1039 int h = 0;
1040 if (!getTextDimensions(text, w, h, font)) {
1041 printText(text, scaled(32, layout.scale_x), y, color, font);
1042 return;
1043 }
1044
1045 const int x = std::max(16, (layout.width - w) / 2);
1046 printText(text, x, y, color, font);
1047 }
1048
1049 bool drawIntro(Uint64 now) {
1050 const float elapsed = intro_start_ms == 0U ? 0.0f : static_cast<float>(now - intro_start_ms) / 1000.0f;
1051 background_intro->setShaderParams(elapsed, 0.0f, 0.0f, 1.0f);
1052 drawSprite(background_intro, 0, 0, layout.width, layout.height);
1053
1054 if (intro_start_ms == 0U) {
1055 intro_start_ms = now;
1056 }
1057
1058 if (now - intro_start_ms > title_screen_time_ms) {
1059 setScreen(Screen::Menu);
1060 return true;
1061 }
1062
1063 return false;
1064 }
1065
1066 void drawMenu() {
1067 const float elapsed = static_cast<float>(SDL_GetTicks()) / 1000.0f;
1068 background_menu->setShaderParams(elapsed, 0.0f, 0.0f, 1.0f);
1069 drawSprite(background_menu, 0, 0, layout.width, layout.height);
1070
1071 for (int i = 0; i < menu_item_count; ++i) {
1072 const int y = layout.menu_y + i * layout.menu_step;
1073 if (i == menu_selection) {
1074 drawSprite(cursor, layout.menu_x - scaled(94, layout.scale_x), y + scaled(12, layout.scale_y), scaled(78, layout.scale_x), scaled(58, layout.scale_y));
1075 }
1076 drawSprite(menu_items[static_cast<std::size_t>(i)], layout.menu_x, y, layout.menu_w, layout.menu_h);
1077 }
1078 }
1079
1080 void drawScores(bool entering_name) {
1081 const float elapsed = static_cast<float>(SDL_GetTicks()) / 1000.0f;
1082 background_menu->setShaderParams(elapsed, 0.0f, 0.0f, 1.0f);
1083 drawSprite(background_menu, 0, 0, layout.width, layout.height);
1084 drawSprite(overlay, scaled(30, layout.scale_x), scaled(70, layout.scale_y),
1085 layout.width - scaled(60, layout.scale_x), layout.height - scaled(120, layout.scale_y));
1086
1087 drawCenteredText("High Scores", scaled(72, layout.scale_y), SDL_Color{255, 245, 200, 255}, title_font);
1088
1089 const auto &entries = high_scores.list();
1090 const int start_y = scaled(140, layout.scale_y);
1091 const int step_y = scaled(30, layout.scale_y);
1092 for (std::size_t i = 0; i < entries.size(); ++i) {
1093 const std::string line = std::format("{:>2}. {:<16} {}", i + 1, entries[i].name, entries[i].score);
1094 printText(line, scaled(70, layout.scale_x), start_y + static_cast<int>(i) * step_y, SDL_Color{255, 255, 255, 255}, ui_font);
1095 }
1096
1097 if (entering_name) {
1098 printText("Type your name and press Enter", scaled(70, layout.scale_x), scaled(450, layout.scale_y), SDL_Color{240, 220, 220, 255}, ui_font);
1099 printText("Name:", scaled(70, layout.scale_x), scaled(490, layout.scale_y), SDL_Color{255, 245, 200, 255}, ui_font);
1100 printText(player_name + "_", scaled(150, layout.scale_x), scaled(490, layout.scale_y), SDL_Color{255, 255, 255, 255}, ui_font);
1101 } else {
1102 printText("Press Enter to return to the menu", scaled(70, layout.scale_x), scaled(490, layout.scale_y), SDL_Color{240, 240, 220, 255}, ui_font);
1103 }
1104 }
1105
1106 void drawCredits() {
1107 const float elapsed = static_cast<float>(SDL_GetTicks()) / 1000.0f;
1108 background_menu->setShaderParams(elapsed, 0.0f, 0.0f, 1.0f);
1109 drawSprite(background_menu, 0, 0, layout.width, layout.height);
1110 drawSprite(overlay, scaled(30, layout.scale_x), scaled(85, layout.scale_y),
1111 layout.width - scaled(60, layout.scale_x), scaled(260, layout.scale_y));
1112 const int logo_w = layout.width / 2;
1113 const int logo_h = static_cast<int>(std::lround(
1114 static_cast<float>(logo_w) * static_cast<float>(mxvk_logo->getHeight()) /
1115 static_cast<float>(mxvk_logo->getWidth())));
1116 const int logo_x = (layout.width - logo_w) / 2;
1117 const int logo_y = (layout.height - logo_h) / 2;
1118 drawSprite(mxvk_logo, logo_x, logo_y, logo_w, logo_h);
1119 drawCenteredText("Credits", scaled(96, layout.scale_y), SDL_Color{255, 245, 200, 255}, title_font);
1120 printText("Original game: MasterPiece.SDL", scaled(70, layout.scale_x), scaled(180, layout.scale_y), SDL_Color{255, 255, 255, 255}, ui_font);
1121 printText("MXVK port and cleanup: 2D Vulkan example", scaled(70, layout.scale_x), scaled(214, layout.scale_y), SDL_Color{255, 255, 255, 255}, ui_font);
1122 printText("Press Enter or Escape to return", scaled(70, layout.scale_x), scaled(270, layout.scale_y), SDL_Color{240, 240, 220, 255}, ui_font);
1123 }
1124
1125 void drawGame(Uint64 now) {
1126 const float scaleX = static_cast<float>(layout.width) / static_cast<float>(game_base_width);
1127 const float scaleY = static_cast<float>(layout.height) / static_cast<float>(game_base_height);
1128 drawSprite(background_game, 0, 0, layout.width, layout.height);
1129 drawBoard(now, scaleX, scaleY);
1130 drawNextPiece(scaleX, scaleY);
1131 drawHud(scaleX, scaleY);
1132
1133 if (paused) {
1134 const char *pausedText = "PAUSED - Press P to Continue";
1135 int pausedWidth = 0;
1136 int pausedHeight = 0;
1137 if (!getTextDimensions(pausedText, pausedWidth, pausedHeight, title_font)) {
1138 pausedWidth = static_cast<int>(std::strlen(pausedText)) * 16;
1139 }
1140 printText(pausedText, layout.width / 2 - pausedWidth / 2, layout.height / 2, SDL_Color{255, 255, 0, 255}, title_font);
1141 }
1142 }
1143
1144 void drawBoard(Uint64 now, float scaleX, float scaleY) {
1145 for (int i = 0; i < board_cols; ++i) {
1146 for (int j = 0; j < board_rows; ++j) {
1147 const Cell &cell = board[static_cast<std::size_t>(j)][static_cast<std::size_t>(i)];
1148 if (cell.color == 0) {
1149 continue;
1150 }
1151
1152 const int sprite_index = cell.flash_until != 0U ? flashingSpriteIndex(i, j, now) : std::clamp(cell.color, 0, 9);
1154 const int y = game_board_start_y + j * (game_block_height + game_block_spacing) + 10;
1155
1156 blocks[static_cast<std::size_t>(sprite_index)]->drawSpriteRect(
1157 static_cast<int>(static_cast<float>(x) * scaleX),
1158 static_cast<int>(static_cast<float>(y) * scaleY) + 10,
1159 static_cast<int>(static_cast<float>(game_block_width) * scaleX),
1160 static_cast<int>(static_cast<float>(game_block_height) * scaleY));
1161 }
1162 }
1163
1164 if (screen != Screen::Game) {
1165 return;
1166 }
1167
1168 for (int i = 0; i < piece_height; ++i) {
1169 const int row = piece.y + i;
1170 if (row < 0 || row >= board_rows || piece.x < 0 || piece.x >= board_cols) {
1171 continue;
1172 }
1173
1174 const int x = game_board_start_x + piece.x * (game_block_width + game_block_spacing);
1175 const int y = game_board_start_y + row * (game_block_height + game_block_spacing) + 10;
1176 blocks[static_cast<std::size_t>(std::clamp(piece.colors[static_cast<std::size_t>(i)], 0, 9))]->drawSpriteRect(
1177 static_cast<int>(static_cast<float>(x) * scaleX),
1178 static_cast<int>(static_cast<float>(y) * scaleY) + 10,
1179 static_cast<int>(static_cast<float>(game_block_width) * scaleX),
1180 static_cast<int>(static_cast<float>(game_block_height) * scaleY));
1181 }
1182 }
1183
1184 void drawNextPiece(float scaleX, float scaleY) {
1185 const int bx = game_next_panel_x + 70;
1186 const int by = game_next_panel_y + 15;
1187
1188 for (int i = 0; i < piece_height; ++i) {
1189 const int sprite_index = std::clamp(piece.next_colors[static_cast<std::size_t>(i)], 0, 9);
1190 blocks[static_cast<std::size_t>(sprite_index)]->drawSpriteRect(
1191 static_cast<int>(static_cast<float>(bx) * scaleX),
1192 static_cast<int>(static_cast<float>(by + i * (game_block_height + game_block_spacing)) * scaleY),
1193 static_cast<int>(static_cast<float>(game_block_width) * scaleX),
1194 static_cast<int>(static_cast<float>(game_block_height) * scaleY));
1195 }
1196 }
1197
1198 void drawHud(float scaleX, float scaleY) {
1199 printText(std::format("Score: {}", score),
1200 static_cast<int>(200.0f * scaleX),
1201 static_cast<int>(80.0f * scaleY) - 24,
1202 SDL_Color{255, 255, 255, 255},
1203 ui_font);
1204 printText(std::format("Tabs: {}", lines),
1205 static_cast<int>(310.0f * scaleX),
1206 static_cast<int>(80.0f * scaleY) - 24,
1207 SDL_Color{255, 255, 255, 255},
1208 ui_font);
1209 }
1210
1211 void handleControllerAxis(Sint16 lx, Sint16 ly) {
1212 const Uint32 now = SDL_GetTicks();
1213
1214 if (screen == Screen::Menu) {
1215 if (ly < -joystick_dead_zone) {
1216 if (now - joy_repeat_up_ms > joy_repeat_delay_ms) {
1217 menu_selection = (menu_selection + menu_item_count - 1) % menu_item_count;
1218 joy_repeat_up_ms = now;
1219 }
1220 } else {
1221 joy_repeat_up_ms = 0U;
1222 }
1223
1224 if (ly > joystick_dead_zone) {
1225 if (now - joy_repeat_down_ms > joy_repeat_delay_ms) {
1226 menu_selection = (menu_selection + 1) % menu_item_count;
1227 joy_repeat_down_ms = now;
1228 }
1229 } else {
1230 joy_repeat_down_ms = 0U;
1231 }
1232 return;
1233 }
1234
1235 if (screen != Screen::Game || paused || awaiting_name) {
1236 return;
1237 }
1238
1239 if (lx < -joystick_dead_zone) {
1240 if (now - joy_repeat_left_ms > joy_repeat_delay_ms) {
1241 movePiece(-1, 0);
1242 joy_repeat_left_ms = now;
1243 }
1244 } else {
1245 joy_repeat_left_ms = 0U;
1246 }
1247
1248 if (lx > joystick_dead_zone) {
1249 if (now - joy_repeat_right_ms > joy_repeat_delay_ms) {
1250 movePiece(1, 0);
1251 joy_repeat_right_ms = now;
1252 }
1253 } else {
1254 joy_repeat_right_ms = 0U;
1255 }
1256
1257 if (ly > joystick_dead_zone) {
1258 if (now - joy_repeat_down_ms > joy_repeat_delay_ms) {
1259 if (!movePiece(0, 1)) {
1260 lockPiece(now);
1261 }
1262 joy_repeat_down_ms = now;
1263 }
1264 } else {
1265 joy_repeat_down_ms = 0U;
1266 }
1267
1268 if (lx == 0 && ly == 0) {
1269 joy_repeat_up_ms = 0U;
1270 }
1271 }
1272
1273 void pollController([[maybe_unused]] Uint64 now) {
1274 if (gamepad == nullptr) {
1275 return;
1276 }
1277
1278 const Sint16 lx = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1279 const Sint16 ly = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1280 handleControllerAxis(lx, ly);
1281 }
1282
1283 bool openGamepad(SDL_JoystickID id) {
1284 if (gamepad != nullptr && gamepadId == id) {
1285 return true;
1286 }
1287
1288 closeGamepad();
1289 gamepad = SDL_OpenGamepad(id);
1290 if (gamepad == nullptr) {
1291 return false;
1292 }
1293
1294 gamepadId = id;
1295 return true;
1296 }
1297
1298 void tryOpenFirstGamepad() {
1299 if (gamepad != nullptr) {
1300 return;
1301 }
1302
1303 int count = 0;
1304 SDL_JoystickID *ids = SDL_GetGamepads(&count);
1305 if (ids == nullptr || count <= 0) {
1306 if (ids != nullptr) {
1307 SDL_free(ids);
1308 }
1309 return;
1310 }
1311
1312 openGamepad(ids[0]);
1313 SDL_free(ids);
1314 }
1315
1316 void closeGamepad() {
1317 if (gamepad != nullptr) {
1318 SDL_CloseGamepad(gamepad);
1319 gamepad = nullptr;
1320 }
1321 gamepadId = 0;
1322 }
1323 };
1324
1325} // namespace example
1326
1327int main(int argc, char **argv) {
1328 try {
1329 const bool explicit_resolution = hasResolutionArgument(argc, argv);
1330 Arguments args = proc_args(argc, argv);
1331 if (!explicit_resolution) {
1332 args.width = base_width;
1333 args.height = base_height;
1334 }
1335
1336 example::MasterPieceWindow window(args.path, args.width, args.height, args.fullscreen, args.enable_vsync);
1337 window.loop();
1338 } catch (mxvk::Exception &e) {
1339 std::cerr << std::format("mxvk: Exception: {}\n", e.text());
1340 return EXIT_FAILURE;
1341 } catch (ArgException<std::string> &e) {
1342 std::cerr << std::format("mxvk: Argument Exception: {}\n", e.text());
1343 return EXIT_FAILURE;
1344 }
1345
1346 return EXIT_SUCCESS;
1347}
Lightweight, header-only, template command-line argument parser.
Arguments proc_args(int &argc, char **argv)
Parse standard libmx2 command-line options from main()'s argv.
Definition argz.hpp:872
Exception thrown by Argz::proc() on unrecognised or malformed options.
Definition argz.hpp:178
const std::vector< ScoreEntry > & list() const
Definition main.cpp:125
HighScores(std::filesystem::path file_path)
Definition main.cpp:106
void add(std::string name, int score)
Definition main.cpp:111
void event(SDL_Event &e) override
Handle one SDL event.
Definition main.cpp:269
MasterPieceWindow(const std::string &path, int width, int height, bool fullscreen, bool enable_vsync)
Definition main.cpp:247
void proc() override
Execute one processing/update step.
Definition main.cpp:326
~MasterPieceWindow() override
Definition main.cpp:265
std::string text() const
void updateTexture(SDL_Surface *surface)
Replace the sprite texture from an SDL_Surface.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
void loop()
Run the main event/render loop.
Definition mxvk.cpp:600
VK_Sprite * createSprite(const std::string &pngPath, const std::string &vertexShaderPath="", const std::string &fragmentShaderPath="")
Create a sprite from a PNG file and register it with this window.
Definition mxvk.cpp:3477
bool getTextDimensions(const std::string &text, int &width, int &height)
Measure text dimensions in pixels.
Definition mxvk.cpp:3069
std::string font_path
Definition mxvk.hpp:568
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
std::unique_ptr< SDL_Window, SDLWindowDeleter > window
Definition mxvk.hpp:480
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VK_Window()=default
Construct an empty window object.
void setFont(const std::string &fontPath, int fontSize=24)
Set the active text-render font.
Definition mxvk.cpp:2991
void printText(const std::string &text, int x, int y, const SDL_Color &col)
Queue a text string for rendering during the current frame.
Definition mxvk.cpp:3018
int main(void)
Definition main.cpp:7
#define MXVK_VALIDATION
Definition mxvk.hpp:27
std::filesystem::path scorePath(const std::filesystem::path &asset_root)
Definition main.cpp:153
constexpr int game_block_width
Definition main.cpp:40
constexpr int piece_height
Definition main.cpp:32
constexpr int max_name_length
Definition main.cpp:51
constexpr int game_base_height
Definition main.cpp:37
std::filesystem::path resolvePuzzleAssetRoot(const std::filesystem::path &asset_root)
Definition main.cpp:145
constexpr int base_width
Definition main.cpp:34
constexpr int menu_item_count
Definition main.cpp:46
constexpr int board_cols
Definition main.cpp:31
constexpr int game_board_start_x
Definition main.cpp:38
constexpr int flash_time_ms
Definition main.cpp:48
constexpr int game_next_panel_y
Definition main.cpp:45
constexpr int game_next_panel_x
Definition main.cpp:44
constexpr Uint32 joy_repeat_delay_ms
Definition main.cpp:52
bool hasResolutionArgument(int argc, char **argv)
Definition main.cpp:128
constexpr int lines_per_speedup
Definition main.cpp:49
std::filesystem::path resolveAssetRoot(const std::string &path)
Definition main.cpp:138
constexpr int title_screen_time_ms
Definition main.cpp:47
constexpr int game_block_height
Definition main.cpp:41
constexpr int game_block_spacing
Definition main.cpp:42
constexpr int score_points_per_match
Definition main.cpp:50
constexpr int game_base_width
Definition main.cpp:36
constexpr Sint16 joystick_dead_zone
Definition main.cpp:53
constexpr int base_height
Definition main.cpp:35
constexpr int max_scores
Definition main.cpp:33
constexpr int game_board_start_y
Definition main.cpp:39
constexpr int board_rows
Definition main.cpp:30
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30
Plain data structure returned by proc_args() with all common libmx2 CLI options.
Definition argz.hpp:730
bool fullscreen
Whether fullscreen mode was requested.
Definition argz.hpp:736
bool enable_vsync
Enable FIFO present mode / v-sync (--enable-vsync).
Definition argz.hpp:750
int height
Viewport height in pixels (default: 720).
Definition argz.hpp:733
std::string path
Asset root; proc_args() defaults it to the executable directory.
Definition argz.hpp:735
int width
Viewport width in pixels (default: 1280).
Definition argz.hpp:732
std::array< int, piece_height > next_colors
Definition main.cpp:82
std::array< int, piece_height > colors
Definition main.cpp:81