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