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