lav-sdl.c (24042B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2023-2025 Strahinya Radich. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <SDL3/SDL.h> 6 #include <SDL3_image/SDL_image.h> 7 #include <errno.h> 8 #include <math.h> 9 #include <stdarg.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 13 #include "local.h" 14 15 #define ANIM_SPEED \ 16 100 /* how long does it take for the animation to go \ 17 * from start to finish (ms) */ 18 #define FPS 60 19 #define ANIM_STEPS (ANIM_SPEED * FPS / 1000) 20 21 #define BUFSIZE 4096 22 #define CELL_SIZE 32 23 #define DEFAULT_WIDTH 1280 24 #define DEFAULT_HEIGHT 720 25 #define HELP_DIALOG_WIDTH 900 26 #define HELP_DIALOG_HEIGHT 450 27 #define LABYRINTH_MAXHEIGHT 20 28 #define LABYRINTH_MAXWIDTH 40 29 #define LOCATION_STACK_MAX (LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT) 30 #define PRINT_BUFSIZE 4096 31 #define PROGNAME "lav-sdl" 32 #define TER12_WIDTH 6 33 #define TER12_HEIGHT 12 34 #define WIN_TITLE "Lavirint (SDL version)" 35 36 #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 37 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 38 39 /* clang-format off */ 40 enum { 41 CELL_EMPTY, 42 CELL_WALL, 43 CELL_FAIL, 44 CELL_OK, 45 CELL_START, 46 CELL_END 47 }; 48 49 enum { 50 STATE_SEARCHING, 51 STATE_SEARCHING_PAUSED, 52 STATE_FAILURE, 53 STATE_SUCCESS 54 }; 55 56 const char* state_descriptions[] = { 57 "Searching for exit", 58 "Searching (paused)", 59 "Failed!", 60 "Success!" 61 }; 62 63 const char* help_text[] = { 64 "lav-sdl - SDL maze solver", 65 "=========================", 66 "", 67 "F1 - Show this help screen", 68 "Esc - Hide this help screen", 69 "Space - Pause/resume simulation", 70 "C-Q - Exit", 71 "", 72 "---", 73 "", 74 "This program is licensed under the terms of GNU GPL v3 or (at your", 75 "option) any later version. Copyright (C) 2023-2025 Strahinya Radich.", 76 "See the file LICENSE for exact copyright and license details.", 77 NULL 78 }; 79 /* clang-format on */ 80 81 struct AnimationState { 82 SDL_FPoint position; 83 SDL_FPoint destination; 84 SDL_FPoint delta; 85 int active; 86 int step; 87 int final_step; 88 }; 89 90 struct Labyrinth { 91 char** cells; 92 int width; 93 int height; 94 }; 95 96 static struct AnimationState animation; 97 static SDL_Point hero, start, end; 98 static struct Labyrinth labyrinth; 99 static int location_pointer = 0; 100 static SDL_Point* location_stack = NULL; 101 static SDL_FPoint pointer; 102 static int display_width = DEFAULT_WIDTH; /* Physical, detected */ 103 static int display_height = DEFAULT_HEIGHT; 104 static int help_shown = 0; 105 static int redraw = 1; 106 static SDL_Renderer* renderer = NULL; 107 static int running = 1; 108 static int screen_width = DEFAULT_WIDTH; /* Logical */ 109 static int screen_height = DEFAULT_HEIGHT; 110 static SDL_Texture* sprites = NULL; 111 static int state = STATE_SEARCHING_PAUSED; 112 static SDL_Texture* ter12_texture = NULL; 113 static SDL_Texture* background = NULL; 114 static SDL_Window* window = NULL; 115 static float text_scale_factor = 2.0f; 116 117 void assign_pointIF(const SDL_Point* from, SDL_FPoint* to); 118 void assign_point_xyIF(const int x, const int y, SDL_FPoint* to); 119 void calculate_delta(void); 120 void calculate_screen_size(void); 121 void cleanup(void); 122 void draw(void); 123 void draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect, 124 SDL_FRect* dest_rect); 125 void draw_help_dialog(void); 126 void draw_text(const int x, const int y, const int font_width, 127 const int font_height, SDL_Texture* font_tex, const char* text, ...); 128 void get_hero_sprite_xy(float* x, float* y); 129 void get_sprite_xy(const char cell, float* x, float* y); 130 void handle_event(SDL_Event* event); 131 void labyrinth_coord_to_screen_coord(const int x, const int y, 132 const float startx, const float starty, float* to_x, float* to_y); 133 void labyrinth_coord_to_screen_coordF(const float x, const float y, 134 const float startx, const float starty, float* to_x, float* to_y); 135 int load_textures(void); 136 int load_labyrinth(const char* pathname); 137 int location_pop(SDL_Point* location); 138 void location_push(const SDL_Point* location); 139 void next_step(void); 140 int passable(const int x, const int y); 141 void print_debug(const char* fmt, ...); 142 void print_error(const char* fmt, ...); 143 void reset(void); 144 void step_animation(void); 145 SDL_Surface* try_load(const char* filename); 146 147 void 148 assign_pointIF(const SDL_Point* from, SDL_FPoint* to) 149 { 150 to->x = (float)from->x; 151 to->y = (float)from->y; 152 } 153 154 void 155 assign_point_xyIF(const int x, const int y, SDL_FPoint* to) 156 { 157 to->x = (float)x; 158 to->y = (float)y; 159 } 160 161 void 162 calculate_delta(void) 163 { 164 animation.delta.x 165 = fabsf(animation.destination.x - animation.position.x) 166 / ANIM_STEPS; 167 if (animation.destination.x < animation.position.x) 168 animation.delta.x *= -1; 169 animation.delta.y 170 = fabsf(animation.destination.y - animation.position.y) 171 / ANIM_STEPS; 172 if (animation.destination.y < animation.position.y) 173 animation.delta.y *= -1; 174 } 175 176 void 177 calculate_screen_size(void) 178 { 179 if (!SDL_GetRenderOutputSize(renderer, &display_width, &display_height)) 180 { 181 print_error("SDL_GetRenderOutputSize failed"); 182 exit(1); 183 } 184 print_debug("output size: (%d, %d)", screen_width, screen_height); 185 } 186 187 void 188 cleanup(void) 189 { 190 free(location_stack); 191 192 if (labyrinth.cells) 193 for (int y = 0; y < LABYRINTH_MAXHEIGHT; y++) 194 free(labyrinth.cells[y]); 195 free(labyrinth.cells); 196 197 if (background) 198 SDL_DestroyTexture(background); 199 if (ter12_texture) 200 SDL_DestroyTexture(ter12_texture); 201 if (sprites) 202 SDL_DestroyTexture(sprites); 203 if (renderer) 204 SDL_DestroyRenderer(renderer); 205 if (window) 206 SDL_DestroyWindow(window); 207 208 // IMG_Quit(); 209 SDL_Quit(); 210 } 211 212 void 213 draw(void) 214 { 215 SDL_FRect bg_rect, cell_rect; 216 SDL_FRect dest_rect; 217 int x, y; 218 int cx, cy; 219 float crx, cry; 220 float drx, dry; 221 float startx, starty; 222 223 cx = screen_width / 2; 224 cy = screen_height / 2; 225 226 startx = cx - labyrinth.width / 2 * CELL_SIZE; 227 starty = cy - labyrinth.height / 2 * CELL_SIZE; 228 229 bg_rect.x = 0; 230 bg_rect.y = 0; 231 bg_rect.w = CELL_SIZE * labyrinth.width; 232 bg_rect.h = CELL_SIZE * labyrinth.height; 233 234 SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 235 SDL_RenderClear(renderer); 236 237 cell_rect.w = CELL_SIZE; 238 cell_rect.h = CELL_SIZE; 239 240 /* Render background */ 241 if (!background) 242 draw_background(&bg_rect, &cell_rect, &dest_rect); 243 244 /* Draw buffered copy of background */ 245 dest_rect.x = startx; 246 dest_rect.y = starty; 247 dest_rect.w = bg_rect.w; 248 dest_rect.h = bg_rect.h; 249 SDL_RenderTexture(renderer, background, &bg_rect, &dest_rect); 250 251 /* Draw nuggets/cobwebs */ 252 dest_rect.w = CELL_SIZE; 253 dest_rect.h = CELL_SIZE; 254 for (y = 0; y < labyrinth.height; y++) 255 for (x = 0; x < labyrinth.width; x++) 256 { 257 if ((labyrinth.cells[y][x] != CELL_OK 258 && labyrinth.cells[y][x] != CELL_FAIL) 259 || (x == start.x && y == start.y) 260 || (x == end.x && y == end.y)) 261 continue; 262 263 get_sprite_xy(labyrinth.cells[y][x], &crx, &cry); 264 cell_rect.x = crx; 265 cell_rect.y = cry; 266 labyrinth_coord_to_screen_coord(x, y, startx, starty, 267 &drx, &dry); 268 dest_rect.x = drx; 269 dest_rect.y = dry; 270 SDL_RenderTexture(renderer, sprites, &cell_rect, 271 &dest_rect); 272 } 273 274 /* Draw hero */ 275 dest_rect.w = CELL_SIZE; 276 dest_rect.h = CELL_SIZE; 277 get_hero_sprite_xy(&crx, &cry); 278 cell_rect.x = crx; 279 cell_rect.y = cry; 280 if (animation.active) 281 labyrinth_coord_to_screen_coordF(animation.position.x, 282 animation.position.y, startx, starty, &drx, &dry); 283 else 284 labyrinth_coord_to_screen_coord(hero.x, hero.y, startx, starty, 285 &drx, &dry); 286 dest_rect.x = drx; 287 dest_rect.y = dry; 288 SDL_RenderTexture(renderer, sprites, &cell_rect, &dest_rect); 289 290 /* Render status */ 291 // draw_text(startx, starty + bg_rect.h, TER12_WIDTH, TER12_HEIGHT, 292 draw_text(0, screen_height - text_scale_factor * TER12_HEIGHT, 293 TER12_WIDTH, TER12_HEIGHT, ter12_texture, 294 "Column: %2d, Row: %2d F1 = Help Status: %s", hero.x + 1, 295 hero.y + 1, state_descriptions[state]); 296 297 if (help_shown) 298 draw_help_dialog(); 299 300 SDL_RenderPresent(renderer); 301 } 302 303 void 304 draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect, SDL_FRect* dest_rect) 305 { 306 const SDL_DisplayMode* mode; 307 SDL_DisplayID display; 308 float crx, cry; 309 float drx, dry; 310 311 if ((display = SDL_GetDisplayForWindow(window)) == 0) 312 { 313 print_error("SDL_GetDisplayForWindow failed: %s", 314 SDL_GetError()); 315 exit(1); 316 } 317 318 if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL) 319 { 320 print_error("SDL_GetCurrentDisplayMode failed: %s", 321 SDL_GetError()); 322 exit(1); 323 } 324 325 background = SDL_CreateTexture(renderer, mode->format, 326 SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h); 327 if (!background) 328 { 329 print_error("SDL_CreateTexture failed: %s", 330 SDL_GetError()); 331 exit(1); 332 } 333 if (!SDL_SetRenderTarget(renderer, background)) 334 { 335 print_error("SDL_SetRenderTarget failed: %s", SDL_GetError()); 336 exit(1); 337 } 338 339 SDL_SetRenderDrawColor(renderer, 0x22, 0x22, 0x22, SDL_ALPHA_OPAQUE); 340 SDL_RenderFillRect(renderer, bg_rect); 341 342 dest_rect->w = CELL_SIZE; 343 dest_rect->h = CELL_SIZE; 344 for (int y = 0; y < labyrinth.height; y++) 345 for (int x = 0; x < labyrinth.width; x++) 346 { 347 if (!((labyrinth.cells[y][x] != CELL_OK 348 && labyrinth.cells[y][x] != CELL_FAIL) 349 || (x == start.x && y == start.y) 350 || (x == end.x && y == end.y))) 351 continue; 352 353 get_sprite_xy(labyrinth.cells[y][x], &crx, &cry); 354 cell_rect->x = crx; 355 cell_rect->y = cry; 356 labyrinth_coord_to_screen_coord(x, y, 0.0f, 0.0f, &drx, 357 &dry); 358 dest_rect->x = drx; 359 dest_rect->y = dry; 360 SDL_RenderTexture(renderer, sprites, cell_rect, 361 dest_rect); 362 } 363 364 SDL_SetRenderTarget(renderer, NULL); 365 } 366 367 void 368 draw_help_dialog(void) 369 { 370 const char** phelp_text = help_text; 371 int sx = screen_width / 2 - HELP_DIALOG_WIDTH / 2; 372 int sy = screen_height / 2 - HELP_DIALOG_HEIGHT / 2; 373 SDL_FRect dialog_rect; 374 375 dialog_rect.x = sx; 376 dialog_rect.y = sy; 377 dialog_rect.w = HELP_DIALOG_WIDTH; 378 dialog_rect.h = HELP_DIALOG_HEIGHT; 379 380 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); 381 SDL_SetRenderDrawColor(renderer, 0, 0, 0, .75 * 255); 382 SDL_RenderFillRect(renderer, &dialog_rect); 383 384 SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); 385 SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); 386 SDL_RenderRect(renderer, &dialog_rect); 387 SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 388 dialog_rect.x--; 389 dialog_rect.y--; 390 dialog_rect.w += 2; 391 dialog_rect.h += 2; 392 SDL_RenderRect(renderer, &dialog_rect); 393 dialog_rect.x--; 394 dialog_rect.y--; 395 dialog_rect.w += 2; 396 dialog_rect.h += 2; 397 SDL_RenderRect(renderer, &dialog_rect); 398 399 while (*phelp_text) 400 { 401 draw_text(sx + text_scale_factor * (2 * TER12_WIDTH), 402 sy 403 + (phelp_text - help_text + 1) 404 * text_scale_factor * (TER12_HEIGHT + 2), 405 TER12_WIDTH, TER12_HEIGHT, ter12_texture, *phelp_text); 406 phelp_text++; 407 } 408 } 409 410 void 411 draw_text(const int x, const int y, const int font_width, const int font_height, 412 SDL_Texture* font_tex, const char* text, ...) 413 { 414 char buf[PRINT_BUFSIZE]; 415 va_list args; 416 const char* pbuf = NULL; 417 int xstart = 0; 418 int ystart = 0; 419 SDL_FRect source_rect; 420 SDL_FRect dest_rect; 421 int index = 0; 422 int chars_per_row = 0; 423 char first_char = ' '; 424 char last_char = '~'; 425 float tex_w, tex_h; 426 427 if (!text || !font_tex) 428 return; 429 430 va_start(args, text); 431 vsnprintf(buf, sizeof(buf), text, args); 432 va_end(args); 433 434 source_rect.w = font_width; 435 source_rect.h = font_height; 436 437 dest_rect.x = x; 438 dest_rect.y = y; 439 dest_rect.w = text_scale_factor * font_width; 440 dest_rect.h = text_scale_factor * font_height; 441 442 if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h)) 443 { 444 print_error("SDL_GetTextureSize failed: %s", SDL_GetError()); 445 exit(1); 446 } 447 448 chars_per_row = tex_w / (font_width + 1); 449 450 pbuf = buf; 451 // SDL_SetRenderScale(renderer, scale_factor, scale_factor); 452 while (*pbuf) 453 { 454 if (*pbuf >= first_char && *pbuf <= last_char) 455 { 456 index = (int)(*pbuf - first_char); 457 458 xstart = 1 + index % chars_per_row * (font_width + 1); 459 ystart = 1 + index / chars_per_row * (font_height + 1); 460 } 461 else 462 break; 463 464 source_rect.x = xstart; 465 source_rect.y = ystart; 466 467 SDL_RenderTexture(renderer, font_tex, &source_rect, &dest_rect); 468 469 dest_rect.x += text_scale_factor * font_width; 470 471 pbuf++; 472 } 473 // SDL_SetRenderScale(renderer, 1.0f, 1.0f); 474 } 475 476 void 477 get_hero_sprite_xy(float* x, float* y) 478 { 479 switch (state) 480 { 481 case STATE_SUCCESS: 482 *x = 1 + 4 * (CELL_SIZE + 1); 483 *y = 1; 484 break; 485 case STATE_FAILURE: 486 *x = 1 + 6 * (CELL_SIZE + 1); 487 *y = 1; 488 break; 489 default: 490 *x = 1 + 3 * (CELL_SIZE + 1); 491 *y = 1; 492 break; 493 } 494 } 495 496 void 497 get_sprite_xy(const char cell, float* x, float* y) 498 { 499 switch (cell) 500 { 501 case CELL_START: 502 *x = 1 + 8 * (CELL_SIZE + 1); 503 *y = 1; 504 break; 505 case CELL_END: 506 *x = 1 + 7 * (CELL_SIZE + 1); 507 *y = 1; 508 break; 509 case CELL_WALL: 510 *x = 1; 511 *y = 1; 512 break; 513 case CELL_FAIL: 514 *x = 1 + 1 * (CELL_SIZE + 1); 515 *y = 1; 516 break; 517 case CELL_OK: 518 *x = 1 + 2 * (CELL_SIZE + 1); 519 *y = 1; 520 break; 521 default: 522 *x = 1 + 5 * (CELL_SIZE + 1); 523 *y = 1; 524 } 525 /*print_debug("get_sprite_xy: %d @ (%0.0f, %0.0f)", 526 cell, *x, *y);*/ 527 } 528 529 void 530 handle_event(SDL_Event* event) 531 { 532 if (!event) 533 return; 534 switch (event->type) 535 { 536 case SDL_EVENT_QUIT: 537 running = 0; 538 break; 539 case SDL_EVENT_WINDOW_RESIZED: 540 redraw = 1; 541 calculate_screen_size(); 542 break; 543 case SDL_EVENT_KEY_DOWN: 544 print_debug("Got Keydown, key = %X, mod = %X", event->key.key, 545 event->key.mod); 546 547 switch (event->key.key) 548 { 549 case SDLK_F1: 550 help_shown = 1; 551 redraw = 1; 552 break; 553 case SDLK_ESCAPE: 554 help_shown = 0; 555 redraw = 1; 556 break; 557 case SDLK_SPACE: 558 if (state == STATE_SEARCHING) 559 { 560 print_debug("State: paused"); 561 state = STATE_SEARCHING_PAUSED; 562 } 563 else if (state == STATE_SEARCHING_PAUSED) 564 { 565 print_debug("State: searching"); 566 state = STATE_SEARCHING; 567 } 568 else 569 { 570 print_debug("Resetting"); 571 reset(); 572 } 573 break; 574 case SDLK_Q: 575 if (event->key.mod & SDL_KMOD_CTRL) 576 running = 0; 577 break; 578 default: 579 print_debug("switch fallthrough"); 580 } 581 break; 582 case SDL_EVENT_MOUSE_MOTION: 583 pointer.x = event->motion.x; 584 pointer.y = event->motion.y; 585 if (pointer.x <= 0 || pointer.y <= 0 586 || pointer.x + 1 >= display_width 587 || pointer.y + 1 >= display_height) 588 SDL_ShowCursor(); 589 else 590 SDL_HideCursor(); 591 break; 592 } 593 } 594 595 void 596 labyrinth_coord_to_screen_coord(const int x, const int y, const float startx, 597 const float starty, float* to_x, float* to_y) 598 { 599 *to_x = startx + x * CELL_SIZE; 600 *to_y = starty + y * CELL_SIZE; 601 } 602 603 void 604 labyrinth_coord_to_screen_coordF(const float x, const float y, 605 const float startx, const float starty, float* to_x, float* to_y) 606 { 607 *to_x = startx + x * CELL_SIZE; 608 *to_y = starty + y * CELL_SIZE; 609 } 610 611 int 612 load_textures(void) 613 { 614 SDL_Surface* surface = NULL; 615 SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART; 616 617 surface = try_load(SPRITES_PNG); 618 if (!surface) 619 return -1; 620 sprites = SDL_CreateTextureFromSurface(renderer, surface); 621 if (!sprites) 622 { 623 print_error("SDL_CreateTextureFromSurface failed: %s", 624 SDL_GetError()); 625 exit(1); 626 } 627 SDL_SetTextureScaleMode(sprites, scale_mode); 628 SDL_DestroySurface(surface); 629 630 surface = try_load(TER12_PNG); 631 if (!surface) 632 return -1; 633 ter12_texture = SDL_CreateTextureFromSurface(renderer, surface); 634 if (!ter12_texture) 635 { 636 print_error("SDL_CreateTextureFromSurface failed: %s", 637 SDL_GetError()); 638 exit(1); 639 } 640 SDL_SetTextureScaleMode(ter12_texture, scale_mode); 641 SDL_DestroySurface(surface); 642 643 return 0; 644 } 645 646 int 647 load_labyrinth(const char* pathname) 648 { 649 FILE* input = NULL; 650 int x, y; 651 int firstline = 1; 652 653 input = fopen(pathname, "rt"); 654 if (!input) 655 return -1; 656 657 labyrinth.cells = (char**)calloc(LABYRINTH_MAXHEIGHT, sizeof(char*)); 658 if (!labyrinth.cells) 659 { 660 print_error("calloc failed"); 661 exit(1); 662 } 663 for (y = 0; y < LABYRINTH_MAXHEIGHT; y++) 664 { 665 labyrinth.cells[y] = (char*)calloc(LABYRINTH_MAXWIDTH, 1); 666 if (!labyrinth.cells[y]) 667 { 668 print_error("calloc failed"); 669 exit(1); 670 } 671 } 672 fscanf(input, "%d %d\n", &start.x, &start.y); 673 /* 1-based coordinates for compatibility */ 674 start.x--; 675 start.y--; 676 fscanf(input, "%d %d\n", &end.x, &end.y); 677 end.x--; 678 end.y--; 679 680 x = 0; 681 y = 0; 682 labyrinth.width = 0; 683 labyrinth.height = 0; 684 while (!feof(input)) 685 { 686 char ch = fgetc(input); 687 if (ch == EOF) 688 continue; 689 690 if (strchr("01", ch)) 691 { 692 if (x == LABYRINTH_MAXWIDTH || y == LABYRINTH_MAXHEIGHT) 693 { 694 print_error("Labyrinth overflow at %d, %d" 695 " (ch == %c)", 696 x, y, ch); 697 exit(1); 698 } 699 labyrinth.cells[y][x] 700 = (ch == '0' ? CELL_EMPTY : CELL_WALL); 701 x++; 702 if (firstline) 703 labyrinth.width++; 704 } 705 else if (ch == '\n') 706 { 707 x = 0; 708 y++; 709 firstline = 0; 710 labyrinth.height++; 711 } 712 else if (strchr(" \r", ch)) 713 /* Ignore CR for compatibility */; 714 else 715 print_debug("Unknown character in labyrinth file: '%c'" 716 " = %X", 717 ch, ch); 718 } 719 720 hero.x = start.x; 721 hero.y = start.y; 722 labyrinth.cells[start.y][start.x] = CELL_START; 723 labyrinth.cells[end.y][end.x] = CELL_END; 724 725 fclose(input); 726 727 return 0; 728 } 729 730 int 731 location_pop(SDL_Point* location) 732 { 733 if (location_pointer == 0) 734 return 1; 735 location_pointer--; 736 location->x = location_stack[location_pointer].x; 737 location->y = location_stack[location_pointer].y; 738 return 0; 739 } 740 741 void 742 location_push(const SDL_Point* location) 743 { 744 location_stack[location_pointer].x = location->x; 745 location_stack[location_pointer].y = location->y; 746 location_pointer++; 747 } 748 749 void 750 next_step(void) 751 { 752 SDL_Point backup; 753 754 if (hero.x == end.x && hero.y == end.y) 755 { 756 print_debug("next_step: Success"); 757 state = STATE_SUCCESS; 758 } 759 else if (passable(hero.x - 1, hero.y)) 760 { 761 print_debug("next_step: Trying x-1, y: (%d, %d), " 762 "pushed to stack (%d, %d)", 763 hero.x - 1, hero.y, hero.x, hero.y); 764 labyrinth.cells[hero.y][hero.x] = CELL_OK; 765 location_push(&hero); 766 assign_point_xyIF(hero.x, hero.y, &animation.position); 767 assign_point_xyIF(hero.x - 1, hero.y, &animation.destination); 768 calculate_delta(); 769 animation.step = 1; 770 animation.final_step = ANIM_STEPS; 771 animation.active = 1; 772 } 773 else if (passable(hero.x, hero.y - 1)) 774 { 775 print_debug("next_step: Trying x, y-1: (%d, %d), " 776 "pushed to stack (%d, %d)", 777 hero.x, hero.y - 1, hero.x, hero.y); 778 labyrinth.cells[hero.y][hero.x] = CELL_OK; 779 location_push(&hero); 780 assign_point_xyIF(hero.x, hero.y, &animation.position); 781 assign_point_xyIF(hero.x, hero.y - 1, &animation.destination); 782 calculate_delta(); 783 animation.step = 1; 784 animation.final_step = ANIM_STEPS; 785 animation.active = 1; 786 } 787 else if (passable(hero.x + 1, hero.y)) 788 { 789 print_debug("next_step: Trying x+1, y: (%d, %d), " 790 "pushed to stack (%d, %d)", 791 hero.x + 1, hero.y, hero.x, hero.y); 792 labyrinth.cells[hero.y][hero.x] = CELL_OK; 793 location_push(&hero); 794 assign_point_xyIF(hero.x, hero.y, &animation.position); 795 assign_point_xyIF(hero.x + 1, hero.y, &animation.destination); 796 calculate_delta(); 797 animation.step = 1; 798 animation.final_step = ANIM_STEPS; 799 animation.active = 1; 800 } 801 else if (passable(hero.x, hero.y + 1)) 802 { 803 print_debug("next_step: Trying x, y+1: (%d, %d), " 804 "pushed to stack (%d, %d)", 805 hero.x, hero.y + 1, hero.x, hero.y); 806 labyrinth.cells[hero.y][hero.x] = CELL_OK; 807 location_push(&hero); 808 assign_point_xyIF(hero.x, hero.y, &animation.position); 809 assign_point_xyIF(hero.x, hero.y + 1, &animation.destination); 810 calculate_delta(); 811 animation.step = 1; 812 animation.final_step = ANIM_STEPS; 813 animation.active = 1; 814 } 815 else 816 { 817 /* Back up */ 818 if (location_pop(&backup)) 819 { 820 print_debug("State: failure"); 821 state = STATE_FAILURE; 822 } 823 else 824 { 825 print_debug("Backing up, stack pop: %d, %d", backup.x, 826 backup.y); 827 labyrinth.cells[hero.y][hero.x] = CELL_FAIL; 828 assign_pointIF(&hero, &animation.position); 829 assign_pointIF(&backup, &animation.destination); 830 calculate_delta(); 831 animation.step = 1; 832 animation.final_step = ANIM_STEPS; 833 animation.active = 1; 834 } 835 } 836 redraw = 1; 837 } 838 839 int 840 passable(const int x, const int y) 841 { 842 return (x >= 0 && x < labyrinth.width && y >= 0 && y < labyrinth.height 843 && (labyrinth.cells[y][x] == CELL_EMPTY 844 || labyrinth.cells[y][x] == CELL_END)); 845 } 846 847 void 848 print_debug(const char* fmt, ...) 849 { 850 char buf[PRINT_BUFSIZE]; 851 va_list args; 852 853 va_start(args, fmt); 854 vsnprintf(buf, sizeof(buf), fmt, args); 855 va_end(args); 856 printf("d: %s\n", buf); 857 fflush(stdout); 858 } 859 860 void 861 print_error(const char* fmt, ...) 862 { 863 char buf[PRINT_BUFSIZE]; 864 va_list args; 865 866 va_start(args, fmt); 867 vsnprintf(buf, sizeof(buf), fmt, args); 868 va_end(args); 869 fprintf(stderr, "%s: %s\n", PROGNAME, buf); 870 fflush(stderr); 871 } 872 873 void 874 reset(void) 875 { 876 for (int y = 0; y < labyrinth.height; y++) 877 for (int x = 0; x < labyrinth.width; x++) 878 if (labyrinth.cells[y][x] == CELL_FAIL 879 || labyrinth.cells[y][x] == CELL_OK) 880 labyrinth.cells[y][x] = CELL_EMPTY; 881 hero.x = start.x; 882 hero.y = start.y; 883 state = STATE_SEARCHING_PAUSED; 884 location_pointer = 0; 885 redraw = 1; 886 } 887 888 void 889 step_animation(void) 890 { 891 animation.position.x += animation.delta.x; 892 animation.position.y += animation.delta.y; 893 animation.step++; 894 if (animation.step == animation.final_step) 895 { 896 animation.active = 0; 897 hero.x = (int)animation.destination.x; 898 hero.y = (int)animation.destination.y; 899 } 900 } 901 902 SDL_Surface* 903 try_load(const char* filename) 904 { 905 char tex_pathname[BUFSIZE]; 906 SDL_Surface* surface = NULL; 907 908 snprintf(tex_pathname, BUFSIZE, "%s/%s", DATADIR, filename); 909 surface = IMG_Load(tex_pathname); 910 if (!surface) 911 { 912 print_debug("try_load: IMG_Load failed (%s): %s", tex_pathname, 913 SDL_GetError()); 914 snprintf(tex_pathname, BUFSIZE, "./%s", filename); 915 print_debug("try_load: trying %s", tex_pathname); 916 surface = IMG_Load(tex_pathname); 917 if (!surface) 918 { 919 print_error("try_load: IMG_Load failed (%s): %s", 920 tex_pathname, SDL_GetError()); 921 exit(1); 922 } 923 } 924 925 return surface; 926 } 927 928 int 929 main(int argc, char** argv) 930 { 931 SDL_Event event; 932 float px, py; 933 unsigned int last_time, current_time; 934 char datafile_pathname[BUFSIZE]; 935 936 atexit(&cleanup); 937 938 if (!SDL_Init(SDL_INIT_VIDEO)) 939 { 940 print_error("SDL_Init failed: %s", SDL_GetError()); 941 exit(1); 942 } 943 944 if (!SDL_CreateWindowAndRenderer(WIN_TITLE, DEFAULT_WIDTH, 945 DEFAULT_HEIGHT, 946 SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, &window, 947 &renderer)) 948 { 949 print_error("SDL_CreateWindowAndRenderer failed: %s", 950 SDL_GetError()); 951 exit(1); 952 } 953 954 // SDL_SetWindowFullscreenMode(window, NULL); 955 // SDL_SetWindowFullscreen(window, 1); 956 SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE); 957 SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH, 958 DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); 959 /*SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH, 960 DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);*/ 961 /*float factor = (float)714/720; 962 print_debug("factor = %0.2f", factor); 963 SDL_SetRenderScale(renderer, 1.0f, factor);*/ 964 965 calculate_screen_size(); 966 967 if (load_textures() < 0) 968 exit(1); 969 970 if (argc > 1) 971 { 972 if (load_labyrinth(*(argv + 1)) < 0) 973 { 974 print_error("load_labyrinth failed: %s (%s)", 975 strerror(errno), *(argv + 1)); 976 exit(1); 977 } 978 } 979 else 980 { 981 snprintf(datafile_pathname, BUFSIZE, "%s/%s", DATADIR, 982 DEFAULT_DATAFILE); 983 if (load_labyrinth(datafile_pathname) < 0) 984 { 985 print_debug("fopen failed: %s (%s)", strerror(errno), 986 datafile_pathname); 987 snprintf(datafile_pathname, BUFSIZE, "./%s", 988 DEFAULT_DATAFILE); 989 print_debug("trying %s", datafile_pathname); 990 if (load_labyrinth(datafile_pathname) < 0) 991 { 992 print_error("fopen failed: %s (%s)", 993 strerror(errno), datafile_pathname); 994 exit(1); 995 } 996 } 997 } 998 location_stack 999 = (SDL_Point*)calloc(LOCATION_STACK_MAX, sizeof(SDL_Point)); 1000 animation.active = 0; 1001 1002 SDL_HideCursor(); 1003 SDL_GetMouseState(&px, &py); 1004 pointer.x = px; 1005 pointer.y = py; 1006 last_time = SDL_GetTicks(); 1007 while (running) 1008 { 1009 current_time = SDL_GetTicks(); 1010 if (animation.active) 1011 { 1012 if (current_time > last_time + 1000 / 60) 1013 { 1014 draw(); 1015 step_animation(); 1016 last_time = current_time; 1017 } 1018 } 1019 else 1020 { 1021 if (state == STATE_SEARCHING) 1022 { 1023 next_step(); 1024 } 1025 1026 if (redraw) 1027 { 1028 draw(); 1029 redraw = 0; 1030 } 1031 } 1032 1033 while (SDL_PollEvent(&event)) 1034 handle_event(&event); 1035 } 1036 1037 return 0; 1038 }