linije

Клон игре Color lines
git clone https://git.sr.ht/~strahinja/linije
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

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 }