lav-sdl

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

чување 4e7cb54194b91d1e601d6de115d855f47567d785
родитељ 363cc05dc5403a9c2252edc1ae2ebb428be3ae3f
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Mon, 17 Aug 2026 19:10:50 +0000

Add -s (find shortest path); build files update; update copyright year

Diffstat:
MMakefile | 1+
MREADME | 10++++++++--
Aconfig.Haiku | 22++++++++++++++++++++++
Aconfig.Linux | 16++++++++++++++++
Aconfig.OpenBSD | 17+++++++++++++++++
Dconfig.mk | 26--------------------------
Mlav-sdl.c | 1983+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mlib/makedate | 42++++++++++++++++++++++++++++++++++++++----
Mlib/makeversion | 46++++++++++++++++++++++++++++++++++++++++------
Mlib/mkpack | 4++--
Mlib/replacepat | 12++++--------
Mlocal.h.in | 2+-
измењених датотека: 12, додавања: 1546(+), брисања: 635(-)

diff --git a/Makefile b/Makefile @@ -2,6 +2,7 @@ include config.mk +.SUFFIXES: .SUFFIXES: .c .o .h.in .h .h.in.h: diff --git a/README b/README @@ -13,7 +13,13 @@ See the file INSTALL in this repository. Usage ----- -$ lav-sdl labyrinth_file.dat +Brute force mode (the default): + + lav-sdl labyrinth_file.dat + +Shortest path mode: + + lav-sdl -s labyrinth_file.dat Format of data file @@ -33,7 +39,7 @@ License ------- lav-sdl - SDL maze solver -Copyright (C) 2023-2025 Strahinya Radich +Copyright (C) 2023-2026 Strahinya Radich This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/config.Haiku b/config.Haiku @@ -0,0 +1,22 @@ +CC = clang +CFLAGS = -I. -Os -Wextra -Wall -pedantic -std=c99 +CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ + -D_XOPEN_SOURCE=700 -DNDEBUG #-I/usr/local/include +DESTDIR = /boot/home/config/non-packaged +INSTALL = install +LIBS = -lSDL3_image -lSDL3 -lm +SRC = lav-sdl.c +HEADERS = local.h +OBJS = lav-sdl.o +PREFIX = +PROG = lav-sdl + +# Derived macros +BINDIR = $(DESTDIR)$(PREFIX)/bin +# TODO: Test +DATADIR = $(DESTDIR)$(PREFIX)/share/$(PROG) +DOCDIR = $(DESTDIR)$(PREFIX)/documentation/$(PROG) +MANPREFIX = $(DESTDIR)$(PREFIX)/documentation/man +#MANPAGE = $(PROG).1 +# TODO: Test +#USERDIR = $(DATADIR) diff --git a/config.Linux b/config.Linux @@ -0,0 +1,16 @@ +CFLAGS = -Os -Wextra -Wall -pedantic -std=c99 +CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ + -D_XOPEN_SOURCE=700 -DNDEBUG -I/usr/local/include +INSTALL = install +LIBS = -lSDL3_image -lSDL3 -lm +SRC = lav-sdl.c +HEADERS = local.h +OBJS = lav-sdl.o +PREFIX = /usr/local +PROG = lav-sdl + +# Derived macros +BINDIR = $(DESTDIR)$(PREFIX)/bin +DATADIR = $(DESTDIR)$(PREFIX)/share/$(PROG) +DOCDIR = $(DESTDIR)$(PREFIX)/share/doc/$(PROG) +MANPREFIX = $(DESTDIR)$(PREFIX)/share/man diff --git a/config.OpenBSD b/config.OpenBSD @@ -0,0 +1,17 @@ +CFLAGS = -Os -Wextra -Wall -pedantic -std=c99 +CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ + -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -DNDEBUG \ + -I/usr/local/include +INSTALL = install +LIBS = -lSDL3_image -lSDL3 -lm +SRC = lav-sdl.c +HEADERS = local.h +OBJS = lav-sdl.o +PREFIX = /usr/local +PROG = lav-sdl + +# Derived macros +BINDIR = $(DESTDIR)$(PREFIX)/bin +DATADIR = $(DESTDIR)$(PREFIX)/share/$(PROG) +DOCDIR = $(DESTDIR)$(PREFIX)/share/doc/$(PROG) +MANPREFIX = $(DESTDIR)$(PREFIX)/man diff --git a/config.mk b/config.mk @@ -1,26 +0,0 @@ -#CC = cc -CFLAGS = -Os -Wall -pedantic -std=c99 -_CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ - -D_XOPEN_SOURCE=700 -INCLUDE = -I/usr/include -# OpenBSD -#_CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ -# -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -#INCLUDE = -I/usr/local/include -INSTALL = install -LIBS = -lSDL3_image -lSDL3 -lm -SRC = lav-sdl.c -HEADERS = local.h -OBJS = lav-sdl.o -PREFIX = /usr/local -PROG = lav-sdl - -# Derived macros -BINDIR = $(PREFIX)/bin -CPPFLAGS = $(_CPPFLAGS) $(INCLUDE) -DATADIR = $(PREFIX)/share/$(PROG) -DOCDIR = $(PREFIX)/share/doc/$(PROG) -MANPREFIX = $(PREFIX)/share/man -# OpenBSD -#MANPREFIX = $(PREFIX)/man -#MANPAGE = $(PROG).1 diff --git a/lav-sdl.c b/lav-sdl.c @@ -1,10 +1,12 @@ /* This program is licensed under the terms of GNU GPL v3 or (at your option) - * any later version. Copyright (C) 2023-2025 Strahinya Radich. + * any later version. Copyright (C) 2023-2026 Strahinya Radich. * See the file LICENSE for exact copyright and license details. */ #include <SDL3/SDL.h> #include <SDL3_image/SDL_image.h> +#include <assert.h> #include <errno.h> +#include <limits.h> #include <math.h> #include <stdarg.h> #include <stdio.h> @@ -18,6 +20,12 @@ #define FPS 60 #define ANIM_STEPS (ANIM_SPEED * FPS / 1000) +#define BOLD_START '\1' +#define BOLD_END '\2' +#define CENTER_LINE '\3' +#define DIM_START '\4' +#define DIM_END '\2' +#define CELL_SCALE_FACTOR 1.0f #define BUFSIZE 4096 #define CELL_SIZE 32 #define DEFAULT_WIDTH 1280 @@ -31,10 +39,16 @@ #define PROGNAME "lav-sdl" #define TER12_WIDTH 6 #define TER12_HEIGHT 12 +#define TEXT_OPACITY_DIM 0.3f +#define TEXT_OPACITY_NORMAL 0.6f +#define TEXT_OPACITY_BOLD 1.0f +#define TEXT_SCALE_FACTOR 2.0f #define WIN_TITLE "Lavirint (SDL version)" -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define INDEX(x, y) ((y) * state->grid_width + (x)) +#define INDEXFI(x, y) (((int)floor(y)) * state->grid_width + ((int)floor(x))) /* clang-format off */ enum { @@ -43,9 +57,12 @@ enum { CELL_FAIL, CELL_OK, CELL_START, - CELL_END + CELL_END, + CELL_PATH }; +static char* cell_image[] = {"[ ]", "[H]", "[/]", "[o]", "[>]", "[v]", "[.]"}; + enum { STATE_SEARCHING, STATE_SEARCHING_PAUSED, @@ -61,92 +78,170 @@ const char* state_descriptions[] = { }; const char* help_text[] = { - "lav-sdl - SDL maze solver", - "=========================", + "\1\3lav-sdl - SDL maze solver\2", + "\1\3=========================\2", "", - "F1 - Show this help screen", - "Esc - Hide this help screen", - "Space - Pause/resume simulation", - "C-Q - Exit", + "\1F1\2 - Show this help screen", + "\1Esc\2 - Hide this help screen", + "\1Space\2 - Pause/resume simulation", + "\1C-Q\2 - Exit", "", - "---", + "\4---\2", "", - "This program is licensed under the terms of GNU GPL v3 or (at your", - "option) any later version. Copyright (C) 2023-2025 Strahinya Radich.", - "See the file LICENSE for exact copyright and license details.", + "\4This program is licensed under the terms of GNU GPL v3 or (at your\2", + "\4option) any later version. Copyright (C) 2023-2026 Strahinya Radich.\2", + "\4See the file LICENSE for exact copyright and license details.\2", NULL }; /* clang-format on */ struct AnimationState { + SDL_FPoint start; SDL_FPoint position; - SDL_FPoint destination; + SDL_FPoint end; SDL_FPoint delta; int active; int step; int final_step; }; -struct Labyrinth { - char** cells; - int width; - int height; +struct Cell { + int empty; /* logical variables */ + int popping; + int path; + int starting; + int ending; /* logical variables end */ + SDL_FPoint coords; + int contents; + struct Cell* down; + struct Cell* left; + struct Cell* right; + struct Cell* up; + struct Cell* path_next; /* next cell in shortest path */ + struct Cell* path_prev; /* previous cell in shortest path */ + int path_dist; /* length of shortest path so far */ +}; + +/* clang-format off */ +struct State { + struct AnimationState animation; + SDL_Texture* background; + float cell_scale_factor; + int check_score; + SDL_Point dialog_dims; + SDL_Point dialog_text_origin; + int dialog_shown; + const char** dialog_text; + int display_width; /* Physical, detected */ + int display_height; + int full_screen; + struct Cell* grid; + int grid_height; + int grid_width; + struct HiScoreEntry* hall_of_fame; + SDL_Point hero, start, end; + void (*input_callback)(struct State*); + int input_col; + int input_instant_reset; + int input_shown; + int location_pointer; + SDL_FPoint* location_stack; + int mode; + SDL_FPoint mouse; + int moveno; + struct Cell* path_start; + struct Cell* path_end; + SDL_FPoint pointer; + int query_shown; + void (*query_callback)(struct State*); + int redraw; + SDL_Renderer* renderer; + int running; + int screen_width; /* Logical */ + int screen_height; + unsigned long int score; + int search_shortest; + SDL_FPoint selection_start, selection_end; + SDL_Texture* sprites; + int state; + SDL_Texture* ter12_texture; + float text_scale_factor; + SDL_Window* window; }; -static struct AnimationState animation; -static SDL_Point hero, start, end; -static struct Labyrinth labyrinth; -static int location_pointer = 0; -static SDL_Point* location_stack = NULL; -static SDL_FPoint pointer; -static int display_width = DEFAULT_WIDTH; /* Physical, detected */ -static int display_height = DEFAULT_HEIGHT; -static int help_shown = 0; -static int redraw = 1; -static SDL_Renderer* renderer = NULL; -static int running = 1; -static int screen_width = DEFAULT_WIDTH; /* Logical */ -static int screen_height = DEFAULT_HEIGHT; -static SDL_Texture* sprites = NULL; -static int state = STATE_SEARCHING_PAUSED; -static SDL_Texture* ter12_texture = NULL; -static SDL_Texture* background = NULL; -static SDL_Window* window = NULL; -static float text_scale_factor = 2.0f; +/* clang-format on */ +void assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to); void assign_pointIF(const SDL_Point* from, SDL_FPoint* to); void assign_point_xyIF(const int x, const int y, SDL_FPoint* to); -void calculate_delta(void); -void calculate_screen_size(void); -void cleanup(void); -void draw(void); -void draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect, - SDL_FRect* dest_rect); -void draw_help_dialog(void); -void draw_text(const int x, const int y, const int font_width, - const int font_height, SDL_Texture* font_tex, const char* text, ...); -void get_hero_sprite_xy(float* x, float* y); -void get_sprite_xy(const char cell, float* x, float* y); -void handle_event(SDL_Event* event); -void labyrinth_coord_to_screen_coord(const int x, const int y, - const float startx, const float starty, float* to_x, float* to_y); +void assign_point_xyII(const int x, const int y, SDL_Point* to); +void calculate_delta(struct State* state); +void calculate_display_size(struct State* state); +void calculate_grid_origin(const struct State* state, float* sx, float* sy); +void cleanup(struct State* state); +void do_exit(const int code, struct State* state); +void draw(struct State* state); +int find_shortest_path(struct Cell* c, struct Cell* source, const int dist); +void get_hero_sprite_xy(struct State* state, float* x, float* y); +void get_sprite_xy(const int cell, float* x, float* y); +void grid_coord_to_screen_coordF(const struct State* state, const float x, + const float y, const float startx, const float starty, float* to_x, + float* to_y); +void grid_coord_to_screen_coordI(const struct State* state, const int x, + const int y, const float startx, const float starty, float* to_x, + float* to_y); +void handle_event(struct State* state, SDL_Event* event); +void handle_key_down(struct State* state, SDL_Event* event); +void init_animation(struct AnimationState* animation); +void init_cell(struct Cell* c, const int full, const SDL_FPoint* coords); +void init_state(struct State* state); void labyrinth_coord_to_screen_coordF(const float x, const float y, const float startx, const float starty, float* to_x, float* to_y); -int load_textures(void); -int load_labyrinth(const char* pathname); -int location_pop(SDL_Point* location); -void location_push(const SDL_Point* location); -void next_step(void); -int passable(const int x, const int y); +void labyrinth_coord_to_screen_coordI(const int x, const int y, + const float startx, const float starty, float* to_x, float* to_y); +int load_labyrinth(struct State* state, const char* pathname); +int load_textures(struct State* state); +void location_delete(struct State* state, const int index); +int location_index(const struct State* state, const SDL_FPoint* location); +int location_pop(struct State* state, SDL_Point* location); +void location_pushF(struct State* state, const SDL_FPoint* location); +void location_pushI(struct State* state, const SDL_Point* location); +void mark_path(struct Cell* c); +void next_step(struct State* state); +int passable(struct State* state, const int x, const int y); +int point_equalI(struct SDL_Point* p1, struct SDL_Point* p2); +void prepare_dialog(struct State* state, const int w, const int h, + const char** text, const int dx, const int show); void print_debug(const char* fmt, ...); void print_error(const char* fmt, ...); -void reset(void); -void step_animation(void); -SDL_Surface* try_load(const char* filename); +void print_matrix(const struct State* state); +void print_path(struct Cell* c); +void render_background(struct State* state, SDL_FRect* bg_rect); +void render_dialog(struct State* state); +void render_hero(struct State* state, const float startx, const float starty); +void render_status(struct State* state); +void render_text(struct State* state, const int x, const int y, + const int font_width, const int font_height, SDL_Texture* font_tex, + const char* text, ...); +void render_things(struct State* state, const float startx, const float starty); +void reset(struct State* state); +void reset_path(struct State* state); +void step_animation(struct State* state); +int strlen_visible(const char* s); +SDL_Texture* try_load(SDL_Renderer* renderer, const char* filename); + +void +assign_pointFF(const SDL_FPoint* from, SDL_FPoint* to) +{ + assert((from != NULL) && (to != NULL)); + to->x = (float)from->x; + to->y = (float)from->y; +} void assign_pointIF(const SDL_Point* from, SDL_FPoint* to) { + assert((from != NULL) && (to != NULL)); to->x = (float)from->x; to->y = (float)from->y; } @@ -154,329 +249,190 @@ assign_pointIF(const SDL_Point* from, SDL_FPoint* to) void assign_point_xyIF(const int x, const int y, SDL_FPoint* to) { + assert(to != NULL); + to->x = (float)x; + to->y = (float)y; +} + +void +assign_point_xyII(const int x, const int y, SDL_Point* to) +{ + assert(to != NULL); to->x = (float)x; to->y = (float)y; } void -calculate_delta(void) +calculate_delta(struct State* state) { - animation.delta.x - = fabsf(animation.destination.x - animation.position.x) - / ANIM_STEPS; - if (animation.destination.x < animation.position.x) - animation.delta.x *= -1; - animation.delta.y - = fabsf(animation.destination.y - animation.position.y) - / ANIM_STEPS; - if (animation.destination.y < animation.position.y) - animation.delta.y *= -1; + SDL_FPoint* start = NULL; + SDL_FPoint* end = NULL; + SDL_FPoint* delta = NULL; + + assert(state != NULL); + + start = &state->animation.start; + end = &state->animation.end; + delta = &state->animation.delta; + + print_debug( + "calculate_delta: start: (%0.2f, %0.2f), end: (%0.2f, %0.2f)", + start->x, start->y, end->x, end->y); + + delta->x = fabsf(end->x - start->x) / ANIM_STEPS; + if (end->x < start->x) + delta->x *= -1; + delta->y = fabsf(end->y - start->y) / ANIM_STEPS; + if (end->y < start->y) + delta->y *= -1; + print_debug("calculate_delta: (%0.2f, %0.2f)", delta->x, delta->y); } void -calculate_screen_size(void) +calculate_display_size(struct State* state) { - if (!SDL_GetRenderOutputSize(renderer, &display_width, &display_height)) + assert(state != NULL); + + if (!SDL_GetRenderOutputSize(state->renderer, &state->display_width, + &state->display_height)) { - print_error("SDL_GetRenderOutputSize failed"); - exit(1); + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "SDL_GetRenderOutputSize failed"); + do_exit(1, state); } - print_debug("output size: (%d, %d)", screen_width, screen_height); + print_debug("output display size: (%d, %d)", state->display_width, + state->display_height); +} + +void +calculate_grid_origin(const struct State* state, float* sx, float* sy) +{ + int cx, cy; + float csf; + + assert((state != NULL) && (sx != NULL) && (sy != NULL)); + + csf = state->cell_scale_factor; + cx = state->screen_width / 2; + cy = state->screen_height / 2; + + *sx = cx - (csf * state->grid_width * CELL_SIZE) / 2; + *sy = cy - (csf * state->grid_height * CELL_SIZE) / 2; } void -cleanup(void) +cleanup(struct State* state) { - free(location_stack); - - if (labyrinth.cells) - for (int y = 0; y < LABYRINTH_MAXHEIGHT; y++) - free(labyrinth.cells[y]); - free(labyrinth.cells); - - if (background) - SDL_DestroyTexture(background); - if (ter12_texture) - SDL_DestroyTexture(ter12_texture); - if (sprites) - SDL_DestroyTexture(sprites); - if (renderer) - SDL_DestroyRenderer(renderer); - if (window) - SDL_DestroyWindow(window); - - // IMG_Quit(); + assert(state != NULL); + + free(state->location_stack); + + if (state->background) + SDL_DestroyTexture(state->background); + if (state->ter12_texture) + SDL_DestroyTexture(state->ter12_texture); + if (state->sprites) + SDL_DestroyTexture(state->sprites); + if (state->renderer) + SDL_DestroyRenderer(state->renderer); + if (state->window) + SDL_DestroyWindow(state->window); + SDL_Quit(); } void -draw(void) +do_exit(const int code, struct State* state) { - SDL_FRect bg_rect, cell_rect; + cleanup(state); + exit(code); +} +void +draw(struct State* state) +{ + SDL_FRect bg_rect; SDL_FRect dest_rect; - int x, y; - int cx, cy; - float crx, cry; - float drx, dry; float startx, starty; + float csf; - cx = screen_width / 2; - cy = screen_height / 2; + assert(state != NULL); + assert(state->cell_scale_factor != 0.0f); - startx = cx - labyrinth.width / 2 * CELL_SIZE; - starty = cy - labyrinth.height / 2 * CELL_SIZE; + csf = state->cell_scale_factor; + calculate_grid_origin(state, &startx, &starty); bg_rect.x = 0; bg_rect.y = 0; - bg_rect.w = CELL_SIZE * labyrinth.width; - bg_rect.h = CELL_SIZE * labyrinth.height; - - SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); - SDL_RenderClear(renderer); + bg_rect.w = csf * (CELL_SIZE * state->grid_width + 1); + bg_rect.h = csf * (CELL_SIZE * state->grid_height + 1); - cell_rect.w = CELL_SIZE; - cell_rect.h = CELL_SIZE; + SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); + SDL_RenderClear(state->renderer); /* Render background */ - if (!background) - draw_background(&bg_rect, &cell_rect, &dest_rect); + if (!state->background) + render_background(state, &bg_rect); /* Draw buffered copy of background */ dest_rect.x = startx; dest_rect.y = starty; dest_rect.w = bg_rect.w; dest_rect.h = bg_rect.h; - SDL_RenderTexture(renderer, background, &bg_rect, &dest_rect); - - /* Draw nuggets/cobwebs */ - dest_rect.w = CELL_SIZE; - dest_rect.h = CELL_SIZE; - for (y = 0; y < labyrinth.height; y++) - for (x = 0; x < labyrinth.width; x++) - { - if ((labyrinth.cells[y][x] != CELL_OK - && labyrinth.cells[y][x] != CELL_FAIL) - || (x == start.x && y == start.y) - || (x == end.x && y == end.y)) - continue; + SDL_RenderTexture(state->renderer, state->background, &bg_rect, + &dest_rect); - get_sprite_xy(labyrinth.cells[y][x], &crx, &cry); - cell_rect.x = crx; - cell_rect.y = cry; - labyrinth_coord_to_screen_coord(x, y, startx, starty, - &drx, &dry); - dest_rect.x = drx; - dest_rect.y = dry; - SDL_RenderTexture(renderer, sprites, &cell_rect, - &dest_rect); - } + /* Render things */ + render_things(state, startx, starty); - /* Draw hero */ - dest_rect.w = CELL_SIZE; - dest_rect.h = CELL_SIZE; - get_hero_sprite_xy(&crx, &cry); - cell_rect.x = crx; - cell_rect.y = cry; - if (animation.active) - labyrinth_coord_to_screen_coordF(animation.position.x, - animation.position.y, startx, starty, &drx, &dry); - else - labyrinth_coord_to_screen_coord(hero.x, hero.y, startx, starty, - &drx, &dry); - dest_rect.x = drx; - dest_rect.y = dry; - SDL_RenderTexture(renderer, sprites, &cell_rect, &dest_rect); + /* Render hero */ + render_hero(state, startx, starty); /* Render status */ - // draw_text(startx, starty + bg_rect.h, TER12_WIDTH, TER12_HEIGHT, - draw_text(0, screen_height - text_scale_factor * TER12_HEIGHT, - TER12_WIDTH, TER12_HEIGHT, ter12_texture, - "Column: %2d, Row: %2d F1 = Help Status: %s", hero.x + 1, - hero.y + 1, state_descriptions[state]); - - if (help_shown) - draw_help_dialog(); - - SDL_RenderPresent(renderer); -} - -void -draw_background(SDL_FRect* bg_rect, SDL_FRect* cell_rect, SDL_FRect* dest_rect) -{ - const SDL_DisplayMode* mode; - SDL_DisplayID display; - float crx, cry; - float drx, dry; - - if ((display = SDL_GetDisplayForWindow(window)) == 0) - { - print_error("SDL_GetDisplayForWindow failed: %s", - SDL_GetError()); - exit(1); - } - - if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL) - { - print_error("SDL_GetCurrentDisplayMode failed: %s", - SDL_GetError()); - exit(1); - } - - background = SDL_CreateTexture(renderer, mode->format, - SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h); - if (!background) - { - print_error("SDL_CreateTexture failed: %s", - SDL_GetError()); - exit(1); - } - if (!SDL_SetRenderTarget(renderer, background)) - { - print_error("SDL_SetRenderTarget failed: %s", SDL_GetError()); - exit(1); - } - - SDL_SetRenderDrawColor(renderer, 0x22, 0x22, 0x22, SDL_ALPHA_OPAQUE); - SDL_RenderFillRect(renderer, bg_rect); - - dest_rect->w = CELL_SIZE; - dest_rect->h = CELL_SIZE; - for (int y = 0; y < labyrinth.height; y++) - for (int x = 0; x < labyrinth.width; x++) - { - if (!((labyrinth.cells[y][x] != CELL_OK - && labyrinth.cells[y][x] != CELL_FAIL) - || (x == start.x && y == start.y) - || (x == end.x && y == end.y))) - continue; - - get_sprite_xy(labyrinth.cells[y][x], &crx, &cry); - cell_rect->x = crx; - cell_rect->y = cry; - labyrinth_coord_to_screen_coord(x, y, 0.0f, 0.0f, &drx, - &dry); - dest_rect->x = drx; - dest_rect->y = dry; - SDL_RenderTexture(renderer, sprites, cell_rect, - dest_rect); - } - - SDL_SetRenderTarget(renderer, NULL); -} - -void -draw_help_dialog(void) -{ - const char** phelp_text = help_text; - int sx = screen_width / 2 - HELP_DIALOG_WIDTH / 2; - int sy = screen_height / 2 - HELP_DIALOG_HEIGHT / 2; - SDL_FRect dialog_rect; - - dialog_rect.x = sx; - dialog_rect.y = sy; - dialog_rect.w = HELP_DIALOG_WIDTH; - dialog_rect.h = HELP_DIALOG_HEIGHT; - - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, .75 * 255); - SDL_RenderFillRect(renderer, &dialog_rect); + render_status(state); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); - SDL_SetRenderDrawColor(renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); - SDL_RenderRect(renderer, &dialog_rect); - SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); - dialog_rect.x--; - dialog_rect.y--; - dialog_rect.w += 2; - dialog_rect.h += 2; - SDL_RenderRect(renderer, &dialog_rect); - dialog_rect.x--; - dialog_rect.y--; - dialog_rect.w += 2; - dialog_rect.h += 2; - SDL_RenderRect(renderer, &dialog_rect); + if (state->dialog_shown) + render_dialog(state); - while (*phelp_text) - { - draw_text(sx + text_scale_factor * (2 * TER12_WIDTH), - sy - + (phelp_text - help_text + 1) - * text_scale_factor * (TER12_HEIGHT + 2), - TER12_WIDTH, TER12_HEIGHT, ter12_texture, *phelp_text); - phelp_text++; - } + SDL_RenderPresent(state->renderer); } -void -draw_text(const int x, const int y, const int font_width, const int font_height, - SDL_Texture* font_tex, const char* text, ...) +int +find_shortest_path(struct Cell* c, struct Cell* source, const int dist) { - char buf[PRINT_BUFSIZE]; - va_list args; - const char* pbuf = NULL; - int xstart = 0; - int ystart = 0; - SDL_FRect source_rect; - SDL_FRect dest_rect; - int index = 0; - int chars_per_row = 0; - char first_char = ' '; - char last_char = '~'; - float tex_w, tex_h; - - if (!text || !font_tex) - return; + int result = 0; - va_start(args, text); - vsnprintf(buf, sizeof(buf), text, args); - va_end(args); + if (!c || c->path_dist <= dist) + return 0; - source_rect.w = font_width; - source_rect.h = font_height; + if (!c->empty && !c->starting && !c->ending) + return 0; - dest_rect.x = x; - dest_rect.y = y; - dest_rect.w = text_scale_factor * font_width; - dest_rect.h = text_scale_factor * font_height; + if (c->starting) + c->path_dist = dist; - if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h)) + if (c->path_dist > dist) { - print_error("SDL_GetTextureSize failed: %s", SDL_GetError()); - exit(1); + c->path_dist = dist; + c->path_prev = source; } - chars_per_row = tex_w / (font_width + 1); - - pbuf = buf; - // SDL_SetRenderScale(renderer, scale_factor, scale_factor); - while (*pbuf) - { - if (*pbuf >= first_char && *pbuf <= last_char) - { - index = (int)(*pbuf - first_char); - - xstart = 1 + index % chars_per_row * (font_width + 1); - ystart = 1 + index / chars_per_row * (font_height + 1); - } - else - break; - - source_rect.x = xstart; - source_rect.y = ystart; - - SDL_RenderTexture(renderer, font_tex, &source_rect, &dest_rect); + if (c->ending) + return 1; - dest_rect.x += text_scale_factor * font_width; + result |= find_shortest_path(c->left, c, dist + 1); + result |= find_shortest_path(c->up, c, dist + 1); + result |= find_shortest_path(c->right, c, dist + 1); + result |= find_shortest_path(c->down, c, dist + 1); - pbuf++; - } - // SDL_SetRenderScale(renderer, 1.0f, 1.0f); + return result; } void -get_hero_sprite_xy(float* x, float* y) +get_hero_sprite_xy(struct State* state, float* x, float* y) { - switch (state) + assert((state != NULL) && (x != NULL) && (y != NULL)); + + switch (state->state) { case STATE_SUCCESS: *x = 1 + 4 * (CELL_SIZE + 1); @@ -494,7 +450,7 @@ get_hero_sprite_xy(float* x, float* y) } void -get_sprite_xy(const char cell, float* x, float* y) +get_sprite_xy(const int cell, float* x, float* y) { switch (cell) { @@ -527,64 +483,49 @@ get_sprite_xy(const char cell, float* x, float* y) } void -handle_event(SDL_Event* event) +grid_coord_to_screen_coordF(const struct State* state, const float x, + const float y, const float startx, const float starty, float* to_x, + float* to_y) +{ + assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); + *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; + *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; +} + +void +grid_coord_to_screen_coordI(const struct State* state, const int x, const int y, + const float startx, const float starty, float* to_x, float* to_y) +{ + assert((state != NULL) && (to_x != NULL) && (to_y != NULL)); + *to_x = startx + x * state->cell_scale_factor * CELL_SIZE; + *to_y = starty + y * state->cell_scale_factor * CELL_SIZE; +} + +void +handle_event(struct State* state, SDL_Event* event) { if (!event) return; switch (event->type) { case SDL_EVENT_QUIT: - running = 0; + state->running = 0; break; case SDL_EVENT_WINDOW_RESIZED: - redraw = 1; - calculate_screen_size(); + state->redraw = 1; + calculate_display_size(state); break; case SDL_EVENT_KEY_DOWN: print_debug("Got Keydown, key = %X, mod = %X", event->key.key, event->key.mod); - - switch (event->key.key) - { - case SDLK_F1: - help_shown = 1; - redraw = 1; - break; - case SDLK_ESCAPE: - help_shown = 0; - redraw = 1; - break; - case SDLK_SPACE: - if (state == STATE_SEARCHING) - { - print_debug("State: paused"); - state = STATE_SEARCHING_PAUSED; - } - else if (state == STATE_SEARCHING_PAUSED) - { - print_debug("State: searching"); - state = STATE_SEARCHING; - } - else - { - print_debug("Resetting"); - reset(); - } - break; - case SDLK_Q: - if (event->key.mod & SDL_KMOD_CTRL) - running = 0; - break; - default: - print_debug("switch fallthrough"); - } + handle_key_down(state, event); break; case SDL_EVENT_MOUSE_MOTION: - pointer.x = event->motion.x; - pointer.y = event->motion.y; - if (pointer.x <= 0 || pointer.y <= 0 - || pointer.x + 1 >= display_width - || pointer.y + 1 >= display_height) + state->pointer.x = event->motion.x; + state->pointer.y = event->motion.y; + if (state->pointer.x <= 0 || state->pointer.y <= 0 + || state->pointer.x + 1 >= state->display_width + || state->pointer.y + 1 >= state->display_height) SDL_ShowCursor(); else SDL_HideCursor(); @@ -593,94 +534,191 @@ handle_event(SDL_Event* event) } void -labyrinth_coord_to_screen_coord(const int x, const int y, const float startx, - const float starty, float* to_x, float* to_y) +handle_key_down(struct State* state, SDL_Event* event) { - *to_x = startx + x * CELL_SIZE; - *to_y = starty + y * CELL_SIZE; + switch (event->key.key) + { + case SDLK_F1: + if (state->dialog_shown) + break; + prepare_dialog(state, HELP_DIALOG_WIDTH, HELP_DIALOG_HEIGHT, + help_text, 0, 1); + break; + case SDLK_ESCAPE: + state->dialog_shown = 0; + state->dialog_text = NULL; + state->redraw = 1; + break; + case SDLK_SPACE: + if (state->state == STATE_SEARCHING) + { + print_debug("State: paused"); + state->state = STATE_SEARCHING_PAUSED; + } + else if (state->state == STATE_SEARCHING_PAUSED) + { + print_debug("State: searching"); + state->state = STATE_SEARCHING; + } + else + { + print_debug("Resetting"); + reset(state); + } + break; + case SDLK_Q: + if (event->key.mod & SDL_KMOD_CTRL) + state->running = 0; + break; + default: + print_debug("switch fallthrough"); + } } void -labyrinth_coord_to_screen_coordF(const float x, const float y, - const float startx, const float starty, float* to_x, float* to_y) +init_animation(struct AnimationState* animation) { - *to_x = startx + x * CELL_SIZE; - *to_y = starty + y * CELL_SIZE; + assert(animation != NULL); + animation->start.x = -1; + animation->start.y = -1; + animation->position.x = -1; + animation->position.y = -1; + animation->end.x = -1; + animation->end.y = -1; + animation->delta.x = 0; + animation->delta.y = 0; + animation->active = 0; + animation->step = 0; + animation->final_step = 0; } -int -load_textures(void) +void +init_cell(struct Cell* c, const int full, const SDL_FPoint* coords) { - SDL_Surface* surface = NULL; - SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART; - - surface = try_load(SPRITES_PNG); - if (!surface) - return -1; - sprites = SDL_CreateTextureFromSurface(renderer, surface); - if (!sprites) + assert(c != NULL); + c->empty = 1; + c->starting = 0; + c->ending = 0; + c->popping = 0; + c->path = 0; + if (full) { - print_error("SDL_CreateTextureFromSurface failed: %s", - SDL_GetError()); - exit(1); + assert(coords != NULL); + c->coords.x = coords->x; + c->coords.y = coords->y; + c->down = c->left = c->right = c->up = NULL; } - SDL_SetTextureScaleMode(sprites, scale_mode); - SDL_DestroySurface(surface); + c->contents = CELL_EMPTY; + c->path_next = NULL; + c->path_prev = NULL; + c->path_dist = INT_MAX; +} - surface = try_load(TER12_PNG); - if (!surface) - return -1; - ter12_texture = SDL_CreateTextureFromSurface(renderer, surface); - if (!ter12_texture) - { - print_error("SDL_CreateTextureFromSurface failed: %s", - SDL_GetError()); - exit(1); - } - SDL_SetTextureScaleMode(ter12_texture, scale_mode); - SDL_DestroySurface(surface); +void +init_state(struct State* state) +{ + assert(state != NULL); + init_animation(&state->animation); + state->background = NULL; + state->grid = NULL; + state->cell_scale_factor = CELL_SCALE_FACTOR; + state->check_score = 1; + assign_point_xyII(-1, -1, &state->dialog_dims); + assign_point_xyII(0, 0, &state->dialog_text_origin); + state->dialog_shown = 0; + state->dialog_text = NULL; + state->display_width = DEFAULT_WIDTH; + state->display_height = DEFAULT_HEIGHT; + state->full_screen = 1; + state->hall_of_fame = NULL; + state->input_callback = NULL; + state->input_col = 0; + state->input_instant_reset = 0; + state->input_shown = 0; + state->location_pointer = 0; + state->location_stack = NULL; + state->moveno = 1; + assign_point_xyIF(0, 0, &state->mouse); + state->path_start = NULL; + state->path_end = NULL; + state->query_shown = 0; + state->query_callback = NULL; + state->redraw = 1; + state->renderer = NULL; + state->running = 1; + state->screen_width = DEFAULT_WIDTH; + state->screen_height = DEFAULT_HEIGHT; + state->score = 0; + state->search_shortest = 0; + assign_point_xyIF(-1, -1, &state->selection_start); + assign_point_xyIF(-1, -1, &state->selection_end); + state->sprites = NULL; + state->state = STATE_SEARCHING_PAUSED; + state->ter12_texture = NULL; + state->text_scale_factor = TEXT_SCALE_FACTOR; + state->window = NULL; +} - return 0; +void +labyrinth_coord_to_screen_coordF(const float x, const float y, + const float startx, const float starty, float* to_x, float* to_y) +{ + *to_x = startx + x * CELL_SIZE; + *to_y = starty + y * CELL_SIZE; +} + +void +labyrinth_coord_to_screen_coordI(const int x, const int y, const float startx, + const float starty, float* to_x, float* to_y) +{ + *to_x = startx + x * CELL_SIZE; + *to_y = starty + y * CELL_SIZE; } int -load_labyrinth(const char* pathname) +load_labyrinth(struct State* state, const char* pathname) { - FILE* input = NULL; + FILE* input = NULL; + struct Cell* c = NULL; + SDL_FPoint coords; int x, y; int firstline = 1; + assert((state != NULL) && (pathname != NULL)); + input = fopen(pathname, "rt"); if (!input) return -1; - labyrinth.cells = (char**)calloc(LABYRINTH_MAXHEIGHT, sizeof(char*)); - if (!labyrinth.cells) + state->grid = malloc( + sizeof(struct Cell) * LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT); + if (!state->grid) { - print_error("calloc failed"); - exit(1); + print_error("malloc failed"); + do_exit(1, state); } - for (y = 0; y < LABYRINTH_MAXHEIGHT; y++) + state->grid_width = LABYRINTH_MAXWIDTH; + state->grid_height = LABYRINTH_MAXHEIGHT; + state->location_stack = malloc( + sizeof(SDL_FPoint) * LABYRINTH_MAXWIDTH * LABYRINTH_MAXHEIGHT); + if (!state->location_stack) { - labyrinth.cells[y] = (char*)calloc(LABYRINTH_MAXWIDTH, 1); - if (!labyrinth.cells[y]) - { - print_error("calloc failed"); - exit(1); - } + print_error("malloc failed"); + do_exit(1, state); } - fscanf(input, "%d %d\n", &start.x, &start.y); + + fscanf(input, "%d %d\n", &state->start.x, &state->start.y); /* 1-based coordinates for compatibility */ - start.x--; - start.y--; - fscanf(input, "%d %d\n", &end.x, &end.y); - end.x--; - end.y--; - - x = 0; - y = 0; - labyrinth.width = 0; - labyrinth.height = 0; + state->start.x--; + state->start.y--; + fscanf(input, "%d %d\n", &state->end.x, &state->end.y); + state->end.x--; + state->end.y--; + + x = 0; + y = 0; + state->grid_width = 0; + state->grid_height = 0; while (!feof(input)) { char ch = fgetc(input); @@ -694,20 +732,26 @@ load_labyrinth(const char* pathname) print_error("Labyrinth overflow at %d, %d" " (ch == %c)", x, y, ch); - exit(1); + do_exit(1, state); } - labyrinth.cells[y][x] - = (ch == '0' ? CELL_EMPTY : CELL_WALL); + c = &state->grid[INDEX(x, y)]; + + coords.x = x; + coords.y = y; + init_cell(c, 1, &coords); + + c->contents = (ch == '0' ? CELL_EMPTY : CELL_WALL); + c->empty = c->contents == CELL_EMPTY; x++; if (firstline) - labyrinth.width++; + state->grid_width++; } else if (ch == '\n') { x = 0; y++; firstline = 0; - labyrinth.height++; + state->grid_height++; } else if (strchr(" \r", ch)) /* Ignore CR for compatibility */; @@ -717,10 +761,33 @@ load_labyrinth(const char* pathname) ch, ch); } - hero.x = start.x; - hero.y = start.y; - labyrinth.cells[start.y][start.x] = CELL_START; - labyrinth.cells[end.y][end.x] = CELL_END; + for (y = 0; y < state->grid_height; y++) + { + for (x = 0; x < state->grid_width; x++) + { + c = &state->grid[INDEX(x, y)]; + c->coords.x = x; + c->coords.y = y; + + c->left = c->up = c->right = c->down = NULL; + if (x > 0) + c->left = &state->grid[INDEX(x - 1, y)]; + if (y > 0) + c->up = &state->grid[INDEX(x, y - 1)]; + if (x < state->grid_width - 1) + c->right = &state->grid[INDEX(x + 1, y)]; + if (y < state->grid_height - 1) + c->down = &state->grid[INDEX(x, y + 1)]; + } + } + + state->hero.x = state->start.x; + state->hero.y = state->start.y; + state->grid[INDEX(state->start.x, state->start.y)].contents + = CELL_START; + state->grid[INDEX(state->start.x, state->start.y)].starting = 1; + state->grid[INDEX(state->end.x, state->end.y)].contents = CELL_END; + state->grid[INDEX(state->end.x, state->end.y)].ending = 1; fclose(input); @@ -728,120 +795,370 @@ load_labyrinth(const char* pathname) } int -location_pop(SDL_Point* location) +load_textures(struct State* state) { - if (location_pointer == 0) - return 1; - location_pointer--; - location->x = location_stack[location_pointer].x; - location->y = location_stack[location_pointer].y; - return 0; -} + SDL_ScaleMode scale_mode = SDL_SCALEMODE_PIXELART; -void -location_push(const SDL_Point* location) -{ - location_stack[location_pointer].x = location->x; - location_stack[location_pointer].y = location->y; - location_pointer++; -} + assert(state != NULL); + + state->sprites = try_load(state->renderer, SPRITES_PNG); + if (!state->sprites) + return -1; + SDL_SetTextureScaleMode(state->sprites, scale_mode); + + state->ter12_texture = try_load(state->renderer, TER12_PNG); + if (!state->ter12_texture) + return -1; + SDL_SetTextureScaleMode(state->ter12_texture, scale_mode); + + return 0; +} void -next_step(void) +location_delete(struct State* state, const int index) +{ + assert(state != NULL); + if (state->location_pointer == 0 || index < 0 + || index >= state->location_pointer) + return; + assign_pointFF(&state->location_stack[state->location_pointer - 1], + &state->location_stack[index]); + state->location_pointer--; +} + +int +location_index(const struct State* state, const SDL_FPoint* location) +{ + SDL_FPoint* p = NULL; + int i; + + assert((state != NULL) && (location != NULL)); + for (i = 0; i < state->location_pointer; i++) + { + p = &state->location_stack[i]; + if (p->x == location->x && p->y == location->y) + return i; + } + return -1; +} + +int +location_pop(struct State* state, SDL_Point* location) { + assert((state != NULL) && (location != NULL)); + if (state->location_pointer == 0) + return 1; + state->location_pointer--; + location->x = state->location_stack[state->location_pointer].x; + location->y = state->location_stack[state->location_pointer].y; + return 0; +} + +void +location_pushF(struct State* state, const SDL_FPoint* location) +{ + assert((state != NULL) && (location != NULL)); + assert(state->location_stack != NULL); + state->location_stack[state->location_pointer].x = location->x; + state->location_stack[state->location_pointer].y = location->y; + state->location_pointer++; +} + +void +location_pushI(struct State* state, const SDL_Point* location) +{ + assert((state != NULL) && (location != NULL)); + state->location_stack[state->location_pointer].x = location->x; + state->location_stack[state->location_pointer].y = location->y; + state->location_pointer++; +} + +void +mark_path(struct Cell* c) +{ + if (!c) + return; + + c->path = 1; + if (c->path_prev) + { + c->path_prev->path_next = c; + print_debug( + "mark_path: Next of (%0.0f, %0.0f) is (%0.0f, %0.0f)", + c->path_prev->coords.x, c->path_prev->coords.y, + c->path_prev->path_next->coords.x, + c->path_prev->path_next->coords.y); + } + mark_path(c->path_prev); +} + +void +next_step(struct State* state) +{ + struct Cell* from = NULL; + struct Cell* to = NULL; SDL_Point backup; + int isx, isy, iex, iey; + int idx; + + if (!state->search_shortest) + goto next_step_brute_force; + + if (!state->animation.active) + { + if (!state->path_end) + { + state->path_start = &state->grid[INDEX(state->start.x, + state->start.y)]; + state->path_end + = &state->grid[INDEX(state->end.x, state->end.y)]; + if (!find_shortest_path(state->path_start, NULL, 0)) + { + reset_path(state); + state->path_start->contents = CELL_FAIL; + state->path_start = NULL; + state->path_end = NULL; + state->animation.active = 0; + state->state = STATE_FAILURE; + print_debug("find_shortest_path FAILED"); + state->redraw = 1; + return; + } + print_debug( + "find_shortest_path successful, path len = %d", + state->path_end->path_dist); + mark_path(state->path_end); + print_debug("M = "); + print_matrix(state); + if (!state->path_start->path_next) + return; + } + + state->animation.start.x = state->path_start->coords.x; + state->animation.start.y = state->path_start->coords.y; + state->animation.end.x = state->path_start->path_next->coords.x; + state->animation.end.y = state->path_start->path_next->coords.y; + state->animation.position.x = state->path_start->coords.x; + state->animation.position.y = state->path_start->coords.y; + print_debug("Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", + state->animation.start.x, state->animation.start.y, + state->animation.end.x, state->animation.end.y); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; + state->redraw = 1; + print_path(&state->grid[INDEXFI(state->animation.start.x, + state->animation.start.y)]); + } + + if (state->animation.step == state->animation.final_step) + { + isx = (int)round(state->animation.start.x); + isy = (int)round(state->animation.start.y); + iex = (int)round(state->animation.end.x); + iey = (int)round(state->animation.end.y); + from = &state->grid[INDEX(isx, isy)]; + to = &state->grid[INDEX(iex, iey)]; + + if (to->ending) + { + state->animation.active = 0; + print_debug("Ending node reached: (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + } + else if (to->path_next) + { + state->animation.start.x = state->animation.end.x; + state->animation.start.y = state->animation.end.y; + state->animation.position.x = state->animation.start.x; + state->animation.position.y = state->animation.start.y; + state->animation.end.x = to->path_next->coords.x; + state->animation.end.y = to->path_next->coords.y; + print_debug("Next node on path: (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + print_debug( + "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", + state->animation.start.x, + state->animation.start.y, + state->animation.end.x, state->animation.end.y); + calculate_delta(state); + state->animation.step = 1; + } + else + { + print_debug("End of path at (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + state->animation.active = 0; + } + + print_debug("grid[%d][%d] = %d <-> grid[%d][%d] = %d", iey, iex, + to->contents, isy, isx, from->contents); + idx = location_index(state, &to->coords); + print_debug("location_index: Index of (%0.0f, %0.0f) is %d", + to->coords.x, to->coords.y, idx); + assert(idx != -1); + location_delete(state, idx); + location_pushF(state, &from->coords); + + if (to->ending) + { + reset_path(state); + state->path_start = NULL; + state->path_end = NULL; + } + } - if (hero.x == end.x && hero.y == end.y) + state->animation.position.x += state->animation.delta.x; + state->animation.position.y += state->animation.delta.y; + state->animation.step++; + print_debug( + "step_animation: (%0.2f, %0.2f) => (%0.2f, %0.2f), [%d/%d] %s", + state->animation.position.x, state->animation.position.y, + state->animation.end.x, state->animation.end.y, + state->animation.step, state->animation.final_step, + state->animation.active ? "ACTIVE" : "INACTIVE"); + return; + +next_step_brute_force: + if (state->hero.x == state->end.x && state->hero.y == state->end.y) { print_debug("next_step: Success"); - state = STATE_SUCCESS; + state->state = STATE_SUCCESS; } - else if (passable(hero.x - 1, hero.y)) + else if (passable(state, state->hero.x - 1, state->hero.y)) { print_debug("next_step: Trying x-1, y: (%d, %d), " "pushed to stack (%d, %d)", - hero.x - 1, hero.y, hero.x, hero.y); - labyrinth.cells[hero.y][hero.x] = CELL_OK; - location_push(&hero); - assign_point_xyIF(hero.x, hero.y, &animation.position); - assign_point_xyIF(hero.x - 1, hero.y, &animation.destination); - calculate_delta(); - animation.step = 1; - animation.final_step = ANIM_STEPS; - animation.active = 1; + state->hero.x - 1, state->hero.y, state->hero.x, + state->hero.y); + state->grid[INDEX(state->hero.x, state->hero.y)].contents + = CELL_OK; + location_pushI(state, &state->hero); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.start); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.position); + assign_point_xyIF(state->hero.x - 1, state->hero.y, + &state->animation.end); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; } - else if (passable(hero.x, hero.y - 1)) + else if (passable(state, state->hero.x, state->hero.y - 1)) { print_debug("next_step: Trying x, y-1: (%d, %d), " "pushed to stack (%d, %d)", - hero.x, hero.y - 1, hero.x, hero.y); - labyrinth.cells[hero.y][hero.x] = CELL_OK; - location_push(&hero); - assign_point_xyIF(hero.x, hero.y, &animation.position); - assign_point_xyIF(hero.x, hero.y - 1, &animation.destination); - calculate_delta(); - animation.step = 1; - animation.final_step = ANIM_STEPS; - animation.active = 1; + state->hero.x, state->hero.y - 1, state->hero.x, + state->hero.y); + state->grid[INDEX(state->hero.x, state->hero.y)].contents + = CELL_OK; + location_pushI(state, &state->hero); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.start); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.position); + assign_point_xyIF(state->hero.x, state->hero.y - 1, + &state->animation.end); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; } - else if (passable(hero.x + 1, hero.y)) + else if (passable(state, state->hero.x + 1, state->hero.y)) { print_debug("next_step: Trying x+1, y: (%d, %d), " "pushed to stack (%d, %d)", - hero.x + 1, hero.y, hero.x, hero.y); - labyrinth.cells[hero.y][hero.x] = CELL_OK; - location_push(&hero); - assign_point_xyIF(hero.x, hero.y, &animation.position); - assign_point_xyIF(hero.x + 1, hero.y, &animation.destination); - calculate_delta(); - animation.step = 1; - animation.final_step = ANIM_STEPS; - animation.active = 1; + state->hero.x + 1, state->hero.y, state->hero.x, + state->hero.y); + state->grid[INDEX(state->hero.x, state->hero.y)].contents + = CELL_OK; + location_pushI(state, &state->hero); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.start); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.position); + assign_point_xyIF(state->hero.x + 1, state->hero.y, + &state->animation.end); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; } - else if (passable(hero.x, hero.y + 1)) + else if (passable(state, state->hero.x, state->hero.y + 1)) { print_debug("next_step: Trying x, y+1: (%d, %d), " "pushed to stack (%d, %d)", - hero.x, hero.y + 1, hero.x, hero.y); - labyrinth.cells[hero.y][hero.x] = CELL_OK; - location_push(&hero); - assign_point_xyIF(hero.x, hero.y, &animation.position); - assign_point_xyIF(hero.x, hero.y + 1, &animation.destination); - calculate_delta(); - animation.step = 1; - animation.final_step = ANIM_STEPS; - animation.active = 1; + state->hero.x, state->hero.y + 1, state->hero.x, + state->hero.y); + state->grid[INDEX(state->hero.x, state->hero.y)].contents + = CELL_OK; + location_pushI(state, &state->hero); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.start); + assign_point_xyIF(state->hero.x, state->hero.y, + &state->animation.position); + assign_point_xyIF(state->hero.x, state->hero.y + 1, + &state->animation.end); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; } else { /* Back up */ - if (location_pop(&backup)) + if (location_pop(state, &backup)) { print_debug("State: failure"); - state = STATE_FAILURE; + state->state = STATE_FAILURE; } else { print_debug("Backing up, stack pop: %d, %d", backup.x, backup.y); - labyrinth.cells[hero.y][hero.x] = CELL_FAIL; - assign_pointIF(&hero, &animation.position); - assign_pointIF(&backup, &animation.destination); - calculate_delta(); - animation.step = 1; - animation.final_step = ANIM_STEPS; - animation.active = 1; + state->grid[INDEX(state->hero.x, state->hero.y)].contents + = CELL_FAIL; + assign_pointIF(&state->hero, &state->animation.start); + assign_pointIF(&state->hero, &state->animation.position); + assign_pointIF(&backup, &state->animation.end); + calculate_delta(state); + state->animation.step = 1; + state->animation.final_step = ANIM_STEPS; + state->animation.active = 1; } } - redraw = 1; + state->redraw = 1; +} + +int +passable(struct State* state, const int x, const int y) +{ + return (x >= 0 && x < state->grid_width && y >= 0 + && y < state->grid_height + && (state->grid[INDEX(x, y)].contents == CELL_EMPTY + || state->grid[INDEX(x, y)].contents == CELL_END)); } int -passable(const int x, const int y) +point_equalI(struct SDL_Point* p1, struct SDL_Point* p2) { - return (x >= 0 && x < labyrinth.width && y >= 0 && y < labyrinth.height - && (labyrinth.cells[y][x] == CELL_EMPTY - || labyrinth.cells[y][x] == CELL_END)); + assert((p1 != NULL) && (p2 != NULL)); + return p1->x == p2->x && p1->y == p2->y; +} + +void +prepare_dialog(struct State* state, const int w, const int h, const char** text, + const int dx, const int show) +{ + assert((state != NULL) && (text != NULL)); + assign_point_xyII(w, h, &state->dialog_dims); + state->dialog_text_origin.x = dx; + state->dialog_text = text; + state->dialog_shown = show; + if (show) + state->redraw = 1; } void @@ -849,91 +1166,583 @@ print_debug(const char* fmt, ...) { char buf[PRINT_BUFSIZE]; va_list args; - va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); va_end(args); - printf("d: %s\n", buf); - fflush(stdout); + SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "d: %s", buf); } void print_error(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + SDL_LogError(SDL_LOG_CATEGORY_ERROR, fmt, args); + va_end(args); +} + +void +print_matrix(const struct State* state) +{ + const struct Cell* c = NULL; + int x, y; + + for (y = 0; y < state->grid_height; y++) + { + for (x = 0; x < state->grid_width; x++) + { + c = &state->grid[INDEX(x, y)]; + if (c->starting) + printf("%s", cell_image[CELL_START]); + else if (c->ending) + printf("%s", cell_image[CELL_END]); + else if (c->path) + printf("%s", cell_image[CELL_PATH]); + else if (!c->empty) + printf("%s", cell_image[c->contents]); + else + printf("%s", cell_image[CELL_EMPTY]); + // printf("(%0.0f,%0.0f) ", c->coords.x, c->coords.y); + /*if (c->empty) + printf("E "); + else + printf("F ");*/ + } + printf("\n"); + } +} + +void +print_path(struct Cell* c) +{ + printf("(%0.0f, %0.0f)", c->coords.x, c->coords.y); + if (c->path_next) + { + printf(" -> "); + print_path(c->path_next); + } + else + printf("\n"); +} +void +render_background(struct State* state, SDL_FRect* bg_rect) +{ + const SDL_DisplayMode* mode; + SDL_FRect cell_rect, dest_rect; + SDL_DisplayID display; + float csf; + float crx, cry; + float drx, dry; + float sx, sy; + float ex, ey; + int x, y; + + assert((state != NULL) && (bg_rect != NULL)); + assert(state->cell_scale_factor != 0.0f); + + csf = state->cell_scale_factor; + sx = state->start.x; + sy = state->start.y; + ex = state->end.x; + ey = state->end.y; + + if ((display = SDL_GetDisplayForWindow(state->window)) == 0) + { + print_error("SDL_GetDisplayForWindow failed: %s", + SDL_GetError()); + do_exit(1, state); + } + + if ((mode = SDL_GetCurrentDisplayMode(display)) == NULL) + { + print_error("SDL_GetCurrentDisplayMode failed: %s", + SDL_GetError()); + do_exit(1, state); + } + + state->background = SDL_CreateTexture(state->renderer, mode->format, + SDL_TEXTUREACCESS_TARGET, bg_rect->w, bg_rect->h); + if (!state->background) + { + print_error("SDL_CreateTexture failed: %s", SDL_GetError()); + do_exit(1, state); + } + if (!SDL_SetRenderTarget(state->renderer, state->background)) + { + print_error("SDL_SetRenderTarget failed: %s", SDL_GetError()); + do_exit(1, state); + } + + SDL_SetRenderDrawColor(state->renderer, 0x22, 0x22, 0x22, + SDL_ALPHA_OPAQUE); + SDL_RenderFillRect(state->renderer, bg_rect); + + cell_rect.w = CELL_SIZE; + cell_rect.h = CELL_SIZE; + dest_rect.w = csf * CELL_SIZE; + dest_rect.h = csf * CELL_SIZE; + for (y = 0; y < state->grid_height; y++) + for (x = 0; x < state->grid_width; x++) + { + if (!((state->grid[INDEX(x, y)].contents != CELL_OK + && state->grid[INDEX(x, y)].contents + != CELL_FAIL) + || (x == sx && y == sy) + || (x == ex && y == ey))) + continue; + + get_sprite_xy(state->grid[INDEX(x, y)].contents, &crx, + &cry); + cell_rect.x = crx; + cell_rect.y = cry; + grid_coord_to_screen_coordI(state, x, y, 0.0f, 0.0f, + &drx, &dry); + dest_rect.x = drx; + dest_rect.y = dry; + SDL_RenderTexture(state->renderer, state->sprites, + &cell_rect, &dest_rect); + } + + SDL_SetRenderTarget(state->renderer, NULL); +} + +/* state is not const because of renderer, etc */ +void +render_dialog(struct State* state) +{ + const char** ptext = NULL; + SDL_FRect dialog_rect /*, input_rect, cursor_rect*/; + float tsf; + int w, h; + int sx, sy, dx, _sx; + + assert(state != NULL); + assert(state->dialog_text != NULL); + assert(state->text_scale_factor != 0.0f); + + ptext = state->dialog_text; + w = state->dialog_dims.x; + h = state->dialog_dims.y; + sx = state->screen_width / 2 - w / 2; + sy = state->screen_height / 2 - h / 2; + dx = state->dialog_text_origin.x; + tsf = state->text_scale_factor; + + dialog_rect.x = sx; + dialog_rect.y = sy; + dialog_rect.w = w; + dialog_rect.h = h; + + SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, .75 * 255); + SDL_RenderFillRect(state->renderer, &dialog_rect); + + SDL_SetRenderDrawBlendMode(state->renderer, SDL_BLENDMODE_NONE); + SDL_SetRenderDrawColor(state->renderer, 255, 255, 255, SDL_ALPHA_OPAQUE); + SDL_RenderRect(state->renderer, &dialog_rect); + SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); + dialog_rect.x--; + dialog_rect.y--; + dialog_rect.w += 2; + dialog_rect.h += 2; + SDL_RenderRect(state->renderer, &dialog_rect); + dialog_rect.x--; + dialog_rect.y--; + dialog_rect.w += 2; + dialog_rect.h += 2; + SDL_RenderRect(state->renderer, &dialog_rect); + + while (*ptext) + { + _sx = sx + dx + tsf * (2 * TER12_WIDTH); + if (strchr(*ptext, CENTER_LINE)) + _sx += w / 2 - dx + - tsf * TER12_WIDTH + * (2 + strlen_visible(*ptext) / 2); + render_text(state, _sx, + sy + + (ptext - state->dialog_text + 1) * tsf + * (TER12_HEIGHT + 2), + TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, "%s", + *ptext); + ptext++; + } +} + +void +render_hero(struct State* state, const float startx, const float starty) +{ + SDL_FRect dest_rect, cell_rect; + float csf; + float crx, cry; + float drx, dry; + + assert(state != NULL); + + csf = state->cell_scale_factor; + cell_rect.w = CELL_SIZE; + cell_rect.h = CELL_SIZE; + dest_rect.w = csf * CELL_SIZE; + dest_rect.h = csf * CELL_SIZE; + get_hero_sprite_xy(state, &crx, &cry); + cell_rect.x = crx; + cell_rect.y = cry; + if (state->animation.active) + labyrinth_coord_to_screen_coordF(state->animation.position.x, + state->animation.position.y, startx, starty, &drx, + &dry); + else + labyrinth_coord_to_screen_coordI(state->hero.x, state->hero.y, + startx, starty, &drx, &dry); + dest_rect.x = drx; + dest_rect.y = dry; + SDL_RenderTexture(state->renderer, state->sprites, &cell_rect, + &dest_rect); +} + +void +render_status(struct State* state) +{ + render_text(state, 0, + state->screen_height - state->text_scale_factor * TER12_HEIGHT, + TER12_WIDTH, TER12_HEIGHT, state->ter12_texture, + "Column: \1%2d\2, Row: \1%2d\2" + " \1F1\2 = Help" + " Status: \1%s\2", + state->hero.x + 1, state->hero.y + 1, + state_descriptions[state->state]); +} + +void +render_text(struct State* state, const int x, const int y, const int font_width, + const int font_height, SDL_Texture* font_tex, const char* text, ...) +{ char buf[PRINT_BUFSIZE]; va_list args; + const char* pbuf = NULL; + SDL_FRect source_rect; + SDL_FRect dest_rect; + float tsf; + int xstart = 0; + int ystart = 0; + int index = 0; + int chars_per_row = 0; + char first_char = ' '; + char last_char = '~'; + float tex_w, tex_h; + float opacity = TEXT_OPACITY_NORMAL; - va_start(args, fmt); - vsnprintf(buf, sizeof(buf), fmt, args); + assert((font_tex != NULL) && (text != NULL)); + assert(state->text_scale_factor != 0.0f); + assert(font_width != -1); + + tsf = state->text_scale_factor; + + va_start(args, text); + vsnprintf(buf, sizeof(buf), text, args); va_end(args); - fprintf(stderr, "%s: %s\n", PROGNAME, buf); - fflush(stderr); + + source_rect.w = font_width; + source_rect.h = font_height; + + dest_rect.x = x; + dest_rect.y = y; + dest_rect.w = tsf * font_width; + dest_rect.h = tsf * font_height; + + if (!SDL_GetTextureSize(font_tex, &tex_w, &tex_h)) + { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "SDL_GetTextureSize failed: %s", SDL_GetError()); + do_exit(1, state); + } + + SDL_SetTextureBlendMode(font_tex, SDL_BLENDMODE_BLEND); + + chars_per_row = tex_w / (font_width + 1); + + pbuf = buf; + while (*pbuf) + { + if (*pbuf >= first_char && *pbuf <= last_char) + { + index = (int)(*pbuf - first_char); + + xstart = 1 + index % chars_per_row * (font_width + 1); + ystart = 1 + index / chars_per_row * (font_height + 1); + } + else if (*pbuf == CENTER_LINE) + { + pbuf++; + continue; + } + else if (*pbuf == BOLD_START) + { + opacity = TEXT_OPACITY_BOLD; + pbuf++; + continue; + } + else if (*pbuf == BOLD_END) + { + opacity = TEXT_OPACITY_NORMAL; + pbuf++; + continue; + } + else if (*pbuf == DIM_START) + { + opacity = TEXT_OPACITY_DIM; + pbuf++; + continue; + } + else + break; + + source_rect.x = xstart; + source_rect.y = ystart; + + if (!SDL_SetTextureAlphaModFloat(font_tex, opacity)) + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "SDL_SetTextureAlphaModFloat is not supported"); + SDL_RenderTexture(state->renderer, font_tex, &source_rect, + &dest_rect); + + dest_rect.x += tsf * font_width; + + pbuf++; + } +} + +void +render_things(struct State* state, const float startx, const float starty) +{ + SDL_FRect cell_rect, dest_rect; + int x, y; + float csf; + float crx, cry; + float drx, dry; + + csf = state->cell_scale_factor; + cell_rect.w = CELL_SIZE; + cell_rect.h = CELL_SIZE; + dest_rect.w = csf * CELL_SIZE; + dest_rect.h = csf * CELL_SIZE; + for (y = 0; y < state->grid_height; y++) + for (x = 0; x < state->grid_width; x++) + { + if ((state->grid[INDEX(x, y)].contents != CELL_OK + && state->grid[INDEX(x, y)].contents + != CELL_FAIL) + || (x == state->start.x && y == state->start.y) + || (x == state->end.x && y == state->end.y)) + continue; + + get_sprite_xy(state->grid[INDEX(x, y)].contents, &crx, + &cry); + cell_rect.x = crx; + cell_rect.y = cry; + labyrinth_coord_to_screen_coordI(x, y, startx, starty, + &drx, &dry); + dest_rect.x = drx; + dest_rect.y = dry; + SDL_RenderTexture(state->renderer, state->sprites, + &cell_rect, &dest_rect); + } } void -reset(void) +reset(struct State* state) { - for (int y = 0; y < labyrinth.height; y++) - for (int x = 0; x < labyrinth.width; x++) - if (labyrinth.cells[y][x] == CELL_FAIL - || labyrinth.cells[y][x] == CELL_OK) - labyrinth.cells[y][x] = CELL_EMPTY; - hero.x = start.x; - hero.y = start.y; - state = STATE_SEARCHING_PAUSED; - location_pointer = 0; - redraw = 1; + int x, y; + assert(state != NULL); + + if (!state->search_shortest) + state->location_pointer = 0; + + for (y = 0; y < state->grid_height; y++) + for (x = 0; x < state->grid_width; x++) + if (state->grid[INDEX(x, y)].contents == CELL_FAIL + || state->grid[INDEX(x, y)].contents == CELL_OK) + state->grid[INDEX(x, y)].contents = CELL_EMPTY; + state->hero.x = state->start.x; + state->hero.y = state->start.y; + state->state = STATE_SEARCHING_PAUSED; + state->redraw = 1; } void -step_animation(void) +reset_path(struct State* state) +{ + struct Cell* c = NULL; + struct Cell* grid = NULL; + int x, y; + + assert(state != NULL); + + grid = state->grid; + for (y = 0; y < LABYRINTH_MAXHEIGHT; y++) + for (x = 0; x < LABYRINTH_MAXWIDTH; x++) + { + c = &grid[INDEX(x, y)]; + c->path_next = NULL; + c->path_prev = NULL; + c->path_dist = INT_MAX; + c->starting = 0; + c->ending = 0; + c->path = 0; + } +} +void +step_animation(struct State* state) +{ + struct Cell* from = NULL; + struct Cell* to = NULL; + int isx, isy, iex, iey; + + assert((state != NULL) + && !(state->animation.delta.x == 0.0f + && state->animation.delta.y == 0.0f)); + + if (state->state == STATE_SEARCHING_PAUSED) + return; + + if (!state->search_shortest) + goto step_animation_brute_force; + + if (state->animation.step == state->animation.final_step) + { + isx = (int)round(state->animation.start.x); + isy = (int)round(state->animation.start.y); + iex = (int)round(state->animation.end.x); + iey = (int)round(state->animation.end.y); + from = &state->grid[INDEX(isx, isy)]; + to = &state->grid[INDEX(iex, iey)]; + + if (!from->starting) + from->contents = CELL_OK; + + if (to->ending) + { + state->animation.active = 0; + print_debug("Ending node reached: (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + } + else if (to->path_next) + { + state->animation.start.x = state->animation.end.x; + state->animation.start.y = state->animation.end.y; + state->animation.position.x = state->animation.start.x; + state->animation.position.y = state->animation.start.y; + state->animation.end.x = to->path_next->coords.x; + state->animation.end.y = to->path_next->coords.y; + + print_debug("Next node on path: (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + print_debug( + "Animation: (%0.0f, %0.0f) to (%0.0f, %0.0f)", + state->animation.start.x, + state->animation.start.y, + state->animation.end.x, state->animation.end.y); + calculate_delta(state); + state->animation.step = 1; + } + else + { + print_debug("End of path at (%0.0f, %0.0f)", + state->animation.end.x, state->animation.end.y); + state->animation.active = 0; + } + + print_debug("grid[%d][%d] = %d <-> grid[%d][%d] = %d", iey, iex, + to->contents, isy, isx, from->contents); + state->hero.x = to->coords.x; + state->hero.y = to->coords.y; + + if (to->ending) + { + state->state = STATE_SUCCESS; + state->redraw = 1; + } + } + +step_animation_brute_force: + print_debug("step_animation: (%0.2f, %0.2f) => (%0.2f, %0.2f)", + state->animation.position.x, state->animation.position.y, + state->animation.position.x + state->animation.delta.x, + state->animation.position.y + state->animation.delta.y); + state->animation.position.x += state->animation.delta.x; + state->animation.position.y += state->animation.delta.y; + state->animation.step++; + + if (state->search_shortest) + return; + + if (state->animation.step == state->animation.final_step) + { + state->animation.active = 0; + state->hero.x = (int)state->animation.end.x; + state->hero.y = (int)state->animation.end.y; + } +} + +int +strlen_visible(const char* s) { - animation.position.x += animation.delta.x; - animation.position.y += animation.delta.y; - animation.step++; - if (animation.step == animation.final_step) + int result = 0; + const char* ps = NULL; + + assert(s != NULL); + ps = s; + while (*ps) { - animation.active = 0; - hero.x = (int)animation.destination.x; - hero.y = (int)animation.destination.y; + if (*ps != CENTER_LINE && *ps != BOLD_START && *ps != BOLD_END + && *ps != DIM_START) + result++; + ps++; } + print_debug("strlen_visible(%s)=%d\n", s, result); + return result; } -SDL_Surface* -try_load(const char* filename) +SDL_Texture* +try_load(SDL_Renderer* renderer, const char* filename) { + SDL_Texture* tex = NULL; char tex_pathname[BUFSIZE]; - SDL_Surface* surface = NULL; + assert((renderer != NULL) && (filename != NULL)); snprintf(tex_pathname, BUFSIZE, "%s/%s", DATADIR, filename); - surface = IMG_Load(tex_pathname); - if (!surface) + tex = IMG_LoadTexture(renderer, tex_pathname); + if (!tex) { - print_debug("try_load: IMG_Load failed (%s): %s", tex_pathname, + print_error("try_load: IMG_LoadTexture failed: %s", SDL_GetError()); snprintf(tex_pathname, BUFSIZE, "./%s", filename); print_debug("try_load: trying %s", tex_pathname); - surface = IMG_Load(tex_pathname); - if (!surface) + tex = IMG_LoadTexture(renderer, tex_pathname); + if (!tex) { - print_error("try_load: IMG_Load failed (%s): %s", - tex_pathname, SDL_GetError()); + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "try_load: IMG_LoadTexture failed: %s", + SDL_GetError()); exit(1); } + print_debug("try_load: success (%s)", tex_pathname); } - return surface; + return tex; } int main(int argc, char** argv) { SDL_Event event; + struct State state; float px, py; unsigned int last_time, current_time; char datafile_pathname[BUFSIZE]; + int optind = 1; - atexit(&cleanup); + init_state(&state); if (!SDL_Init(SDL_INIT_VIDEO)) { @@ -941,97 +1750,99 @@ main(int argc, char** argv) exit(1); } + SDL_SetLogPriorities(SDL_LOG_PRIORITY_DEBUG); if (!SDL_CreateWindowAndRenderer(WIN_TITLE, DEFAULT_WIDTH, DEFAULT_HEIGHT, - SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, &window, - &renderer)) + SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS, + &state.window, &state.renderer)) { print_error("SDL_CreateWindowAndRenderer failed: %s", SDL_GetError()); - exit(1); + do_exit(1, &state); } - // SDL_SetWindowFullscreenMode(window, NULL); - // SDL_SetWindowFullscreen(window, 1); SDL_SetHintWithPriority(SDL_HINT_RENDER_VSYNC, "1", SDL_HINT_OVERRIDE); - SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH, + SDL_SetRenderLogicalPresentation(state.renderer, DEFAULT_WIDTH, DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); - /*SDL_SetRenderLogicalPresentation(renderer, DEFAULT_WIDTH, - DEFAULT_HEIGHT, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);*/ - /*float factor = (float)714/720; - print_debug("factor = %0.2f", factor); - SDL_SetRenderScale(renderer, 1.0f, factor);*/ - calculate_screen_size(); + calculate_display_size(&state); - if (load_textures() < 0) - exit(1); + if (load_textures(&state) < 0) + do_exit(1, &state); if (argc > 1) { - if (load_labyrinth(*(argv + 1)) < 0) + if (!strcmp(argv[1], "-s")) + { + state.search_shortest = 1; + optind++; + if (optind >= argc) + goto load_default_file; + } + + if (load_labyrinth(&state, argv[optind]) < 0) { print_error("load_labyrinth failed: %s (%s)", - strerror(errno), *(argv + 1)); - exit(1); + strerror(errno), argv[optind]); + do_exit(1, &state); } } else { + load_default_file: snprintf(datafile_pathname, BUFSIZE, "%s/%s", DATADIR, DEFAULT_DATAFILE); - if (load_labyrinth(datafile_pathname) < 0) + if (load_labyrinth(&state, datafile_pathname) < 0) { print_debug("fopen failed: %s (%s)", strerror(errno), datafile_pathname); snprintf(datafile_pathname, BUFSIZE, "./%s", DEFAULT_DATAFILE); print_debug("trying %s", datafile_pathname); - if (load_labyrinth(datafile_pathname) < 0) + if (load_labyrinth(&state, datafile_pathname) < 0) { print_error("fopen failed: %s (%s)", strerror(errno), datafile_pathname); - exit(1); + do_exit(1, &state); } } } - location_stack - = (SDL_Point*)calloc(LOCATION_STACK_MAX, sizeof(SDL_Point)); - animation.active = 0; + state.location_stack = calloc(LOCATION_STACK_MAX, sizeof(SDL_Point)); + state.animation.active = 0; SDL_HideCursor(); SDL_GetMouseState(&px, &py); - pointer.x = px; - pointer.y = py; - last_time = SDL_GetTicks(); - while (running) + state.pointer.x = px; + state.pointer.y = py; + last_time = SDL_GetTicks(); + while (state.running) { current_time = SDL_GetTicks(); - if (animation.active) + if (state.animation.active) { if (current_time > last_time + 1000 / 60) { - draw(); - step_animation(); + draw(&state); + step_animation(&state); last_time = current_time; } } else { - if (state == STATE_SEARCHING) + if (state.state == STATE_SEARCHING) { - next_step(); + next_step(&state); } - if (redraw) + if (state.redraw) { - draw(); - redraw = 0; + draw(&state); + state.redraw = 0; } } while (SDL_PollEvent(&event)) - handle_event(&event); + handle_event(&state, &event); } return 0; diff --git a/lib/makedate b/lib/makedate @@ -4,7 +4,38 @@ FALLBACKDATE=${FALLBACKDATE:-unknown} LC_ALL=C export LC_ALL -if [ -d .got ] && command -v got >/dev/null 2>&1; then +die() +{ + printf "%s\n" "$@" >&2 + exit 1 +} + +exists() +{ + command -v $* >/dev/null 2>&1 +} + +_getent() +{ + if exists getent; then + command getent "$@" 2>/dev/null + return + fi + case "$1" in + passwd|group) + if [ -z "$2" ]; then + cat /etc/"$1" + else + awk -F: -vu="$2" '{ if ($3 == u) print }' /etc/"$1" + fi + ;; + *) ;; + esac +} + +exists diff || die "The build script $0 requires diff(1), none found" + +if [ -d .got ] && exists got; then got log | awk ' /^date: / { @@ -25,9 +56,12 @@ if [ -d .got ] && command -v got >/dev/null 2>&1; then print month_names[$2] " " $3 ", " $5 exit }' >date.new -elif [ -d .git ] && command -v git >/dev/null 2>&1; then - user=$(getent passwd "$(unalias ls >/dev/null 2>&1 || true; \ - command ls -lnd . | awk '{print $3}')" | awk -F: '{print $1}') +elif [ -d .git ] && exists git; then + + user=$(printf "%s\n" "$(_getent passwd \ + "$(unalias ls >/dev/null 2>&1 || true; \ + command ls -nd . | awk '{print $3}')")" | + awk -F: '{print $1}') if [ -z "$user" ]; then printf "$0: error: Cannot determine owner of %s" "$(pwd)" >&2 exit 1 diff --git a/lib/makeversion b/lib/makeversion @@ -1,8 +1,41 @@ #!/bin/sh -x # vim: set ft=bash: FALLBACKVER=${FALLBACKVER:-unknown} +LC_ALL=C +export LC_ALL -if [ -d .got ] && command -v got >/dev/null 2>&1; then +die() +{ + printf "%s\n" "$@" >&2 + exit 1 +} + +exists() +{ + command -v $* >/dev/null 2>&1 +} + +_getent() +{ + if exists getent; then + command getent "$@" 2>/dev/null + return + fi + case "$1" in + passwd|group) + if [ -z "$2" ]; then + cat /etc/"$1" + else + awk -F: -vu="$2" '{ if ($3 == u) print }' /etc/"$1" + fi + ;; + *) ;; + esac +} + +exists diff || die "The build script $0 requires diff(1), none found" + +if [ -d .got ] && exists got; then lastcommit=$(got log | awk '/^commit/ {print substr($2,1,7); exit}') got tag -l | @@ -30,11 +63,12 @@ if [ -d .got ] && command -v got >/dev/null 2>&1; then } printf "\n" }' >version.new -elif [ -d .git ] && command -v git >/dev/null 2>&1; then - LC_ALL=C - export LC_ALL - user=$(getent passwd "$(unalias ls >/dev/null 2>&1 || true; \ - command ls -lnd . | awk '{print $3}')" | awk -F: '{print $1}') +elif [ -d .git ] && exists git; then + + user=$(printf "%s\n" "$(_getent passwd \ + "$(unalias ls >/dev/null 2>&1 || true; \ + command ls -nd . | awk '{print $3}')")" | + awk -F: '{print $1}') if [ -z "$user" ]; then printf "$0: error: Cannot determine owner of %s" "$(pwd)" >&2 exit 1 diff --git a/lib/mkpack b/lib/mkpack @@ -4,7 +4,7 @@ DISTDIR=$(grep '^PROG[[:space:]]\{1,\}=' config.mk | sed -E 's,.*=[[:space:]]+([[:alpha:]]+),\1,')-$(cat version) mkdir "$DISTDIR" if [ -d .got ] && command -v got >/dev/null 2>&1; then - files=$(got tree | sed -e 's,[@/*$]$,,g') + files=$(got tree -R | sed -e 's,[@/*$]$,,g') elif [ -d .git ] && command -v git >/dev/null 2>&1; then files=$(git ls-files) else @@ -14,7 +14,7 @@ fi for f in $files; do d="$DISTDIR"/"$(dirname "$f")" mkdir -p "$d" - cp "$f" "$d"/ + [ -f "$f" ] && cp "$f" "$d"/ done tar -cf - "$DISTDIR" | xz -9v - > "$DISTDIR".tar.xz rm -fr "$DISTDIR" diff --git a/lib/replacepat b/lib/replacepat @@ -1,10 +1,6 @@ #!/bin/sh # vim: set ft=bash: -infile="$1" -outfile="$2" -DATE=$(cat date 2>/dev/null) -VERSION=$(cat version 2>/dev/null) - -sed -e 's|%VERSION%|'"$VERSION"'|g' -e 's|%DATE%|'"$DATE"'|g' \ - -e 's|%DATADIR%|'"$DATADIR"'|g' "$infile" \ - > "$outfile" +DATE=$(cat date) +VERSION=$(cat version) +sed -e 's|%VERSION%|'"$VERSION"'|g' -e 's|%DATE%|'"$DATE"'|g' \ + -e 's|%DATADIR%|'"$DATADIR"'|g' "$1" > "$2" diff --git a/local.h.in b/local.h.in @@ -1,5 +1,5 @@ /* This program is licensed under the terms of GNU GPL v3 or (at your option) - * any later version. Copyright (C) 2023-2025 Страхиња Радић. + * any later version. Copyright (C) 2023-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ #define DATADIR "%DATADIR%"