118 SpaceRoxWindow(
const std::string &path,
int wx,
int wy,
bool full,
bool enable_vsync) :
mxvk::
VK_Window(
"-[ SpaceRox - MXVK ]-", wx, wy, full,
MXVK_VALIDATION, enable_vsync), current_path((path.empty() || path ==
".") ? std::string(
asteroids_ASSET_DIR) : path), w(wx), h(wy) { srand(
static_cast<unsigned>(time(
nullptr))); }
120 if (gamepad !=
nullptr) {
121 SDL_CloseGamepad(gamepad);
127 initializeResources();
128 if (!resources_initialized || pixel ==
nullptr || starSprite ==
nullptr) {
134 Uint32 currentTime = SDL_GetTicks();
135 Uint32 frameTime = currentTime - lastFrameTime;
136 if (frameTime < targetFrameTime) {
137 SDL_Delay(targetFrameTime - frameTime);
138 currentTime = SDL_GetTicks();
139 frameTime = currentTime - lastFrameTime;
142 if (lastFrameTime > 0) {
143 float deltaTime = frameTime / 1000.0f;
144 globalTime += deltaTime;
146 lastFrameTime = currentTime;
152 const bool *keys = SDL_GetKeyboardState(
nullptr);
165 if ((keys[SDL_SCANCODE_SPACE] || joyFire) && canFire()) {
166 fireProjectile(ship.x, ship.y, ship.angle);
169 updateFireTimer(keys);
173 checkAsteroidCollisions();
174 checkShipAsteroidCollision();
175 checkProjectileAsteroidCollisions();
176 checkAndSpawnAsteroids();
177 handleDeathSequence();
191 case SDL_EVENT_KEY_DOWN:
192 if (e.key.key == SDLK_ESCAPE) {
199 if (e.key.key == SDLK_LEFT)
201 if (e.key.key == SDLK_RIGHT)
203 if (e.key.key == SDLK_UP)
207 case SDL_EVENT_KEY_UP:
209 if (e.key.key == SDLK_LEFT)
211 if (e.key.key == SDLK_RIGHT)
213 if (e.key.key == SDLK_UP)
217 case SDL_EVENT_GAMEPAD_ADDED:
218 openGamepad(e.gdevice.which);
222 case SDL_EVENT_GAMEPAD_REMOVED:
223 if (gamepad !=
nullptr && e.gdevice.which == gamepadId) {
224 SDL_CloseGamepad(gamepad);
227 joyLeft = joyRight = joyThrust = joyFire =
false;
233 case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
236 case SDL_EVENT_GAMEPAD_BUTTON_UP:
245 if (button == SDL_GAMEPAD_BUTTON_SOUTH || button == SDL_GAMEPAD_BUTTON_START) {
250 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
252 }
else if (button == SDL_GAMEPAD_BUTTON_EAST || button == SDL_GAMEPAD_BUTTON_BACK) {
254 }
else if (button == SDL_GAMEPAD_BUTTON_START) {
264 if (button == SDL_GAMEPAD_BUTTON_SOUTH) {
271 if (gamepad ==
nullptr) {
272 joyLeft = joyRight = joyThrust = joyFire =
false;
277 if (SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_START)) {
283 joyLeft = joyRight = joyThrust = joyFire =
false;
287 Sint16 rawLX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
288 Sint16 rawLY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
290 if (abs(rawLX) < JOY_RECENTER_ZONE && abs(rawLY) < JOY_RECENTER_ZONE) {
291 if (joyCenterStable < JOY_RECENTER_FRAMES) {
294 if (joyCenterStable >= JOY_RECENTER_FRAMES) {
302 int lx =
static_cast<int>(rawLX) -
static_cast<int>(joyRestLX);
303 int ly =
static_cast<int>(rawLY) -
static_cast<int>(joyRestLY);
304 joyLeft = (lx < -JOY_ROTATE_ZONE);
305 joyRight = (lx > JOY_ROTATE_ZONE);
306 joyThrust = (ly < -JOY_THRUST_ZONE);
307 Sint16 rt = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
308 joyFire = (rt > JOY_THRUST_ZONE) || SDL_GetGamepadButton(gamepad, SDL_GAMEPAD_BUTTON_SOUTH);
312 std::string current_path;
313 bool resources_initialized =
false;
320 Uint32 lastFrameTime = 0;
321 static constexpr Uint32 targetFrameTime = 1000 / 60;
322 float globalTime = 0.0f;
329 bool starsInit =
false;
332 bool keyLeft =
false, keyRight =
false, keyThrust =
false;
333 bool keyFire =
false;
334 bool joyLeft =
false, joyRight =
false, joyThrust =
false;
335 bool joyFire =
false;
336 bool wasExploding =
false;
338 SDL_Gamepad *gamepad =
nullptr;
339 SDL_JoystickID gamepadId = 0;
340 static constexpr Sint16 JOY_ROTATE_ZONE = 16000;
341 static constexpr Sint16 JOY_THRUST_ZONE = 12000;
342 static constexpr Sint16 JOY_RECENTER_ZONE = 8000;
343 static constexpr int JOY_RECENTER_FRAMES = 12;
344 Sint16 joyRestLX = 0, joyRestLY = 0;
345 int joyCenterStable = 0;
347 Uint32 countdownTimer = 0;
348 Uint32 countdownDuration = 1000;
349 Uint32 countdownStartMs = 0;
350 int countdownNumber = 3;
351 Uint32 launchTimer = 0;
352 Uint32 launchDuration = 1000;
353 Uint32 launchStartMs = 0;
354 float shipLaunchY = 0;
356 int lastFontSize = 0;
358 float sx()
const {
return static_cast<float>(w) /
GAME_W; }
359 float sy()
const {
return static_cast<float>(h) /
GAME_H; }
361 int toScreenX(
float gx)
const {
return static_cast<int>(gx * sx()); }
362 int toScreenY(
float gy)
const {
return static_cast<int>(gy * sy()); }
363 int toScreenW(
float gw)
const {
return std::max(1,
static_cast<int>(gw * sx())); }
364 int toScreenH(
float gh)
const {
return std::max(1,
static_cast<int>(gh * sy())); }
366 void refreshWindowSize() {
368 SDL_GetWindowSizeInPixels(
window.get(), &w, &h);
372 bool openGamepad(SDL_JoystickID
id) {
373 if (gamepad !=
nullptr && gamepadId ==
id) {
376 if (gamepad !=
nullptr) {
377 SDL_CloseGamepad(gamepad);
381 gamepad = SDL_OpenGamepad(
id);
382 if (gamepad ==
nullptr) {
389 void tryOpenFirstGamepad() {
390 if (gamepad !=
nullptr) {
394 SDL_JoystickID *ids = SDL_GetGamepads(&count);
395 if (ids ==
nullptr || count <= 0) {
396 if (ids !=
nullptr) {
405 void initializeResources() {
406 if (resources_initialized) {
410 tryOpenFirstGamepad();
412 std::array<std::uint8_t, 4 * 4 * 4> starPixels{};
413 for (
int y = 0; y < 4; y++) {
414 for (
int x = 0; x < 4; x++) {
415 float dx = (x - 1.5f) / 1.5f;
416 float dy = (y - 1.5f) / 1.5f;
417 float dist = sqrtf(dx * dx + dy * dy);
418 std::uint8_t alpha = (dist < 1.0f) ?
static_cast<std::uint8_t
>(255.0f * (1.0f - dist)) : 0;
419 const int idx = (y * 4 + x) * 4;
420 starPixels[idx + 0] = 255;
421 starPixels[idx + 1] = 255;
422 starPixels[idx + 2] = 255;
423 starPixels[idx + 3] = alpha;
427 starSprite =
createSprite(4, 4, current_path +
"/data/sprite_vert.spv", current_path +
"/data/sprite_frag.spv");
428 starSprite->updateTexture(starPixels.data(), 4, 4, 4 * 4);
430 const std::array<std::uint8_t, 2 * 2 * 4> whitePixels{
449 pixel =
createSprite(2, 2, current_path +
"/data/sprite_vert.spv", current_path +
"/data/solid_frag.spv");
450 pixel->updateTexture(whitePixels.data(), 2, 2, 2 * 4);
453 resources_initialized =
true;
456 void setColor(
float r,
float g,
float b,
float a = 1.0f) { pixel->setShaderParams(r, g, b, a); }
458 void drawPixel(
float gx,
float gy) {
459 int psz = std::max(1,
static_cast<int>(2.0f * std::min(sx(), sy())));
460 pixel->drawSpriteRect(toScreenX(gx), toScreenY(gy), psz, psz);
463 void drawRect(
float gx,
float gy,
float gw,
float gh) { pixel->drawSpriteRect(toScreenX(gx), toScreenY(gy), toScreenW(gw), toScreenH(gh)); }
465 void drawLine(
float x0f,
float y0f,
float x1f,
float y1f) {
466 int x0 = toScreenX(x0f), y0 = toScreenY(y0f);
467 int x1 = toScreenX(x1f), y1 = toScreenY(y1f);
468 int psz = std::max(1,
static_cast<int>(1.5f * std::min(sx(), sy())));
469 bresenhamLine(x0, y0, x1, y1, [&](
int px,
int py) { pixel->drawSpriteRect(px, py, psz, psz); });
472 void drawPoint(
int screenX,
int screenY,
int size = 2) { pixel->drawSpriteRect(screenX, screenY, size, size); }
474 void updateFontSize() {
475 int fontSize =
static_cast<int>(20.0f * (
static_cast<float>(h) / 480.0f));
476 fontSize = std::max(14, std::min(128, fontSize));
477 if (fontSize != lastFontSize) {
478 lastFontSize = fontSize;
479 setFont(current_path +
"/data/font.ttf", fontSize);
492 countdownStartMs = SDL_GetTicks();
494 wasExploding =
false;
497 void restartGame() { initGame(); }
502 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
503 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
504 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
505 stars[starIndex].speed = 0.15f + (rand() % 40) / 100.0f;
506 stars[starIndex].size = 0.8f + (rand() % 12) / 20.0f;
507 stars[starIndex].brightness = 0.25f + (rand() % 45) / 100.0f;
508 stars[starIndex].layer = 0;
509 assignStarColor(stars[starIndex]);
510 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
511 stars[starIndex].twinkleSpeed = 0.4f + (rand() % 150) / 100.0f;
514 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
515 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
516 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
517 stars[starIndex].speed = 0.4f + (rand() % 80) / 100.0f;
518 stars[starIndex].size = 1.2f + (rand() % 18) / 20.0f;
519 stars[starIndex].brightness = 0.45f + (rand() % 55) / 100.0f;
520 stars[starIndex].layer = 1;
521 assignStarColor(stars[starIndex]);
522 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
523 stars[starIndex].twinkleSpeed = 0.8f + (rand() % 250) / 100.0f;
526 for (
int i = 0; i <
STAR_COUNT; ++i, ++starIndex) {
527 stars[starIndex].x =
static_cast<float>(rand() %
GAME_W);
528 stars[starIndex].y =
static_cast<float>(rand() %
GAME_H);
529 stars[starIndex].speed = 0.8f + (rand() % 180) / 100.0f;
530 stars[starIndex].size = 1.8f + (rand() % 25) / 20.0f;
531 stars[starIndex].brightness = 0.65f + (rand() % 35) / 100.0f;
532 stars[starIndex].layer = 2;
533 assignStarColor(stars[starIndex]);
534 stars[starIndex].twinklePhase = (rand() % 628) / 100.0f;
535 stars[starIndex].twinkleSpeed = 1.2f + (rand() % 350) / 100.0f;
540 void assignStarColor(Star &s) {
541 float roll = (rand() % 1000) / 1000.0f;
544 s.
r = 0.85f + (rand() % 15) / 100.0f;
545 s.
g = 0.90f + (rand() % 10) / 100.0f;
547 }
else if (roll < 0.55f) {
549 s.
r = 0.5f + (rand() % 20) / 100.0f;
550 s.
g = 0.75f + (rand() % 15) / 100.0f;
554 }
else if (roll < 0.70f) {
557 s.
g = 0.95f + (rand() % 5) / 100.0f;
558 s.
b = 0.6f + (rand() % 30) / 100.0f;
560 }
else if (roll < 0.82f) {
563 s.
g = 0.6f + (rand() % 20) / 100.0f;
564 s.
b = 0.2f + (rand() % 20) / 100.0f;
566 }
else if (roll < 0.90f) {
569 s.
g = 0.3f + (rand() % 20) / 100.0f;
570 s.
b = 0.2f + (rand() % 15) / 100.0f;
572 }
else if (roll < 0.96f) {
596 ship.fire_cooldown = 0;
597 ship.burst_count = 0;
598 ship.exploding =
false;
599 ship.explosion_timer = 0;
601 ship.continuous_fire_timer = 0;
602 ship.overheated =
false;
603 ship.overheat_cooldown = 0;
604 for (
auto &p : particles)
608 void initProjectiles() {
609 for (
auto &p : projectiles)
613 void initAsteroids() {
614 for (
auto &a : asteroids)
616 for (
int i = 0; i < 4; ++i) {
619 x =
static_cast<float>(rand() %
GAME_W);
620 y =
static_cast<float>(rand() %
GAME_H);
621 }
while (hypotf(x - ship.x, y - ship.y) < 100);
622 float vx = ((rand() % 100) - 50) / 50.0f;
623 float vy = ((rand() % 100) - 50) / 50.0f;
624 float radius = 20.0f + (rand() % 20);
625 spawnAsteroid(x, y, vx, vy, radius);
632 if (keyLeft || joyLeft)
634 if (keyRight || joyRight)
636 if (keyThrust || joyThrust) {
638 ship.vx += sinf(ship.angle) * thrust;
639 ship.vy += -cosf(ship.angle) * thrust;
640 float spd = sqrtf(ship.vx * ship.vx + ship.vy * ship.vy);
643 ship.vx = (ship.vx / spd) * maxSpd;
644 ship.vy = (ship.vy / spd) * maxSpd;
660 if (ship.fire_cooldown > 0)
661 ship.fire_cooldown--;
664 void updateFireTimer(
const bool *keys) {
665 bool firing = keys[SDL_SCANCODE_SPACE] || joyFire;
666 if (firing && !ship.overheated) {
667 ship.continuous_fire_timer++;
668 ship.overheat_cooldown = 0;
669 if (ship.continuous_fire_timer >= 180) {
670 ship.overheated =
true;
671 ship.overheat_cooldown = 0;
672 ship.continuous_fire_timer = 0;
673 ship.burst_count = 0;
675 }
else if (firing && ship.overheated) {
676 ship.overheat_cooldown = 0;
678 if (ship.overheated) {
679 ship.overheat_cooldown++;
680 if (ship.overheat_cooldown >= 180) {
681 ship.overheated =
false;
682 ship.overheat_cooldown = 0;
683 ship.continuous_fire_timer = 0;
686 if (ship.continuous_fire_timer > 0)
687 ship.continuous_fire_timer--;
695 if (ship.fire_cooldown <= 0) {
702 ship.burst_count = 0;
709 void fireProjectile(
float x,
float y,
float angle) {
710 for (
auto &p : projectiles) {
712 float c = cosf(angle), s = sinf(angle);
713 float lx = 0, ly = -12;
714 p.x = x + (lx * c - ly * s);
715 p.y = y + (lx * s + ly * c);
725 void updateProjectiles() {
726 for (
auto &p : projectiles) {
731 if (p.x < 0 || p.x >=
GAME_W || p.y < 0 || p.y >=
GAME_H) {
741 void spawnAsteroid(
float x,
float y,
float vx,
float vy,
float radius) {
742 for (
auto &a : asteroids) {
750 a.rotation_angle = 0;
751 a.rotation_speed = ((rand() % 100) - 50) / 1000.0f;
754 float vr = radius * (0.7f + (rand() % 30) / 100.0f);
755 a.vertices[j][0] = cosf(ang) * vr;
756 a.vertices[j][1] = sinf(ang) * vr;
763 void updateAsteroids() {
764 for (
auto &a : asteroids) {
769 a.rotation_angle += a.rotation_speed;
781 void splitAsteroid(
int idx) {
782 float x = asteroids[idx].x;
783 float y = asteroids[idx].y;
784 float r = asteroids[idx].radius / 2.0f;
786 createAsteroidExplosion(x, y);
788 asteroids[idx].active =
false;
791 int splits = 2 + (rand() % 2);
792 for (
int i = 0; i < splits; ++i) {
793 float ang = (rand() % 628) / 100.0f;
794 float spd = 0.5f + (rand() % 15) / 10.0f;
795 float nvx = cosf(ang) * spd + asteroids[idx].vx * 0.3f;
796 float nvy = sinf(ang) * spd + asteroids[idx].vy * 0.3f;
797 float off = r * 0.5f;
798 spawnAsteroid(x + cosf(ang) * off, y + sinf(ang) * off, nvx, nvy, r);
804 createAsteroidExplosion(x, y);
805 asteroids[idx].active =
false;
808 void checkAndSpawnAsteroids() {
810 for (
auto &a : asteroids)
813 int minAsteroids = 3;
814 if (
active < minAsteroids) {
815 for (
int s = 0; s < minAsteroids -
active; ++s) {
820 x = (rand() % 2) ? -30.0f :
GAME_W + 30.0f;
821 y =
static_cast<float>(rand() %
GAME_H);
823 x =
static_cast<float>(rand() %
GAME_W);
824 y = (rand() % 2) ? -30.0f :
GAME_H + 30.0f;
827 }
while (hypotf(x - ship.x, y - ship.y) < 100.0f && attempts < 10);
828 float vx = ((rand() % 100) - 50) / 50.0f;
829 float vy = ((rand() % 100) - 50) / 50.0f;
831 int roll = rand() % 100;
833 radius = 30.0f + (rand() % 15);
835 radius = 20.0f + (rand() % 10);
837 radius = 12.0f + (rand() % 8);
838 spawnAsteroid(x, y, vx, vy, radius);
843 void checkAsteroidCollisions() {
850 float dx = asteroids[i].x - asteroids[j].x;
851 float dy = asteroids[i].y - asteroids[j].y;
852 float dist = sqrtf(dx * dx + dy * dy);
853 float minDist = asteroids[i].radius + asteroids[j].radius;
854 if (dist < minDist && dist > 0) {
855 float nx = dx / dist, ny = dy / dist;
856 float overlap = (minDist - dist) * 0.5f;
857 asteroids[i].x += nx * overlap;
858 asteroids[i].y += ny * overlap;
859 asteroids[j].x -= nx * overlap;
860 asteroids[j].y -= ny * overlap;
861 float rvx = asteroids[i].vx - asteroids[j].vx;
862 float rvy = asteroids[i].vy - asteroids[j].vy;
863 float van = rvx * nx + rvy * ny;
866 float restitution = 0.8f;
867 float impulse = -(1 + restitution) * van * 0.5f;
868 asteroids[i].vx += impulse * nx;
869 asteroids[i].vy += impulse * ny;
870 asteroids[j].vx -= impulse * nx;
871 asteroids[j].vy -= impulse * ny;
877 void checkShipAsteroidCollision() {
883 float dx = ship.x - asteroids[i].x;
884 float dy = ship.y - asteroids[i].y;
885 float dist = sqrtf(dx * dx + dy * dy);
886 float shipRadius = 8.0f;
887 if (dist < asteroids[i].radius + shipRadius) {
888 startShipExplosion();
889 createAsteroidExplosion(ship.x + dx * 0.5f, ship.y + dy * 0.5f);
891 float nx = -dx / dist, ny = -dy / dist;
892 asteroids[i].vx += nx * 2.0f;
893 asteroids[i].vy += ny * 2.0f;
900 void checkProjectileAsteroidCollisions() {
901 for (
auto &p : projectiles) {
907 float dx = p.x - asteroids[j].x;
908 float dy = p.y - asteroids[j].y;
909 if (sqrtf(dx * dx + dy * dy) < asteroids[j].radius) {
911 if (asteroids[j].radius >= 20.0f)
924 void startShipExplosion() {
927 ship.exploding =
true;
930 ship.overheated =
false;
931 ship.overheat_cooldown = 0;
932 ship.continuous_fire_timer = 0;
933 ship.burst_count = 0;
934 for (
auto &p : particles) {
935 p.x = ship.x + ((rand() % 20) - 10);
936 p.y = ship.y + ((rand() % 20) - 10);
937 float ang = (rand() % 628) / 100.0f;
938 float spd = 0.5f + (rand() % 20) / 10.0f;
939 p.vx = cosf(ang) * spd;
940 p.vy = sinf(ang) * spd;
941 p.lifetime = 30 + (rand() % 60);
946 void createAsteroidExplosion(
float x,
float y) {
947 int toCreate = 15 + (rand() % 10);
949 for (
auto &p : particles) {
950 if (created >= toCreate)
953 p.x = x + ((rand() % 16) - 8);
954 p.y = y + ((rand() % 16) - 8);
955 float ang = (rand() % 628) / 100.0f;
956 float spd = 1.0f + (rand() % 20) / 10.0f;
957 p.vx = cosf(ang) * spd;
958 p.vy = sinf(ang) * spd;
959 p.lifetime = 15 + (rand() % 25);
966 void updateExplosion() {
967 for (
auto &p : particles) {
975 if (p.lifetime <= 0) {
988 if (ship.exploding) {
989 ship.explosion_timer--;
990 if (ship.explosion_timer <= 0)
991 ship.exploding =
false;
995 void handleDeathSequence() {
996 if (wasExploding && !ship.exploding) {
997 if (ship.lives > 0) {
1002 wasExploding =
false;
1003 }
else if (ship.exploding) {
1004 wasExploding =
true;
1008 void triggerRespawn() {
1014 ship.exploding =
false;
1015 ship.explosion_timer = 0;
1016 keyLeft = keyRight = keyThrust = keyFire =
false;
1017 joyLeft = joyRight = joyThrust = joyFire =
false;
1020 countdownNumber = 3;
1021 countdownStartMs = SDL_GetTicks();
1025 void updateCountdown() {
1026 const Uint32 now = SDL_GetTicks();
1027 const Uint32 elapsed = now - countdownStartMs;
1029 countdownTimer = elapsed % countdownDuration;
1030 countdownNumber = 3 -
static_cast<int>(elapsed / countdownDuration);
1032 if (elapsed >= (countdownDuration * 4U)) {
1035 launchStartMs = now;
1036 shipLaunchY =
static_cast<float>(
GAME_H);
1040 void updateLaunch() {
1041 const Uint32 now = SDL_GetTicks();
1042 launchTimer = std::min<Uint32>(now - launchStartMs, launchDuration);
1043 if (launchTimer < launchDuration / 2U) {
1044 shipLaunchY =
static_cast<float>(
GAME_H) - (
static_cast<float>(launchTimer) * (
static_cast<float>(
GAME_H) - ship.y) /
static_cast<float>(launchDuration / 2U));
1046 shipLaunchY = ship.y;
1048 if (launchTimer >= launchDuration) {
1050 if (gamepad !=
nullptr) {
1051 joyRestLX = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1052 joyRestLY = SDL_GetGamepadAxis(gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1057 void drawStars(
float speedMul = 1.0f) {
1059 for (
auto &s : stars) {
1060 s.
y += s.
speed * speedMul;
1062 s.
x =
static_cast<float>(rand() %
GAME_W);
1066 s.
speed = 0.15f + (rand() % 40) / 100.0f;
1067 s.
size = 0.8f + (rand() % 12) / 20.0f;
1068 s.
brightness = 0.25f + (rand() % 45) / 100.0f;
1069 }
else if (s.
layer == 1) {
1070 s.
speed = 0.4f + (rand() % 80) / 100.0f;
1071 s.
size = 1.2f + (rand() % 18) / 20.0f;
1072 s.
brightness = 0.45f + (rand() % 55) / 100.0f;
1074 s.
speed = 0.8f + (rand() % 180) / 100.0f;
1075 s.
size = 1.8f + (rand() % 25) / 20.0f;
1076 s.
brightness = 0.65f + (rand() % 35) / 100.0f;
1084 float finalBrightness = s.
brightness * twinkle;
1086 starSprite->setShaderParams(s.
r * finalBrightness, s.
g * finalBrightness, s.
b * finalBrightness, finalBrightness);
1088 int screenSize =
static_cast<int>(s.
size * std::min(sx(), sy()));
1089 screenSize = std::max(2, screenSize);
1090 starSprite->drawSpriteRect(toScreenX(s.
x), toScreenY(s.
y), screenSize, screenSize);
1094 void drawShipAt(
float px,
float py) {
1095 if (ship.exploding) {
1099 float c = cosf(ship.angle), s = sinf(ship.angle);
1100 auto rot = [&](
float lx,
float ly,
float &ox,
float &oy) {
1101 ox = px + (lx * c - ly * s);
1102 oy = py + (lx * s + ly * c);
1105 float nose_x, nose_y, lw_x, lw_y, rw_x, rw_y;
1106 float le_x, le_y, re_x, re_y, ce_x, ce_y;
1107 float ld_x, ld_y, rd_x, rd_y;
1108 float cl_x, cl_y, cr_x, cr_y, cc_x, cc_y;
1110 rot(0, -15, nose_x, nose_y);
1111 rot(-12, 8, lw_x, lw_y);
1112 rot(12, 8, rw_x, rw_y);
1113 rot(-8, 12, le_x, le_y);
1114 rot(8, 12, re_x, re_y);
1115 rot(0, 10, ce_x, ce_y);
1116 rot(-6, 2, ld_x, ld_y);
1117 rot(6, 2, rd_x, rd_y);
1118 rot(-3, -8, cl_x, cl_y);
1119 rot(3, -8, cr_x, cr_y);
1120 rot(0, -5, cc_x, cc_y);
1122 if (ship.overheated) {
1123 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1124 }
else if (ship.continuous_fire_timer > 120) {
1125 int heat = ship.continuous_fire_timer - 120;
1126 setColor(1.0f, std::max(0.0f, 1.0f - heat / 64.0f), 0.0f, 1.0f);
1128 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1131 drawLine(nose_x, nose_y, lw_x, lw_y);
1132 drawLine(nose_x, nose_y, rw_x, rw_y);
1133 drawLine(lw_x, lw_y, rw_x, rw_y);
1135 drawLine(lw_x, lw_y, le_x, le_y);
1136 drawLine(rw_x, rw_y, re_x, re_y);
1137 drawLine(le_x, le_y, ce_x, ce_y);
1138 drawLine(re_x, re_y, ce_x, ce_y);
1140 if (ship.overheated)
1141 setColor(0.78f, 0.0f, 0.0f, 1.0f);
1142 else if (ship.continuous_fire_timer > 120)
1143 setColor(0.78f, std::max(0.0f, 0.78f - (ship.continuous_fire_timer - 120) / 80.0f), 0.0f, 1.0f);
1145 setColor(0.78f, 0.78f, 0.78f, 1.0f);
1146 drawLine(nose_x, nose_y, ld_x, ld_y);
1147 drawLine(nose_x, nose_y, rd_x, rd_y);
1148 drawLine(ld_x, ld_y, rd_x, rd_y);
1149 drawLine(ld_x, ld_y, lw_x, lw_y);
1150 drawLine(rd_x, rd_y, rw_x, rw_y);
1152 if (ship.overheated)
1153 setColor(0.0f, 0.5f, 0.5f, 1.0f);
1155 setColor(0.0f, 1.0f, 1.0f, 1.0f);
1156 drawLine(cl_x, cl_y, cr_x, cr_y);
1157 drawLine(cl_x, cl_y, cc_x, cc_y);
1158 drawLine(cr_x, cr_y, cc_x, cc_y);
1160 setColor(0.59f, 0.59f, 0.59f, 1.0f);
1161 drawLine(cc_x, cc_y, ce_x, ce_y);
1163 if (keyThrust || joyThrust) {
1164 float f1x, f1y, f2x, f2y, f3x, f3y;
1165 rot(-6, 18, f1x, f1y);
1166 rot(0, 22, f2x, f2y);
1167 rot(6, 18, f3x, f3y);
1168 setColor(1.0f, 0.39f, 0.0f, 1.0f);
1169 drawLine(le_x, le_y, f1x, f1y);
1170 drawLine(ce_x, ce_y, f2x, f2y);
1171 drawLine(re_x, re_y, f3x, f3y);
1173 rot(0, 20, ifx, ify);
1174 setColor(1.0f, 1.0f, 0.0f, 1.0f);
1175 drawLine(ce_x, ce_y, ifx, ify);
1179 void drawProjectiles() {
1180 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1181 for (
auto &p : projectiles) {
1184 float endX = p.x - p.vx * 0.5f;
1185 float endY = p.y - p.vy * 0.5f;
1186 drawLine(p.x, p.y, endX, endY);
1190 void drawAsteroids() {
1192 if (!asteroids[i].
active)
1194 auto &a = asteroids[i];
1195 float cosR = cosf(a.rotation_angle);
1196 float sinR = sinf(a.rotation_angle);
1200 rv[j][0] = a.vertices[j][0] * cosR - a.vertices[j][1] * sinR;
1201 rv[j][1] = a.vertices[j][0] * sinR + a.vertices[j][1] * cosR;
1204 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1207 drawLine(a.x + rv[j][0], a.y + rv[j][1], a.x + rv[next][0], a.y + rv[next][1]);
1212 iv[j][0] = rv[j][0] * 0.6f;
1213 iv[j][1] = rv[j][1] * 0.6f;
1215 setColor(0.59f, 0.59f, 0.59f, 1.0f);
1218 drawLine(a.x + iv[j][0], a.y + iv[j][1], a.x + iv[next][0], a.y + iv[next][1]);
1221 setColor(0.39f, 0.39f, 0.39f, 1.0f);
1223 drawLine(a.x + rv[j][0], a.y + rv[j][1], a.x + iv[j][0], a.y + iv[j][1]);
1226 setColor(0.71f, 0.71f, 0.71f, 1.0f);
1229 drawLine(a.x + rv[j][0], a.y + rv[j][1], a.x + iv[next][0], a.y + iv[next][1]);
1234 void drawExplosion() {
1235 for (
auto &p : particles) {
1238 float lp =
static_cast<float>(p.lifetime) / 50.0f;
1240 setColor(1.0f, 1.0f, 1.0f, 1.0f);
1242 setColor(1.0f, 1.0f, 0.0f, 1.0f);
1244 setColor(1.0f, 0.5f, 0.0f, 1.0f);
1246 setColor(1.0f, 0.0f, 0.0f, 1.0f);
1247 drawRect(p.x, p.y, 2, 2);
1252 std::string buf = std::format(
"Score: {}", ship.score);
1253 printText(buf.c_str(), toScreenX(10), toScreenY(10), {255, 255, 255, 255});
1254 buf = std::format(
"Lives: {}", ship.lives);
1255 printText(buf.c_str(), toScreenX(10), toScreenY(30), {255, 255, 255, 255});
1258 void drawCountdown() {
1260 if (countdownNumber > 0) {
1261 std::string buf = std::format(
"{}", countdownNumber);
1266 int centerX = w / 2;
1267 int centerY = h / 2;
1268 int textX = centerX - textW / 2;
1269 int textY = centerY - textH / 2;
1270 printText(buf.c_str(), textX, textY, {255, 255, 0, 255});
1272 if (((countdownTimer / 167U) % 2U) != 0U) {
1273 setColor(1.0f, 1.0f, 0.0f, 0.5f);
1274 int padding = std::max(15,
static_cast<int>(15 * std::min(sx(), sy())));
1275 int rectX = textX - padding;
1276 int rectY = textY - padding;
1277 int rectW = textW + padding * 2;
1278 int rectH = textH + padding * 2;
1279 int thickness = std::max(3,
static_cast<int>(3 * std::min(sx(), sy())));
1281 pixel->drawSpriteRect(rectX, rectY, rectW, thickness);
1283 pixel->drawSpriteRect(rectX, rectY + rectH - thickness, rectW, thickness);
1285 pixel->drawSpriteRect(rectX, rectY, thickness, rectH);
1287 pixel->drawSpriteRect(rectX + rectW - thickness, rectY, thickness, rectH);
1294 printText(
"LAUNCH!", w / 2 - textW / 2, h / 2 - textH / 2, {0, 255, 0, 255});
1296 printText(
"LAUNCH!", toScreenX(270), toScreenY(160), {0, 255, 0, 255});
1302 printText(
"PREPARE FOR MISSION", w / 2 - textW / 2, h / 2 + textH * 2, {255, 255, 255, 255});
1304 printText(
"PREPARE FOR MISSION", toScreenX(220), toScreenY(195), {255, 255, 255, 255});
1307 std::string lbuf = std::format(
"Lives: {}", ship.lives);
1308 std::string sbuf = std::format(
"Score: {}", ship.score);
1310 printText(lbuf.c_str(), w / 2 - textW / 2, h / 2 + textH * 3 + 10, {255, 255, 255, 255});
1312 printText(sbuf.c_str(), w / 2 - textW / 2, h / 2 + textH * 4 + 15, {255, 255, 255, 255});
1315 printText(lbuf.c_str(), toScreenX(260), toScreenY(215), {255, 255, 255, 255});
1316 printText(sbuf.c_str(), toScreenX(260), toScreenY(235), {255, 255, 255, 255});
1322 if (launchTimer > launchDuration / 4)
1325 float tempY = ship.y;
1326 ship.y = shipLaunchY;
1327 drawShipAt(ship.x, shipLaunchY);
1330 if (launchTimer < launchDuration / 2) {
1331 setColor(1.0f, 0.39f, 0.0f, 1.0f);
1332 for (
int i = 0; i < 12; ++i) {
1333 float tx = ship.x + (rand() % 8) - 4;
1334 float ty = shipLaunchY + 15 + i * 2;
1339 if (launchTimer >= launchDuration / 2) {
1342 printText(
"MISSION START!", w / 2 - textW / 2, h / 2 - textH / 2, {0, 255, 255, 255});
1344 printText(
"MISSION START!", toScreenX(240), toScreenY(170), {0, 255, 255, 255});
1351 drawShipAt(ship.x, ship.y);
1358 void drawGameOver() {
1360 printText(
"GAME OVER", toScreenX(255), toScreenY(130), {255, 0, 0, 255});
1362 std::string buf = std::format(
"Final Score: {}", ship.score);
1363 printText(buf.c_str(), toScreenX(235), toScreenY(165), {255, 255, 255, 255});
1364 printText(
"Press SPACE to begin", toScreenX(215), toScreenY(195), {255, 255, 0, 255});