linije.c (61754B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2025-2026 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 /* 6 * Term definitions 7 * 8 * ball string A set of adjacent (horizontally, vertically or 9 * diagonally) balls with the same color 10 * 11 * popping the string 12 * Erasing all the balls in the string from the board 13 */ 14 #include <SDL3/SDL.h> 15 #include <SDL3_image/SDL_image.h> 16 #include <assert.h> 17 #include <errno.h> 18 #include <limits.h> 19 #include <math.h> 20 #include <stdarg.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <time.h> 24 #include <unistd.h> 25 26 #include "hiscore.h" 27 #include "local.h" 28 #include "version.h" 29 30 /* clang-format off */ 31 #define ANIM_SPEED 100 /* how long does it take for the animation to go 32 * from start to finish (ms) */ 33 /* clang-format on */ 34 #define FPS 60 35 #define ANIM_STEPS (ANIM_SPEED * FPS / 1000) 36 37 #define BOLD_START '\1' 38 #define BOLD_END '\2' 39 #define CENTER_LINE '\3' 40 #define DIM_START '\4' 41 #define DIM_END '\2' 42 #define CELL_SCALE_FACTOR 2.0f 43 #define BUFSIZE 4096 44 #define CELL_SIZE 20 45 #define DEFAULT_WIDTH 1280 46 #define DEFAULT_HEIGHT 720 47 #define HOF_DIALOG_MAX_LINE_LEN 80 48 // #define HOF_DIALOG_TEXT_ORIGIN_X 70 49 #define HOF_DIALOG_TEXT_ORIGIN_X 0 50 #define HOF_STANDOUT_ENTRIES 3 51 #define GAME_OVER_DIALOG_WIDTH 270 52 #define GAME_OVER_DIALOG_HEIGHT 110 53 #define GRID_WIDTH 15 54 #define GRID_HEIGHT 15 55 #define INPUT_WIDTH 600 56 #define INPUT_HEIGHT 120 57 #define LOCATION_STACK_MAX (GRID_WIDTH * GRID_HEIGHT) 58 #define MIN_POP_LEN 5 59 /* clang-format off */ 60 #define POP_STRING_CELL_VALUE 100 /* Multiplied by # of balls in a string and 61 * added to score when popping a string */ 62 /* clang-format on */ 63 #define PRINT_BUFSIZE 4096 64 #define PROGNAME "linije" 65 #define QUIT_MSGBOX_WIDTH 450 66 #define QUIT_MSGBOX_HEIGHT 150 67 #define TER12_WIDTH 6 68 #define TER12_HEIGHT 12 69 #define TEXT_OPACITY_DIM 0.3f 70 #define TEXT_OPACITY_NORMAL 0.6f 71 #define TEXT_OPACITY_BOLD 1.0f 72 #define TEXT_SCALE_FACTOR 2.0f 73 #define WIN_TITLE "Linije" 74 75 #define HELP_DIALOG_WIDTH TER12_WIDTH * 75 * TEXT_SCALE_FACTOR 76 #define HELP_DIALOG_HEIGHT TER12_HEIGHT * 20 * TEXT_SCALE_FACTOR 77 #define HOF_DIALOG_WIDTH \ 78 TER12_WIDTH*(HOF_DIALOG_MAX_LINE_LEN + 3) * TEXT_SCALE_FACTOR 79 #define HOF_DIALOG_HEIGHT TER12_HEIGHT * 19 * TEXT_SCALE_FACTOR 80 81 #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 82 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 83 #define INDEX(x, y) ((y) * GRID_WIDTH + (x)) 84 #define INDEXFI(x, y) (((int)floor(y)) * GRID_WIDTH + ((int)floor(x))) 85 #define UNUSED(x) ((void)(x)) 86 #define VALNAME(n) [n] = #n 87 88 /* clang-format off */ 89 enum { 90 CELL_EMPTY, 91 CELL_RED, 92 CELL_BLUE, 93 CELL_GREEN, 94 CELL_PURPLE, 95 CELL_ORANGE, 96 CELL_SELECTED 97 }; 98 99 enum { 100 STATE_IDLE, 101 STATE_SELECTION, 102 STATE_MOVING, 103 STATE_GAME_OVER 104 }; 105 106 const char* cell_names[] = { 107 VALNAME(CELL_EMPTY), 108 VALNAME(CELL_RED), 109 VALNAME(CELL_BLUE), 110 VALNAME(CELL_GREEN), 111 VALNAME(CELL_PURPLE), 112 VALNAME(CELL_ORANGE), 113 VALNAME(CELL_SELECTED), 114 }; 115 116 const int check_five_deltas[][4] = { 117 { -1, -1, 1, 1 }, 118 { 1, -1, -1, 1 }, 119 { 0, -1, 0, 1 }, 120 { -1, 0, 1, 0 }, 121 }; 122 123 const SDL_Point sprite_positions[] = { 124 [CELL_EMPTY] = { .x = 0, .y = 0 }, 125 [CELL_RED] = { .x = 1 * CELL_SIZE, .y = 0 }, 126 [CELL_BLUE] = { .x = 2 * CELL_SIZE, .y = 0 }, 127 [CELL_GREEN] = { .x = 3 * CELL_SIZE, .y = 0 }, 128 [CELL_PURPLE] = { .x = 4 * CELL_SIZE, .y = 0 }, 129 [CELL_ORANGE] = { .x = 5 * CELL_SIZE, .y = 0 }, 130 [CELL_SELECTED] = { .x = 0, .y = 1 * CELL_SIZE }, 131 }; 132 133 const char* state_descriptions[] = { 134 "Idle", 135 "Selecting", 136 "Moving", 137 "Game over" 138 }; 139 140 const char* game_over_text[] = { 141 "\3==== \1GAME OVER\2 ====", 142 "\3Press \1F2\2 to restart", 143 NULL 144 }; 145 146 const char* help_text[] = { 147 "\3==== \1linije - Color lines clone\2 ====", 148 "\3\\\\ Version " VERSION " //", 149 "", 150 "\1F1\2 - Show this help screen", 151 "\1F2\2 - Restart the game", 152 "\1F3\2 - Show the hall of fame", 153 "\1Esc\2 - Hide this help screen", 154 "\1Alt+Enter\2 - Toggle fullscreen", 155 "\1Ctrl+Q\2 - Exit", 156 "", 157 "---", 158 "", 159 "\4This program is licensed under the terms of GNU GPL v3 or (at your\2", 160 "\4option) any later version. Copyright (C) 2025-2026 Strahinya Radich.\2", 161 "\4See the file LICENSE for exact copyright and license details.\2", 162 NULL 163 }; 164 165 const char* name_text[] = { 166 "\3==== \1ENTER YOUR NAME\2 ====", 167 "", 168 NULL 169 }; 170 171 const char* quit_msgbox_text[] = { 172 "\3==== \1CONFIRM\2 ====", 173 "", 174 "\3Do you really want to quit [\1y\2/\1n\2]?", 175 NULL 176 }; 177 178 const char* restart_msgbox_text[] = { 179 "\3==== \1CONFIRM\2 ====", 180 "", 181 "\3Do you really want to start a new game [\1y\2/\1n\2]?", 182 NULL 183 }; 184 185 const char ascii_shift[255] = { 186 ['`'] = '~', ['1'] = '!', ['2'] = '@', ['3'] = '#', ['4'] = '$', 187 ['5'] = '%', ['6'] = '^', ['7'] = '&', ['8'] = '*', ['9'] = '(', 188 ['0'] = ')', ['-'] = '_', ['='] = '+', 189 190 ['q'] = 'Q', ['w'] = 'W', ['e'] = 'E', ['r'] = 'R', ['t'] = 'T', 191 ['y'] = 'Y', ['u'] = 'U', ['i'] = 'I', ['o'] = 'O', ['p'] = 'P', 192 ['['] = '{', [']'] = '}', 193 194 ['a'] = 'A', ['s'] = 'S', ['d'] = 'D', ['f'] = 'F', ['g'] = 'G', 195 ['h'] = 'H', ['j'] = 'J', ['k'] = 'K', ['l'] = 'L', [';'] = ':', 196 ['\''] = '"',['\\'] = '|', 197 198 ['z'] = 'Z', ['x'] = 'X', ['c'] = 'C', ['v'] = 'V', ['b'] = 'B', 199 ['n'] = 'N', ['m'] = 'M', [','] = '<', ['.'] = '>', ['/'] = '?' 200 }; 201 /* clang-format on */ 202 203 struct AnimationState { 204 SDL_FPoint start; 205 SDL_FPoint position; 206 SDL_FPoint end; 207 SDL_FPoint delta; 208 int active; 209 int step; 210 int final_step; 211 }; 212 213 struct Cell { 214 int empty; /* logical variables */ 215 int popping; 216 int path; 217 int starting; 218 int ending; /* logical variables end */ 219 SDL_FPoint coords; 220 int contents; 221 struct Cell* down; 222 struct Cell* left; 223 struct Cell* right; 224 struct Cell* up; 225 struct Cell* path_next; /* next cell in shortest path */ 226 struct Cell* path_prev; /* previous cell in shortest path */ 227 int path_dist; /* length of shortest path so far */ 228 }; 229 230 /* clang-format off */ 231 struct State { 232 struct AnimationState animation; 233 SDL_Texture* background; 234 struct Cell* grid; 235 float cell_scale_factor; 236 int check_score; 237 SDL_Point dialog_dims; 238 SDL_Point dialog_text_origin; 239 int dialog_shown; 240 const char** dialog_text; 241 int display_width; /* Physical, detected */ 242 int display_height; 243 int full_screen; 244 struct HiScoreEntry* hall_of_fame; 245 void (*input_callback)(struct State*); 246 int input_col; 247 int input_instant_reset; 248 int input_shown; 249 int location_pointer; 250 SDL_FPoint* location_stack; 251 int mode; 252 SDL_FPoint mouse; 253 int moveno; 254 int nfree; 255 struct Cell* path_start; 256 struct Cell* path_end; 257 char player_name[HI_SCORE_NAME_MAX]; 258 int player_name_length; 259 int query_shown; 260 void (*query_callback)(struct State*); 261 int redraw; 262 SDL_Renderer* renderer; 263 int running; 264 int screen_width; /* Logical */ 265 int screen_height; 266 unsigned long int score; 267 SDL_FPoint selection_start, selection_end; 268 SDL_Texture* sprites; 269 SDL_Texture* ter12_texture; 270 float text_scale_factor; 271 SDL_Window* window; 272 }; 273 /* clang-format on */ 274 275 void add_cell(struct State* state, struct Cell* c, const int color, 276 const int index); 277 void assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to); 278 void assign_pointIF(const SDL_Point* from, SDL_FPoint* to); 279 void assign_point_xyIF(const int x, const int y, SDL_FPoint* to); 280 void assign_point_xyII(const int x, const int y, SDL_Point* to); 281 void calculate_delta(struct State* state); 282 void calculate_grid_origin(const struct State* state, float* sx, float* sy); 283 void calculate_display_size(struct State* state); 284 int check_five(struct State* state, struct Cell* c); 285 void cleanup(struct State* state); 286 void do_exit(const int code, struct State* state); 287 void draw(struct State* state); 288 void erase_cell(struct State* state, struct Cell* c); 289 void fill_triplet(struct State* state); 290 int find_shortest_path(struct Cell* c, struct Cell* source, const int dist); 291 void game_over(struct State* state); 292 void game_over_dialog(struct State* state); 293 void get_sprite_xy(const char cell, float* x, float* y); 294 void grid_coord_to_screen_coord(const struct State* state, const int x, 295 const int y, const float startx, const float starty, float* to_x, 296 float* to_y); 297 void grid_coord_to_screen_coordF(const struct State* state, const float x, 298 const float y, const float startx, const float starty, float* to_x, 299 float* to_y); 300 void handle_click(struct State* state); 301 void handle_event(struct State* state, SDL_Event* event); 302 void handle_input(struct State* state, SDL_Event* event); 303 void handle_key_down(struct State* state, SDL_Event* event); 304 void init_cell(struct Cell* c, const int full, const SDL_FPoint* coords); 305 void init_state(struct State* state); 306 void input_name(struct State* state); 307 void input_name_callback(struct State* state); 308 int load_textures(struct State* state); 309 void location_delete(struct State* state, const int index); 310 int location_index(const struct State* state, const SDL_FPoint* location); 311 void location_push(struct State* state, const SDL_FPoint* location); 312 void mark_five(struct State* state, SDL_FPoint* start, SDL_FPoint* end); 313 void mark_path(struct Cell* c); 314 int num_size(const unsigned int num); 315 void pop_strings(struct State* state); 316 void prepare_dialog(struct State* state, const int w, const int h, 317 const char** text, const int dx, const int show); 318 void prepare_hof_dialog(struct State* state, const int show); 319 void print_matrix(const struct Cell* M); 320 void print_path(struct Cell* c); 321 void quit_callback(struct State* state); 322 void really_quit(struct State* state); 323 void render_background(struct State* state, SDL_FRect* bg_rect); 324 void render_balls(struct State* state, const float startx, const float starty); 325 void render_dialog(struct State* state); 326 void render_selection(struct State* state, const float startx, 327 const float starty); 328 void render_status(struct State* state); 329 void render_text(struct State* state, const int x, const int y, 330 const int font_width, const int font_height, SDL_Texture* font_tex, 331 const char* text, ...); 332 void repeat_char(char** s, const char ch, const int num_times); 333 void reset(struct State* state); 334 void reset_path(struct Cell* grid); 335 void screen_coord_to_grid_coord(struct State* state, const int x, const int y, 336 const float startx, const float starty, float* to_x, float* to_y); 337 void screen_coord_to_grid_coordF(struct State* state, const float x, 338 const float y, const float startx, const float starty, float* to_x, 339 float* to_y); 340 void step_animation(struct State* state); 341 int strlen_visible(const char* s); 342 SDL_Texture* try_load(SDL_Renderer* renderer, const char* filename); 343 344 void 345 add_cell(struct State* state, struct Cell* c, const int color, const int index) 346 { 347 assert((state != NULL) && (c != NULL)); 348 c->contents = color; 349 c->empty = 0; 350 state->nfree--; 351 location_delete(state, index); 352 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 353 "add_cell: Cell at (%0.0f, %0.0f) is %s, " 354 "new location_pointer is %d", 355 c->coords.x, c->coords.y, cell_names[c->contents], 356 state->location_pointer); 357 } 358 359 void 360 assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to) 361 { 362 assert((from != NULL) && (to != NULL)); 363 to->x = from->x; 364 to->y = from->y; 365 } 366 367 void 368 assign_pointIF(const SDL_Point* from, SDL_FPoint* to) 369 { 370 assert((from != NULL) && (to != NULL)); 371 to->x = (float)from->x; 372 to->y = (float)from->y; 373 } 374 375 void 376 assign_point_xyIF(const int x, const int y, SDL_FPoint* to) 377 { 378 assert(to != NULL); 379 to->x = (float)x; 380 to->y = (float)y; 381 } 382 383 void 384 assign_point_xyII(const int x, const int y, SDL_Point* to) 385 { 386 assert(to != NULL); 387 to->x = x; 388 to->y = y; 389 } 390 391 void 392 calculate_delta(struct State* state) 393 { 394 SDL_FPoint* start = NULL; 395 SDL_FPoint* end = NULL; 396 SDL_FPoint* delta = NULL; 397 398 assert(state != NULL); 399 400 start = &state->animation.start; 401 end = &state->animation.end; 402 delta = &state->animation.delta; 403 404 delta->x = fabsf(end->x - start->x) / ANIM_STEPS; 405 if (end->x < start->x) 406 delta->x *= -1; 407 delta->y = fabsf(end->y - start->y) / ANIM_STEPS; 408 if (end->y < start->y) 409 delta->y *= -1; 410 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 411 "calculate_delta: (%0.2f, %0.2f)", delta->x, delta->y); 412 } 413 414 void 415 calculate_grid_origin(const struct State* state, float* sx, float* sy) 416 { 417 int cx, cy; 418 float csf; 419 420 assert((state != NULL) && (sx != NULL) && (sy != NULL)); 421 422 csf = state->cell_scale_factor; 423 cx = state->screen_width / 2; 424 cy = state->screen_height / 2; 425 426 *sx = cx - (csf * GRID_WIDTH * CELL_SIZE) / 2; 427 *sy = cy - (csf * GRID_HEIGHT * CELL_SIZE) / 2; 428 } 429 430 void 431 calculate_display_size(struct State* state) 432 { 433 assert(state != NULL); 434 435 if (!SDL_GetRenderOutputSize(state->renderer, &state->display_width, 436 &state->display_height)) 437 { 438 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 439 "SDL_GetRenderOutputSize failed"); 440 do_exit(1, state); 441 } 442 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 443 "output display size: (%d, %d)", state->display_width, 444 state->display_height); 445 } 446 447 /* Returns: 0 if no strings, 1 if present */ 448 int 449 check_five(struct State* state, struct Cell* c) 450 { 451 SDL_FPoint start, end; 452 int x, y, len; 453 int i; 454 int found_string = 0; 455 456 assert((state != NULL) && (c != NULL)); 457 458 i = 0; 459 check_five_loop: 460 assign_pointFF(&c->coords, &start); 461 assign_pointFF(&c->coords, &end); 462 x = c->coords.x; 463 y = c->coords.y; 464 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Deltas: %d, %d", 465 check_five_deltas[i][0], check_five_deltas[i][1]); 466 len = 1; 467 while ((x >= 0 && y >= 0) && (x < GRID_WIDTH && y < GRID_HEIGHT)) 468 { 469 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "At (%d, %d): %s", x, 470 y, cell_names[state->grid[INDEX(x, y)].contents]); 471 if (state->grid[INDEX(x, y)].contents == c->contents) 472 { 473 assign_point_xyIF(x, y, &start); 474 len++; 475 } 476 else 477 break; 478 x += check_five_deltas[i][0]; 479 y += check_five_deltas[i][1]; 480 } 481 x = c->coords.x + check_five_deltas[i][2]; 482 y = c->coords.y + check_five_deltas[i][3]; 483 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Deltas: %d, %d", 484 check_five_deltas[i][2], check_five_deltas[i][3]); 485 while ((x >= 0 && y >= 0) && (x < GRID_WIDTH && y < GRID_HEIGHT)) 486 { 487 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "At (%d, %d): %s", x, 488 y, cell_names[state->grid[INDEX(x, y)].contents]); 489 if (state->grid[INDEX(x, y)].contents == c->contents) 490 { 491 assign_point_xyIF(x, y, &end); 492 len++; 493 } 494 else 495 break; 496 x += check_five_deltas[i][2]; 497 y += check_five_deltas[i][3]; 498 } 499 if (len > MIN_POP_LEN) 500 { 501 mark_five(state, &start, &end); 502 state->score += (len - 1) * POP_STRING_CELL_VALUE; 503 found_string = 1; 504 } 505 else 506 { 507 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 508 "check_five: len = %d, not popping", len); 509 } 510 i++; 511 if (i < (ssize_t)(sizeof(check_five_deltas) 512 / sizeof(check_five_deltas[0]))) 513 goto check_five_loop; 514 515 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 516 "check_five: nfree = %d < 3? %s", state->nfree, 517 state->nfree < 3 ? "TRUE" : "FALSE"); 518 519 state->mode = STATE_IDLE; 520 521 return found_string; 522 } 523 524 void 525 cleanup(struct State* state) 526 { 527 assert(state != NULL); 528 529 free(state->location_stack); 530 531 if (state->background) 532 SDL_DestroyTexture(state->background); 533 if (state->ter12_texture) 534 SDL_DestroyTexture(state->ter12_texture); 535 if (state->sprites) 536 SDL_DestroyTexture(state->sprites); 537 if (state->renderer) 538 SDL_DestroyRenderer(state->renderer); 539 if (state->window) 540 SDL_DestroyWindow(state->window); 541 542 SDL_Quit(); 543 } 544 545 void 546 do_exit(const int code, struct State* state) 547 { 548 cleanup(state); 549 exit(code); 550 } 551 552 void 553 draw(struct State* state) 554 { 555 SDL_FRect bg_rect; 556 SDL_FRect dest_rect; 557 float startx, starty; 558 float csf; 559 560 assert(state != NULL); 561 assert(state->cell_scale_factor != 0.0f); 562 563 csf = state->cell_scale_factor; 564 calculate_grid_origin(state, &startx, &starty); 565 566 bg_rect.x = 0; 567 bg_rect.y = 0; 568 bg_rect.w = csf * (CELL_SIZE * GRID_WIDTH + 1); 569 bg_rect.h = csf * (CELL_SIZE * GRID_HEIGHT + 1); 570 571 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 572 SDL_RenderClear(state->renderer); 573 574 /* Render background */ 575 if (!state->background) 576 render_background(state, &bg_rect); 577 578 /* Draw buffered copy of background */ 579 dest_rect.x = startx; 580 dest_rect.y = starty; 581 dest_rect.w = bg_rect.w; 582 dest_rect.h = bg_rect.h; 583 SDL_RenderTexture(state->renderer, state->background, &bg_rect, 584 &dest_rect); 585 586 /* Render balls */ 587 render_balls(state, startx, starty); 588 589 /* Render selection */ 590 if (state->selection_start.x != -1) 591 render_selection(state, startx, starty); 592 593 /* Render status */ 594 render_status(state); 595 596 if (state->dialog_shown) 597 render_dialog(state); 598 599 /*render_text(600, screen_height - text_scale_factor * TER12_HEIGHT, 600 TER12_WIDTH, TER12_HEIGHT, ter12_texture, "(%05.2f, %05.2f)", 601 pointer.x, pointer.y);*/ 602 603 SDL_RenderPresent(state->renderer); 604 } 605 606 void 607 erase_cell(struct State* state, struct Cell* c) 608 { 609 assert((state != NULL) && (c != NULL)); 610 c->contents = CELL_EMPTY; 611 c->empty = 1; 612 location_push(state, &c->coords); 613 } 614 615 void 616 fill_triplet(struct State* state) 617 { 618 struct Cell* c = NULL; 619 SDL_FPoint* loc = NULL; 620 int idx; 621 int pass; 622 char color; 623 624 pass = 0; 625 fill_triplet_generate: 626 if (state->location_pointer == 0) 627 goto fill_triplet_fail; 628 idx = random() % state->location_pointer; 629 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 630 "fill_triplet: idx=%d/%d (%0.0f, %0.0f)", idx, 631 state->location_pointer, state->location_stack[idx].x, 632 state->location_stack[idx].y); 633 loc = &state->location_stack[idx]; 634 c = &state->grid[INDEXFI(loc->x, loc->y)]; 635 636 /* Should never happen */ 637 if (c->contents != CELL_EMPTY) 638 { 639 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 640 "fill_triplet: Should not happen! Contents of cell " 641 "at (%0.0f, %0.0f) is %s", 642 c->coords.x, c->coords.y, cell_names[c->contents]); 643 do_exit(1, state); 644 goto fill_triplet_generate; 645 } 646 647 switch (random() % 5) 648 { 649 case 0: 650 color = CELL_RED; 651 break; 652 case 1: 653 color = CELL_BLUE; 654 break; 655 case 2: 656 color = CELL_GREEN; 657 break; 658 case 3: 659 color = CELL_PURPLE; 660 break; 661 default: 662 color = CELL_ORANGE; 663 } 664 add_cell(state, c, color, idx); 665 if (check_five(state, c)) 666 { 667 pop_strings(state); 668 goto fill_triplet_generate; 669 } 670 /* Shouldn't happen */ 671 if (state->nfree < 3) 672 goto fill_triplet_fail; 673 pass++; 674 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 675 "fill_triplet: %s at (%0.0f, %0.0f)", cell_names[(int)color], 676 c->coords.x, c->coords.y); 677 if (pass < 3) 678 goto fill_triplet_generate; 679 680 return; 681 682 fill_triplet_fail: 683 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 684 "fill_triplet: input_instant_reset = %s", 685 state->input_instant_reset ? "TRUE" : "FALSE"); 686 game_over(state); 687 } 688 689 int 690 find_shortest_path(struct Cell* c, struct Cell* source, const int dist) 691 { 692 int result = 0; 693 694 if (!c || c->path_dist <= dist) 695 return 0; 696 697 if (!c->empty && !c->starting && !c->ending) 698 return 0; 699 700 if (c->starting) 701 c->path_dist = dist; 702 703 if (c->path_dist > dist) 704 { 705 c->path_dist = dist; 706 c->path_prev = source; 707 } 708 709 if (c->ending) 710 return 1; 711 712 result |= find_shortest_path(c->left, c, dist + 1); 713 result |= find_shortest_path(c->up, c, dist + 1); 714 result |= find_shortest_path(c->right, c, dist + 1); 715 result |= find_shortest_path(c->down, c, dist + 1); 716 717 return result; 718 } 719 720 void 721 game_over(struct State* state) 722 { 723 assert(state != NULL); 724 state->mode = STATE_GAME_OVER; 725 if (state->check_score 726 && state->score > state->hall_of_fame[HI_SCORE_MAX - 1].score) 727 { 728 state->check_score = 0; 729 input_name(state); 730 } 731 else 732 { 733 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 734 "game_over: input_instant_reset = %s", 735 state->input_instant_reset ? "TRUE" : "FALSE"); 736 if (state->input_instant_reset) 737 { 738 reset(state); 739 fill_triplet(state); 740 state->check_score = 1; 741 state->input_instant_reset = 0; 742 } 743 else 744 game_over_dialog(state); 745 } 746 } 747 748 void 749 game_over_dialog(struct State* state) 750 { 751 prepare_dialog(state, GAME_OVER_DIALOG_WIDTH, GAME_OVER_DIALOG_HEIGHT, 752 game_over_text, 0, 1); 753 } 754 755 void 756 get_sprite_xy(const char cell, float* x, float* y) 757 { 758 assert((x != NULL) && (y != NULL)); 759 *x = sprite_positions[(int)cell].x; 760 *y = sprite_positions[(int)cell].y; 761 } 762 763 void 764 grid_coord_to_screen_coord(const struct State* state, const int x, const int y, 765 const float startx, const float starty, float* to_x, float* to_y) 766 { 767 assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); 768 *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; 769 *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; 770 } 771 772 void 773 grid_coord_to_screen_coordF(const struct State* state, const float x, 774 const float y, const float startx, const float starty, float* to_x, 775 float* to_y) 776 { 777 assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); 778 *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; 779 *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; 780 } 781 782 static char* cell_image[] = {"[ ]", "[R]", "[B]", "[G]", "[P]", "[O]", "[/]", 783 "[S]", "[E]", "[.]"}; 784 785 #define CELL_START 7 786 #define CELL_END 8 787 #define CELL_PATH 9 788 789 void 790 handle_click(struct State* state) 791 { 792 float startx, starty; 793 794 assert(state != NULL); 795 796 if (state->dialog_shown) 797 return; 798 799 if (state->mode == STATE_IDLE) 800 { 801 calculate_grid_origin(state, &startx, &starty); 802 screen_coord_to_grid_coord(state, state->mouse.x, 803 state->mouse.y, startx, starty, 804 &state->selection_start.x, &state->selection_start.y); 805 state->animation.start.x = (int)state->selection_start.x; 806 state->animation.start.y = (int)state->selection_start.y; 807 state->animation.position.x = state->animation.start.x; 808 state->animation.position.y = state->animation.start.y; 809 state->path_start 810 = &state->grid[INDEXFI(state->animation.start.x, 811 state->animation.start.y)]; 812 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 813 "path_start(%0.2f, %0.2f)->empty = %s", 814 state->path_start->coords.x, 815 state->path_start->coords.y, 816 state->path_start->empty ? "TRUE" : "FALSE"); 817 if (state->path_start->empty) 818 { 819 state->path_start = NULL; 820 state->selection_start.x = -1; 821 state->selection_start.y = -1; 822 return; 823 } 824 825 state->mode = STATE_SELECTION; 826 state->path_start->starting = 1; 827 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 828 "[origin = (%0.2f, %0.2f)] " 829 "mouse = (%0.2f, %0.2f) => " 830 "selected = (%0.2f, %0.2f)", 831 startx, starty, state->mouse.x, state->mouse.y, 832 state->selection_start.x, state->selection_start.y); 833 state->redraw = 1; 834 } 835 else if (state->mode == STATE_SELECTION) 836 { 837 state->mode = STATE_MOVING; 838 calculate_grid_origin(state, &startx, &starty); 839 screen_coord_to_grid_coord(state, state->mouse.x, 840 state->mouse.y, startx, starty, &state->selection_end.x, 841 &state->selection_end.y); 842 state->path_end = &state->grid[INDEXFI(state->selection_end.x, 843 state->selection_end.y)]; 844 845 assert((state->path_start != NULL) && (state->path_end != NULL)); 846 if (!state->path_end->empty) 847 goto handle_click_reset; 848 849 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 850 "[origin = (%0.2f, %0.2f)] " 851 "mouse = (%0.2f, %0.2f) => " 852 "selected_end = (%0.2f, %0.2f)", 853 startx, starty, state->mouse.x, state->mouse.y, 854 state->selection_end.x, state->selection_end.y); 855 856 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 857 "Path: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 858 state->path_start->coords.x, 859 state->path_start->coords.y, state->path_end->coords.x, 860 state->path_end->coords.y); 861 state->path_end->ending = 1; 862 863 if (!find_shortest_path(state->path_start, NULL, 0)) 864 { 865 reset_path(state->grid); 866 goto handle_click_reset; 867 } 868 869 mark_path(state->path_end); 870 printf("M =\n"); 871 print_matrix(state->grid); 872 873 if (!state->path_start->path_next) 874 return; 875 876 state->animation.end.x = state->path_start->path_next->coords.x; 877 state->animation.end.y = state->path_start->path_next->coords.y; 878 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 879 "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 880 state->animation.start.x, state->animation.start.y, 881 state->animation.end.x, state->animation.end.y); 882 state->selection_start.x = -1; 883 state->selection_start.y = -1; 884 calculate_delta(state); 885 state->animation.step = 1; 886 state->animation.final_step = ANIM_STEPS; 887 state->animation.active = 1; 888 state->redraw = 1; 889 print_path(&state->grid[INDEXFI(state->animation.start.x, 890 state->animation.start.y)]); 891 } 892 return; 893 894 handle_click_reset: 895 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "handle_click: Reset"); 896 state->selection_start.x = -1; 897 state->selection_start.y = -1; 898 state->path_start->starting = 0; 899 state->path_start = NULL; 900 state->path_end->ending = 0; 901 state->path_end = NULL; 902 state->mode = STATE_IDLE; 903 state->redraw = 1; 904 } 905 906 void 907 handle_event(struct State* state, SDL_Event* event) 908 { 909 /* assert(3) within the SDL_PollEvent(3) loop hangs the entire system 910 * when given an argument which evaluates to zero, so we are taking the 911 * risk of not using assert(3) here (and only here) */ 912 /*assert((state != NULL) && (event != NULL));*/ 913 914 switch (event->type) 915 { 916 case SDL_EVENT_QUIT: 917 state->running = 0; 918 break; 919 case SDL_EVENT_WINDOW_EXPOSED: 920 state->redraw = 1; 921 break; 922 case SDL_EVENT_WINDOW_RESIZED: 923 state->redraw = 1; 924 calculate_display_size(state); 925 break; 926 case SDL_EVENT_KEY_DOWN: 927 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 928 "handle_event (before): player = {%s} [%d]", 929 state->player_name, state->player_name_length); 930 handle_key_down(state, event); 931 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 932 "handle_event (after): player = {%s} [%d]", 933 state->player_name, state->player_name_length); 934 break; 935 case SDL_EVENT_MOUSE_BUTTON_UP: 936 handle_click(state); 937 break; 938 case SDL_EVENT_MOUSE_MOTION: 939 state->mouse.x = event->motion.x; 940 state->mouse.y = event->motion.y; 941 break; 942 } 943 } 944 945 void 946 handle_input(struct State* state, SDL_Event* event) 947 { 948 int i; 949 950 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 951 "handle_input (before): state->player_name = {%s} [%d], %d", 952 state->player_name, state->player_name_length, 953 state->input_col); 954 switch (event->key.key) 955 { 956 case SDLK_ESCAPE: 957 state->input_shown = 0; 958 game_over_dialog(state); 959 break; 960 case SDLK_RETURN: 961 state->input_shown = 0; 962 if (state->input_callback) 963 state->input_callback(state); 964 break; 965 case SDLK_BACKSPACE: 966 if (state->input_col == 0) 967 break; 968 for (i = state->input_col - 1; i < state->player_name_length; 969 i++) 970 state->player_name[i] = state->player_name[i + 1]; 971 state->player_name_length--; 972 state->player_name[state->player_name_length] = 0; 973 state->input_col--; 974 state->redraw = 1; 975 break; 976 case SDLK_DELETE: 977 if (state->input_col == state->player_name_length) 978 break; 979 for (i = state->input_col; i < state->player_name_length; i++) 980 state->player_name[i] = state->player_name[i + 1]; 981 state->player_name_length--; 982 state->player_name[state->player_name_length] = 0; 983 state->redraw = 1; 984 break; 985 case SDLK_LEFT: 986 if (state->input_col > 0) 987 { 988 state->input_col--; 989 state->redraw = 1; 990 } 991 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 992 "SDLK_LEFT: player_name = {%s} [%d]", 993 state->player_name, state->player_name_length); 994 break; 995 case SDLK_RIGHT: 996 if (state->input_col < state->player_name_length) 997 { 998 state->input_col++; 999 state->redraw = 1; 1000 } 1001 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1002 "SDLK_RIGHT: player_name = {%s} [%d]", 1003 state->player_name, state->player_name_length); 1004 break; 1005 case SDLK_HOME: 1006 state->input_col = 0; 1007 state->redraw = 1; 1008 break; 1009 case SDLK_END: 1010 state->input_col = state->player_name_length; 1011 state->redraw = 1; 1012 break; 1013 default: 1014 if (event->key.key == SDLK_A && event->key.mod & SDL_KMOD_CTRL) 1015 { 1016 state->input_col = 0; 1017 state->redraw = 1; 1018 break; 1019 } 1020 if (event->key.key == SDLK_E && event->key.mod & SDL_KMOD_CTRL) 1021 { 1022 state->input_col = state->player_name_length; 1023 state->redraw = 1; 1024 break; 1025 } 1026 if (event->key.key == SDLK_U && event->key.mod & SDL_KMOD_CTRL) 1027 { 1028 *state->player_name = 0; 1029 state->player_name_length = 0; 1030 state->input_col = 0; 1031 state->redraw = 1; 1032 break; 1033 } 1034 1035 if (state->player_name_length < HI_SCORE_NAME_MAX 1036 && event->key.key >= SDLK_SPACE 1037 && event->key.key <= SDLK_TILDE) 1038 { 1039 for (i = state->player_name_length; 1040 i > state->input_col; i--) 1041 state->player_name[i] 1042 = state->player_name[i - 1]; 1043 state->player_name[state->input_col++] 1044 = event->key.mod & SDL_KMOD_SHIFT 1045 ? ascii_shift[event->key.key] 1046 : (char)event->key.key; 1047 state->player_name_length++; 1048 state->player_name[state->player_name_length] = 0; 1049 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1050 "handle_input: event->key.key = %d", 1051 event->key.key); 1052 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1053 "handle_input (after): state->player_name = " 1054 "{%s} [%d], %d", 1055 state->player_name, state->player_name_length, 1056 state->input_col); 1057 state->redraw = 1; 1058 } 1059 } 1060 } 1061 1062 void 1063 handle_key_down(struct State* state, SDL_Event* event) 1064 { 1065 if (state->input_shown) 1066 { 1067 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1068 "handle_key_down (before): player = {%s} [%d]", 1069 state->player_name, state->player_name_length); 1070 handle_input(state, event); 1071 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1072 "handle_key_down (after): player = {%s} [%d]", 1073 state->player_name, state->player_name_length); 1074 return; 1075 } 1076 1077 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1078 "Got Keydown, key = %X, mod = %X", event->key.key, 1079 event->key.mod); 1080 1081 switch (event->key.key) 1082 { 1083 case SDLK_F1: 1084 if (event->key.mod & SDL_KMOD_CTRL) 1085 { 1086 game_over(state); 1087 break; 1088 } 1089 1090 if (state->dialog_shown) 1091 break; 1092 prepare_dialog(state, HELP_DIALOG_WIDTH, HELP_DIALOG_HEIGHT, 1093 help_text, 0, 1); 1094 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Dialog shown: %s", 1095 state->dialog_shown ? "YES" : "NO"); 1096 break; 1097 case SDLK_F2: 1098 if (state->dialog_shown && state->dialog_text == help_text) 1099 break; 1100 state->input_instant_reset = 1; 1101 game_over(state); 1102 break; 1103 case SDLK_F3: 1104 if (state->dialog_shown) 1105 break; 1106 prepare_hof_dialog(state, 1); 1107 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Dialog shown: %s", 1108 state->dialog_shown ? "YES" : "NO"); 1109 break; 1110 case SDLK_RETURN: 1111 if (!(event->key.mod & SDL_KMOD_ALT)) 1112 break; 1113 state->full_screen = !state->full_screen; 1114 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1115 "Set fullscreen state: %s", 1116 state->full_screen ? "ON" : "OFF"); 1117 SDL_SetWindowFullscreen(state->window, state->full_screen); 1118 SDL_SetWindowBordered(state->window, !state->full_screen); 1119 break; 1120 case SDLK_ESCAPE: 1121 if (state->dialog_shown && state->mode == STATE_GAME_OVER) 1122 break; 1123 state->dialog_shown = 0; 1124 state->dialog_text = NULL; 1125 state->query_shown = 0; 1126 // assign_point_xyII(-1, -1, &state->dialog_dims); 1127 state->redraw = 1; 1128 break; 1129 case SDLK_Y: 1130 if (state->query_shown) 1131 { 1132 state->dialog_shown = 0; 1133 state->dialog_text = NULL; 1134 state->query_shown = 0; 1135 state->redraw = 1; 1136 if (state->query_callback) 1137 state->query_callback(state); 1138 } 1139 break; 1140 case SDLK_N: 1141 if (state->query_shown) 1142 { 1143 state->dialog_shown = 0; 1144 state->dialog_text = NULL; 1145 state->query_callback = NULL; 1146 state->query_shown = 0; 1147 state->redraw = 1; 1148 } 1149 break; 1150 case SDLK_Q: 1151 if (event->key.mod & SDL_KMOD_CTRL) 1152 really_quit(state); 1153 // state->running = 0; 1154 break; 1155 default: 1156 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1157 "Handle keyboard event: switch fallthrough"); 1158 } 1159 } 1160 1161 void 1162 init_animation(struct AnimationState* animation) 1163 { 1164 assert(animation != NULL); 1165 animation->start.x = -1; 1166 animation->start.y = -1; 1167 animation->position.x = -1; 1168 animation->position.y = -1; 1169 animation->end.x = -1; 1170 animation->end.y = -1; 1171 animation->delta.x = 0; 1172 animation->delta.y = 0; 1173 animation->active = 0; 1174 animation->step = 0; 1175 animation->final_step = 0; 1176 } 1177 1178 void 1179 init_cell(struct Cell* c, const int full, const SDL_FPoint* coords) 1180 { 1181 assert(c != NULL); 1182 c->empty = 1; 1183 c->starting = 0; 1184 c->ending = 0; 1185 c->popping = 0; 1186 c->path = 0; 1187 if (full) 1188 { 1189 assert(coords != NULL); 1190 c->coords.x = coords->x; 1191 c->coords.y = coords->y; 1192 c->down = c->left = c->right = c->up = NULL; 1193 } 1194 c->contents = CELL_EMPTY; 1195 c->path_next = NULL; 1196 c->path_prev = NULL; 1197 c->path_dist = INT_MAX; 1198 } 1199 1200 void 1201 init_state(struct State* state) 1202 { 1203 assert(state != NULL); 1204 init_animation(&state->animation); 1205 state->background = NULL; 1206 state->grid = NULL; 1207 state->cell_scale_factor = CELL_SCALE_FACTOR; 1208 state->check_score = 1; 1209 assign_point_xyII(-1, -1, &state->dialog_dims); 1210 assign_point_xyII(0, 0, &state->dialog_text_origin); 1211 state->dialog_shown = 0; 1212 state->dialog_text = NULL; 1213 state->display_width = DEFAULT_WIDTH; 1214 state->display_height = DEFAULT_HEIGHT; 1215 state->full_screen = 1; 1216 state->hall_of_fame = NULL; 1217 // state->hof_text = NULL; 1218 state->input_callback = NULL; 1219 state->input_col = 0; 1220 state->input_instant_reset = 0; 1221 state->input_shown = 0; 1222 state->location_pointer = 0; 1223 state->location_stack = NULL; 1224 state->mode = STATE_IDLE; 1225 state->moveno = 1; 1226 state->nfree = GRID_WIDTH * GRID_HEIGHT; 1227 assign_point_xyIF(0, 0, &state->mouse); 1228 state->path_start = NULL; 1229 state->path_end = NULL; 1230 state->player_name[0] = 0; 1231 state->player_name_length = 0; 1232 state->query_shown = 0; 1233 state->query_callback = NULL; 1234 state->redraw = 1; 1235 state->renderer = NULL; 1236 state->running = 1; 1237 state->screen_width = DEFAULT_WIDTH; 1238 state->screen_height = DEFAULT_HEIGHT; 1239 state->score = 0; 1240 assign_point_xyIF(-1, -1, &state->selection_start); 1241 assign_point_xyIF(-1, -1, &state->selection_end); 1242 state->sprites = NULL; 1243 state->ter12_texture = NULL; 1244 state->text_scale_factor = TEXT_SCALE_FACTOR; 1245 state->window = NULL; 1246 } 1247 1248 void 1249 input_name(struct State* state) 1250 { 1251 state->input_shown = 1; 1252 state->query_shown = 0; 1253 state->input_col = 0; 1254 state->player_name_length = 0; 1255 state->player_name[0] = 0; 1256 state->input_callback = &input_name_callback; 1257 prepare_dialog(state, INPUT_WIDTH, INPUT_HEIGHT, name_text, 0, 1); 1258 } 1259 1260 void 1261 input_name_callback(struct State* state) 1262 { 1263 int i; 1264 assert(state != NULL); 1265 state->input_callback = NULL; 1266 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1267 "input_name_callback: player_name = %s", state->player_name); 1268 for (i = 0; i < state->player_name_length; i++) 1269 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%d", 1270 state->player_name[i]); 1271 hs_add_now(&state->hall_of_fame, state->player_name, 1272 state->player_name_length, state->score); 1273 1274 if (hs_write(state->hall_of_fame, hs_get_hofdir())) 1275 { 1276 state->running = 1; 1277 return; 1278 } 1279 1280 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1281 "input_name_callback: input_instant_reset = %s", 1282 state->input_instant_reset ? "TRUE" : "FALSE"); 1283 if (state->input_instant_reset) 1284 { 1285 reset(state); 1286 fill_triplet(state); 1287 state->check_score = 1; 1288 state->input_instant_reset = 0; 1289 } 1290 else 1291 game_over_dialog(state); 1292 } 1293 1294 int 1295 load_textures(struct State* state) 1296 { 1297 SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART; 1298 1299 assert(state != NULL); 1300 1301 state->sprites = try_load(state->renderer, SPRITES_PNG); 1302 if (!state->sprites) 1303 return -1; 1304 SDL_SetTextureScaleMode(state->sprites, scale_mode); 1305 1306 state->ter12_texture = try_load(state->renderer, TER12_PNG); 1307 if (!state->ter12_texture) 1308 return -1; 1309 SDL_SetTextureScaleMode(state->ter12_texture, scale_mode); 1310 1311 return 0; 1312 } 1313 1314 void 1315 location_delete(struct State* state, const int index) 1316 { 1317 assert(state != NULL); 1318 if (state->location_pointer == 0 || index < 0 1319 || index >= state->location_pointer) 1320 return; 1321 assign_pointFF(&state->location_stack[state->location_pointer - 1], 1322 &state->location_stack[index]); 1323 state->location_pointer--; 1324 } 1325 1326 int 1327 location_index(const struct State* state, const SDL_FPoint* location) 1328 { 1329 SDL_FPoint* p = NULL; 1330 int i; 1331 1332 assert((state != NULL) && (location != NULL)); 1333 for (i = 0; i < state->location_pointer; i++) 1334 { 1335 p = &state->location_stack[i]; 1336 if (p->x == location->x && p->y == location->y) 1337 return i; 1338 } 1339 return -1; 1340 } 1341 1342 void 1343 location_push(struct State* state, const SDL_FPoint* location) 1344 { 1345 assert((state != NULL) && (location != NULL)); 1346 state->location_stack[state->location_pointer].x = location->x; 1347 state->location_stack[state->location_pointer].y = location->y; 1348 state->location_pointer++; 1349 } 1350 1351 void 1352 mark_five(struct State* state, SDL_FPoint* start, SDL_FPoint* end) 1353 { 1354 struct Cell* c = NULL; 1355 float dx, dy, x, y; 1356 1357 assert((state != NULL) && (start != NULL) && (end != NULL)); 1358 assert(!((start->x == end->x) && (start->y == end->y))); 1359 1360 dx = end->x - start->x; 1361 dy = end->y - start->y; 1362 if (dx != 0.0f) 1363 dx = dx / fabsf(dx); 1364 if (dy != 0.0f) 1365 dy = dy / fabsf(dy); 1366 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1367 "mark_five: String[%d], from (%0.0f, %0.0f) to (%0.0f, " 1368 "%0.0f)", 1369 (int)floor( 1370 MAX(fabsf(end->x - start->x), fabsf(end->y - start->y))) 1371 + 1, 1372 start->x, start->y, end->x, end->y); 1373 x = start->x; 1374 y = start->y; 1375 do 1376 { 1377 c = &state->grid[INDEXFI(x, y)]; 1378 c->popping = 1; 1379 x += dx; 1380 y += dy; 1381 /* clang-format off */ 1382 } 1383 while (!((x == end->x + dx) && (y == end->y + dy))); 1384 /* clang-format on */ 1385 } 1386 1387 void 1388 mark_path(struct Cell* c) 1389 { 1390 if (!c) 1391 return; 1392 1393 c->path = 1; 1394 if (c->path_prev) 1395 { 1396 c->path_prev->path_next = c; 1397 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1398 "mark_path: Next of (%0.0f, %0.0f) is (%0.0f, %0.0f)", 1399 c->path_prev->coords.x, c->path_prev->coords.y, 1400 c->path_prev->path_next->coords.x, 1401 c->path_prev->path_next->coords.y); 1402 } 1403 mark_path(c->path_prev); 1404 } 1405 1406 int 1407 num_size(const unsigned int num) 1408 { 1409 if (num > 999999999) 1410 return 10; 1411 if (num > 99999999) 1412 return 9; 1413 if (num > 9999999) 1414 return 8; 1415 if (num > 999999) 1416 return 7; 1417 if (num > 99999) 1418 return 6; 1419 if (num > 9999) 1420 return 5; 1421 if (num > 999) 1422 return 4; 1423 if (num > 99) 1424 return 3; 1425 if (num > 9) 1426 return 2; 1427 return 1; 1428 } 1429 1430 void 1431 pop_strings(struct State* state) 1432 { 1433 struct Cell* c = NULL; 1434 int x, y; 1435 1436 assert(state != NULL); 1437 for (y = 0; y < GRID_HEIGHT; y++) 1438 for (x = 0; x < GRID_WIDTH; x++) 1439 { 1440 c = &state->grid[INDEX(x, y)]; 1441 if (c->popping) 1442 { 1443 erase_cell(state, c); 1444 state->nfree++; 1445 c->popping = 0; 1446 } 1447 } 1448 } 1449 1450 void 1451 prepare_dialog(struct State* state, const int w, const int h, const char** text, 1452 const int dx, const int show) 1453 { 1454 assert((state != NULL) && (text != NULL)); 1455 1456 assign_point_xyII(w, h, &state->dialog_dims); 1457 state->dialog_text_origin.x = dx; 1458 state->dialog_text = text; 1459 state->dialog_shown = show; 1460 if (show) 1461 state->redraw = 1; 1462 } 1463 1464 #define HOF_SCORES_START_IDX 2 1465 1466 void 1467 prepare_hof_dialog(struct State* state, const int show) 1468 { 1469 struct HiScoreEntry* e = NULL; 1470 static char** hof_text = NULL; 1471 static int hof_size = HI_SCORE_MAX + 2 + 2 + 1; 1472 char* dots = NULL; 1473 int i; 1474 int hof_line_len = 0; 1475 1476 assert(state != NULL); 1477 errno = 0; 1478 1479 if (!(dots = malloc(HOF_DIALOG_MAX_LINE_LEN + 1))) 1480 goto prepare_hof_dialog_malloc_error; 1481 1482 /*if (hof_text) 1483 for (i = 3; i < hof_size - 3; i++) 1484 free(hof_text[i]); 1485 free(hof_text);*/ 1486 1487 if (!hof_text) 1488 { 1489 if (!(hof_text = calloc(hof_size, sizeof(char*)))) 1490 goto prepare_hof_dialog_malloc_error; 1491 1492 hof_text[0] = "\3==== \1HALL OF FAME\2 ===="; 1493 hof_text[1] = ""; 1494 hof_text[hof_size - 3] = ""; 1495 hof_text[hof_size - 2] = "\3Press \1Esc\2 to close"; 1496 hof_text[hof_size - 1] = NULL; 1497 } 1498 1499 for (i = 0; i < HI_SCORE_MAX; i++) 1500 { 1501 if (!(hof_text[HOF_SCORES_START_IDX + i])) 1502 { 1503 if (!(hof_text[HOF_SCORES_START_IDX + i] 1504 = malloc(HOF_DIALOG_MAX_LINE_LEN + 1))) 1505 goto prepare_hof_dialog_malloc_error; 1506 } 1507 e = &state->hall_of_fame[i]; 1508 repeat_char(&dots, '.', 1509 HOF_DIALOG_MAX_LINE_LEN - 4 - e->name_len 1510 - num_size(e->score) - 1 - TIMESTAMP_MAX - 1); 1511 1512 /* +4 is the number of special characters (the number of visible 1513 * characters remains HOF_DIALOG_MAX_LINE_LEN) */ 1514 hof_line_len = HOF_DIALOG_MAX_LINE_LEN + 5; 1515 1516 /* For the first HOF_STANDOUT_ENTRIES entries, the number of 1517 * special characters increases by 1 */ 1518 if (i < HOF_STANDOUT_ENTRIES) 1519 hof_line_len++; 1520 1521 snprintf(hof_text[HOF_SCORES_START_IDX + i], hof_line_len, 1522 "\4%2d. %s%s\4%s\2%s%lu %s%s", i + 1, 1523 i < HOF_STANDOUT_ENTRIES ? "\1" : "\2", e->name, dots, 1524 i < HOF_STANDOUT_ENTRIES ? "\1" : "", e->score, 1525 i < HOF_STANDOUT_ENTRIES ? "\2" : "\4", 1526 e->timestamp); 1527 1528 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 1529 "HOF[%d] = \"%s\" (%ld [vis=%d])\n", i, 1530 hof_text[HOF_SCORES_START_IDX + i], 1531 strlen(hof_text[HOF_SCORES_START_IDX + i]), 1532 strlen_visible(hof_text[HOF_SCORES_START_IDX + i])); 1533 } 1534 1535 prepare_dialog(state, HOF_DIALOG_WIDTH, HOF_DIALOG_HEIGHT, 1536 (const char**)hof_text, HOF_DIALOG_TEXT_ORIGIN_X, show); 1537 1538 free(dots); 1539 return; 1540 1541 prepare_hof_dialog_malloc_error: 1542 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "malloc failed"); 1543 exit(1); 1544 } 1545 1546 void 1547 print_matrix(const struct Cell* M) 1548 { 1549 const struct Cell* c = NULL; 1550 int x, y; 1551 1552 for (y = 0; y < GRID_HEIGHT; y++) 1553 { 1554 for (x = 0; x < GRID_WIDTH; x++) 1555 { 1556 c = &M[INDEX(x, y)]; 1557 if (c->starting) 1558 printf("%s", cell_image[CELL_START]); 1559 else if (c->ending) 1560 printf("%s", cell_image[CELL_END]); 1561 else if (c->path) 1562 printf("%s", cell_image[CELL_PATH]); 1563 else if (!c->empty) 1564 printf("%s", cell_image[c->contents]); 1565 else 1566 printf("%s", cell_image[CELL_EMPTY]); 1567 } 1568 printf("\n"); 1569 } 1570 } 1571 1572 void 1573 print_path(struct Cell* c) 1574 { 1575 printf("(%0.0f, %0.0f)", c->coords.x, c->coords.y); 1576 if (c->path_next) 1577 { 1578 printf(" -> "); 1579 print_path(c->path_next); 1580 } 1581 else 1582 printf("\n"); 1583 } 1584 1585 void 1586 quit_callback(struct State* state) 1587 { 1588 assert(state != NULL); 1589 UNUSED(state); 1590 state->query_callback = NULL; 1591 state->running = 0; 1592 } 1593 1594 void 1595 really_quit(struct State* state) 1596 { 1597 assert(state != NULL); 1598 state->query_shown = 1; 1599 state->query_callback = quit_callback; 1600 prepare_dialog(state, QUIT_MSGBOX_WIDTH, QUIT_MSGBOX_HEIGHT, 1601 quit_msgbox_text, 0, 1); 1602 } 1603 1604 void 1605 render_background(struct State* state, SDL_FRect* bg_rect) 1606 { 1607 const SDL_DisplayMode* mode; 1608 SDL_FRect cell_rect, dest_rect; 1609 SDL_DisplayID display; 1610 int x, y; 1611 1612 assert((state != NULL) && (bg_rect != NULL)); 1613 assert(state->cell_scale_factor != 0.0f); 1614 1615 if ((display = SDL_GetDisplayForWindow(state->window)) == 0) 1616 { 1617 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1618 "SDL_GetDisplayForWindow failed: %s", SDL_GetError()); 1619 do_exit(1, state); 1620 } 1621 1622 if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL) 1623 { 1624 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1625 "SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()); 1626 do_exit(1, state); 1627 } 1628 1629 state->background = SDL_CreateTexture(state->renderer, mode->format, 1630 SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h); 1631 if (!state->background) 1632 { 1633 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1634 "SDL_CreateTexture failed: %s", SDL_GetError()); 1635 do_exit(1, state); 1636 } 1637 if (!SDL_SetRenderTarget(state->renderer, state->background)) 1638 { 1639 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1640 "SDL_SetRenderTarget failed: %s", SDL_GetError()); 1641 do_exit(1, state); 1642 } 1643 1644 dest_rect.w = state->cell_scale_factor * CELL_SIZE; 1645 dest_rect.h = state->cell_scale_factor * CELL_SIZE; 1646 get_sprite_xy(CELL_EMPTY, &cell_rect.x, &cell_rect.y); 1647 cell_rect.w = CELL_SIZE; 1648 cell_rect.h = CELL_SIZE; 1649 for (y = 0; y < GRID_HEIGHT; y++) 1650 for (x = 0; x < GRID_WIDTH; x++) 1651 { 1652 grid_coord_to_screen_coord(state, x, y, 0.0f, 0.0f, 1653 &dest_rect.x, &dest_rect.y); 1654 dest_rect.x += state->cell_scale_factor; 1655 dest_rect.y += state->cell_scale_factor; 1656 SDL_RenderTexture(state->renderer, state->sprites, 1657 &cell_rect, &dest_rect); 1658 } 1659 1660 /* Draw upper border */ 1661 for (x = -1; x < GRID_WIDTH; x++) 1662 { 1663 grid_coord_to_screen_coord(state, x, -1, 0.0f, 0.0f, 1664 &dest_rect.x, &dest_rect.y); 1665 dest_rect.x += state->cell_scale_factor; 1666 dest_rect.y += state->cell_scale_factor; 1667 SDL_RenderTexture(state->renderer, state->sprites, &cell_rect, 1668 &dest_rect); 1669 } 1670 1671 /* Draw left border */ 1672 for (y = 0; y < GRID_WIDTH; y++) 1673 { 1674 grid_coord_to_screen_coord(state, -1, y, 0.0f, 0.0f, 1675 &dest_rect.x, &dest_rect.y); 1676 dest_rect.x += state->cell_scale_factor; 1677 dest_rect.y += state->cell_scale_factor; 1678 SDL_RenderTexture(state->renderer, state->sprites, &cell_rect, 1679 &dest_rect); 1680 } 1681 1682 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_NONE); 1683 SDL_SetRenderDrawColor(state->renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); 1684 1685 SDL_SetRenderTarget(state->renderer, NULL); 1686 } 1687 1688 void 1689 render_balls(struct State* state, const float startx, const float starty) 1690 { 1691 struct Cell* c = NULL; 1692 SDL_FRect cell_rect, dest_rect; 1693 float csf; 1694 int x, y; 1695 1696 assert(state != NULL); 1697 assert(state->cell_scale_factor != 0.0f); 1698 1699 csf = state->cell_scale_factor; 1700 cell_rect.w = CELL_SIZE; 1701 cell_rect.h = CELL_SIZE; 1702 dest_rect.w = csf * CELL_SIZE; 1703 dest_rect.h = csf * CELL_SIZE; 1704 for (y = 0; y < GRID_HEIGHT; y++) 1705 for (x = 0; x < GRID_WIDTH; x++) 1706 { 1707 c = &state->grid[INDEX(x, y)]; 1708 if (c->contents == CELL_EMPTY) 1709 continue; 1710 1711 get_sprite_xy(c->contents, &cell_rect.x, &cell_rect.y); 1712 if (c->path) 1713 grid_coord_to_screen_coord(state, 1714 state->animation.position.x, 1715 state->animation.position.y, startx + 1, 1716 starty + 1, &dest_rect.x, &dest_rect.y); 1717 else 1718 grid_coord_to_screen_coord(state, x, y, 1719 startx + 1, starty + 1, &dest_rect.x, 1720 &dest_rect.y); 1721 SDL_RenderTexture(state->renderer, state->sprites, 1722 &cell_rect, &dest_rect); 1723 } 1724 } 1725 1726 /* state is not const because of renderer, etc */ 1727 void 1728 render_dialog(struct State* state) 1729 { 1730 const char** ptext = NULL; 1731 SDL_FRect dialog_rect, input_rect, cursor_rect; 1732 float tsf; 1733 int w, h; 1734 int sx, sy, dx, _sx; 1735 1736 assert(state != NULL); 1737 assert(state->dialog_text != NULL); 1738 assert(state->text_scale_factor != 0.0f); 1739 1740 ptext = state->dialog_text; 1741 w = state->dialog_dims.x; 1742 h = state->dialog_dims.y; 1743 sx = state->screen_width / 2 - w / 2; 1744 sy = state->screen_height / 2 - h / 2; 1745 dx = state->dialog_text_origin.x; 1746 tsf = state->text_scale_factor; 1747 1748 dialog_rect.x = sx; 1749 dialog_rect.y = sy; 1750 dialog_rect.w = w; 1751 dialog_rect.h = h; 1752 1753 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_BLEND); 1754 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, .75 * 255); 1755 SDL_RenderFillRect(state->renderer, &dialog_rect); 1756 1757 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_NONE); 1758 SDL_SetRenderDrawColor(state->renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); 1759 SDL_RenderRect(state->renderer, &dialog_rect); 1760 SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); 1761 dialog_rect.x--; 1762 dialog_rect.y--; 1763 dialog_rect.w += 2; 1764 dialog_rect.h += 2; 1765 SDL_RenderRect(state->renderer, &dialog_rect); 1766 dialog_rect.x--; 1767 dialog_rect.y--; 1768 dialog_rect.w += 2; 1769 dialog_rect.h += 2; 1770 SDL_RenderRect(state->renderer, &dialog_rect); 1771 1772 while (*ptext) 1773 { 1774 _sx = sx + dx + tsf * (2 * TER12_WIDTH); 1775 // SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "1. _sx=%d", _sx); 1776 if (strchr(*ptext, CENTER_LINE)) 1777 _sx += w / 2 - dx 1778 - tsf * TER12_WIDTH 1779 * (2 + strlen_visible(*ptext) / 2); 1780 // SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "2. _sx=%d", _sx); 1781 1782 render_text(state, _sx, 1783 sy 1784 + (ptext - state->dialog_text + 1) * tsf 1785 * (TER12_HEIGHT + 2), 1786 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, "%s", 1787 *ptext); 1788 ptext++; 1789 } 1790 1791 if (state->input_shown) 1792 { 1793 input_rect.w = tsf * TER12_WIDTH * (HI_SCORE_NAME_MAX + 2); 1794 input_rect.h = tsf * TER12_HEIGHT; 1795 input_rect.x = sx + w / 2 1796 - tsf * TER12_WIDTH * HI_SCORE_NAME_MAX / 2; 1797 input_rect.y = sy + h - tsf * 2 * TER12_HEIGHT; 1798 1799 cursor_rect.w = TER12_WIDTH; 1800 cursor_rect.h = 3; 1801 1802 SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_BLEND); 1803 SDL_SetRenderDrawColor(state->renderer, 55, 55, 55, .75 * 255); 1804 SDL_RenderFillRect(state->renderer, &input_rect); 1805 _sx = input_rect.x + tsf * TER12_WIDTH; 1806 1807 render_text(state, _sx, input_rect.y, TER12_WIDTH, TER12_HEIGHT, 1808 state->ter12_texture, "%s", state->player_name); 1809 1810 cursor_rect.x = _sx + tsf * TER12_WIDTH * state->input_col; 1811 cursor_rect.y 1812 = input_rect.y + tsf * TER12_HEIGHT - cursor_rect.h; 1813 SDL_SetRenderDrawColor(state->renderer, 255, 255, 255, 1814 SDL_ALPHA_OPAQUE); 1815 SDL_RenderFillRect(state->renderer, &cursor_rect); 1816 } 1817 } 1818 1819 void 1820 render_selection(struct State* state, const float startx, const float starty) 1821 { 1822 SDL_FRect cell_rect, dest_rect; 1823 float csf; 1824 1825 assert(state != NULL); 1826 assert(state->cell_scale_factor != 0.0f); 1827 1828 csf = state->cell_scale_factor; 1829 get_sprite_xy(CELL_SELECTED, &cell_rect.x, &cell_rect.y); 1830 cell_rect.w = CELL_SIZE; 1831 cell_rect.h = CELL_SIZE; 1832 grid_coord_to_screen_coord(state, state->selection_start.x, 1833 state->selection_start.y, startx + 1, starty + 1, &dest_rect.x, 1834 &dest_rect.y); 1835 dest_rect.w = csf * CELL_SIZE; 1836 dest_rect.h = csf * CELL_SIZE; 1837 SDL_RenderTexture(state->renderer, state->sprites, &cell_rect, 1838 &dest_rect); 1839 } 1840 1841 void 1842 render_status(struct State* state) 1843 { 1844 float h, tsf; 1845 int maxw = 30; 1846 1847 assert(state != NULL); 1848 1849 h = state->screen_height; 1850 tsf = state->text_scale_factor; 1851 1852 render_text(state, 0, h - tsf * TER12_HEIGHT, TER12_WIDTH, TER12_HEIGHT, 1853 state->ter12_texture, "Score: \1%u\2", state->score); 1854 render_text(state, maxw * TER12_WIDTH, h - tsf * TER12_HEIGHT, 1855 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, 1856 "\1F1\2 = Help"); 1857 render_text(state, 2 * maxw * TER12_WIDTH, h - tsf * TER12_HEIGHT, 1858 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, "Move: \1%u\2", 1859 state->moveno); 1860 render_text(state, 3 * maxw * TER12_WIDTH, h - tsf * TER12_HEIGHT, 1861 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, "Free: \1%u\2", 1862 state->nfree); 1863 render_text(state, 4 * maxw * TER12_WIDTH, h - tsf * TER12_HEIGHT, 1864 TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, 1865 "Status: \1%s\2", state_descriptions[state->mode]); 1866 } 1867 1868 void 1869 render_text(struct State* state, const int x, const int y, const int font_width, 1870 const int font_height, SDL_Texture* font_tex, const char* text, ...) 1871 { 1872 char buf[PRINT_BUFSIZE]; 1873 va_list args; 1874 const char* pbuf = NULL; 1875 SDL_FRect source_rect; 1876 SDL_FRect dest_rect; 1877 float tsf; 1878 int xstart = 0; 1879 int ystart = 0; 1880 int index = 0; 1881 int chars_per_row = 0; 1882 char first_char = ' '; 1883 char last_char = '~'; 1884 float tex_w, tex_h; 1885 float opacity = TEXT_OPACITY_NORMAL; 1886 1887 assert((state != NULL) && (font_tex != NULL) && (text != NULL)); 1888 assert(state->text_scale_factor != 0.0f); 1889 assert(font_width != -1); 1890 1891 tsf = state->text_scale_factor; 1892 1893 va_start(args, text); 1894 vsnprintf(buf, sizeof(buf), text, args); 1895 va_end(args); 1896 1897 source_rect.w = font_width; 1898 source_rect.h = font_height; 1899 1900 dest_rect.x = x; 1901 dest_rect.y = y; 1902 dest_rect.w = tsf * font_width; 1903 dest_rect.h = tsf * font_height; 1904 1905 if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h)) 1906 { 1907 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1908 "SDL_GetTextureSize failed: %s", SDL_GetError()); 1909 do_exit(1, state); 1910 } 1911 1912 SDL_SetTextureBlendMode(font_tex, SDL_BLENDMODE_BLEND); 1913 1914 chars_per_row = tex_w / (font_width + 1); 1915 1916 pbuf = buf; 1917 while (*pbuf) 1918 { 1919 if (*pbuf >= first_char && *pbuf <= last_char) 1920 { 1921 index = (int)(*pbuf - first_char); 1922 1923 xstart = 1 + index % chars_per_row * (font_width + 1); 1924 ystart = 1 + index / chars_per_row * (font_height + 1); 1925 } 1926 else if (*pbuf == CENTER_LINE) 1927 { 1928 pbuf++; 1929 continue; 1930 } 1931 else if (*pbuf == BOLD_START) 1932 { 1933 opacity = TEXT_OPACITY_BOLD; 1934 pbuf++; 1935 continue; 1936 } 1937 else if (*pbuf == BOLD_END) 1938 { 1939 opacity = TEXT_OPACITY_NORMAL; 1940 pbuf++; 1941 continue; 1942 } 1943 else if (*pbuf == DIM_START) 1944 { 1945 opacity = TEXT_OPACITY_DIM; 1946 pbuf++; 1947 continue; 1948 } 1949 else 1950 break; 1951 1952 source_rect.x = xstart; 1953 source_rect.y = ystart; 1954 1955 if (!SDL_SetTextureAlphaModFloat(font_tex, opacity)) 1956 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 1957 "SDL_SetTextureAlphaModFloat is not supported"); 1958 SDL_RenderTexture(state->renderer, font_tex, &source_rect, 1959 &dest_rect); 1960 1961 dest_rect.x += tsf * font_width; 1962 1963 pbuf++; 1964 } 1965 } 1966 1967 void 1968 repeat_char(char** s, const char ch, const int num_times) 1969 { 1970 int i; 1971 assert(s != NULL); 1972 for (i = 0; i < num_times; i++) 1973 (*s)[i] = ch; 1974 (*s)[i] = 0; 1975 } 1976 1977 void 1978 reset(struct State* state) 1979 { 1980 struct Cell* c = NULL; 1981 int x, y; 1982 1983 assert(state != NULL); 1984 state->location_pointer = 0; 1985 for (y = 0; y < GRID_HEIGHT; y++) 1986 for (x = 0; x < GRID_WIDTH; x++) 1987 { 1988 c = &state->grid[INDEX(x, y)]; 1989 c->contents = CELL_EMPTY; 1990 c->empty = 1; 1991 c->popping = 0; 1992 location_push(state, &c->coords); 1993 } 1994 1995 state->dialog_text = NULL; 1996 state->dialog_shown = 0; 1997 state->mode = STATE_IDLE; 1998 state->moveno = 1; 1999 state->nfree = GRID_WIDTH * GRID_HEIGHT; 2000 state->score = 0; 2001 state->selection_start.x = -1; 2002 state->selection_start.y = -1; 2003 state->selection_end.x = -1; 2004 state->selection_end.y = -1; 2005 state->animation.active = 0; 2006 state->redraw = 1; 2007 } 2008 2009 void 2010 reset_path(struct Cell* grid) 2011 { 2012 struct Cell* c = NULL; 2013 int x, y; 2014 2015 assert(grid != NULL); 2016 2017 for (y = 0; y < GRID_HEIGHT; y++) 2018 for (x = 0; x < GRID_WIDTH; x++) 2019 { 2020 c = &grid[INDEX(x, y)]; 2021 c->path_next = NULL; 2022 c->path_prev = NULL; 2023 c->path_dist = INT_MAX; 2024 c->starting = 0; 2025 c->ending = 0; 2026 c->path = 0; 2027 } 2028 } 2029 2030 void 2031 screen_coord_to_grid_coord(struct State* state, const int x, const int y, 2032 const float startx, const float starty, float* to_x, float* to_y) 2033 { 2034 float _factor_x, _factor_y; 2035 float _startx, _starty; 2036 float csf; 2037 2038 assert(state != NULL); 2039 assert((to_x != NULL) && (to_y != NULL) 2040 && (state->cell_scale_factor != 0.0f)); 2041 csf = state->cell_scale_factor; 2042 _factor_x = (float)state->display_width / state->screen_width; 2043 _factor_y = (float)state->display_height / state->screen_height; 2044 _startx = startx * _factor_x; 2045 _starty = starty * _factor_y; 2046 2047 /* clang-format off */ 2048 *to_x = x >= _startx 2049 ? (x < _startx + _factor_x * csf 2050 * CELL_SIZE * GRID_WIDTH 2051 ? (x - _startx) / (_factor_x * csf * CELL_SIZE) 2052 : GRID_WIDTH - 1) 2053 : 0; 2054 *to_y = y >= _starty 2055 ? (y < _starty + _factor_y * csf 2056 * CELL_SIZE * GRID_HEIGHT 2057 ? (y - _starty) / (_factor_y * csf * CELL_SIZE) 2058 : GRID_HEIGHT - 1) 2059 : 0; 2060 /* clang-format on */ 2061 } 2062 2063 void 2064 screen_coord_to_grid_coordF(struct State* state, const float x, const float y, 2065 const float startx, const float starty, float* to_x, float* to_y) 2066 { 2067 float _factor_x, _factor_y; 2068 float _startx, _starty; 2069 float csf; 2070 2071 assert(state != NULL); 2072 assert((to_x != NULL) && (to_y != NULL) 2073 && (state->cell_scale_factor != 0.0f)); 2074 csf = state->cell_scale_factor; 2075 _factor_x = (float)state->display_width / state->screen_width; 2076 _factor_y = (float)state->display_height / state->screen_height; 2077 _startx = startx * _factor_x; 2078 _starty = starty * _factor_y; 2079 2080 /* clang-format off */ 2081 *to_x = x >= _startx 2082 ? (x < _startx + _factor_x * csf * CELL_SIZE * GRID_WIDTH 2083 ? (x - _startx) / (_factor_x * csf * CELL_SIZE) 2084 : GRID_WIDTH - 1) 2085 : 0; 2086 *to_y = y >= _starty 2087 ? (y < _starty + _factor_y * csf * CELL_SIZE * GRID_HEIGHT 2088 ? (y - _starty) / (_factor_y * csf * CELL_SIZE) 2089 : GRID_HEIGHT - 1) 2090 : 0; 2091 /* clang-format on */ 2092 } 2093 2094 void 2095 step_animation(struct State* state) 2096 { 2097 struct Cell* from = NULL; 2098 struct Cell* to = NULL; 2099 int isx, isy, iex, iey; 2100 int idx; 2101 2102 if (state->animation.step == state->animation.final_step) 2103 { 2104 isx = (int)round(state->animation.start.x); 2105 isy = (int)round(state->animation.start.y); 2106 iex = (int)round(state->animation.end.x); 2107 iey = (int)round(state->animation.end.y); 2108 from = &state->grid[INDEX(isx, isy)]; 2109 to = &state->grid[INDEX(iex, iey)]; 2110 2111 if (to->ending) 2112 { 2113 state->animation.active = 0; 2114 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2115 "Ending node reached: (%0.0f, %0.0f)", 2116 state->animation.end.x, state->animation.end.y); 2117 } 2118 else if (to->path_next) 2119 { 2120 state->animation.start.x = state->animation.end.x; 2121 state->animation.start.y = state->animation.end.y; 2122 state->animation.position.x = state->animation.start.x; 2123 state->animation.position.y = state->animation.start.y; 2124 state->animation.end.x = to->path_next->coords.x; 2125 state->animation.end.y = to->path_next->coords.y; 2126 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2127 "Next node on path: (%0.0f, %0.0f)", 2128 state->animation.end.x, state->animation.end.y); 2129 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2130 "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", 2131 state->animation.start.x, 2132 state->animation.start.y, 2133 state->animation.end.x, state->animation.end.y); 2134 calculate_delta(state); 2135 state->animation.step = 1; 2136 } 2137 else 2138 { 2139 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2140 "End of path at (%0.0f, %0.0f)", 2141 state->animation.end.x, state->animation.end.y); 2142 state->animation.active = 0; 2143 } 2144 2145 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2146 "grid[%d][%d] = %d <-> grid[%d][%d] = %d", iey, iex, 2147 to->contents, isy, isx, from->contents); 2148 idx = location_index(state, &to->coords); 2149 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2150 "location_index: Index of (%0.0f, %0.0f) is %d", 2151 to->coords.x, to->coords.y, idx); 2152 assert(idx != -1); 2153 location_delete(state, idx); 2154 location_push(state, &from->coords); 2155 to->contents = from->contents; 2156 from->contents = CELL_EMPTY; 2157 to->empty = 0; 2158 from->empty = 1; 2159 2160 if (to->ending) 2161 { 2162 reset_path(state->grid); 2163 state->path_start = NULL; 2164 state->path_end = NULL; 2165 2166 if (check_five(state, to)) 2167 pop_strings(state); 2168 else if (state->nfree < 3) 2169 game_over(state); 2170 else 2171 fill_triplet(state); 2172 state->moveno++; 2173 } 2174 } 2175 2176 state->animation.position.x += state->animation.delta.x; 2177 state->animation.position.y += state->animation.delta.y; 2178 state->animation.step++; 2179 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2180 "step_animation: (%0.2f, %0.2f) => (%0.2f, %0.2f), [%d/%d] %s", 2181 state->animation.position.x, state->animation.position.y, 2182 state->animation.end.x, state->animation.end.y, 2183 state->animation.step, state->animation.final_step, 2184 state->animation.active ? "ACTIVE" : "INACTIVE"); 2185 } 2186 2187 int 2188 strlen_visible(const char* s) 2189 { 2190 int result = 0; 2191 const char* ps = NULL; 2192 2193 assert(s != NULL); 2194 ps = s; 2195 while (*ps) 2196 { 2197 if (*ps != CENTER_LINE && *ps != BOLD_START && *ps != BOLD_END 2198 && *ps != DIM_START) 2199 result++; 2200 ps++; 2201 } 2202 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "strlen_visible(%s)=%d\n", s, 2203 result); 2204 return result; 2205 } 2206 2207 SDL_Texture* 2208 try_load(SDL_Renderer* renderer, const char* filename) 2209 { 2210 SDL_Texture* tex = NULL; 2211 char tex_pathname[BUFSIZE]; 2212 2213 assert((renderer != NULL) && (filename != NULL)); 2214 snprintf(tex_pathname, BUFSIZE, "%s/%s", DATADIR, filename); 2215 tex = IMG_LoadTexture(renderer, tex_pathname); 2216 if (!tex) 2217 { 2218 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 2219 "try_load: IMG_LoadTexture failed: %s", SDL_GetError()); 2220 snprintf(tex_pathname, BUFSIZE, "./%s", filename); 2221 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2222 "try_load: trying %s", tex_pathname); 2223 tex = IMG_LoadTexture(renderer, tex_pathname); 2224 if (!tex) 2225 { 2226 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 2227 "try_load: IMG_LoadTexture failed: %s", 2228 SDL_GetError()); 2229 exit(1); 2230 } 2231 SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, 2232 "try_load: success (%s)", tex_pathname); 2233 } 2234 2235 return tex; 2236 } 2237 2238 int 2239 main(int argc, char** argv) 2240 { 2241 SDL_Event event; 2242 struct Cell* c = NULL; 2243 struct State state; 2244 struct HiScoreEntry* hof = NULL; 2245 // struct HiScoreEntry* p = NULL; 2246 struct timespec now; 2247 SDL_FPoint coords; 2248 float px, py; 2249 clockid_t cid; 2250 unsigned int last_time, current_time; 2251 int x, y; 2252 2253 UNUSED(argc); 2254 UNUSED(argv); 2255 2256 tzset(); 2257 init_state(&state); 2258 hs_init(&hof); 2259 if (hs_load(&hof, hs_get_hofdir())) 2260 { 2261 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "hs_load: %s", 2262 strerror(errno)); 2263 exit(1); 2264 } 2265 2266 state.hall_of_fame = hof; 2267 2268 if (!SDL_Init(SDL_INIT_VIDEO)) 2269 { 2270 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "SDL_Init failed: %s", 2271 SDL_GetError()); 2272 exit(1); 2273 } 2274 2275 SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG); 2276 if (!SDL_CreateWindowAndRenderer(WIN_TITLE, DEFAULT_WIDTH, 2277 DEFAULT_HEIGHT, 2278 SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, 2279 &state.window, &state.renderer)) 2280 { 2281 SDL_LogError(SDL_LOG_CATEGORY_ERROR, 2282 "SDL_CreateWindowAndRenderer failed: %s", 2283 SDL_GetError()); 2284 do_exit(1, &state); 2285 } 2286 2287 SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE); 2288 SDL_SetRenderLogicalPresentation(state.renderer, DEFAULT_WIDTH, 2289 DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); 2290 2291 calculate_display_size(&state); 2292 2293 state.grid = malloc(sizeof(struct Cell) * GRID_WIDTH * GRID_HEIGHT + 1); 2294 if (!state.grid) 2295 { 2296 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "malloc failed"); 2297 do_exit(1, &state); 2298 } 2299 state.location_stack = malloc(sizeof(SDL_FPoint) * LOCATION_STACK_MAX); 2300 if (!state.location_stack) 2301 { 2302 SDL_LogError(SDL_LOG_CATEGORY_ERROR, "malloc failed"); 2303 do_exit(1, &state); 2304 } 2305 for (y = 0; y < GRID_HEIGHT; y++) 2306 for (x = 0; x < GRID_WIDTH; x++) 2307 { 2308 c = &state.grid[INDEX(x, y)]; 2309 coords.x = x; 2310 coords.y = y; 2311 init_cell(c, 1, &coords); 2312 location_push(&state, &c->coords); 2313 if (x > 0) 2314 c->left = &state.grid[INDEX(x - 1, y)]; 2315 if (y > 0) 2316 c->up = &state.grid[INDEX(x, y - 1)]; 2317 if (x < GRID_WIDTH - 1) 2318 c->right = &state.grid[INDEX(x + 1, y)]; 2319 if (y < GRID_HEIGHT - 1) 2320 c->down = &state.grid[INDEX(x, y + 1)]; 2321 } 2322 2323 if (load_textures(&state) < 0) 2324 do_exit(1, &state); 2325 2326 SDL_GetMouseState(&px, &py); 2327 assign_point_xyIF(px, py, &state.mouse); 2328 2329 last_time = SDL_GetTicks(); 2330 clock_getcpuclockid(getpid(), &cid); 2331 clock_gettime(cid, &now); 2332 srandom(now.tv_nsec); 2333 2334 fill_triplet(&state); 2335 while (state.running) 2336 { 2337 current_time = SDL_GetTicks(); 2338 if (state.animation.active) 2339 { 2340 if (current_time > last_time + 1000 / FPS) 2341 { 2342 draw(&state); 2343 step_animation(&state); 2344 last_time = current_time; 2345 } 2346 if (!state.animation.active) 2347 draw(&state); 2348 } 2349 else if (state.redraw) 2350 { 2351 state.redraw = 0; 2352 draw(&state); 2353 } 2354 2355 /* For some reason, SDL_RenderPresent is not enough for MSYS2/Windoze... 2356 * When moving a ball, the state of the screen remains stuck at the next-to-last 2357 * cell, until a mouse is moved, then it updates. This is a duct tape fix. 2358 */ 2359 /* 2026-08-28: Probably a result of needing to respond to 2360 * SDL_EVENT_WINDOW_EXPOSED. Trying with that, now only to find a test machine 2361 * with MSYS2... 2362 */ 2363 /*#if __WIN32 2364 draw(&state); 2365 #endif*/ 2366 2367 while (SDL_PollEvent(&event)) 2368 handle_event(&state, &event); 2369 } 2370 2371 return 0; 2372 }