MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mutatris_window.cpp
Go to the documentation of this file.
1#include "mutatris_window.hpp"
2
3#include <algorithm>
4#include <array>
5#include <cmath>
6#include <filesystem>
7#include <format>
8#include <iostream>
9
11
12#ifndef mutatris_ASSET_DIR
13#define mutatris_ASSET_DIR "."
14#endif
15
16namespace mutatris {
17
18 namespace {
19 constexpr int CONSOLE_DESIGN_FONT_SIZE = 20;
20 constexpr int START_PROMPT_RECT_X = 530;
21 constexpr int START_PROMPT_RECT_Y = 548;
22 constexpr int START_PROMPT_RECT_W = 217;
23 constexpr int START_PROMPT_RECT_H = 78;
24 }
25
26 MutatrisWindow::MutatrisWindow(const std::string &path, int width, int height, bool fullscreen, bool enableVsync, bool enableCrt)
27 : mxvk::VK_Window("Mutatris", width, height, fullscreen, MXVK_VALIDATION, enableVsync),
28 assetRoot((path.empty() || path == ".") ? std::string(mutatris_ASSET_DIR) : path),
29 dataRoot(assetRoot + "/data"),
30 shaderRoot(assetRoot + "/shaders"),
31 fontPath(dataRoot + "/font.ttf"),
32 crtEnabled(enableCrt) {
33 setClearColor(0.0f, 0.0f, 0.0f, 1.0f);
34 setFont(fontPath, 24);
35 attachPostProcessingShader(shaderRoot + "/crt.frag.spv", 0.0f, 3.0f, 0.5f, 0.002f);
37 setPostProcessingEnabled(crtEnabled);
38 configureConsole();
39 loadSprites();
40 loadSoundEffects();
41 syncControllerConnection();
42 playSound(openSound);
43 startupStartTick = SDL_GetTicks();
44 logMutatris(std::format("Mutatris ready. {} shader effect(s) available.", effectShaders.size()));
45 }
46
47 void MutatrisWindow::event(SDL_Event &e) {
48 if (e.type == SDL_EVENT_GAMEPAD_ADDED) {
49 if (!controller.active()) {
50 controller.connectEvent(e);
51 }
52 syncControllerConnection();
53 return;
54 }
55 if (e.type == SDL_EVENT_GAMEPAD_REMOVED) {
56 controller.connectEvent(e);
57 syncControllerConnection();
58 return;
59 }
60
61 const bool wasConsoleVisible = console.isVisible();
62 const bool consoleToggle = e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_F3;
63 console.handleEvent(e);
64 if (consoleToggle) {
65 logMutatris(console.isVisible() ? "Console opened." : "Console closed.");
66 return;
67 }
68 if (wasConsoleVisible) {
69 return;
70 }
71 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
72 if (e.key.repeat) {
73 return;
74 }
75 if (screen == Screen::Playing) {
76 game.reset();
77 setScreen(Screen::Difficulty, "game cancelled");
78 } else {
79 logMutatris("Exit requested.");
80 exit();
81 }
82 return;
83 }
84 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_F8 && !e.key.repeat) {
85 crtEnabled = !crtEnabled;
86 setPostProcessingEnabled(crtEnabled);
87 logMutatris(std::string("CRT effect ") + (crtEnabled ? "enabled." : "disabled."));
88 return;
89 }
90 if (e.type == SDL_EVENT_KEY_DOWN && !e.key.repeat) {
91 handleKey(e.key.key);
92 } else if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN || e.type == SDL_EVENT_FINGER_DOWN) {
93 handleConfirm();
94 } else if (e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
95 handleGamepad(e.gbutton.button);
96 }
97 }
98
100 const VkExtent2D extent = getSwapchainExtent();
101 width = extent.width > 0U ? static_cast<int>(extent.width) : DESIGN_WIDTH;
102 height = extent.height > 0U ? static_cast<int>(extent.height) : DESIGN_HEIGHT;
103 ensureUiFont();
104
105 ensureMusicPlaying();
106 switch (screen) {
107 case Screen::Startup:
108 drawStartup();
109 break;
110 case Screen::Title:
111 drawTitle();
112 break;
114 drawDifficulty();
115 break;
116 case Screen::Playing:
117 updatePlaying();
118 break;
119 case Screen::GameOver:
120 drawGameOver();
121 break;
122 }
123 console.draw();
124 }
125
126 void MutatrisWindow::configureConsole() {
127 console.attach(*this, fontPath, CONSOLE_DESIGN_FONT_SIZE);
128 console.setSpriteYOriginTopLeft(true);
129 console.setPrompt("mutatris> ");
130 console.printLine("Press F3 to open/close the console.");
131 console.printLine("Type 'help' for commands.");
132 console.setCommandCallback([this](mxvk::VK_Window &, const std::vector<std::string> &args, std::ostream &out) {
133 if (args.empty()) {
134 return true;
135 }
136
137 if (args[0] == "help") {
138 out << "Commands:\n"
139 << " help Show this help message\n"
140 << " clear Clear the console output\n"
141 << " echo <text> Print text to the console\n"
142 << " switch_shader Pick a random background and shader\n"
143 << " about Show app information\n"
144 << " quit | exit Close Mutatris";
145 return true;
146 }
147
148 if (args[0] == "echo") {
149 for (std::size_t i = 1; i < args.size(); ++i) {
150 if (i > 1) {
151 out << ' ';
152 }
153 out << args[i];
154 }
155 return true;
156 }
157
158 if (args[0] == "switch_shader") {
159 const std::string shaderName = switchBackgroundShader(true);
160 if (shaderName.empty()) {
161 out << "No shader effects are available.";
162 } else {
163 logMutatris(std::format("Console command switch_shader selected {} with shader {}", backgroundName(backgroundIndex), shaderName));
164 out << std::format("Switched to {} with shader {}", backgroundName(backgroundIndex), shaderName);
165 }
166 return true;
167 }
168
169 if (args[0] == "about") {
170 out << "Mutatris: rotating-grid puzzle game built with MXVK.";
171 return true;
172 }
173
174 if (args[0] == "quit" || args[0] == "exit") {
175 out << "Closing Mutatris...";
176 logMutatris("Exit requested from console.");
177 exit();
178 return true;
179 }
180
181 return false;
182 });
183 }
184
185 void MutatrisWindow::logMutatris(const std::string &message) {
186 std::cout << std::format("mutatris: {}\n", message);
187 console.printLine(message, SDL_Color{180, 220, 255, 255});
188 }
189
190 const char *MutatrisWindow::screenName(Screen value) const {
191 switch (value) {
192 case Screen::Startup:
193 return "Startup";
194 case Screen::Title:
195 return "Title";
197 return "Difficulty";
198 case Screen::Playing:
199 return "Playing";
200 case Screen::GameOver:
201 return "GameOver";
202 }
203 return "Unknown";
204 }
205
206 const char *MutatrisWindow::focusName(int value) const {
207 switch (value) {
208 case 0:
209 return "Top";
210 case 1:
211 return "Left";
212 case 2:
213 return "Bottom";
214 case 3:
215 return "Right";
216 default:
217 return "Unknown";
218 }
219 }
220
221 void MutatrisWindow::setScreen(Screen nextScreen, const std::string &reason) {
222 if (screen == nextScreen) {
223 return;
224 }
225 const Screen previousScreen = screen;
226 screen = nextScreen;
227 logMutatris(std::format("Screen changed {} -> {} ({})", screenName(previousScreen), screenName(nextScreen), reason));
228 }
229
230 void MutatrisWindow::loadSprites() {
231 const std::string backgroundVertexShader = shaderRoot + "/background.vert.spv";
232 const std::string backgroundShader = shaderRoot + "/background.frag.spv";
233 const std::string fadeShader = shaderRoot + "/fade.frag.spv";
234 loadEffectShaders();
235 for (std::size_t i = 0; i < backgrounds.size(); ++i) {
236 backgrounds[i] = createSprite(dataRoot + "/blocks" + (i == 0 ? std::string("") : std::to_string(i)) + ".png", backgroundVertexShader, backgroundShader);
237 }
238 intro = createSprite(dataRoot + "/intro.png", backgroundVertexShader, fadeShader);
239 start = createSprite(dataRoot + "/start.png");
240 lostLogo = createSprite(dataRoot + "/lostlogo.png", backgroundVertexShader, fadeShader);
241 jbLogo = createSprite(dataRoot + "/jblogo.png", backgroundVertexShader, fadeShader);
242 gameOver = createSprite(dataRoot + "/gameover.png");
243 highScore = createSprite(dataRoot + "/highscore.png");
244
245 const std::array<std::string, 8> blockFiles{{
246 "block_black.png",
247 "block_clear.png",
248 "block_dblue.png",
249 "block_green.png",
250 "block_ltblue.png",
251 "block_orange.png",
252 "block_red.png",
253 "block_yellow.png",
254 }};
255 for (std::size_t i = 0; i < blockFiles.size(); ++i) {
256 blocks[i] = createSprite(dataRoot + "/" + blockFiles[i]);
257 }
258 }
259
260 void MutatrisWindow::loadEffectShaders() {
261 effectShaders.clear();
262 const std::filesystem::path effectDir = std::filesystem::path(shaderRoot) / "effects";
263 if (!std::filesystem::exists(effectDir)) {
264 return;
265 }
266 for (const std::filesystem::directory_entry &entry : std::filesystem::directory_iterator(effectDir)) {
267 if (entry.is_regular_file() && entry.path().extension() == ".spv") {
268 effectShaders.push_back(entry.path().string());
269 }
270 }
271 std::sort(effectShaders.begin(), effectShaders.end());
272 std::cout << std::format("mutatris: loaded {} shader effect(s)\n", effectShaders.size());
273 }
274
275 void MutatrisWindow::loadSoundEffects() {
276#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
277 soundEffects = std::make_unique<mxvk::VK_Mixer>();
278 musicTrack = soundEffects->loadMusic(dataRoot + "/music.ogg");
279 lineSound = soundEffects->loadWav(dataRoot + "/line.wav");
280 openSound = soundEffects->loadWav(dataRoot + "/open.wav");
281 logMutatris("Loaded original Mutatris sound effects.");
282 ensureMusicPlaying();
283#endif
284 }
285
286 void MutatrisWindow::playSound(int soundId) {
287#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
288 if (soundEffects == nullptr || soundId < 0) {
289 return;
290 }
291 soundEffects->playWav(soundId);
292#else
293 (void)soundId;
294#endif
295 }
296
297 void MutatrisWindow::ensureMusicPlaying() {
298#if defined(MXVK_WITH_MIXER) || defined(WITH_MIXER)
299 if (soundEffects == nullptr || musicTrack < 0 || soundEffects->isMusicPlaying(musicTrack)) {
300 return;
301 }
302 if (soundEffects->playMusic(musicTrack, -1) != 0) {
303 throw mxvk::Exception("Could not start Mutatris background music");
304 }
305#endif
306 }
307
308 void MutatrisWindow::handleKey(SDL_Keycode key) {
309 if (screen == Screen::Startup) {
310 if ((key == SDLK_RETURN || key == SDLK_KP_ENTER || key == SDLK_SPACE) && isStartupTitleFullyVisible()) {
311 setScreen(Screen::Difficulty, "difficulty select opened");
312 } else {
313 setScreen(Screen::Title, "startup confirmed");
314 }
315 return;
316 }
317 if (screen == Screen::Title && (key == SDLK_RETURN || key == SDLK_KP_ENTER || key == SDLK_SPACE)) {
318 setScreen(Screen::Difficulty, "difficulty select opened");
319 return;
320 }
321 if (screen == Screen::Difficulty) {
322 if (key == SDLK_LEFT && difficulty > 0) {
323 --difficulty;
324 logMutatris(std::format("Difficulty changed to {}", difficulty));
325 } else if (key == SDLK_RIGHT && difficulty < 2) {
326 ++difficulty;
327 logMutatris(std::format("Difficulty changed to {}", difficulty));
328 } else if (key == SDLK_RETURN || key == SDLK_KP_ENTER || key == SDLK_SPACE) {
329 startGame();
330 }
331 return;
332 }
333 if (screen == Screen::GameOver && (key == SDLK_SPACE || key == SDLK_RETURN || key == SDLK_KP_ENTER)) {
334 setScreen(Screen::Title, "returned from game over");
335 return;
336 }
337 if (screen != Screen::Playing || game == nullptr) {
338 return;
339 }
340 if (key == SDLK_W) {
341 game->grid[focus].gamePiece.shiftColors();
342 return;
343 }
344 if (key == SDLK_A || key == SDLK_SPACE) {
345 game->grid[focus].gamePiece.shiftDirection();
346 return;
347 }
348 if (key == SDLK_S) {
349 if (game->grid[focus].gamePiece.drop()) {
350 processGrid();
351 advanceFocus();
352 } else if (activePiecePlacementBlocked()) {
353 triggerGameOver("active piece placement blocked");
354 }
355 return;
356 }
357 }
358
359 void MutatrisWindow::handleGamepad(Uint8 button) {
360 if (button == SDL_GAMEPAD_BUTTON_BACK) {
361 logMutatris("Exit requested.");
362 exit();
363 return;
364 }
365 if (screen != Screen::Playing) {
366 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
367 handleConfirm();
368 } else if (screen == Screen::Difficulty && button == SDL_GAMEPAD_BUTTON_DPAD_LEFT && difficulty > 0) {
369 --difficulty;
370 logMutatris(std::format("Difficulty changed to {}", difficulty));
371 } else if (screen == Screen::Difficulty && button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT && difficulty < 2) {
372 ++difficulty;
373 logMutatris(std::format("Difficulty changed to {}", difficulty));
374 }
375 return;
376 }
377 if (game == nullptr) {
378 return;
379 }
380 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
381 game->grid[focus].gamePiece.shiftColors();
382 } else if (button == SDL_GAMEPAD_BUTTON_EAST) {
383 game->grid[focus].gamePiece.shiftDirection();
384 } else if (button == SDL_GAMEPAD_BUTTON_NORTH) {
385 if (game->grid[focus].gamePiece.drop()) {
386 processGrid();
387 advanceFocus();
388 } else if (activePiecePlacementBlocked()) {
389 triggerGameOver("active piece placement blocked");
390 }
391 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
392 handleGamepadDpad(button);
393 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
394 handleGamepadDpad(button);
395 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
396 handleGamepadDpad(button);
397 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
398 handleGamepadDpad(button);
399 }
400 }
401
402 void MutatrisWindow::handleGamepadDpad(Uint8 button) {
403 if (game == nullptr) {
404 return;
405 }
406 const Uint32 now = SDL_GetTicks();
407 if (now - lastInputTick < KEY_REPEAT_MS) {
408 return;
409 }
410
411 Piece &piece = game->grid[focus].gamePiece;
412 bool handled = true;
413 if (focus == 0) {
414 if (button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
415 piece.moveLeft();
416 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
417 piece.moveRight();
418 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
419 softDropActivePiece();
420 } else {
421 handled = false;
422 }
423 } else if (focus == 1) {
424 if (button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
425 piece.moveLeft();
426 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
427 piece.moveRight();
428 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
429 softDropActivePiece();
430 } else {
431 handled = false;
432 }
433 } else if (focus == 2) {
434 if (button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
435 piece.moveLeft();
436 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
437 piece.moveRight();
438 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
439 softDropActivePiece();
440 } else {
441 handled = false;
442 }
443 } else if (focus == 3) {
444 if (button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
445 piece.moveLeft();
446 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
447 piece.moveRight();
448 } else if (button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
449 softDropActivePiece();
450 } else {
451 handled = false;
452 }
453 }
454
455 if (handled) {
456 lastInputTick = now;
457 }
458 }
459
460 void MutatrisWindow::handleConfirm() {
461 if (screen == Screen::Startup) {
462 if (isStartupTitleFullyVisible()) {
463 setScreen(Screen::Difficulty, "difficulty select opened");
464 } else {
465 setScreen(Screen::Title, "startup confirmed");
466 }
467 } else if (screen == Screen::Title) {
468 setScreen(Screen::Difficulty, "difficulty select opened");
469 } else if (screen == Screen::Difficulty) {
470 startGame();
471 } else if (screen == Screen::GameOver) {
472 setScreen(Screen::Title, "returned from game over");
473 }
474 }
475
476 bool MutatrisWindow::isStartupTitleFullyVisible() const {
477 const Uint32 elapsed = SDL_GetTicks() - startupStartTick;
478 constexpr Uint32 FIRST_FADE_IN_END = STARTUP_FADE_MS;
479 constexpr Uint32 FIRST_FADE_OUT_END = FIRST_FADE_IN_END + STARTUP_FADE_MS;
480 constexpr Uint32 SECOND_FADE_IN_END = FIRST_FADE_OUT_END + STARTUP_FADE_MS;
481 constexpr Uint32 SECOND_HOLD_END = SECOND_FADE_IN_END + STARTUP_HOLD_MS;
482 constexpr Uint32 SECOND_FADE_OUT_END = SECOND_HOLD_END + STARTUP_FADE_MS;
483
484 return elapsed >= SECOND_FADE_OUT_END;
485 }
486
487 void MutatrisWindow::pollKeyboardInput() {
488 const bool *keyboardState = SDL_GetKeyboardState(nullptr);
489 SDL_Keycode key = SDLK_UNKNOWN;
490 if (keyboardState[SDL_SCANCODE_LEFT]) {
491 key = SDLK_LEFT;
492 } else if (keyboardState[SDL_SCANCODE_RIGHT]) {
493 key = SDLK_RIGHT;
494 } else if (keyboardState[SDL_SCANCODE_UP]) {
495 key = SDLK_UP;
496 } else if (keyboardState[SDL_SCANCODE_DOWN]) {
497 key = SDLK_DOWN;
498 }
499
500 if (key == SDLK_UNKNOWN) {
501 lastKeyboardInputTick = 0;
502 return;
503 }
504
505 const Uint32 now = SDL_GetTicks();
506 if (lastKeyboardInputTick != 0U && now - lastKeyboardInputTick < KEY_REPEAT_MS) {
507 return;
508 }
509 lastKeyboardInputTick = now;
510 handleDirectionalKey(key);
511 }
512
513 void MutatrisWindow::handleDirectionalKey(SDL_Keycode key) {
514 if (game == nullptr) {
515 return;
516 }
517 Piece &piece = game->grid[focus].gamePiece;
518 if (focus == 0) {
519 if (key == SDLK_LEFT) {
520 piece.moveLeft();
521 } else if (key == SDLK_RIGHT) {
522 piece.moveRight();
523 } else if (key == SDLK_UP) {
524 piece.shiftColors();
525 } else if (key == SDLK_DOWN) {
526 if (piece.moveDown()) {
527 processGrid();
528 advanceFocus();
529 } else if (activePiecePlacementBlocked()) {
530 triggerGameOver("active piece placement blocked");
531 }
532 }
533 } else if (focus == 1) {
534 if (key == SDLK_DOWN) {
535 piece.moveRight();
536 } else if (key == SDLK_UP) {
537 piece.moveLeft();
538 } else if (key == SDLK_LEFT) {
539 piece.shiftColors();
540 } else if (key == SDLK_RIGHT) {
541 if (piece.moveDown()) {
542 processGrid();
543 advanceFocus();
544 } else if (activePiecePlacementBlocked()) {
545 triggerGameOver("active piece placement blocked");
546 }
547 }
548 } else if (focus == 2) {
549 if (key == SDLK_LEFT) {
550 piece.moveLeft();
551 } else if (key == SDLK_RIGHT) {
552 piece.moveRight();
553 } else if (key == SDLK_DOWN) {
554 piece.shiftColors();
555 } else if (key == SDLK_UP) {
556 if (piece.moveDown()) {
557 processGrid();
558 advanceFocus();
559 } else if (activePiecePlacementBlocked()) {
560 triggerGameOver("active piece placement blocked");
561 }
562 }
563 } else if (focus == 3) {
564 if (key == SDLK_DOWN) {
565 piece.moveLeft();
566 } else if (key == SDLK_UP) {
567 piece.moveRight();
568 } else if (key == SDLK_RIGHT) {
569 piece.shiftColors();
570 } else if (key == SDLK_LEFT) {
571 if (piece.moveDown()) {
572 processGrid();
573 advanceFocus();
574 } else if (activePiecePlacementBlocked()) {
575 triggerGameOver("active piece placement blocked");
576 }
577 }
578 }
579 }
580
581 void MutatrisWindow::softDropActivePiece() {
582 if (game == nullptr) {
583 return;
584 }
585 if (game->grid[focus].gamePiece.moveDown()) {
586 processGrid();
587 advanceFocus();
588 } else if (activePiecePlacementBlocked()) {
589 triggerGameOver("active piece placement blocked");
590 }
591 }
592
593 bool MutatrisWindow::activePiecePlacementBlocked() const {
594 if (game == nullptr) {
595 return false;
596 }
597 const Piece &piece = game->grid[focus].gamePiece;
598 return !piece.checkLocation(piece.getX(), piece.getY());
599 }
600
601 void MutatrisWindow::triggerGameOver(const std::string &reason) {
602 if (game == nullptr) {
603 return;
604 }
605 finalScore = game->score;
606 finalClears = game->clears;
607 setScreen(Screen::GameOver, reason);
608 logMutatris(std::format("Game over. score={} clears={}", finalScore, finalClears));
609 }
610
611 bool MutatrisWindow::openController() {
612 for (int index = 0; index < mxvk::VK_Controller::joysticks(); ++index) {
613 if (controller.open(index)) {
614 logMutatris("Controller connected: " + controller.name());
615 return true;
616 }
617 }
618 return false;
619 }
620
621 void MutatrisWindow::syncControllerConnection() {
622 if (!controller.active()) {
623 openController();
624 }
625 }
626
627 void MutatrisWindow::processGrid() {
628 if (game == nullptr) {
629 return;
630 }
631 const int previousScore = game->score;
632 const int previousClears = game->clears;
633 const int previousLevel = game->level;
634 const unsigned int previousTimeout = game->timeout;
635 for (int i = 0; i < 4; ++i) {
636 game->procBlocks();
637 }
638 if (game->score != previousScore || game->clears != previousClears || game->level != previousLevel || game->timeout != previousTimeout) {
639 logMutatris(std::format("Grid processed. score {} -> {}, clears {} -> {}, level {} -> {}, timeout {} -> {}",
640 previousScore,
641 game->score,
642 previousClears,
643 game->clears,
644 previousLevel + 1,
645 game->level + 1,
646 previousTimeout,
647 game->timeout));
648 }
649 }
650
651 void MutatrisWindow::advanceFocus() {
652 const int previousFocus = focus;
653 if (focus == 0) {
654 focus = 3;
655 } else if (focus == 3) {
656 focus = 2;
657 } else if (focus == 2) {
658 focus = 1;
659 } else {
660 focus = 0;
661 }
662 lastDropTick = SDL_GetTicks();
663 logMutatris(std::format("Focus advanced {} -> {}.", focusName(previousFocus), focusName(focus)));
664 }
665
666 void MutatrisWindow::startGame() {
667 game = std::make_unique<PuzzleGame>(difficulty, [this]() {
668 playSound(lineSound);
669 });
670 focus = 0;
671 lastDropTick = SDL_GetTicks();
672 lastKeyboardInputTick = 0;
673 lastClearAnimationTick = lastDropTick;
674 shaderLevel = -1;
675 shaderIndex = -1;
676 backgroundIndex = -1;
677 setScreen(Screen::Playing, "new game");
678 logMutatris(std::format("New game started. difficulty={} timeout={}", difficulty, game->timeout));
679 }
680
681 void MutatrisWindow::drawStartup() {
682 const Uint32 now = SDL_GetTicks();
683 const Uint32 elapsed = now - startupStartTick;
684 constexpr Uint32 FIRST_FADE_IN_END = STARTUP_FADE_MS;
685 constexpr Uint32 FIRST_FADE_OUT_END = FIRST_FADE_IN_END + STARTUP_FADE_MS;
686 constexpr Uint32 SECOND_FADE_IN_END = FIRST_FADE_OUT_END + STARTUP_FADE_MS;
687 constexpr Uint32 SECOND_HOLD_END = SECOND_FADE_IN_END + STARTUP_HOLD_MS;
688 constexpr Uint32 SECOND_FADE_OUT_END = SECOND_HOLD_END + STARTUP_FADE_MS;
689 constexpr Uint32 TITLE_FADE_IN_END = SECOND_FADE_OUT_END + STARTUP_FADE_MS;
690
691 if (elapsed < FIRST_FADE_IN_END) {
692 drawStartupLogo(lostLogo, static_cast<float>(elapsed) / static_cast<float>(STARTUP_FADE_MS));
693 } else if (elapsed < FIRST_FADE_OUT_END) {
694 const Uint32 phaseElapsed = elapsed - FIRST_FADE_IN_END;
695 drawStartupLogo(lostLogo, 1.0f - (static_cast<float>(phaseElapsed) / static_cast<float>(STARTUP_FADE_MS)));
696 } else if (elapsed < SECOND_FADE_IN_END) {
697 const Uint32 phaseElapsed = elapsed - FIRST_FADE_OUT_END;
698 drawStartupLogo(jbLogo, static_cast<float>(phaseElapsed) / static_cast<float>(STARTUP_FADE_MS));
699 } else if (elapsed < SECOND_HOLD_END) {
700 drawStartupLogo(jbLogo, 1.0f);
701 } else if (elapsed < SECOND_FADE_OUT_END) {
702 const Uint32 phaseElapsed = elapsed - SECOND_HOLD_END;
703 const float transitionAlpha = static_cast<float>(phaseElapsed) / static_cast<float>(STARTUP_FADE_MS);
704 const float titleAlpha = std::clamp((transitionAlpha - 0.78f) / 0.22f, 0.0f, 1.0f);
705 drawTitleWithAlpha(titleAlpha);
706 drawStartupLogo(jbLogo, 1.0f - transitionAlpha);
707 } else if (elapsed < TITLE_FADE_IN_END) {
708 drawTitleWithAlpha(1.0f);
709 } else {
710 setScreen(Screen::Title, "startup sequence complete");
711 }
712 }
713
714 void MutatrisWindow::drawStartupLogo(mxvk::VK_Sprite *sprite, float alphaValue) {
715 if (sprite == nullptr) {
716 return;
717 }
718 sprite->setShaderParams(std::clamp(alphaValue, 0.0f, 1.0f), 0.0f, 0.0f, 0.0f);
719 sprite->drawSpriteRect(0, 0, width, height);
720 }
721
722 void MutatrisWindow::drawTitle() {
723 drawTitleWithAlpha(1.0f);
724 }
725
726 void MutatrisWindow::drawTitleWithAlpha(float alphaValue) {
727 ensureIntroFonts();
728 const Uint8 alphaByte = static_cast<Uint8>(std::clamp(static_cast<int>(std::lround(alphaValue * 255.0f)), 0, 255));
729 if (intro != nullptr) {
730 intro->setShaderParams(std::clamp(alphaValue, 0.0f, 1.0f), 0.0f, 0.0f, 0.0f);
731 intro->drawSpriteRect(0, 0, width, height);
732 }
733 printScaledText("[Press Enter to Play]", 25, 25, {255, 255, 255, alphaByte}, titleFont);
734 printScaledText("Mutatris", 50, 250, {255, 255, 255, alphaByte}, titleLargeFont);
735 printScaledText("lostsidedead.biz", 780, 670, {80, 120, 255, alphaByte}, titleFont);
736 }
737
738 void MutatrisWindow::drawDifficulty() {
739 ensureIntroFonts();
740 if (start != nullptr) {
741 start->drawSpriteRect(0, 0, width, height);
742 }
743 printScaledText("Mutatris", 560, 325, {255, 255, 255, 255}, startMediumFont);
744 drawTextCenteredInRect(
745 "Press Space",
746 START_PROMPT_RECT_X,
747 START_PROMPT_RECT_Y,
748 START_PROMPT_RECT_W,
749 START_PROMPT_RECT_H,
750 {255, 255, 255, 255},
751 startSmallFont);
752 const std::array<std::string, 3> labels{{"Easy", "Medium", "Hard"}};
753 for (int i = 0; i < static_cast<int>(labels.size()); ++i) {
754 const SDL_Color color = i == difficulty ? SDL_Color{255, 230, 64, 255} : SDL_Color{255, 255, 255, 255};
755 printScaledText(labels[static_cast<std::size_t>(i)], 430 + i * 180, 475, color, difficultyFont);
756 }
757 }
758
759 void MutatrisWindow::updatePlaying() {
760 if (game == nullptr) {
761 startGame();
762 }
763 pollKeyboardInput();
764 if (screen != Screen::Playing) {
765 return;
766 }
767 processGrid();
768 updateBackgroundShaderForLevel();
769 mxvk::VK_Sprite *background = currentBackground();
770 if (background != nullptr) {
771 background->setShaderParams(static_cast<float>(SDL_GetTicks()) / 1000.0f, 0.0f, 0.0f, 0.0f);
772 background->drawSpriteRect(0, 0, width, height);
773 }
774 twistClearedBlocks();
775 drawGrid(0);
776 drawGrid(1);
777 drawGrid(2);
778 drawGrid(3);
779 if (!console.isVisible()) {
780 printScaledText("Level: " + std::to_string(game->level + 1) + " Timeout: " + std::to_string(game->timeout), 25, 25, {255, 255, 255, 255});
781 printScaledText("Score: " + std::to_string(game->score), 25, 55, {255, 255, 255, 255});
782 printScaledText("Direction: " + std::to_string(focus), 25, 85, {255, 255, 255, 255});
783 printScaledText("Arrows move W shift A rotate S drop", 25, 670, {220, 220, 220, 255});
784 }
785
786 const Uint32 now = SDL_GetTicks();
787 if (now - lastDropTick >= game->timeout) {
788 if (game->grid[focus].canMoveDown()) {
789 if (game->grid[focus].gamePiece.moveDown()) {
790 logMutatris(std::format("Timer drop advanced piece on {} grid.", focusName(focus)));
791 processGrid();
792 advanceFocus();
793 } else if (activePiecePlacementBlocked()) {
794 triggerGameOver("active piece placement blocked");
795 }
796 lastDropTick = now;
797 } else {
798 triggerGameOver("active grid blocked");
799 }
800 }
801 }
802
803 void MutatrisWindow::updateBackgroundShaderForLevel() {
804 if (game == nullptr || game->level == shaderLevel) {
805 return;
806 }
807 shaderLevel = game->level;
808 const std::string shaderName = switchBackgroundShader(false);
809 if (!shaderName.empty()) {
810 logMutatris(std::format("Level {} selected {} with shader {}", game->level + 1, backgroundName(backgroundIndex), shaderName));
811 }
812 }
813
814 std::string MutatrisWindow::switchBackgroundShader(bool force) {
815 selectRandomBackground();
816 if (effectShaders.empty()) {
817 return {};
818 }
819 std::uniform_int_distribution<int> shaderDistribution(0, static_cast<int>(effectShaders.size()) - 1);
820 int nextShaderIndex = shaderDistribution(shaderRandom);
821 if (effectShaders.size() > 1U) {
822 while (nextShaderIndex == shaderIndex) {
823 nextShaderIndex = shaderDistribution(shaderRandom);
824 }
825 }
826 shaderIndex = nextShaderIndex;
827
828 const int targetLevel = game != nullptr ? game->level : 0;
829 mxvk::VK_Sprite *background = currentBackground();
830 if (background != nullptr) {
831 background->setFragmentShaderPath(effectShaders[static_cast<std::size_t>(shaderIndex)]);
832 }
833 if (force) {
834 shaderLevel = targetLevel;
835 }
836 return std::filesystem::path(effectShaders[static_cast<std::size_t>(shaderIndex)]).filename().string();
837 }
838
839 void MutatrisWindow::selectRandomBackground() {
840 std::uniform_int_distribution<int> backgroundDistribution(0, static_cast<int>(backgrounds.size()) - 1);
841 int nextBackgroundIndex = backgroundDistribution(shaderRandom);
842 if (backgrounds.size() > 1U) {
843 while (nextBackgroundIndex == backgroundIndex) {
844 nextBackgroundIndex = backgroundDistribution(shaderRandom);
845 }
846 }
847 backgroundIndex = nextBackgroundIndex;
848 }
849
850 mxvk::VK_Sprite *MutatrisWindow::currentBackground() {
851 if (backgroundIndex < 0) {
852 selectRandomBackground();
853 }
854 return backgrounds[static_cast<std::size_t>(std::clamp(backgroundIndex, 0, static_cast<int>(backgrounds.size()) - 1))];
855 }
856
857 std::string MutatrisWindow::backgroundName(int index) {
858 if (index <= 0) {
859 return "blocks.png";
860 }
861 return std::format("blocks{}.png", index);
862 }
863
864 void MutatrisWindow::drawGameOver() {
865 mxvk::VK_Sprite *sprite = finalScore >= 200 ? highScore : gameOver;
866 if (sprite != nullptr) {
867 sprite->drawSpriteRect(0, 0, width, height);
868 }
869 printScaledText((finalScore >= 200 ? "High Score: " : "Game Over Score: ") + std::to_string(finalScore) + " Clears: " + std::to_string(finalClears), 25, 25, {255, 255, 255, 255});
870 printScaledText("[ Press Space ]", 25, finalScore >= 200 ? 400 : 100, {255, 255, 255, 255});
871 }
872
873 void MutatrisWindow::twistClearedBlocks() {
874 const Uint32 now = SDL_GetTicks();
875 const Uint32 elapsed = lastClearAnimationTick == 0U ? 0U : now - lastClearAnimationTick;
876 lastClearAnimationTick = now;
877 for (GameGrid &focusGrid : game->grid) {
878 for (int y = 0; y < focusGrid.height(); ++y) {
879 for (int x = 0; x < focusGrid.width(); ++x) {
880 Block *block = focusGrid.at(x, y);
881 if (block != nullptr && block->color < 0) {
882 block->clearElapsedMs += elapsed;
883 if (block->clearElapsedMs >= CLEAR_ANIMATION_MS) {
884 block->color = 0;
885 block->clearElapsedMs = 0;
886 } else {
887 const Uint32 frame = std::min<Uint32>((block->clearElapsedMs * CLEAR_ANIMATION_FRAMES) / CLEAR_ANIMATION_MS, CLEAR_ANIMATION_FRAMES - 1U);
888 block->color = -static_cast<int>(frame + 1U);
889 }
890 }
891 }
892 }
893 }
894 }
895
896 void MutatrisWindow::drawGrid(int gridIndex) {
897 const GameGrid &focusGrid = game->grid[static_cast<std::size_t>(gridIndex)];
898 const BoardLayout layout = boardLayout(gridIndex);
899 drawGridFrame(layout, gridIndex == focus);
900 for (int y = 0; y < focusGrid.height(); ++y) {
901 for (int x = 0; x < focusGrid.width(); ++x) {
902 const Block *block = focusGrid.at(x, y);
903 if (block != nullptr) {
904 drawCell(layout, x, y, block->color);
905 }
906 }
907 }
908 if (gridIndex == focus) {
909 const Piece &piece = focusGrid.gamePiece;
910 for (int i = 0; i < 3; ++i) {
911 int x = piece.getX();
912 int y = piece.getY();
913 if (piece.getDirection() == 0) {
914 y += i;
915 } else if (piece.getDirection() == 1) {
916 x += i;
917 } else if (piece.getDirection() == 2) {
918 x -= i;
919 } else if (piece.getDirection() == 3) {
920 y -= i;
921 }
922 const Block *block = piece.at(i);
923 if (block != nullptr) {
924 drawCell(layout, x, y, block->color);
925 }
926 }
927 }
928 }
929
930 MutatrisWindow::BoardLayout MutatrisWindow::boardLayout(int gridIndex) const {
931 if (gridIndex == 0) {
932 return {CENTER_GRID_X, TOP_GRID_Y, gridIndex};
933 }
934 if (gridIndex == 1) {
935 return {0, SIDE_GRID_Y, gridIndex};
936 }
937 if (gridIndex == 2) {
938 return {CENTER_GRID_X, BOTTOM_GRID_Y, gridIndex};
939 }
940 return {RIGHT_GRID_X, SIDE_GRID_Y, gridIndex};
941 }
942
943 void MutatrisWindow::drawGridFrame(const MutatrisWindow::BoardLayout &layout, bool selected) {
944 const bool sideGrid = layout.gridIndex == 1 || layout.gridIndex == 3;
945 int rows = TOP_GRID_HEIGHT;
946 if (sideGrid) {
947 rows = SIDE_GRID_HEIGHT;
948 } else if (layout.gridIndex == 2) {
949 rows = BOTTOM_GRID_HEIGHT;
950 }
951 const int columns = sideGrid ? SIDE_GRID_WIDTH : GRID_WIDTH;
952 const int boardW = sideGrid ? rows * BLOCK_HEIGHT : GRID_WIDTH * BLOCK_WIDTH;
953 const int boardH = sideGrid ? columns * BLOCK_WIDTH : rows * BLOCK_HEIGHT;
954 const int thickness = selected ? 4 : 2;
955 mxvk::VK_Sprite *pixel = blocks[0];
956 const auto drawHorizontal = [&](int x, int y, int w) {
957 pixel->drawSpriteRect(scaleX(x), scaleY(y), scaleX(w), scaleY(thickness));
958 };
959 const auto drawVertical = [&](int x, int y, int h) {
960 pixel->drawSpriteRect(scaleX(x), scaleY(y), scaleX(thickness), scaleY(h));
961 };
962
963 if (layout.gridIndex == 0) {
964 drawHorizontal(layout.x - thickness, layout.y - thickness, boardW + thickness * 2);
965 drawVertical(layout.x - thickness, layout.y, SIDE_GRID_Y - layout.y);
966 drawVertical(layout.x + boardW, layout.y, SIDE_GRID_Y - layout.y);
967 return;
968 }
969
970 if (layout.gridIndex == 1) {
971 drawHorizontal(layout.x - thickness, layout.y - thickness, boardW + thickness);
972 drawHorizontal(layout.x - thickness, layout.y + boardH, boardW + thickness);
973 drawVertical(layout.x - thickness, layout.y, boardH);
974 return;
975 }
976
977 if (layout.gridIndex == 2) {
978 const int exposedY = SIDE_GRID_Y + SIDE_GRID_WIDTH * BLOCK_WIDTH;
979 const int exposedH = layout.y + boardH - exposedY;
980 drawHorizontal(layout.x - thickness, layout.y + boardH, boardW + thickness * 2);
981 drawVertical(layout.x - thickness, exposedY, exposedH);
982 drawVertical(layout.x + boardW, exposedY, exposedH);
983 return;
984 }
985
986 drawHorizontal(layout.x, layout.y - thickness, boardW + thickness);
987 drawHorizontal(layout.x, layout.y + boardH, boardW + thickness);
988 drawVertical(layout.x + boardW, layout.y, boardH);
989 }
990
991 void MutatrisWindow::drawCell(const MutatrisWindow::BoardLayout &layout, int cellX, int cellY, int color) {
992 if (cellX < 0 || cellY < 0) {
993 return;
994 }
995 const int spriteIndex = color < 0 ? 1 + (std::abs(color) % 6) : std::clamp(color, 0, 7);
996 mxvk::VK_Sprite *sprite = blocks[static_cast<std::size_t>(spriteIndex)];
997 if (sprite == nullptr) {
998 return;
999 }
1000 int px = 0;
1001 int py = 0;
1002 int pw = BLOCK_DRAW_WIDTH;
1003 int ph = BLOCK_DRAW_HEIGHT;
1004 if (layout.gridIndex == 0) {
1005 px = layout.x + cellX * BLOCK_WIDTH;
1006 py = layout.y + cellY * BLOCK_HEIGHT;
1007 } else if (layout.gridIndex == 1) {
1008 px = layout.x + cellY * BLOCK_HEIGHT;
1009 py = layout.y + cellX * BLOCK_WIDTH;
1010 pw = BLOCK_DRAW_HEIGHT;
1011 ph = BLOCK_DRAW_WIDTH;
1012 } else if (layout.gridIndex == 2) {
1013 px = layout.x + cellX * BLOCK_WIDTH;
1014 py = layout.y + (BOTTOM_GRID_HEIGHT - 1 - cellY) * BLOCK_HEIGHT;
1015 } else {
1016 px = layout.x + (SIDE_GRID_HEIGHT - 1 - cellY) * BLOCK_HEIGHT;
1017 py = layout.y + (SIDE_GRID_WIDTH - 1 - cellX) * BLOCK_WIDTH;
1018 pw = BLOCK_DRAW_HEIGHT;
1019 ph = BLOCK_DRAW_WIDTH;
1020 }
1021 const int scaledWidth = scaleX(pw);
1022 const int scaledHeight = scaleY(ph);
1023 if (color < 0) {
1024 const int clearFrame = std::clamp(std::abs(color) - 1, 0, CLEAR_ANIMATION_FRAMES - 1);
1025 const float rotation = static_cast<float>(clearFrame) * (360.0f / static_cast<float>(CLEAR_ANIMATION_FRAMES));
1026 const float spriteScaleX = static_cast<float>(scaledWidth) / static_cast<float>(sprite->getWidth());
1027 const float spriteScaleY = static_cast<float>(scaledHeight) / static_cast<float>(sprite->getHeight());
1028 sprite->drawSprite(scaleX(px), scaleY(py), spriteScaleX, spriteScaleY, rotation);
1029 } else {
1030 sprite->drawSpriteRect(scaleX(px), scaleY(py), scaledWidth, scaledHeight);
1031 }
1032 }
1033
1034 float MutatrisWindow::layoutScale() const {
1035 return std::max(0.2f, std::min(static_cast<float>(width) / static_cast<float>(DESIGN_WIDTH), static_cast<float>(height) / static_cast<float>(DESIGN_HEIGHT)));
1036 }
1037
1038 void MutatrisWindow::ensureUiFont() {
1039 const int scaledFontSize = std::max(1, static_cast<int>(std::lround(24.0f * layoutScale())));
1040 if (scaledFontSize == uiFontSize) {
1041 return;
1042 }
1043 uiFontSize = scaledFontSize;
1044 setFont(fontPath, uiFontSize);
1045 }
1046
1047 void MutatrisWindow::ensureIntroFonts() {
1048 const float scale = layoutScale();
1049 const int scaledBaseSize = std::max(1, static_cast<int>(std::lround(54.0f * scale)));
1050 if (scaledBaseSize == introFontSize) {
1051 return;
1052 }
1053 introFontSize = scaledBaseSize;
1054 resetScaledFont(titleFont, 44, scale);
1055 resetScaledFont(titleLargeFont, 220, scale);
1056 resetScaledFont(startMediumFont, 36, scale);
1057 resetScaledFont(startSmallFont, 18, scale);
1058 resetScaledFont(difficultyFont, 30, scale);
1059 }
1060
1061 void MutatrisWindow::resetScaledFont(mxvk::Font &font, int designSize, float scale) {
1062 font.reset(fontPath, std::max(1, static_cast<int>(std::lround(static_cast<float>(designSize) * scale))));
1063 }
1064
1065 void MutatrisWindow::drawTextCentered(const std::string &text, int y, SDL_Color color) {
1066 int textWidth = 0;
1067 int textHeight = 0;
1068 if (!getTextDimensions(text, textWidth, textHeight)) {
1069 textWidth = static_cast<int>(text.size()) * 14;
1070 }
1071 printScaledText(text, (DESIGN_WIDTH - textWidth) / 2, y, color);
1072 }
1073
1074 void MutatrisWindow::drawTextCenteredInRect(const std::string &text, int x, int y, int w, int h, SDL_Color color, const mxvk::Font &font) {
1075 int textWidth = 0;
1076 int textHeight = 0;
1077 if (!getTextDimensions(text, textWidth, textHeight, font)) {
1078 textWidth = static_cast<int>(text.size()) * scaleX(10);
1079 textHeight = scaleY(18);
1080 }
1081
1082 const int rectX = scaleX(x);
1083 const int rectY = scaleY(y);
1084 const int rectW = scaleX(w);
1085 const int rectH = scaleY(h);
1086 const int textX = rectX + ((rectW - textWidth) / 2);
1087 const int textY = rectY + ((rectH - textHeight) / 2);
1088 printText(text, textX, textY, color, font);
1089 }
1090
1091 void MutatrisWindow::printScaledText(const std::string &text, int x, int y, SDL_Color color) {
1092 printText(text, scaleX(x), scaleY(y), color);
1093 }
1094
1095 void MutatrisWindow::printScaledText(const std::string &text, int x, int y, SDL_Color color, const mxvk::Font &font) {
1096 printText(text, scaleX(x), scaleY(y), color, font);
1097 }
1098
1099 int MutatrisWindow::scaleX(int value) const {
1100 return static_cast<int>(static_cast<float>(value) * static_cast<float>(width) / static_cast<float>(DESIGN_WIDTH));
1101 }
1102
1103 int MutatrisWindow::scaleY(int value) const {
1104 return static_cast<int>(static_cast<float>(value) * static_cast<float>(height) / static_cast<float>(DESIGN_HEIGHT));
1105 }
1106
1107} // namespace mutatris
void event(SDL_Event &e) override
Handle one SDL event.
MutatrisWindow(const std::string &path, int width, int height, bool fullscreen, bool enableVsync, bool enableCrt)
void proc() override
Execute one processing/update step.
void reset()
Definition mxvk_text.cpp:35
static int joysticks()
Return the number of connected joysticks/controllers.
int getHeight() const
void setShaderParams(float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Set up to four custom shader float parameters.
int getWidth() const
void drawSprite(int x, int y)
Queue a draw at the given pixel position.
void setFragmentShaderPath(const std::string &path)
Replace the fragment shader path and rebuild the custom pipeline.
void drawSpriteRect(int x, int y, int w, int h)
Queue a draw into an explicit destination rectangle.
Main Vulkan window wrapper for MXVK.
Definition mxvk.hpp:37
VkExtent2D getSwapchainExtent() const noexcept
Get the current swapchain extent.
Definition mxvk.hpp:186
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
void setClearColor(float r, float g, float b, float a=1.0f)
Set the per-frame color attachment clear color.
Definition mxvk.cpp:593
void exit()
Request loop termination.
Definition mxvk.cpp:1126
VK_Sprite * attachPostProcessingShader(const std::string &fragmentShaderPath, float p1=0.0f, float p2=0.0f, float p3=0.0f, float p4=0.0f)
Attach a full-screen post-processing fragment shader.
Definition mxvk.cpp:1154
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 setPostProcessingEnabled(bool enabled)
Definition mxvk.hpp:284
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
void setPostProcessingShaderTimeEnabled(bool enabled)
Keep shader param 1 updated with elapsed render time in seconds.
Definition mxvk.cpp:1248
#define mutatris_ASSET_DIR
#define MXVK_VALIDATION
Definition mxvk.hpp:27
constexpr int GRID_WIDTH
Definition common.hpp:16
constexpr Uint32 STARTUP_FADE_MS
Definition common.hpp:26
constexpr int BLOCK_HEIGHT
Definition common.hpp:11
constexpr int SIDE_GRID_Y
Definition common.hpp:24
constexpr int SIDE_GRID_WIDTH
Definition common.hpp:19
constexpr int CENTER_GRID_X
Definition common.hpp:21
constexpr Uint32 KEY_REPEAT_MS
Definition common.hpp:28
constexpr int BLOCK_DRAW_WIDTH
Definition common.hpp:12
constexpr int BOTTOM_GRID_Y
Definition common.hpp:23
constexpr Uint32 CLEAR_ANIMATION_MS
Definition common.hpp:29
constexpr int TOP_GRID_Y
Definition common.hpp:22
constexpr int SIDE_GRID_HEIGHT
Definition common.hpp:20
constexpr int CLEAR_ANIMATION_FRAMES
Definition common.hpp:30
constexpr int BOTTOM_GRID_HEIGHT
Definition common.hpp:18
constexpr int DESIGN_WIDTH
Definition common.hpp:8
constexpr int DESIGN_HEIGHT
Definition common.hpp:9
constexpr int TOP_GRID_HEIGHT
Definition common.hpp:17
constexpr int RIGHT_GRID_X
Definition common.hpp:25
constexpr Uint32 STARTUP_HOLD_MS
Definition common.hpp:27
constexpr int BLOCK_DRAW_HEIGHT
Definition common.hpp:13
constexpr int BLOCK_WIDTH
Definition common.hpp:10
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30