lav-sdl

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

lav-sdl.c (24042B)


      1 /* This program is licensed under the terms of GNU GPL v3 or (at your option)
      2  * any later version. Copyright (C) 2023-2025  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 <errno.h>
      8 #include <math.h>
      9 #include <stdarg.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 
     13 #include "local.h"
     14 
     15 #define ANIM_SPEED                                           \
     16 	100 /* how long does it take for the animation to go \
     17 	     * from start to finish (ms) */
     18 #define FPS	   60
     19 #define ANIM_STEPS (ANIM_SPEED * FPS / 1000)
     20 
     21 #define BUFSIZE		    4096
     22 #define CELL_SIZE	    32
     23 #define DEFAULT_WIDTH	    1280
     24 #define DEFAULT_HEIGHT	    720
     25 #define HELP_DIALOG_WIDTH   900
     26 #define HELP_DIALOG_HEIGHT  450
     27 #define LABYRINTH_MAXHEIGHT 20
     28 #define LABYRINTH_MAXWIDTH  40
     29 #define LOCATION_STACK_MAX  (LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT)
     30 #define PRINT_BUFSIZE	    4096
     31 #define PROGNAME	    "lav-sdl"
     32 #define TER12_WIDTH	    6
     33 #define TER12_HEIGHT	    12
     34 #define WIN_TITLE	    "Lavirint (SDL version)"
     35 
     36 #define MIN(a, b) (((a) < (b)) ? (a) : (b))
     37 #define MAX(a, b) (((a) > (b)) ? (a) : (b))
     38 
     39 /* clang-format off */
     40 enum {
     41 	CELL_EMPTY,
     42 	CELL_WALL,
     43 	CELL_FAIL,
     44 	CELL_OK,
     45 	CELL_START,
     46 	CELL_END
     47 };
     48 
     49 enum {
     50 	STATE_SEARCHING,
     51 	STATE_SEARCHING_PAUSED,
     52 	STATE_FAILURE,
     53 	STATE_SUCCESS
     54 };
     55 
     56 const char* state_descriptions[] = {
     57 	"Searching for exit",
     58 	"Searching (paused)",
     59 	"Failed!",
     60 	"Success!"
     61 };
     62 
     63 const char* help_text[] = {
     64 	"lav-sdl - SDL maze solver",
     65 	"=========================",
     66 	"",
     67 	"F1     - Show this help screen",
     68 	"Esc    - Hide this help screen",
     69 	"Space  - Pause/resume simulation",
     70 	"C-Q    - Exit",
     71 	"",
     72 	"---",
     73 	"",
     74 	"This program is licensed under the terms of GNU GPL v3 or (at your",
     75 	"option) any later version. Copyright (C) 2023-2025  Strahinya Radich.",
     76 	"See the file LICENSE for exact copyright and license details.",
     77 	NULL
     78 };
     79 /* clang-format on */
     80 
     81 struct AnimationState {
     82 	SDL_FPoint position;
     83 	SDL_FPoint destination;
     84 	SDL_FPoint delta;
     85 	int active;
     86 	int step;
     87 	int final_step;
     88 };
     89 
     90 struct Labyrinth {
     91 	char** cells;
     92 	int width;
     93 	int height;
     94 };
     95 
     96 static struct AnimationState animation;
     97 static SDL_Point hero, start, end;
     98 static struct Labyrinth labyrinth;
     99 static int location_pointer	 = 0;
    100 static SDL_Point* location_stack = NULL;
    101 static SDL_FPoint pointer;
    102 static int display_width	  = DEFAULT_WIDTH; /* Physical, detected */
    103 static int display_height	  = DEFAULT_HEIGHT;
    104 static int help_shown		  = 0;
    105 static int redraw		  = 1;
    106 static SDL_Renderer* renderer	  = NULL;
    107 static int running		  = 1;
    108 static int screen_width		  = DEFAULT_WIDTH; /* Logical */
    109 static int screen_height	  = DEFAULT_HEIGHT;
    110 static SDL_Texture* sprites	  = NULL;
    111 static int state		  = STATE_SEARCHING_PAUSED;
    112 static SDL_Texture* ter12_texture = NULL;
    113 static SDL_Texture* background	  = NULL;
    114 static SDL_Window* window	  = NULL;
    115 static float text_scale_factor	  = 2.0f;
    116 
    117 void assign_pointIF(const SDL_Point* from, SDL_FPoint* to);
    118 void assign_point_xyIF(const int x, const int y, SDL_FPoint* to);
    119 void calculate_delta(void);
    120 void calculate_screen_size(void);
    121 void cleanup(void);
    122 void draw(void);
    123 void draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect,
    124 	SDL_FRect* dest_rect);
    125 void draw_help_dialog(void);
    126 void draw_text(const int x, const int y, const int font_width,
    127 	const int font_height, SDL_Texture* font_tex, const char* text, ...);
    128 void get_hero_sprite_xy(float* x, float* y);
    129 void get_sprite_xy(const char cell, float* x, float* y);
    130 void handle_event(SDL_Event* event);
    131 void labyrinth_coord_to_screen_coord(const int x, const int y,
    132 	const float startx, const float starty, float* to_x, float* to_y);
    133 void labyrinth_coord_to_screen_coordF(const float x, const float y,
    134 	const float startx, const float starty, float* to_x, float* to_y);
    135 int load_textures(void);
    136 int load_labyrinth(const char* pathname);
    137 int location_pop(SDL_Point* location);
    138 void location_push(const SDL_Point* location);
    139 void next_step(void);
    140 int passable(const int x, const int y);
    141 void print_debug(const char* fmt, ...);
    142 void print_error(const char* fmt, ...);
    143 void reset(void);
    144 void step_animation(void);
    145 SDL_Surface* try_load(const char* filename);
    146 
    147 void
    148 assign_pointIF(const SDL_Point* from, SDL_FPoint* to)
    149 {
    150 	to->x = (float)from->x;
    151 	to->y = (float)from->y;
    152 }
    153 
    154 void
    155 assign_point_xyIF(const int x, const int y, SDL_FPoint* to)
    156 {
    157 	to->x = (float)x;
    158 	to->y = (float)y;
    159 }
    160 
    161 void
    162 calculate_delta(void)
    163 {
    164 	animation.delta.x
    165 		= fabsf(animation.destination.x - animation.position.x)
    166 		/ ANIM_STEPS;
    167 	if (animation.destination.x < animation.position.x)
    168 		animation.delta.x *= -1;
    169 	animation.delta.y
    170 		= fabsf(animation.destination.y - animation.position.y)
    171 		/ ANIM_STEPS;
    172 	if (animation.destination.y < animation.position.y)
    173 		animation.delta.y *= -1;
    174 }
    175 
    176 void
    177 calculate_screen_size(void)
    178 {
    179 	if (!SDL_GetRenderOutputSize(renderer, &display_width, &display_height))
    180 	{
    181 		print_error("SDL_GetRenderOutputSize failed");
    182 		exit(1);
    183 	}
    184 	print_debug("output size: (%d, %d)", screen_width, screen_height);
    185 }
    186 
    187 void
    188 cleanup(void)
    189 {
    190 	free(location_stack);
    191 
    192 	if (labyrinth.cells)
    193 		for (int y = 0; y < LABYRINTH_MAXHEIGHT; y++)
    194 			free(labyrinth.cells[y]);
    195 	free(labyrinth.cells);
    196 
    197 	if (background)
    198 		SDL_DestroyTexture(background);
    199 	if (ter12_texture)
    200 		SDL_DestroyTexture(ter12_texture);
    201 	if (sprites)
    202 		SDL_DestroyTexture(sprites);
    203 	if (renderer)
    204 		SDL_DestroyRenderer(renderer);
    205 	if (window)
    206 		SDL_DestroyWindow(window);
    207 
    208 	// IMG_Quit();
    209 	SDL_Quit();
    210 }
    211 
    212 void
    213 draw(void)
    214 {
    215 	SDL_FRect bg_rect, cell_rect;
    216 	SDL_FRect dest_rect;
    217 	int x, y;
    218 	int cx, cy;
    219 	float crx, cry;
    220 	float drx, dry;
    221 	float startx, starty;
    222 
    223 	cx = screen_width / 2;
    224 	cy = screen_height / 2;
    225 
    226 	startx = cx - labyrinth.width / 2 * CELL_SIZE;
    227 	starty = cy - labyrinth.height / 2 * CELL_SIZE;
    228 
    229 	bg_rect.x = 0;
    230 	bg_rect.y = 0;
    231 	bg_rect.w = CELL_SIZE * labyrinth.width;
    232 	bg_rect.h = CELL_SIZE * labyrinth.height;
    233 
    234 	SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    235 	SDL_RenderClear(renderer);
    236 
    237 	cell_rect.w = CELL_SIZE;
    238 	cell_rect.h = CELL_SIZE;
    239 
    240 	/* Render background */
    241 	if (!background)
    242 		draw_background(&bg_rect, &cell_rect, &dest_rect);
    243 
    244 	/* Draw buffered copy of background */
    245 	dest_rect.x = startx;
    246 	dest_rect.y = starty;
    247 	dest_rect.w = bg_rect.w;
    248 	dest_rect.h = bg_rect.h;
    249 	SDL_RenderTexture(renderer, background, &bg_rect, &dest_rect);
    250 
    251 	/* Draw nuggets/cobwebs */
    252 	dest_rect.w = CELL_SIZE;
    253 	dest_rect.h = CELL_SIZE;
    254 	for (y = 0; y < labyrinth.height; y++)
    255 		for (x = 0; x < labyrinth.width; x++)
    256 		{
    257 			if ((labyrinth.cells[y][x] != CELL_OK
    258 				    && labyrinth.cells[y][x] != CELL_FAIL)
    259 				|| (x == start.x && y == start.y)
    260 				|| (x == end.x && y == end.y))
    261 				continue;
    262 
    263 			get_sprite_xy(labyrinth.cells[y][x], &crx, &cry);
    264 			cell_rect.x = crx;
    265 			cell_rect.y = cry;
    266 			labyrinth_coord_to_screen_coord(x, y, startx, starty,
    267 				&drx, &dry);
    268 			dest_rect.x = drx;
    269 			dest_rect.y = dry;
    270 			SDL_RenderTexture(renderer, sprites, &cell_rect,
    271 				&dest_rect);
    272 		}
    273 
    274 	/* Draw hero */
    275 	dest_rect.w = CELL_SIZE;
    276 	dest_rect.h = CELL_SIZE;
    277 	get_hero_sprite_xy(&crx, &cry);
    278 	cell_rect.x = crx;
    279 	cell_rect.y = cry;
    280 	if (animation.active)
    281 		labyrinth_coord_to_screen_coordF(animation.position.x,
    282 			animation.position.y, startx, starty, &drx, &dry);
    283 	else
    284 		labyrinth_coord_to_screen_coord(hero.x, hero.y, startx, starty,
    285 			&drx, &dry);
    286 	dest_rect.x = drx;
    287 	dest_rect.y = dry;
    288 	SDL_RenderTexture(renderer, sprites, &cell_rect, &dest_rect);
    289 
    290 	/* Render status */
    291 	// draw_text(startx, starty + bg_rect.h, TER12_WIDTH, TER12_HEIGHT,
    292 	draw_text(0, screen_height - text_scale_factor * TER12_HEIGHT,
    293 		TER12_WIDTH, TER12_HEIGHT, ter12_texture,
    294 		"Column: %2d,  Row: %2d    F1 = Help    Status: %s", hero.x + 1,
    295 		hero.y + 1, state_descriptions[state]);
    296 
    297 	if (help_shown)
    298 		draw_help_dialog();
    299 
    300 	SDL_RenderPresent(renderer);
    301 }
    302 
    303 void
    304 draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect, SDL_FRect* dest_rect)
    305 {
    306 	const SDL_DisplayMode* mode;
    307 	SDL_DisplayID display;
    308 	float crx, cry;
    309 	float drx, dry;
    310 
    311 	if ((display = SDL_GetDisplayForWindow(window)) == 0)
    312 	{
    313 		print_error("SDL_GetDisplayForWindow failed: %s",
    314 			SDL_GetError());
    315 		exit(1);
    316 	}
    317 
    318 	if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL)
    319 	{
    320 		print_error("SDL_GetCurrentDisplayMode failed: %s",
    321 			SDL_GetError());
    322 		exit(1);
    323 	}
    324 
    325 	background = SDL_CreateTexture(renderer, mode->format,
    326 		SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h);
    327 	if (!background)
    328 	{
    329 		print_error("SDL_CreateTexture failed: %s",
    330 			SDL_GetError());
    331 		exit(1);
    332 	}
    333 	if (!SDL_SetRenderTarget(renderer, background))
    334 	{
    335 		print_error("SDL_SetRenderTarget failed: %s", SDL_GetError());
    336 		exit(1);
    337 	}
    338 
    339 	SDL_SetRenderDrawColor(renderer, 0x22, 0x22, 0x22, SDL_ALPHA_OPAQUE);
    340 	SDL_RenderFillRect(renderer, bg_rect);
    341 
    342 	dest_rect->w = CELL_SIZE;
    343 	dest_rect->h = CELL_SIZE;
    344 	for (int y = 0; y < labyrinth.height; y++)
    345 		for (int x = 0; x < labyrinth.width; x++)
    346 		{
    347 			if (!((labyrinth.cells[y][x] != CELL_OK
    348 				      && labyrinth.cells[y][x] != CELL_FAIL)
    349 				    || (x == start.x && y == start.y)
    350 				    || (x == end.x && y == end.y)))
    351 				continue;
    352 
    353 			get_sprite_xy(labyrinth.cells[y][x], &crx, &cry);
    354 			cell_rect->x = crx;
    355 			cell_rect->y = cry;
    356 			labyrinth_coord_to_screen_coord(x, y, 0.0f, 0.0f, &drx,
    357 				&dry);
    358 			dest_rect->x = drx;
    359 			dest_rect->y = dry;
    360 			SDL_RenderTexture(renderer, sprites, cell_rect,
    361 				dest_rect);
    362 		}
    363 
    364 	SDL_SetRenderTarget(renderer, NULL);
    365 }
    366 
    367 void
    368 draw_help_dialog(void)
    369 {
    370 	const char** phelp_text = help_text;
    371 	int sx			= screen_width / 2 - HELP_DIALOG_WIDTH / 2;
    372 	int sy			= screen_height / 2 - HELP_DIALOG_HEIGHT / 2;
    373 	SDL_FRect dialog_rect;
    374 
    375 	dialog_rect.x = sx;
    376 	dialog_rect.y = sy;
    377 	dialog_rect.w = HELP_DIALOG_WIDTH;
    378 	dialog_rect.h = HELP_DIALOG_HEIGHT;
    379 
    380 	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    381 	SDL_SetRenderDrawColor(renderer, 0, 0, 0, .75 * 255);
    382 	SDL_RenderFillRect(renderer, &dialog_rect);
    383 
    384 	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
    385 	SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE);
    386 	SDL_RenderRect(renderer, &dialog_rect);
    387 	SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
    388 	dialog_rect.x--;
    389 	dialog_rect.y--;
    390 	dialog_rect.w += 2;
    391 	dialog_rect.h += 2;
    392 	SDL_RenderRect(renderer, &dialog_rect);
    393 	dialog_rect.x--;
    394 	dialog_rect.y--;
    395 	dialog_rect.w += 2;
    396 	dialog_rect.h += 2;
    397 	SDL_RenderRect(renderer, &dialog_rect);
    398 
    399 	while (*phelp_text)
    400 	{
    401 		draw_text(sx + text_scale_factor * (2 * TER12_WIDTH),
    402 			sy
    403 				+ (phelp_text - help_text + 1)
    404 					* text_scale_factor * (TER12_HEIGHT + 2),
    405 			TER12_WIDTH, TER12_HEIGHT, ter12_texture, *phelp_text);
    406 		phelp_text++;
    407 	}
    408 }
    409 
    410 void
    411 draw_text(const int x, const int y, const int font_width, const int font_height,
    412 	SDL_Texture* font_tex, const char* text, ...)
    413 {
    414 	char buf[PRINT_BUFSIZE];
    415 	va_list args;
    416 	const char* pbuf = NULL;
    417 	int xstart	 = 0;
    418 	int ystart	 = 0;
    419 	SDL_FRect source_rect;
    420 	SDL_FRect dest_rect;
    421 	int index	  = 0;
    422 	int chars_per_row = 0;
    423 	char first_char	  = ' ';
    424 	char last_char	  = '~';
    425 	float tex_w, tex_h;
    426 
    427 	if (!text || !font_tex)
    428 		return;
    429 
    430 	va_start(args, text);
    431 	vsnprintf(buf, sizeof(buf), text, args);
    432 	va_end(args);
    433 
    434 	source_rect.w = font_width;
    435 	source_rect.h = font_height;
    436 
    437 	dest_rect.x = x;
    438 	dest_rect.y = y;
    439 	dest_rect.w = text_scale_factor * font_width;
    440 	dest_rect.h = text_scale_factor * font_height;
    441 
    442 	if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h))
    443 	{
    444 		print_error("SDL_GetTextureSize failed: %s", SDL_GetError());
    445 		exit(1);
    446 	}
    447 
    448 	chars_per_row = tex_w / (font_width + 1);
    449 
    450 	pbuf = buf;
    451 	// SDL_SetRenderScale(renderer, scale_factor, scale_factor);
    452 	while (*pbuf)
    453 	{
    454 		if (*pbuf >= first_char && *pbuf <= last_char)
    455 		{
    456 			index = (int)(*pbuf - first_char);
    457 
    458 			xstart = 1 + index % chars_per_row * (font_width + 1);
    459 			ystart = 1 + index / chars_per_row * (font_height + 1);
    460 		}
    461 		else
    462 			break;
    463 
    464 		source_rect.x = xstart;
    465 		source_rect.y = ystart;
    466 
    467 		SDL_RenderTexture(renderer, font_tex, &source_rect, &dest_rect);
    468 
    469 		dest_rect.x += text_scale_factor * font_width;
    470 
    471 		pbuf++;
    472 	}
    473 	// SDL_SetRenderScale(renderer, 1.0f, 1.0f);
    474 }
    475 
    476 void
    477 get_hero_sprite_xy(float* x, float* y)
    478 {
    479 	switch (state)
    480 	{
    481 	case STATE_SUCCESS:
    482 		*x = 1 + 4 * (CELL_SIZE + 1);
    483 		*y = 1;
    484 		break;
    485 	case STATE_FAILURE:
    486 		*x = 1 + 6 * (CELL_SIZE + 1);
    487 		*y = 1;
    488 		break;
    489 	default:
    490 		*x = 1 + 3 * (CELL_SIZE + 1);
    491 		*y = 1;
    492 		break;
    493 	}
    494 }
    495 
    496 void
    497 get_sprite_xy(const char cell, float* x, float* y)
    498 {
    499 	switch (cell)
    500 	{
    501 	case CELL_START:
    502 		*x = 1 + 8 * (CELL_SIZE + 1);
    503 		*y = 1;
    504 		break;
    505 	case CELL_END:
    506 		*x = 1 + 7 * (CELL_SIZE + 1);
    507 		*y = 1;
    508 		break;
    509 	case CELL_WALL:
    510 		*x = 1;
    511 		*y = 1;
    512 		break;
    513 	case CELL_FAIL:
    514 		*x = 1 + 1 * (CELL_SIZE + 1);
    515 		*y = 1;
    516 		break;
    517 	case CELL_OK:
    518 		*x = 1 + 2 * (CELL_SIZE + 1);
    519 		*y = 1;
    520 		break;
    521 	default:
    522 		*x = 1 + 5 * (CELL_SIZE + 1);
    523 		*y = 1;
    524 	}
    525 	/*print_debug("get_sprite_xy: %d @ (%0.0f, %0.0f)",
    526 		cell, *x, *y);*/
    527 }
    528 
    529 void
    530 handle_event(SDL_Event* event)
    531 {
    532 	if (!event)
    533 		return;
    534 	switch (event->type)
    535 	{
    536 	case SDL_EVENT_QUIT:
    537 		running = 0;
    538 		break;
    539 	case SDL_EVENT_WINDOW_RESIZED:
    540 		redraw = 1;
    541 		calculate_screen_size();
    542 		break;
    543 	case SDL_EVENT_KEY_DOWN:
    544 		print_debug("Got Keydown, key = %X, mod = %X", event->key.key,
    545 			event->key.mod);
    546 
    547 		switch (event->key.key)
    548 		{
    549 		case SDLK_F1:
    550 			help_shown = 1;
    551 			redraw	   = 1;
    552 			break;
    553 		case SDLK_ESCAPE:
    554 			help_shown = 0;
    555 			redraw	   = 1;
    556 			break;
    557 		case SDLK_SPACE:
    558 			if (state == STATE_SEARCHING)
    559 			{
    560 				print_debug("State: paused");
    561 				state = STATE_SEARCHING_PAUSED;
    562 			}
    563 			else if (state == STATE_SEARCHING_PAUSED)
    564 			{
    565 				print_debug("State: searching");
    566 				state = STATE_SEARCHING;
    567 			}
    568 			else
    569 			{
    570 				print_debug("Resetting");
    571 				reset();
    572 			}
    573 			break;
    574 		case SDLK_Q:
    575 			if (event->key.mod & SDL_KMOD_CTRL)
    576 				running = 0;
    577 			break;
    578 		default:
    579 			print_debug("switch fallthrough");
    580 		}
    581 		break;
    582 	case SDL_EVENT_MOUSE_MOTION:
    583 		pointer.x = event->motion.x;
    584 		pointer.y = event->motion.y;
    585 		if (pointer.x <= 0 || pointer.y <= 0
    586 			|| pointer.x + 1 >= display_width
    587 			|| pointer.y + 1 >= display_height)
    588 			SDL_ShowCursor();
    589 		else
    590 			SDL_HideCursor();
    591 		break;
    592 	}
    593 }
    594 
    595 void
    596 labyrinth_coord_to_screen_coord(const int x, const int y, const float startx,
    597 	const float starty, float* to_x, float* to_y)
    598 {
    599 	*to_x = startx + x * CELL_SIZE;
    600 	*to_y = starty + y * CELL_SIZE;
    601 }
    602 
    603 void
    604 labyrinth_coord_to_screen_coordF(const float x, const float y,
    605 	const float startx, const float starty, float* to_x, float* to_y)
    606 {
    607 	*to_x = startx + x * CELL_SIZE;
    608 	*to_y = starty + y * CELL_SIZE;
    609 }
    610 
    611 int
    612 load_textures(void)
    613 {
    614 	SDL_Surface* surface	 = NULL;
    615 	SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART;
    616 
    617 	surface = try_load(SPRITES_PNG);
    618 	if (!surface)
    619 		return -1;
    620 	sprites = SDL_CreateTextureFromSurface(renderer, surface);
    621 	if (!sprites)
    622 	{
    623 		print_error("SDL_CreateTextureFromSurface failed: %s",
    624 			SDL_GetError());
    625 		exit(1);
    626 	}
    627 	SDL_SetTextureScaleMode(sprites, scale_mode);
    628 	SDL_DestroySurface(surface);
    629 
    630 	surface = try_load(TER12_PNG);
    631 	if (!surface)
    632 		return -1;
    633 	ter12_texture = SDL_CreateTextureFromSurface(renderer, surface);
    634 	if (!ter12_texture)
    635 	{
    636 		print_error("SDL_CreateTextureFromSurface failed: %s",
    637 			SDL_GetError());
    638 		exit(1);
    639 	}
    640 	SDL_SetTextureScaleMode(ter12_texture, scale_mode);
    641 	SDL_DestroySurface(surface);
    642 
    643 	return 0;
    644 }
    645 
    646 int
    647 load_labyrinth(const char* pathname)
    648 {
    649 	FILE* input = NULL;
    650 	int x, y;
    651 	int firstline = 1;
    652 
    653 	input = fopen(pathname, "rt");
    654 	if (!input)
    655 		return -1;
    656 
    657 	labyrinth.cells = (char**)calloc(LABYRINTH_MAXHEIGHT, sizeof(char*));
    658 	if (!labyrinth.cells)
    659 	{
    660 		print_error("calloc failed");
    661 		exit(1);
    662 	}
    663 	for (y = 0; y < LABYRINTH_MAXHEIGHT; y++)
    664 	{
    665 		labyrinth.cells[y] = (char*)calloc(LABYRINTH_MAXWIDTH, 1);
    666 		if (!labyrinth.cells[y])
    667 		{
    668 			print_error("calloc failed");
    669 			exit(1);
    670 		}
    671 	}
    672 	fscanf(input, "%d %d\n", &start.x, &start.y);
    673 	/* 1-based coordinates for compatibility */
    674 	start.x--;
    675 	start.y--;
    676 	fscanf(input, "%d %d\n", &end.x, &end.y);
    677 	end.x--;
    678 	end.y--;
    679 
    680 	x		 = 0;
    681 	y		 = 0;
    682 	labyrinth.width	 = 0;
    683 	labyrinth.height = 0;
    684 	while (!feof(input))
    685 	{
    686 		char ch = fgetc(input);
    687 		if (ch == EOF)
    688 			continue;
    689 
    690 		if (strchr("01", ch))
    691 		{
    692 			if (x == LABYRINTH_MAXWIDTH || y == LABYRINTH_MAXHEIGHT)
    693 			{
    694 				print_error("Labyrinth overflow at %d, %d"
    695 					    " (ch == %c)",
    696 					x, y, ch);
    697 				exit(1);
    698 			}
    699 			labyrinth.cells[y][x]
    700 				= (ch == '0' ? CELL_EMPTY : CELL_WALL);
    701 			x++;
    702 			if (firstline)
    703 				labyrinth.width++;
    704 		}
    705 		else if (ch == '\n')
    706 		{
    707 			x = 0;
    708 			y++;
    709 			firstline = 0;
    710 			labyrinth.height++;
    711 		}
    712 		else if (strchr(" \r", ch))
    713 			/* Ignore CR for compatibility */;
    714 		else
    715 			print_debug("Unknown character in labyrinth file: '%c'"
    716 				    " = %X",
    717 				ch, ch);
    718 	}
    719 
    720 	hero.x				  = start.x;
    721 	hero.y				  = start.y;
    722 	labyrinth.cells[start.y][start.x] = CELL_START;
    723 	labyrinth.cells[end.y][end.x]	  = CELL_END;
    724 
    725 	fclose(input);
    726 
    727 	return 0;
    728 }
    729 
    730 int
    731 location_pop(SDL_Point* location)
    732 {
    733 	if (location_pointer == 0)
    734 		return 1;
    735 	location_pointer--;
    736 	location->x = location_stack[location_pointer].x;
    737 	location->y = location_stack[location_pointer].y;
    738 	return 0;
    739 }
    740 
    741 void
    742 location_push(const SDL_Point* location)
    743 {
    744 	location_stack[location_pointer].x = location->x;
    745 	location_stack[location_pointer].y = location->y;
    746 	location_pointer++;
    747 }
    748 
    749 void
    750 next_step(void)
    751 {
    752 	SDL_Point backup;
    753 
    754 	if (hero.x == end.x && hero.y == end.y)
    755 	{
    756 		print_debug("next_step: Success");
    757 		state = STATE_SUCCESS;
    758 	}
    759 	else if (passable(hero.x - 1, hero.y))
    760 	{
    761 		print_debug("next_step: Trying x-1, y: (%d, %d), "
    762 			    "pushed to stack (%d, %d)",
    763 			hero.x - 1, hero.y, hero.x, hero.y);
    764 		labyrinth.cells[hero.y][hero.x] = CELL_OK;
    765 		location_push(&hero);
    766 		assign_point_xyIF(hero.x, hero.y, &animation.position);
    767 		assign_point_xyIF(hero.x - 1, hero.y, &animation.destination);
    768 		calculate_delta();
    769 		animation.step	     = 1;
    770 		animation.final_step = ANIM_STEPS;
    771 		animation.active     = 1;
    772 	}
    773 	else if (passable(hero.x, hero.y - 1))
    774 	{
    775 		print_debug("next_step: Trying x, y-1: (%d, %d), "
    776 			    "pushed to stack (%d, %d)",
    777 			hero.x, hero.y - 1, hero.x, hero.y);
    778 		labyrinth.cells[hero.y][hero.x] = CELL_OK;
    779 		location_push(&hero);
    780 		assign_point_xyIF(hero.x, hero.y, &animation.position);
    781 		assign_point_xyIF(hero.x, hero.y - 1, &animation.destination);
    782 		calculate_delta();
    783 		animation.step	     = 1;
    784 		animation.final_step = ANIM_STEPS;
    785 		animation.active     = 1;
    786 	}
    787 	else if (passable(hero.x + 1, hero.y))
    788 	{
    789 		print_debug("next_step: Trying x+1, y: (%d, %d), "
    790 			    "pushed to stack (%d, %d)",
    791 			hero.x + 1, hero.y, hero.x, hero.y);
    792 		labyrinth.cells[hero.y][hero.x] = CELL_OK;
    793 		location_push(&hero);
    794 		assign_point_xyIF(hero.x, hero.y, &animation.position);
    795 		assign_point_xyIF(hero.x + 1, hero.y, &animation.destination);
    796 		calculate_delta();
    797 		animation.step	     = 1;
    798 		animation.final_step = ANIM_STEPS;
    799 		animation.active     = 1;
    800 	}
    801 	else if (passable(hero.x, hero.y + 1))
    802 	{
    803 		print_debug("next_step: Trying x, y+1: (%d, %d), "
    804 			    "pushed to stack (%d, %d)",
    805 			hero.x, hero.y + 1, hero.x, hero.y);
    806 		labyrinth.cells[hero.y][hero.x] = CELL_OK;
    807 		location_push(&hero);
    808 		assign_point_xyIF(hero.x, hero.y, &animation.position);
    809 		assign_point_xyIF(hero.x, hero.y + 1, &animation.destination);
    810 		calculate_delta();
    811 		animation.step	     = 1;
    812 		animation.final_step = ANIM_STEPS;
    813 		animation.active     = 1;
    814 	}
    815 	else
    816 	{
    817 		/* Back up */
    818 		if (location_pop(&backup))
    819 		{
    820 			print_debug("State: failure");
    821 			state = STATE_FAILURE;
    822 		}
    823 		else
    824 		{
    825 			print_debug("Backing up, stack pop: %d, %d", backup.x,
    826 				backup.y);
    827 			labyrinth.cells[hero.y][hero.x] = CELL_FAIL;
    828 			assign_pointIF(&hero, &animation.position);
    829 			assign_pointIF(&backup, &animation.destination);
    830 			calculate_delta();
    831 			animation.step	     = 1;
    832 			animation.final_step = ANIM_STEPS;
    833 			animation.active     = 1;
    834 		}
    835 	}
    836 	redraw = 1;
    837 }
    838 
    839 int
    840 passable(const int x, const int y)
    841 {
    842 	return (x >= 0 && x < labyrinth.width && y >= 0 && y < labyrinth.height
    843 		&& (labyrinth.cells[y][x] == CELL_EMPTY
    844 			|| labyrinth.cells[y][x] == CELL_END));
    845 }
    846 
    847 void
    848 print_debug(const char* fmt, ...)
    849 {
    850 	char buf[PRINT_BUFSIZE];
    851 	va_list args;
    852 
    853 	va_start(args, fmt);
    854 	vsnprintf(buf, sizeof(buf), fmt, args);
    855 	va_end(args);
    856 	printf("d: %s\n", buf);
    857 	fflush(stdout);
    858 }
    859 
    860 void
    861 print_error(const char* fmt, ...)
    862 {
    863 	char buf[PRINT_BUFSIZE];
    864 	va_list args;
    865 
    866 	va_start(args, fmt);
    867 	vsnprintf(buf, sizeof(buf), fmt, args);
    868 	va_end(args);
    869 	fprintf(stderr, "%s: %s\n", PROGNAME, buf);
    870 	fflush(stderr);
    871 }
    872 
    873 void
    874 reset(void)
    875 {
    876 	for (int y = 0; y < labyrinth.height; y++)
    877 		for (int x = 0; x < labyrinth.width; x++)
    878 			if (labyrinth.cells[y][x] == CELL_FAIL
    879 				|| labyrinth.cells[y][x] == CELL_OK)
    880 				labyrinth.cells[y][x] = CELL_EMPTY;
    881 	hero.x		 = start.x;
    882 	hero.y		 = start.y;
    883 	state		 = STATE_SEARCHING_PAUSED;
    884 	location_pointer = 0;
    885 	redraw		 = 1;
    886 }
    887 
    888 void
    889 step_animation(void)
    890 {
    891 	animation.position.x += animation.delta.x;
    892 	animation.position.y += animation.delta.y;
    893 	animation.step++;
    894 	if (animation.step == animation.final_step)
    895 	{
    896 		animation.active = 0;
    897 		hero.x		 = (int)animation.destination.x;
    898 		hero.y		 = (int)animation.destination.y;
    899 	}
    900 }
    901 
    902 SDL_Surface*
    903 try_load(const char* filename)
    904 {
    905 	char tex_pathname[BUFSIZE];
    906 	SDL_Surface* surface = NULL;
    907 
    908 	snprintf(tex_pathname, BUFSIZE, "%s/%s", DATADIR, filename);
    909 	surface = IMG_Load(tex_pathname);
    910 	if (!surface)
    911 	{
    912 		print_debug("try_load: IMG_Load failed (%s): %s", tex_pathname,
    913 			SDL_GetError());
    914 		snprintf(tex_pathname, BUFSIZE, "./%s", filename);
    915 		print_debug("try_load: trying %s", tex_pathname);
    916 		surface = IMG_Load(tex_pathname);
    917 		if (!surface)
    918 		{
    919 			print_error("try_load: IMG_Load failed (%s): %s",
    920 				tex_pathname, SDL_GetError());
    921 			exit(1);
    922 		}
    923 	}
    924 
    925 	return surface;
    926 }
    927 
    928 int
    929 main(int argc, char** argv)
    930 {
    931 	SDL_Event event;
    932 	float px, py;
    933 	unsigned int last_time, current_time;
    934 	char datafile_pathname[BUFSIZE];
    935 
    936 	atexit(&cleanup);
    937 
    938 	if (!SDL_Init(SDL_INIT_VIDEO))
    939 	{
    940 		print_error("SDL_Init failed: %s", SDL_GetError());
    941 		exit(1);
    942 	}
    943 
    944 	if (!SDL_CreateWindowAndRenderer(WIN_TITLE, DEFAULT_WIDTH,
    945 		    DEFAULT_HEIGHT,
    946 		    SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, &window,
    947 		    &renderer))
    948 	{
    949 		print_error("SDL_CreateWindowAndRenderer failed: %s",
    950 			SDL_GetError());
    951 		exit(1);
    952 	}
    953 
    954 	// SDL_SetWindowFullscreenMode(window, NULL);
    955 	// SDL_SetWindowFullscreen(window, 1);
    956 	SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE);
    957 	SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH,
    958 		DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);
    959 	/*SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH,
    960 		DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);*/
    961 	/*float factor = (float)714/720;
    962 	print_debug("factor = %0.2f", factor);
    963 	SDL_SetRenderScale(renderer, 1.0f, factor);*/
    964 
    965 	calculate_screen_size();
    966 
    967 	if (load_textures() < 0)
    968 		exit(1);
    969 
    970 	if (argc > 1)
    971 	{
    972 		if (load_labyrinth(*(argv + 1)) < 0)
    973 		{
    974 			print_error("load_labyrinth failed: %s (%s)",
    975 				strerror(errno), *(argv + 1));
    976 			exit(1);
    977 		}
    978 	}
    979 	else
    980 	{
    981 		snprintf(datafile_pathname, BUFSIZE, "%s/%s", DATADIR,
    982 			DEFAULT_DATAFILE);
    983 		if (load_labyrinth(datafile_pathname) < 0)
    984 		{
    985 			print_debug("fopen failed: %s (%s)", strerror(errno),
    986 				datafile_pathname);
    987 			snprintf(datafile_pathname, BUFSIZE, "./%s",
    988 				DEFAULT_DATAFILE);
    989 			print_debug("trying %s", datafile_pathname);
    990 			if (load_labyrinth(datafile_pathname) < 0)
    991 			{
    992 				print_error("fopen failed: %s (%s)",
    993 					strerror(errno), datafile_pathname);
    994 				exit(1);
    995 			}
    996 		}
    997 	}
    998 	location_stack
    999 		= (SDL_Point*)calloc(LOCATION_STACK_MAX, sizeof(SDL_Point));
   1000 	animation.active = 0;
   1001 
   1002 	SDL_HideCursor();
   1003 	SDL_GetMouseState(&px, &py);
   1004 	pointer.x = px;
   1005 	pointer.y = py;
   1006 	last_time = SDL_GetTicks();
   1007 	while (running)
   1008 	{
   1009 		current_time = SDL_GetTicks();
   1010 		if (animation.active)
   1011 		{
   1012 			if (current_time > last_time + 1000 / 60)
   1013 			{
   1014 				draw();
   1015 				step_animation();
   1016 				last_time = current_time;
   1017 			}
   1018 		}
   1019 		else
   1020 		{
   1021 			if (state == STATE_SEARCHING)
   1022 			{
   1023 				next_step();
   1024 			}
   1025 
   1026 			if (redraw)
   1027 			{
   1028 				draw();
   1029 				redraw = 0;
   1030 			}
   1031 		}
   1032 
   1033 		while (SDL_PollEvent(&event))
   1034 			handle_event(&event);
   1035 	}
   1036 
   1037 	return 0;
   1038 }