lav-sdl.c (47324B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2023-2026 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 <assert.h> 8 #include <errno.h> 9 #include <limits.h> 10 #include <math.h> 11 #include <stdarg.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 15 #include "local.h" 16 17 #define ANIM_SPEED \ 18 100 /* how long does it take for the animation to go \ 19 * from start to finish (ms) */ 20 #define FPS 60 21 #define ANIM_STEPS (ANIM_SPEED * FPS / 1000) 22 23 #define BOLD_START '\1' 24 #define BOLD_END '\2' 25 #define CENTER_LINE '\3' 26 #define DIM_START '\4' 27 #define DIM_END '\2' 28 #define CELL_SCALE_FACTOR 1.0f 29 #define BUFSIZE 4096 30 #define CELL_SIZE 32 31 #define DEFAULT_WIDTH 1280 32 #define DEFAULT_HEIGHT 720 33 #define HELP_DIALOG_WIDTH 900 34 #define HELP_DIALOG_HEIGHT 450 35 #define LABYRINTH_MAXHEIGHT 20 36 #define LABYRINTH_MAXWIDTH 40 37 #define LOCATION_STACK_MAX (LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT) 38 #define PRINT_BUFSIZE 4096 39 #define PROGNAME "lav-sdl" 40 #define TER12_WIDTH 6 41 #define TER12_HEIGHT 12 42 #define TEXT_OPACITY_DIM 0.3f 43 #define TEXT_OPACITY_NORMAL 0.6f 44 #define TEXT_OPACITY_BOLD 1.0f 45 #define TEXT_SCALE_FACTOR 2.0f 46 #define WIN_TITLE "Lavirint (SDL version)" 47 48 #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 49 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 50 #define INDEX(x, y) ((y) * state->grid_width + (x)) 51 #define INDEXFI(x, y) (((int)floor(y)) * state->grid_width + ((int)floor(x))) 52 53 /* clang-format off */ 54 enum { 55 CELL_EMPTY, 56 CELL_WALL, 57 CELL_FAIL, 58 CELL_OK, 59 CELL_START, 60 CELL_END, 61 CELL_PATH 62 }; 63 64 static char* cell_image[] = {"[ ]", "[H]", "[/]", "[o]", "[>]", "[v]", "[.]"}; 65 66 enum { 67 STATE_SEARCHING, 68 STATE_SEARCHING_PAUSED, 69 STATE_FAILURE, 70 STATE_SUCCESS 71 }; 72 73 const char* state_descriptions[] = { 74 "Searching for exit", 75 "Searching (paused)", 76 "Failed!", 77 "Success!" 78 }; 79 80 const char* help_text[] = { 81 "\1\3lav-sdl - SDL maze solver\2", 82 "\1\3=========================\2", 83 "", 84 "\1F1\2 - Show this help screen", 85 "\1Esc\2 - Hide this help screen", 86 "\1Space\2 - Pause/resume simulation", 87 "\1C-Q\2 - Exit", 88 "", 89 "\4---\2", 90 "", 91 "\4This program is licensed under the terms of GNU GPL v3 or (at your\2", 92 "\4option) any later version. Copyright (C) 2023-2026 Strahinya Radich.\2", 93 "\4See the file LICENSE for exact copyright and license details.\2", 94 NULL 95 }; 96 /* clang-format on */ 97 98 struct AnimationState { 99 SDL_FPoint start; 100 SDL_FPoint position; 101 SDL_FPoint end; 102 SDL_FPoint delta; 103 int active; 104 int step; 105 int final_step; 106 }; 107 108 struct Cell { 109 int empty; /* logical variables */ 110 int popping; 111 int path; 112 int starting; 113 int ending; /* logical variables end */ 114 SDL_FPoint coords; 115 int contents; 116 struct Cell* down; 117 struct Cell* left; 118 struct Cell* right; 119 struct Cell* up; 120 struct Cell* path_next; /* next cell in shortest path */ 121 struct Cell* path_prev; /* previous cell in shortest path */ 122 int path_dist; /* length of shortest path so far */ 123 }; 124 125 /* clang-format off */ 126 struct State { 127 struct AnimationState animation; 128 SDL_Texture* background; 129 float cell_scale_factor; 130 int check_score; 131 SDL_Point dialog_dims; 132 SDL_Point dialog_text_origin; 133 int dialog_shown; 134 const char** dialog_text; 135 int display_width; /* Physical, detected */ 136 int display_height; 137 int full_screen; 138 struct Cell* grid; 139 int grid_height; 140 int grid_width; 141 struct HiScoreEntry* hall_of_fame; 142 SDL_Point hero, start, end; 143 void (*input_callback)(struct State*); 144 int input_col; 145 int input_instant_reset; 146 int input_shown; 147 int location_pointer; 148 SDL_FPoint* location_stack; 149 int mode; 150 SDL_FPoint mouse; 151 int moveno; 152 struct Cell* path_start; 153 struct Cell* path_end; 154 SDL_FPoint pointer; 155 int query_shown; 156 void (*query_callback)(struct State*); 157 int redraw; 158 SDL_Renderer* renderer; 159 int running; 160 int screen_width; /* Logical */ 161 int screen_height; 162 unsigned long int score; 163 int search_shortest; 164 SDL_FPoint selection_start, selection_end; 165 SDL_Texture* sprites; 166 int state; 167 SDL_Texture* ter12_texture; 168 float text_scale_factor; 169 SDL_Window* window; 170 }; 171 172 /* clang-format on */ 173 174 void assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to); 175 void assign_pointIF(const SDL_Point* from, SDL_FPoint* to); 176 void assign_point_xyIF(const int x, const int y, SDL_FPoint* to); 177 void assign_point_xyII(const int x, const int y, SDL_Point* to); 178 void calculate_delta(struct State* state); 179 void calculate_display_size(struct State* state); 180 void calculate_grid_origin(const struct State* state, float* sx, float* sy); 181 void cleanup(struct State* state); 182 void do_exit(const int code, struct State* state); 183 void draw(struct State* state); 184 int find_shortest_path(struct Cell* c, struct Cell* source, const int dist); 185 void get_hero_sprite_xy(struct State* state, float* x, float* y); 186 void get_sprite_xy(const int cell, float* x, float* y); 187 void grid_coord_to_screen_coordF(const struct State* state, const float x, 188 const float y, const float startx, const float starty, float* to_x, 189 float* to_y); 190 void grid_coord_to_screen_coordI(const struct State* state, const int x, 191 const int y, const float startx, const float starty, float* to_x, 192 float* to_y); 193 void handle_event(struct State* state, SDL_Event* event); 194 void handle_key_down(struct State* state, SDL_Event* event); 195 void init_animation(struct AnimationState* animation); 196 void init_cell(struct Cell* c, const int full, const SDL_FPoint* coords); 197 void init_state(struct State* state); 198 void labyrinth_coord_to_screen_coordF(const float x, const float y, 199 const float startx, const float starty, float* to_x, float* to_y); 200 void labyrinth_coord_to_screen_coordI(const int x, const int y, 201 const float startx, const float starty, float* to_x, float* to_y); 202 int load_labyrinth(struct State* state, const char* pathname); 203 int load_textures(struct State* state); 204 void location_delete(struct State* state, const int index); 205 int location_index(const struct State* state, const SDL_FPoint* location); 206 int location_pop(struct State* state, SDL_Point* location); 207 void location_pushF(struct State* state, const SDL_FPoint* location); 208 void location_pushI(struct State* state, const SDL_Point* location); 209 void mark_path(struct Cell* c); 210 void next_step(struct State* state); 211 int passable(struct State* state, const int x, const int y); 212 int point_equalI(struct SDL_Point* p1, struct SDL_Point* p2); 213 void prepare_dialog(struct State* state, const int w, const int h, 214 const char** text, const int dx, const int show); 215 void print_debug(const char* fmt, ...); 216 void print_error(const char* fmt, ...); 217 void print_matrix(const struct State* state); 218 void print_path(struct Cell* c); 219 void render_background(struct State* state, SDL_FRect* bg_rect); 220 void render_dialog(struct State* state); 221 void render_hero(struct State* state, const float startx, const float starty); 222 void render_status(struct State* state); 223 void render_text(struct State* state, const int x, const int y, 224 const int font_width, const int font_height, SDL_Texture* font_tex, 225 const char* text, ...); 226 void render_things(struct State* state, const float startx, const float starty); 227 void reset(struct State* state); 228 void reset_path(struct State* state); 229 void step_animation(struct State* state); 230 int strlen_visible(const char* s); 231 SDL_Texture* try_load(SDL_Renderer* renderer, const char* filename); 232 233 void 234 assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to) 235 { 236 assert((from != NULL) && (to != NULL)); 237 to->x = (float)from->x; 238 to->y = (float)from->y; 239 } 240 241 void 242 assign_pointIF(const SDL_Point* from, SDL_FPoint* to) 243 { 244 assert((from != NULL) && (to != NULL)); 245 to->x = (float)from->x; 246 to->y = (float)from->y; 247 } 248 249 void 250 assign_point_xyIF(const int x, const int y, SDL_FPoint* to) 251 { 252 assert(to != NULL); 253 to->x = (float)x; 254 to->y = (float)y; 255 } 256 257 void 258 assign_point_xyII(const int x, const int y, SDL_Point* to) 259 { 260 assert(to != NULL); 261 to->x = (float)x; 262 to->y = (float)y; 263 } 264 265 void 266 calculate_delta(struct State* state) 267 { 268 SDL_FPoint* start = NULL; 269 SDL_FPoint* end = NULL; 270 SDL_FPoint* delta = NULL; 271 272 assert(state != NULL); 273 274 start = &state->animation.start; 275 end = &state->animation.end; 276 delta = &state->animation.delta; 277 278 print_debug( 279 "calculate_delta: start: (%0.2f, %0.2f), end: (%0.2f, %0.2f)", 280 start->x, start->y, end->x, end->y); 281 282 delta->x = fabsf(end->x - start->x) / ANIM_STEPS; 283 if (end->x < start->x) 284 delta->x *= -1; 285 delta->y = fabsf(end->y - start->y) / ANIM_STEPS; 286 if (end->y < start->y) 287 delta->y *= -1; 288 print_debug("calculate_delta: (%0.2f, %0.2f)", delta->x, delta->y); 289 } 290 291 void 292 calculate_display_size(struct State* state) 293 { 294 assert(state != NULL); 295 296 if (!SDL_GetRenderOutputSize(state->renderer, &state->display_width, 297 &state->display_height)) 298 { 299 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 300 "SDL_GetRenderOutputSize failed"); 301 do_exit(1, state); 302 } 303 print_debug("output display size: (%d, %d)", state->display_width, 304 state->display_height); 305 } 306 307 void 308 calculate_grid_origin(const struct State* state, float* sx, float* sy) 309 { 310 int cx, cy; 311 float csf; 312 313 assert((state != NULL) && (sx != NULL) && (sy != NULL)); 314 315 csf = state->cell_scale_factor; 316 cx = state->screen_width / 2; 317 cy = state->screen_height / 2; 318 319 *sx = cx - (csf * state->grid_width * CELL_SIZE) / 2; 320 *sy = cy - (csf * state->grid_height * CELL_SIZE) / 2; 321 } 322 323 void 324 cleanup(struct State* state) 325 { 326 assert(state != NULL); 327 328 free(state->location_stack); 329 330 if (state->background) 331 SDL_DestroyTexture(state->background); 332 if (state->ter12_texture) 333 SDL_DestroyTexture(state->ter12_texture); 334 if (state->sprites) 335 SDL_DestroyTexture(state->sprites); 336 if (state->renderer) 337 SDL_DestroyRenderer(state->renderer); 338 if (state->window) 339 SDL_DestroyWindow(state->window); 340 341 SDL_Quit(); 342 } 343 344 void 345 do_exit(const int code, struct State* state) 346 { 347 cleanup(state); 348 exit(code); 349 } 350 void 351 draw(struct State* state) 352 { 353 SDL_FRect bg_rect; 354 SDL_FRect dest_rect; 355 float startx, starty; 356 float csf; 357 358 assert(state != NULL); 359 assert(state->cell_scale_factor != 0.0f); 360 361 csf = state->cell_scale_factor; 362 calculate_grid_origin(state, &startx, &starty); 363 364 bg_rect.x = 0; 365 bg_rect.y = 0; 366 bg_rect.w = csf * (CELL_SIZE * state->grid_width + 1); 367 bg_rect.h = csf * (CELL_SIZE * state->grid_height + 1); 368 369 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 370 SDL_RenderClear(state->renderer); 371 372 /* Render background */ 373 if (!state->background) 374 render_background(state, &bg_rect); 375 376 /* Draw buffered copy of background */ 377 dest_rect.x = startx; 378 dest_rect.y = starty; 379 dest_rect.w = bg_rect.w; 380 dest_rect.h = bg_rect.h; 381 SDL_RenderTexture(state->renderer, state->background, &bg_rect, 382 &dest_rect); 383 384 /* Render things */ 385 render_things(state, startx, starty); 386 387 /* Render hero */ 388 render_hero(state, startx, starty); 389 390 /* Render status */ 391 render_status(state); 392 393 if (state->dialog_shown) 394 render_dialog(state); 395 396 SDL_RenderPresent(state->renderer); 397 } 398 399 int 400 find_shortest_path(struct Cell* c, struct Cell* source, const int dist) 401 { 402 int result = 0; 403 404 if (!c || c->path_dist <= dist) 405 return 0; 406 407 if (!c->empty && !c->starting && !c->ending) 408 return 0; 409 410 if (c->starting) 411 c->path_dist = dist; 412 413 if (c->path_dist > dist) 414 { 415 c->path_dist = dist; 416 c->path_prev = source; 417 } 418 419 if (c->ending) 420 return 1; 421 422 result |= find_shortest_path(c->left, c, dist + 1); 423 result |= find_shortest_path(c->up, c, dist + 1); 424 result |= find_shortest_path(c->right, c, dist + 1); 425 result |= find_shortest_path(c->down, c, dist + 1); 426 427 return result; 428 } 429 430 void 431 get_hero_sprite_xy(struct State* state, float* x, float* y) 432 { 433 assert((state != NULL) && (x != NULL) && (y != NULL)); 434 435 switch (state->state) 436 { 437 case STATE_SUCCESS: 438 *x = 1 + 4 * (CELL_SIZE + 1); 439 *y = 1; 440 break; 441 case STATE_FAILURE: 442 *x = 1 + 6 * (CELL_SIZE + 1); 443 *y = 1; 444 break; 445 default: 446 *x = 1 + 3 * (CELL_SIZE + 1); 447 *y = 1; 448 break; 449 } 450 } 451 452 void 453 get_sprite_xy(const int cell, float* x, float* y) 454 { 455 switch (cell) 456 { 457 case CELL_START: 458 *x = 1 + 8 * (CELL_SIZE + 1); 459 *y = 1; 460 break; 461 case CELL_END: 462 *x = 1 + 7 * (CELL_SIZE + 1); 463 *y = 1; 464 break; 465 case CELL_WALL: 466 *x = 1; 467 *y = 1; 468 break; 469 case CELL_FAIL: 470 *x = 1 + 1 * (CELL_SIZE + 1); 471 *y = 1; 472 break; 473 case CELL_OK: 474 *x = 1 + 2 * (CELL_SIZE + 1); 475 *y = 1; 476 break; 477 default: 478 *x = 1 + 5 * (CELL_SIZE + 1); 479 *y = 1; 480 } 481 /*print_debug("get_sprite_xy: %d @ (%0.0f, %0.0f)", 482 cell, *x, *y);*/ 483 } 484 485 void 486 grid_coord_to_screen_coordF(const struct State* state, const float x, 487 const float y, const float startx, const float starty, float* to_x, 488 float* to_y) 489 { 490 assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); 491 *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; 492 *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; 493 } 494 495 void 496 grid_coord_to_screen_coordI(const struct State* state, const int x, const int y, 497 const float startx, const float starty, float* to_x, float* to_y) 498 { 499 assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); 500 *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; 501 *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; 502 } 503 504 void 505 handle_event(struct State* state, SDL_Event* event) 506 { 507 if (!event) 508 return; 509 switch (event->type) 510 { 511 case SDL_EVENT_QUIT: 512 state->running = 0; 513 break; 514 case SDL_EVENT_WINDOW_RESIZED: 515 state->redraw = 1; 516 calculate_display_size(state); 517 break; 518 case SDL_EVENT_KEY_DOWN: 519 print_debug("Got Keydown, key = %X, mod = %X", event->key.key, 520 event->key.mod); 521 handle_key_down(state, event); 522 break; 523 case SDL_EVENT_MOUSE_MOTION: 524 state->pointer.x = event->motion.x; 525 state->pointer.y = event->motion.y; 526 if (state->pointer.x <= 0 || state->pointer.y <= 0 527 || state->pointer.x + 1 >= state->display_width 528 || state->pointer.y + 1 >= state->display_height) 529 SDL_ShowCursor(); 530 else 531 SDL_HideCursor(); 532 break; 533 } 534 } 535 536 void 537 handle_key_down(struct State* state, SDL_Event* event) 538 { 539 switch (event->key.key) 540 { 541 case SDLK_F1: 542 if (state->dialog_shown) 543 break; 544 prepare_dialog(state, HELP_DIALOG_WIDTH, HELP_DIALOG_HEIGHT, 545 help_text, 0, 1); 546 break; 547 case SDLK_ESCAPE: 548 state->dialog_shown = 0; 549 state->dialog_text = NULL; 550 state->redraw = 1; 551 break; 552 case SDLK_SPACE: 553 if (state->state == STATE_SEARCHING) 554 { 555 print_debug("State: paused"); 556 state->state = STATE_SEARCHING_PAUSED; 557 } 558 else if (state->state == STATE_SEARCHING_PAUSED) 559 { 560 print_debug("State: searching"); 561 state->state = STATE_SEARCHING; 562 } 563 else 564 { 565 print_debug("Resetting"); 566 reset(state); 567 } 568 break; 569 case SDLK_Q: 570 if (event->key.mod & SDL_KMOD_CTRL) 571 state->running = 0; 572 break; 573 default: 574 print_debug("switch fallthrough"); 575 } 576 } 577 578 void 579 init_animation(struct AnimationState* animation) 580 { 581 assert(animation != NULL); 582 animation->start.x = -1; 583 animation->start.y = -1; 584 animation->position.x = -1; 585 animation->position.y = -1; 586 animation->end.x = -1; 587 animation->end.y = -1; 588 animation->delta.x = 0; 589 animation->delta.y = 0; 590 animation->active = 0; 591 animation->step = 0; 592 animation->final_step = 0; 593 } 594 595 void 596 init_cell(struct Cell* c, const int full, const SDL_FPoint* coords) 597 { 598 assert(c != NULL); 599 c->empty = 1; 600 c->starting = 0; 601 c->ending = 0; 602 c->popping = 0; 603 c->path = 0; 604 if (full) 605 { 606 assert(coords != NULL); 607 c->coords.x = coords->x; 608 c->coords.y = coords->y; 609 c->down = c->left = c->right = c->up = NULL; 610 } 611 c->contents = CELL_EMPTY; 612 c->path_next = NULL; 613 c->path_prev = NULL; 614 c->path_dist = INT_MAX; 615 } 616 617 void 618 init_state(struct State* state) 619 { 620 assert(state != NULL); 621 init_animation(&state->animation); 622 state->background = NULL; 623 state->grid = NULL; 624 state->cell_scale_factor = CELL_SCALE_FACTOR; 625 state->check_score = 1; 626 assign_point_xyII(-1, -1, &state->dialog_dims); 627 assign_point_xyII(0, 0, &state->dialog_text_origin); 628 state->dialog_shown = 0; 629 state->dialog_text = NULL; 630 state->display_width = DEFAULT_WIDTH; 631 state->display_height = DEFAULT_HEIGHT; 632 state->full_screen = 1; 633 state->hall_of_fame = NULL; 634 state->input_callback = NULL; 635 state->input_col = 0; 636 state->input_instant_reset = 0; 637 state->input_shown = 0; 638 state->location_pointer = 0; 639 state->location_stack = NULL; 640 state->moveno = 1; 641 assign_point_xyIF(0, 0, &state->mouse); 642 state->path_start = NULL; 643 state->path_end = NULL; 644 state->query_shown = 0; 645 state->query_callback = NULL; 646 state->redraw = 1; 647 state->renderer = NULL; 648 state->running = 1; 649 state->screen_width = DEFAULT_WIDTH; 650 state->screen_height = DEFAULT_HEIGHT; 651 state->score = 0; 652 state->search_shortest = 0; 653 assign_point_xyIF(-1, -1, &state->selection_start); 654 assign_point_xyIF(-1, -1, &state->selection_end); 655 state->sprites = NULL; 656 state->state = STATE_SEARCHING_PAUSED; 657 state->ter12_texture = NULL; 658 state->text_scale_factor = TEXT_SCALE_FACTOR; 659 state->window = NULL; 660 } 661 662 void 663 labyrinth_coord_to_screen_coordF(const float x, const float y, 664 const float startx, const float starty, float* to_x, float* to_y) 665 { 666 *to_x = startx + x * CELL_SIZE; 667 *to_y = starty + y * CELL_SIZE; 668 } 669 670 void 671 labyrinth_coord_to_screen_coordI(const int x, const int y, const float startx, 672 const float starty, float* to_x, float* to_y) 673 { 674 *to_x = startx + x * CELL_SIZE; 675 *to_y = starty + y * CELL_SIZE; 676 } 677 678 int 679 load_labyrinth(struct State* state, const char* pathname) 680 { 681 FILE* input = NULL; 682 struct Cell* c = NULL; 683 SDL_FPoint coords; 684 int x, y; 685 int firstline = 1; 686 687 assert((state != NULL) && (pathname != NULL)); 688 689 input = fopen(pathname, "rt"); 690 if (!input) 691 return -1; 692 693 state->grid = malloc( 694 sizeof(struct Cell) * LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT); 695 if (!state->grid) 696 { 697 print_error("malloc failed"); 698 do_exit(1, state); 699 } 700 state->grid_width = LABYRINTH_MAXWIDTH; 701 state->grid_height = LABYRINTH_MAXHEIGHT; 702 state->location_stack = malloc( 703 sizeof(SDL_FPoint) * LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT); 704 if (!state->location_stack) 705 { 706 print_error("malloc failed"); 707 do_exit(1, state); 708 } 709 710 fscanf(input, "%d %d\n", &state->start.x, &state->start.y); 711 /* 1-based coordinates for compatibility */ 712 state->start.x--; 713 state->start.y--; 714 fscanf(input, "%d %d\n", &state->end.x, &state->end.y); 715 state->end.x--; 716 state->end.y--; 717 718 x = 0; 719 y = 0; 720 state->grid_width = 0; 721 state->grid_height = 0; 722 while (!feof(input)) 723 { 724 char ch = fgetc(input); 725 if (ch == EOF) 726 continue; 727 728 if (strchr("01", ch)) 729 { 730 if (x == LABYRINTH_MAXWIDTH || y == LABYRINTH_MAXHEIGHT) 731 { 732 print_error("Labyrinth overflow at %d, %d" 733 " (ch == %c)", 734 x, y, ch); 735 do_exit(1, state); 736 } 737 c = &state->grid[INDEX(x, y)]; 738 739 coords.x = x; 740 coords.y = y; 741 init_cell(c, 1, &coords); 742 743 c->contents = (ch == '0' ? CELL_EMPTY : CELL_WALL); 744 c->empty = c->contents == CELL_EMPTY; 745 x++; 746 if (firstline) 747 state->grid_width++; 748 } 749 else if (ch == '\n') 750 { 751 x = 0; 752 y++; 753 firstline = 0; 754 state->grid_height++; 755 } 756 else if (strchr(" \r", ch)) 757 /* Ignore CR for compatibility */; 758 else 759 print_debug("Unknown character in labyrinth file: '%c'" 760 " = %X", 761 ch, ch); 762 } 763 764 for (y = 0; y < state->grid_height; y++) 765 { 766 for (x = 0; x < state->grid_width; x++) 767 { 768 c = &state->grid[INDEX(x, y)]; 769 c->coords.x = x; 770 c->coords.y = y; 771 772 c->left = c->up = c->right = c->down = NULL; 773 if (x > 0) 774 c->left = &state->grid[INDEX(x - 1, y)]; 775 if (y > 0) 776 c->up = &state->grid[INDEX(x, y - 1)]; 777 if (x < state->grid_width - 1) 778 c->right = &state->grid[INDEX(x + 1, y)]; 779 if (y < state->grid_height - 1) 780 c->down = &state->grid[INDEX(x, y + 1)]; 781 } 782 } 783 784 state->hero.x = state->start.x; 785 state->hero.y = state->start.y; 786 state->grid[INDEX(state->start.x, state->start.y)].contents 787 = CELL_START; 788 state->grid[INDEX(state->start.x, state->start.y)].starting = 1; 789 state->grid[INDEX(state->end.x, state->end.y)].contents = CELL_END; 790 state->grid[INDEX(state->end.x, state->end.y)].ending = 1; 791 792 fclose(input); 793 794 return 0; 795 } 796 797 int 798 load_textures(struct State* state) 799 { 800 SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART; 801 802 assert(state != NULL); 803 804 state->sprites = try_load(state->renderer, SPRITES_PNG); 805 if (!state->sprites) 806 return -1; 807 SDL_SetTextureScaleMode(state->sprites, scale_mode); 808 809 state->ter12_texture = try_load(state->renderer, TER12_PNG); 810 if (!state->ter12_texture) 811 return -1; 812 SDL_SetTextureScaleMode(state->ter12_texture, scale_mode); 813 814 return 0; 815 } 816 817 void 818 location_delete(struct State* state, const int index) 819 { 820 assert(state != NULL); 821 if (state->location_pointer == 0 || index < 0 822 || index >= state->location_pointer) 823 return; 824 assign_pointFF(&state->location_stack[state->location_pointer - 1], 825 &state->location_stack[index]); 826 state->location_pointer--; 827 } 828 829 int 830 location_index(const struct State* state, const SDL_FPoint* location) 831 { 832 SDL_FPoint* p = NULL; 833 int i; 834 835 assert((state != NULL) && (location != NULL)); 836 for (i = 0; i < state->location_pointer; i++) 837 { 838 p = &state->location_stack[i]; 839 if (p->x == location->x && p->y == location->y) 840 return i; 841 } 842 return -1; 843 } 844 845 int 846 location_pop(struct State* state, SDL_Point* location) 847 { 848 assert((state != NULL) && (location != NULL)); 849 if (state->location_pointer == 0) 850 return 1; 851 state->location_pointer--; 852 location->x = state->location_stack[state->location_pointer].x; 853 location->y = state->location_stack[state->location_pointer].y; 854 return 0; 855 } 856 857 void 858 location_pushF(struct State* state, const SDL_FPoint* location) 859 { 860 assert((state != NULL) && (location != NULL)); 861 assert(state->location_stack != NULL); 862 state->location_stack[state->location_pointer].x = location->x; 863 state->location_stack[state->location_pointer].y = location->y; 864 state->location_pointer++; 865 } 866 867 void 868 location_pushI(struct State* state, const SDL_Point* location) 869 { 870 assert((state != NULL) && (location != NULL)); 871 state->location_stack[state->location_pointer].x = location->x; 872 state->location_stack[state->location_pointer].y = location->y; 873 state->location_pointer++; 874 } 875 876 void 877 mark_path(struct Cell* c) 878 { 879 if (!c) 880 return; 881 882 c->path = 1; 883 if (c->path_prev) 884 { 885 c->path_prev->path_next = c; 886 print_debug( 887 "mark_path: Next of (%0.0f, %0.0f) is (%0.0f, %0.0f)", 888 c->path_prev->coords.x, c->path_prev->coords.y, 889 c->path_prev->path_next->coords.x, 890 c->path_prev->path_next->coords.y); 891 } 892 mark_path(c->path_prev); 893 } 894 895 void 896 next_step(struct State* state) 897 { 898 struct Cell* from = NULL; 899 struct Cell* to = NULL; 900 SDL_Point backup; 901 int isx, isy, iex, iey; 902 int idx; 903 904 if (!state->search_shortest) 905 goto next_step_brute_force; 906 907 if (!state->animation.active) 908 { 909 if (!state->path_end) 910 { 911 state->path_start = &state->grid[INDEX(state->start.x, 912 state->start.y)]; 913 state->path_end 914 = &state->grid[INDEX(state->end.x, state->end.y)]; 915 if (!find_shortest_path(state->path_start, NULL, 0)) 916 { 917 reset_path(state); 918 state->path_start->contents = CELL_FAIL; 919 state->path_start = NULL; 920 state->path_end = NULL; 921 state->animation.active = 0; 922 state->state = STATE_FAILURE; 923 print_debug("find_shortest_path FAILED"); 924 state->redraw = 1; 925 return; 926 } 927 print_debug( 928 "find_shortest_path successful, path len = %d", 929 state->path_end->path_dist); 930 mark_path(state->path_end); 931 print_debug("M = "); 932 print_matrix(state); 933 if (!state->path_start->path_next) 934 return; 935 } 936 937 state->animation.start.x = state->path_start->coords.x; 938 state->animation.start.y = state->path_start->coords.y; 939 state->animation.end.x = state->path_start->path_next->coords.x; 940 state->animation.end.y = state->path_start->path_next->coords.y; 941 state->animation.position.x = state->path_start->coords.x; 942 state->animation.position.y = state->path_start->coords.y; 943 print_debug("Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 944 state->animation.start.x, state->animation.start.y, 945 state->animation.end.x, state->animation.end.y); 946 calculate_delta(state); 947 state->animation.step = 1; 948 state->animation.final_step = ANIM_STEPS; 949 state->animation.active = 1; 950 state->redraw = 1; 951 print_path(&state->grid[INDEXFI(state->animation.start.x, 952 state->animation.start.y)]); 953 } 954 955 if (state->animation.step == state->animation.final_step) 956 { 957 isx = (int)round(state->animation.start.x); 958 isy = (int)round(state->animation.start.y); 959 iex = (int)round(state->animation.end.x); 960 iey = (int)round(state->animation.end.y); 961 from = &state->grid[INDEX(isx, isy)]; 962 to = &state->grid[INDEX(iex, iey)]; 963 964 if (to->ending) 965 { 966 state->animation.active = 0; 967 print_debug("Ending node reached: (%0.0f, %0.0f)", 968 state->animation.end.x, state->animation.end.y); 969 } 970 else if (to->path_next) 971 { 972 state->animation.start.x = state->animation.end.x; 973 state->animation.start.y = state->animation.end.y; 974 state->animation.position.x = state->animation.start.x; 975 state->animation.position.y = state->animation.start.y; 976 state->animation.end.x = to->path_next->coords.x; 977 state->animation.end.y = to->path_next->coords.y; 978 print_debug("Next node on path: (%0.0f, %0.0f)", 979 state->animation.end.x, state->animation.end.y); 980 print_debug( 981 "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 982 state->animation.start.x, 983 state->animation.start.y, 984 state->animation.end.x, state->animation.end.y); 985 calculate_delta(state); 986 state->animation.step = 1; 987 } 988 else 989 { 990 print_debug("End of path at (%0.0f, %0.0f)", 991 state->animation.end.x, state->animation.end.y); 992 state->animation.active = 0; 993 } 994 995 print_debug("grid[%d][%d] = %d <-> grid[%d][%d] = %d", iey, iex, 996 to->contents, isy, isx, from->contents); 997 idx = location_index(state, &to->coords); 998 print_debug("location_index: Index of (%0.0f, %0.0f) is %d", 999 to->coords.x, to->coords.y, idx); 1000 assert(idx != -1); 1001 location_delete(state, idx); 1002 location_pushF(state, &from->coords); 1003 1004 if (to->ending) 1005 { 1006 reset_path(state); 1007 state->path_start = NULL; 1008 state->path_end = NULL; 1009 } 1010 } 1011 1012 state->animation.position.x += state->animation.delta.x; 1013 state->animation.position.y += state->animation.delta.y; 1014 state->animation.step++; 1015 print_debug( 1016 "step_animation: (%0.2f, %0.2f) => (%0.2f, %0.2f), [%d/%d] %s", 1017 state->animation.position.x, state->animation.position.y, 1018 state->animation.end.x, state->animation.end.y, 1019 state->animation.step, state->animation.final_step, 1020 state->animation.active ? "ACTIVE" : "INACTIVE"); 1021 return; 1022 1023 next_step_brute_force: 1024 if (state->hero.x == state->end.x && state->hero.y == state->end.y) 1025 { 1026 print_debug("next_step: Success"); 1027 state->state = STATE_SUCCESS; 1028 } 1029 else if (passable(state, state->hero.x - 1, state->hero.y)) 1030 { 1031 print_debug("next_step: Trying x-1, y: (%d, %d), " 1032 "pushed to stack (%d, %d)", 1033 state->hero.x - 1, state->hero.y, state->hero.x, 1034 state->hero.y); 1035 state->grid[INDEX(state->hero.x, state->hero.y)].contents 1036 = CELL_OK; 1037 location_pushI(state, &state->hero); 1038 assign_point_xyIF(state->hero.x, state->hero.y, 1039 &state->animation.start); 1040 assign_point_xyIF(state->hero.x, state->hero.y, 1041 &state->animation.position); 1042 assign_point_xyIF(state->hero.x - 1, state->hero.y, 1043 &state->animation.end); 1044 calculate_delta(state); 1045 state->animation.step = 1; 1046 state->animation.final_step = ANIM_STEPS; 1047 state->animation.active = 1; 1048 } 1049 else if (passable(state, state->hero.x, state->hero.y - 1)) 1050 { 1051 print_debug("next_step: Trying x, y-1: (%d, %d), " 1052 "pushed to stack (%d, %d)", 1053 state->hero.x, state->hero.y - 1, state->hero.x, 1054 state->hero.y); 1055 state->grid[INDEX(state->hero.x, state->hero.y)].contents 1056 = CELL_OK; 1057 location_pushI(state, &state->hero); 1058 assign_point_xyIF(state->hero.x, state->hero.y, 1059 &state->animation.start); 1060 assign_point_xyIF(state->hero.x, state->hero.y, 1061 &state->animation.position); 1062 assign_point_xyIF(state->hero.x, state->hero.y - 1, 1063 &state->animation.end); 1064 calculate_delta(state); 1065 state->animation.step = 1; 1066 state->animation.final_step = ANIM_STEPS; 1067 state->animation.active = 1; 1068 } 1069 else if (passable(state, state->hero.x + 1, state->hero.y)) 1070 { 1071 print_debug("next_step: Trying x+1, y: (%d, %d), " 1072 "pushed to stack (%d, %d)", 1073 state->hero.x + 1, state->hero.y, state->hero.x, 1074 state->hero.y); 1075 state->grid[INDEX(state->hero.x, state->hero.y)].contents 1076 = CELL_OK; 1077 location_pushI(state, &state->hero); 1078 assign_point_xyIF(state->hero.x, state->hero.y, 1079 &state->animation.start); 1080 assign_point_xyIF(state->hero.x, state->hero.y, 1081 &state->animation.position); 1082 assign_point_xyIF(state->hero.x + 1, state->hero.y, 1083 &state->animation.end); 1084 calculate_delta(state); 1085 state->animation.step = 1; 1086 state->animation.final_step = ANIM_STEPS; 1087 state->animation.active = 1; 1088 } 1089 else if (passable(state, state->hero.x, state->hero.y + 1)) 1090 { 1091 print_debug("next_step: Trying x, y+1: (%d, %d), " 1092 "pushed to stack (%d, %d)", 1093 state->hero.x, state->hero.y + 1, state->hero.x, 1094 state->hero.y); 1095 state->grid[INDEX(state->hero.x, state->hero.y)].contents 1096 = CELL_OK; 1097 location_pushI(state, &state->hero); 1098 assign_point_xyIF(state->hero.x, state->hero.y, 1099 &state->animation.start); 1100 assign_point_xyIF(state->hero.x, state->hero.y, 1101 &state->animation.position); 1102 assign_point_xyIF(state->hero.x, state->hero.y + 1, 1103 &state->animation.end); 1104 calculate_delta(state); 1105 state->animation.step = 1; 1106 state->animation.final_step = ANIM_STEPS; 1107 state->animation.active = 1; 1108 } 1109 else 1110 { 1111 /* Back up */ 1112 if (location_pop(state, &backup)) 1113 { 1114 print_debug("State: failure"); 1115 state->state = STATE_FAILURE; 1116 } 1117 else 1118 { 1119 print_debug("Backing up, stack pop: %d, %d", backup.x, 1120 backup.y); 1121 state->grid[INDEX(state->hero.x, state->hero.y)].contents 1122 = CELL_FAIL; 1123 assign_pointIF(&state->hero, &state->animation.start); 1124 assign_pointIF(&state->hero, &state->animation.position); 1125 assign_pointIF(&backup, &state->animation.end); 1126 calculate_delta(state); 1127 state->animation.step = 1; 1128 state->animation.final_step = ANIM_STEPS; 1129 state->animation.active = 1; 1130 } 1131 } 1132 state->redraw = 1; 1133 } 1134 1135 int 1136 passable(struct State* state, const int x, const int y) 1137 { 1138 return (x >= 0 && x < state->grid_width && y >= 0 1139 && y < state->grid_height 1140 && (state->grid[INDEX(x, y)].contents == CELL_EMPTY 1141 || state->grid[INDEX(x, y)].contents == CELL_END)); 1142 } 1143 1144 int 1145 point_equalI(struct SDL_Point* p1, struct SDL_Point* p2) 1146 { 1147 assert((p1 != NULL) && (p2 != NULL)); 1148 return p1->x == p2->x && p1->y == p2->y; 1149 } 1150 1151 void 1152 prepare_dialog(struct State* state, const int w, const int h, const char** text, 1153 const int dx, const int show) 1154 { 1155 assert((state != NULL) && (text != NULL)); 1156 assign_point_xyII(w, h, &state->dialog_dims); 1157 state->dialog_text_origin.x = dx; 1158 state->dialog_text = text; 1159 state->dialog_shown = show; 1160 if (show) 1161 state->redraw = 1; 1162 } 1163 1164 void 1165 print_debug(const char* fmt, ...) 1166 { 1167 char buf[PRINT_BUFSIZE]; 1168 va_list args; 1169 va_start(args, fmt); 1170 vsnprintf(buf, sizeof(buf), fmt, args); 1171 va_end(args); 1172 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "d: %s", buf); 1173 } 1174 1175 void 1176 print_error(const char* fmt, ...) 1177 { 1178 va_list args; 1179 va_start(args, fmt); 1180 SDL_LogError(SDL_LOG_CATEGORY_ERROR, fmt, args); 1181 va_end(args); 1182 } 1183 1184 void 1185 print_matrix(const struct State* state) 1186 { 1187 const struct Cell* c = NULL; 1188 int x, y; 1189 1190 for (y = 0; y < state->grid_height; y++) 1191 { 1192 for (x = 0; x < state->grid_width; x++) 1193 { 1194 c = &state->grid[INDEX(x, y)]; 1195 if (c->starting) 1196 printf("%s", cell_image[CELL_START]); 1197 else if (c->ending) 1198 printf("%s", cell_image[CELL_END]); 1199 else if (c->path) 1200 printf("%s", cell_image[CELL_PATH]); 1201 else if (!c->empty) 1202 printf("%s", cell_image[c->contents]); 1203 else 1204 printf("%s", cell_image[CELL_EMPTY]); 1205 // printf("(%0.0f,%0.0f) ", c->coords.x, c->coords.y); 1206 /*if (c->empty) 1207 printf("E "); 1208 else 1209 printf("F ");*/ 1210 } 1211 printf("\n"); 1212 } 1213 } 1214 1215 void 1216 print_path(struct Cell* c) 1217 { 1218 printf("(%0.0f, %0.0f)", c->coords.x, c->coords.y); 1219 if (c->path_next) 1220 { 1221 printf(" -> "); 1222 print_path(c->path_next); 1223 } 1224 else 1225 printf("\n"); 1226 } 1227 void 1228 render_background(struct State* state, SDL_FRect* bg_rect) 1229 { 1230 const SDL_DisplayMode* mode; 1231 SDL_FRect cell_rect, dest_rect; 1232 SDL_DisplayID display; 1233 float csf; 1234 float crx, cry; 1235 float drx, dry; 1236 float sx, sy; 1237 float ex, ey; 1238 int x, y; 1239 1240 assert((state != NULL) && (bg_rect != NULL)); 1241 assert(state->cell_scale_factor != 0.0f); 1242 1243 csf = state->cell_scale_factor; 1244 sx = state->start.x; 1245 sy = state->start.y; 1246 ex = state->end.x; 1247 ey = state->end.y; 1248 1249 if ((display = SDL_GetDisplayForWindow(state->window)) == 0) 1250 { 1251 print_error("SDL_GetDisplayForWindow failed: %s", 1252 SDL_GetError()); 1253 do_exit(1, state); 1254 } 1255 1256 if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL) 1257 { 1258 print_error("SDL_GetCurrentDisplayMode failed: %s", 1259 SDL_GetError()); 1260 do_exit(1, state); 1261 } 1262 1263 state->background = SDL_CreateTexture(state->renderer, mode->format, 1264 SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h); 1265 if (!state->background) 1266 { 1267 print_error("SDL_CreateTexture failed: %s", SDL_GetError()); 1268 do_exit(1, state); 1269 } 1270 if (!SDL_SetRenderTarget(state->renderer, state->background)) 1271 { 1272 print_error("SDL_SetRenderTarget failed: %s", SDL_GetError()); 1273 do_exit(1, state); 1274 } 1275 1276 SDL_SetRenderDrawColor(state->renderer, 0x22, 0x22, 0x22, 1277 SDL_ALPHA_OPAQUE); 1278 SDL_RenderFillRect(state->renderer, bg_rect); 1279 1280 cell_rect.w = CELL_SIZE; 1281 cell_rect.h = CELL_SIZE; 1282 dest_rect.w = csf * CELL_SIZE; 1283 dest_rect.h = csf * CELL_SIZE; 1284 for (y = 0; y < state->grid_height; y++) 1285 for (x = 0; x < state->grid_width; x++) 1286 { 1287 if (!((state->grid[INDEX(x, y)].contents != CELL_OK 1288 && state->grid[INDEX(x, y)].contents 1289 != CELL_FAIL) 1290 || (x == sx && y == sy) 1291 || (x == ex && y == ey))) 1292 continue; 1293 1294 get_sprite_xy(state->grid[INDEX(x, y)].contents, &crx, 1295 &cry); 1296 cell_rect.x = crx; 1297 cell_rect.y = cry; 1298 grid_coord_to_screen_coordI(state, x, y, 0.0f, 0.0f, 1299 &drx, &dry); 1300 dest_rect.x = drx; 1301 dest_rect.y = dry; 1302 SDL_RenderTexture(state->renderer, state->sprites, 1303 &cell_rect, &dest_rect); 1304 } 1305 1306 SDL_SetRenderTarget(state->renderer, NULL); 1307 } 1308 1309 /* state is not const because of renderer, etc */ 1310 void 1311 render_dialog(struct State* state) 1312 { 1313 const char** ptext = NULL; 1314 SDL_FRect dialog_rect /*, input_rect, cursor_rect*/; 1315 float tsf; 1316 int w, h; 1317 int sx, sy, dx, _sx; 1318 1319 assert(state != NULL); 1320 assert(state->dialog_text != NULL); 1321 assert(state->text_scale_factor != 0.0f); 1322 1323 ptext = state->dialog_text; 1324 w = state->dialog_dims.x; 1325 h = state->dialog_dims.y; 1326 sx = state->screen_width / 2 - w / 2; 1327 sy = state->screen_height / 2 - h / 2; 1328 dx = state->dialog_text_origin.x; 1329 tsf = state->text_scale_factor; 1330 1331 dialog_rect.x = sx; 1332 dialog_rect.y = sy; 1333 dialog_rect.w = w; 1334 dialog_rect.h = h; 1335 1336 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_BLEND); 1337 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, .75 * 255); 1338 SDL_RenderFillRect(state->renderer, &dialog_rect); 1339 1340 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_NONE); 1341 SDL_SetRenderDrawColor(state->renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); 1342 SDL_RenderRect(state->renderer, &dialog_rect); 1343 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 1344 dialog_rect.x--; 1345 dialog_rect.y--; 1346 dialog_rect.w += 2; 1347 dialog_rect.h += 2; 1348 SDL_RenderRect(state->renderer, &dialog_rect); 1349 dialog_rect.x--; 1350 dialog_rect.y--; 1351 dialog_rect.w += 2; 1352 dialog_rect.h += 2; 1353 SDL_RenderRect(state->renderer, &dialog_rect); 1354 1355 while (*ptext) 1356 { 1357 _sx = sx + dx + tsf * (2 * TER12_WIDTH); 1358 if (strchr(*ptext, CENTER_LINE)) 1359 _sx += w / 2 - dx 1360 - tsf * TER12_WIDTH 1361 * (2 + strlen_visible(*ptext) / 2); 1362 render_text(state, _sx, 1363 sy 1364 + (ptext - state->dialog_text + 1) * tsf 1365 * (TER12_HEIGHT + 2), 1366 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, "%s", 1367 *ptext); 1368 ptext++; 1369 } 1370 } 1371 1372 void 1373 render_hero(struct State* state, const float startx, const float starty) 1374 { 1375 SDL_FRect dest_rect, cell_rect; 1376 float csf; 1377 float crx, cry; 1378 float drx, dry; 1379 1380 assert(state != NULL); 1381 1382 csf = state->cell_scale_factor; 1383 cell_rect.w = CELL_SIZE; 1384 cell_rect.h = CELL_SIZE; 1385 dest_rect.w = csf * CELL_SIZE; 1386 dest_rect.h = csf * CELL_SIZE; 1387 get_hero_sprite_xy(state, &crx, &cry); 1388 cell_rect.x = crx; 1389 cell_rect.y = cry; 1390 if (state->animation.active) 1391 labyrinth_coord_to_screen_coordF(state->animation.position.x, 1392 state->animation.position.y, startx, starty, &drx, 1393 &dry); 1394 else 1395 labyrinth_coord_to_screen_coordI(state->hero.x, state->hero.y, 1396 startx, starty, &drx, &dry); 1397 dest_rect.x = drx; 1398 dest_rect.y = dry; 1399 SDL_RenderTexture(state->renderer, state->sprites, &cell_rect, 1400 &dest_rect); 1401 } 1402 1403 void 1404 render_status(struct State* state) 1405 { 1406 render_text(state, 0, 1407 state->screen_height - state->text_scale_factor * TER12_HEIGHT, 1408 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, 1409 "Column: \1%2d\2, Row: \1%2d\2" 1410 " \1F1\2 = Help" 1411 " Status: \1%s\2", 1412 state->hero.x + 1, state->hero.y + 1, 1413 state_descriptions[state->state]); 1414 } 1415 1416 void 1417 render_text(struct State* state, const int x, const int y, const int font_width, 1418 const int font_height, SDL_Texture* font_tex, const char* text, ...) 1419 { 1420 char buf[PRINT_BUFSIZE]; 1421 va_list args; 1422 const char* pbuf = NULL; 1423 SDL_FRect source_rect; 1424 SDL_FRect dest_rect; 1425 float tsf; 1426 int xstart = 0; 1427 int ystart = 0; 1428 int index = 0; 1429 int chars_per_row = 0; 1430 char first_char = ' '; 1431 char last_char = '~'; 1432 float tex_w, tex_h; 1433 float opacity = TEXT_OPACITY_NORMAL; 1434 1435 assert((font_tex != NULL) && (text != NULL)); 1436 assert(state->text_scale_factor != 0.0f); 1437 assert(font_width != -1); 1438 1439 tsf = state->text_scale_factor; 1440 1441 va_start(args, text); 1442 vsnprintf(buf, sizeof(buf), text, args); 1443 va_end(args); 1444 1445 source_rect.w = font_width; 1446 source_rect.h = font_height; 1447 1448 dest_rect.x = x; 1449 dest_rect.y = y; 1450 dest_rect.w = tsf * font_width; 1451 dest_rect.h = tsf * font_height; 1452 1453 if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h)) 1454 { 1455 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1456 "SDL_GetTextureSize failed: %s", SDL_GetError()); 1457 do_exit(1, state); 1458 } 1459 1460 SDL_SetTextureBlendMode(font_tex, SDL_BLENDMODE_BLEND); 1461 1462 chars_per_row = tex_w / (font_width + 1); 1463 1464 pbuf = buf; 1465 while (*pbuf) 1466 { 1467 if (*pbuf >= first_char && *pbuf <= last_char) 1468 { 1469 index = (int)(*pbuf - first_char); 1470 1471 xstart = 1 + index % chars_per_row * (font_width + 1); 1472 ystart = 1 + index / chars_per_row * (font_height + 1); 1473 } 1474 else if (*pbuf == CENTER_LINE) 1475 { 1476 pbuf++; 1477 continue; 1478 } 1479 else if (*pbuf == BOLD_START) 1480 { 1481 opacity = TEXT_OPACITY_BOLD; 1482 pbuf++; 1483 continue; 1484 } 1485 else if (*pbuf == BOLD_END) 1486 { 1487 opacity = TEXT_OPACITY_NORMAL; 1488 pbuf++; 1489 continue; 1490 } 1491 else if (*pbuf == DIM_START) 1492 { 1493 opacity = TEXT_OPACITY_DIM; 1494 pbuf++; 1495 continue; 1496 } 1497 else 1498 break; 1499 1500 source_rect.x = xstart; 1501 source_rect.y = ystart; 1502 1503 if (!SDL_SetTextureAlphaModFloat(font_tex, opacity)) 1504 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1505 "SDL_SetTextureAlphaModFloat is not supported"); 1506 SDL_RenderTexture(state->renderer, font_tex, &source_rect, 1507 &dest_rect); 1508 1509 dest_rect.x += tsf * font_width; 1510 1511 pbuf++; 1512 } 1513 } 1514 1515 void 1516 render_things(struct State* state, const float startx, const float starty) 1517 { 1518 SDL_FRect cell_rect, dest_rect; 1519 int x, y; 1520 float csf; 1521 float crx, cry; 1522 float drx, dry; 1523 1524 csf = state->cell_scale_factor; 1525 cell_rect.w = CELL_SIZE; 1526 cell_rect.h = CELL_SIZE; 1527 dest_rect.w = csf * CELL_SIZE; 1528 dest_rect.h = csf * CELL_SIZE; 1529 for (y = 0; y < state->grid_height; y++) 1530 for (x = 0; x < state->grid_width; x++) 1531 { 1532 if ((state->grid[INDEX(x, y)].contents != CELL_OK 1533 && state->grid[INDEX(x, y)].contents 1534 != CELL_FAIL) 1535 || (x == state->start.x && y == state->start.y) 1536 || (x == state->end.x && y == state->end.y)) 1537 continue; 1538 1539 get_sprite_xy(state->grid[INDEX(x, y)].contents, &crx, 1540 &cry); 1541 cell_rect.x = crx; 1542 cell_rect.y = cry; 1543 labyrinth_coord_to_screen_coordI(x, y, startx, starty, 1544 &drx, &dry); 1545 dest_rect.x = drx; 1546 dest_rect.y = dry; 1547 SDL_RenderTexture(state->renderer, state->sprites, 1548 &cell_rect, &dest_rect); 1549 } 1550 } 1551 1552 void 1553 reset(struct State* state) 1554 { 1555 int x, y; 1556 assert(state != NULL); 1557 1558 if (!state->search_shortest) 1559 state->location_pointer = 0; 1560 1561 for (y = 0; y < state->grid_height; y++) 1562 for (x = 0; x < state->grid_width; x++) 1563 if (state->grid[INDEX(x, y)].contents == CELL_FAIL 1564 || state->grid[INDEX(x, y)].contents == CELL_OK) 1565 state->grid[INDEX(x, y)].contents = CELL_EMPTY; 1566 state->hero.x = state->start.x; 1567 state->hero.y = state->start.y; 1568 state->state = STATE_SEARCHING_PAUSED; 1569 state->redraw = 1; 1570 } 1571 1572 void 1573 reset_path(struct State* state) 1574 { 1575 struct Cell* c = NULL; 1576 struct Cell* grid = NULL; 1577 int x, y; 1578 1579 assert(state != NULL); 1580 1581 grid = state->grid; 1582 for (y = 0; y < LABYRINTH_MAXHEIGHT; y++) 1583 for (x = 0; x < LABYRINTH_MAXWIDTH; x++) 1584 { 1585 c = &grid[INDEX(x, y)]; 1586 c->path_next = NULL; 1587 c->path_prev = NULL; 1588 c->path_dist = INT_MAX; 1589 c->starting = 0; 1590 c->ending = 0; 1591 c->path = 0; 1592 } 1593 } 1594 void 1595 step_animation(struct State* state) 1596 { 1597 struct Cell* from = NULL; 1598 struct Cell* to = NULL; 1599 int isx, isy, iex, iey; 1600 1601 assert((state != NULL) 1602 && !(state->animation.delta.x == 0.0f 1603 && state->animation.delta.y == 0.0f)); 1604 1605 if (state->state == STATE_SEARCHING_PAUSED) 1606 return; 1607 1608 if (!state->search_shortest) 1609 goto step_animation_brute_force; 1610 1611 if (state->animation.step == state->animation.final_step) 1612 { 1613 isx = (int)round(state->animation.start.x); 1614 isy = (int)round(state->animation.start.y); 1615 iex = (int)round(state->animation.end.x); 1616 iey = (int)round(state->animation.end.y); 1617 from = &state->grid[INDEX(isx, isy)]; 1618 to = &state->grid[INDEX(iex, iey)]; 1619 1620 if (!from->starting) 1621 from->contents = CELL_OK; 1622 1623 if (to->ending) 1624 { 1625 state->animation.active = 0; 1626 print_debug("Ending node reached: (%0.0f, %0.0f)", 1627 state->animation.end.x, state->animation.end.y); 1628 } 1629 else if (to->path_next) 1630 { 1631 state->animation.start.x = state->animation.end.x; 1632 state->animation.start.y = state->animation.end.y; 1633 state->animation.position.x = state->animation.start.x; 1634 state->animation.position.y = state->animation.start.y; 1635 state->animation.end.x = to->path_next->coords.x; 1636 state->animation.end.y = to->path_next->coords.y; 1637 1638 print_debug("Next node on path: (%0.0f, %0.0f)", 1639 state->animation.end.x, state->animation.end.y); 1640 print_debug( 1641 "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 1642 state->animation.start.x, 1643 state->animation.start.y, 1644 state->animation.end.x, state->animation.end.y); 1645 calculate_delta(state); 1646 state->animation.step = 1; 1647 } 1648 else 1649 { 1650 print_debug("End of path at (%0.0f, %0.0f)", 1651 state->animation.end.x, state->animation.end.y); 1652 state->animation.active = 0; 1653 } 1654 1655 print_debug("grid[%d][%d] = %d <-> grid[%d][%d] = %d", iey, iex, 1656 to->contents, isy, isx, from->contents); 1657 state->hero.x = to->coords.x; 1658 state->hero.y = to->coords.y; 1659 1660 if (to->ending) 1661 { 1662 state->state = STATE_SUCCESS; 1663 state->redraw = 1; 1664 } 1665 } 1666 1667 step_animation_brute_force: 1668 print_debug("step_animation: (%0.2f, %0.2f) => (%0.2f, %0.2f)", 1669 state->animation.position.x, state->animation.position.y, 1670 state->animation.position.x + state->animation.delta.x, 1671 state->animation.position.y + state->animation.delta.y); 1672 state->animation.position.x += state->animation.delta.x; 1673 state->animation.position.y += state->animation.delta.y; 1674 state->animation.step++; 1675 1676 if (state->search_shortest) 1677 return; 1678 1679 if (state->animation.step == state->animation.final_step) 1680 { 1681 state->animation.active = 0; 1682 state->hero.x = (int)state->animation.end.x; 1683 state->hero.y = (int)state->animation.end.y; 1684 } 1685 } 1686 1687 int 1688 strlen_visible(const char* s) 1689 { 1690 int result = 0; 1691 const char* ps = NULL; 1692 1693 assert(s != NULL); 1694 ps = s; 1695 while (*ps) 1696 { 1697 if (*ps != CENTER_LINE && *ps != BOLD_START && *ps != BOLD_END 1698 && *ps != DIM_START) 1699 result++; 1700 ps++; 1701 } 1702 print_debug("strlen_visible(%s)=%d\n", s, result); 1703 return result; 1704 } 1705 1706 SDL_Texture* 1707 try_load(SDL_Renderer* renderer, const char* filename) 1708 { 1709 SDL_Texture* tex = NULL; 1710 char tex_pathname[BUFSIZE]; 1711 1712 assert((renderer != NULL) && (filename != NULL)); 1713 snprintf(tex_pathname, BUFSIZE, "%s/%s", DATADIR, filename); 1714 tex = IMG_LoadTexture(renderer, tex_pathname); 1715 if (!tex) 1716 { 1717 print_error("try_load: IMG_LoadTexture failed: %s", 1718 SDL_GetError()); 1719 snprintf(tex_pathname, BUFSIZE, "./%s", filename); 1720 print_debug("try_load: trying %s", tex_pathname); 1721 tex = IMG_LoadTexture(renderer, tex_pathname); 1722 if (!tex) 1723 { 1724 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1725 "try_load: IMG_LoadTexture failed: %s", 1726 SDL_GetError()); 1727 exit(1); 1728 } 1729 print_debug("try_load: success (%s)", tex_pathname); 1730 } 1731 1732 return tex; 1733 } 1734 1735 int 1736 main(int argc, char** argv) 1737 { 1738 SDL_Event event; 1739 struct State state; 1740 float px, py; 1741 unsigned int last_time, current_time; 1742 char datafile_pathname[BUFSIZE]; 1743 int optind = 1; 1744 1745 init_state(&state); 1746 1747 if (!SDL_Init(SDL_INIT_VIDEO)) 1748 { 1749 print_error("SDL_Init failed: %s", SDL_GetError()); 1750 exit(1); 1751 } 1752 1753 SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG); 1754 if (!SDL_CreateWindowAndRenderer(WIN_TITLE, DEFAULT_WIDTH, 1755 DEFAULT_HEIGHT, 1756 SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, 1757 &state.window, &state.renderer)) 1758 { 1759 print_error("SDL_CreateWindowAndRenderer failed: %s", 1760 SDL_GetError()); 1761 do_exit(1, &state); 1762 } 1763 1764 SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE); 1765 SDL_SetRenderLogicalPresentation(state.renderer, DEFAULT_WIDTH, 1766 DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); 1767 1768 calculate_display_size(&state); 1769 1770 if (load_textures(&state) < 0) 1771 do_exit(1, &state); 1772 1773 if (argc > 1) 1774 { 1775 if (!strcmp(argv[1], "-s")) 1776 { 1777 state.search_shortest = 1; 1778 optind++; 1779 if (optind >= argc) 1780 goto load_default_file; 1781 } 1782 1783 if (load_labyrinth(&state, argv[optind]) < 0) 1784 { 1785 print_error("load_labyrinth failed: %s (%s)", 1786 strerror(errno), argv[optind]); 1787 do_exit(1, &state); 1788 } 1789 } 1790 else 1791 { 1792 load_default_file: 1793 snprintf(datafile_pathname, BUFSIZE, "%s/%s", DATADIR, 1794 DEFAULT_DATAFILE); 1795 if (load_labyrinth(&state, datafile_pathname) < 0) 1796 { 1797 print_debug("fopen failed: %s (%s)", strerror(errno), 1798 datafile_pathname); 1799 snprintf(datafile_pathname, BUFSIZE, "./%s", 1800 DEFAULT_DATAFILE); 1801 print_debug("trying %s", datafile_pathname); 1802 if (load_labyrinth(&state, datafile_pathname) < 0) 1803 { 1804 print_error("fopen failed: %s (%s)", 1805 strerror(errno), datafile_pathname); 1806 do_exit(1, &state); 1807 } 1808 } 1809 } 1810 state.location_stack = calloc(LOCATION_STACK_MAX, sizeof(SDL_Point)); 1811 state.animation.active = 0; 1812 1813 SDL_HideCursor(); 1814 SDL_GetMouseState(&px, &py); 1815 state.pointer.x = px; 1816 state.pointer.y = py; 1817 last_time = SDL_GetTicks(); 1818 while (state.running) 1819 { 1820 current_time = SDL_GetTicks(); 1821 if (state.animation.active) 1822 { 1823 if (current_time > last_time + 1000 / 60) 1824 { 1825 draw(&state); 1826 step_animation(&state); 1827 last_time = current_time; 1828 } 1829 } 1830 else 1831 { 1832 if (state.state == STATE_SEARCHING) 1833 { 1834 next_step(&state); 1835 } 1836 1837 if (state.redraw) 1838 { 1839 draw(&state); 1840 state.redraw = 0; 1841 } 1842 } 1843 1844 while (SDL_PollEvent(&event)) 1845 handle_event(&state, &event); 1846 } 1847 1848 return 0; 1849 }