lav-sdl

SDL верзија програма Лавиринт
git clone https://git.sr.ht/~strahinja/lav-sdl
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

lav-sdl.c (47324B)


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