#include // bool #include // EXIT_{FAILURE,SUCCESS} #include // strerror #include // errno #include #include #include "../text/levels.h" #include "../libsok/sok.h" #include "action.h" #define warn(...) do { \ SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__); \ } while (0) #define die(...) do { \ SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__); \ exit(EXIT_FAILURE); \ } while (0) static void solve_on_error( const char * const err ) { die("Error solving level: %s", err); } static void draw_moves( const sok_ctx_t * const ctx, const size_t skip_moves ) { printf("Solution (%zu moves): ", ctx->num_moves - skip_moves); for (size_t i = skip_moves; i < ctx->num_moves; i++) { if (ctx->moves[i].dir >= SOK_DIR_LAST) { die("invalid move: %u", ctx->moves[i].dir); } fputs(SOK_DIR_TO_STR(ctx->moves[i].dir), stdout); } printf("\n"); } static const SDL_Color RED = { .r = 0x00, .g = 0xFF, .b = 0x00, .a = 0xFF }, BLACK = { .r = 0x00, .g = 0x00, .b = 0x00, .a = 0xFF }; static SDL_Color get_bg( const sok_ctx_t * const ctx ) { return (sok_ctx_is_done(ctx)) ? RED : BLACK; } bool draw_on_wall( const sok_ctx_t * const ctx, const sok_pos_t pos, void * const data ) { SDL_Renderer *renderer = data; SDL_Rect rect = { 32 * pos.x, 32 * pos.y, 32, 32 }; SDL_SetRenderDrawColor(renderer, 0x66, 0x66, 0x66, 0xFF); if (SDL_RenderFillRect(renderer, &rect)) { die("SDL_RenderFillRect(): %s", SDL_GetError()); } return true; } bool draw_on_goal( const sok_ctx_t * const ctx, const sok_pos_t pos, const bool has_player, const bool has_box, void * const data ) { SDL_Renderer *renderer = data; SDL_Rect rect = { 32 * pos.x, 32 * pos.y, 32, 32 }; SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF); if (SDL_RenderFillRect(renderer, &rect)) { die("SDL_RenderFillRect(): %s", SDL_GetError()); } return true; } bool draw_on_home( const sok_ctx_t * const ctx, const sok_pos_t pos, const bool has_goal, void * const data ) { SDL_Renderer *renderer = data; SDL_Rect rect = { 32 * pos.x, 32 * pos.y, 32, 32 }; SDL_SetRenderDrawColor(renderer, 0xFF, has_goal ? 0xFF : 0x00, 0x00, 0xFF); if (SDL_RenderFillRect(renderer, &rect)) { die("SDL_RenderFillRect(): %s", SDL_GetError()); } return true; } bool draw_on_box( const sok_ctx_t * const ctx, const sok_pos_t pos, const bool has_goal, void * const data ) { SDL_Renderer *renderer = data; SDL_Rect rect = { 32 * pos.x, 32 * pos.y, 32, 32 }; SDL_SetRenderDrawColor(renderer, 0x00, has_goal ? 0xFF : 0x00, 0xFF, 0xFF); if (SDL_RenderFillRect(renderer, &rect)) { die("SDL_RenderFillRect(): %s", SDL_GetError()); } return true; } static sok_ctx_walk_cbs_t DRAW_CBS = { .on_wall = draw_on_wall, .on_goal = draw_on_goal, .on_home = draw_on_home, .on_box = draw_on_box, }; /* * typedef struct { * sok_ctx_walk_pos_cb_t on_size, * on_wall; * sok_ctx_walk_tile_cb_t on_home, * on_box; * sok_ctx_walk_goal_cb_t on_goal; * sok_ctx_walk_move_cb_t on_move; * } sok_ctx_walk_cbs_t; * * _Bool sok_ctx_walk( * const sok_ctx_t * const, * const sok_ctx_walk_cbs_t * const, * void * const * ); */ static void draw( SDL_Renderer * const renderer, const sok_ctx_t * const ctx, const size_t level_num, const level_t * const level ) { // clear const SDL_Color c = get_bg(ctx); SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a); SDL_RenderClear(renderer); sok_ctx_walk(ctx, &DRAW_CBS, renderer); // flip SDL_RenderPresent(renderer); } int main(int argc, char *argv[]) { size_t warp_buf = 0; size_t level_num = (argc > 1) ? atoi(argv[1]) : 0; const level_t *level = levels_get_level(level_num); // init context sok_ctx_t ctx; sok_ctx_init(&ctx, NULL); if (!sok_ctx_set_level(&ctx, level->data)) { die("Couldn't load level %zu", level_num); } // init sdl if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { die("SDL_Init(): %s", SDL_GetError()); } if (atexit(SDL_Quit)) { die("atexit(): %s", strerror(errno)); exit(EXIT_FAILURE); } // create window and renderer SDL_Window *win; SDL_Renderer *renderer; if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &win, &renderer)) { die("SDL_CreateWindowAndRenderer(): %s", SDL_GetError()); } bool done = false; SDL_Event ev; while (!done) { while (SDL_PollEvent(&ev)) { switch (ev.type) { case SDL_QUIT: done = true; break; case SDL_KEYUP: { const action_t action = get_action(ev.key.keysym.sym); switch (action.type) { case ACTION_QUIT: done = true; break; case ACTION_MOVE: if (!sok_ctx_move(&ctx, (sok_dir_t) action.data)) { warn("move %s failed", SOK_DIR_TO_STR((sok_dir_t) action.data)); } break; case ACTION_UNDO: if (!sok_ctx_undo(&ctx)) { warn("undo failed"); } break; case ACTION_NEXT: if (sok_ctx_is_done(&ctx)) { // advance level level_num++; level = levels_get_level(level_num); // load next level if (!sok_ctx_set_level(&ctx, level->data)) { die("Couldn't load level %zu", level_num); } } else { warn("cannot advance to next level"); } break; case ACTION_RESET: // reset level if (!sok_ctx_set_level(&ctx, level->data)) { die("Couldn't load level %zu", level_num); } break; case ACTION_WARP: level_num = warp_buf; level = levels_get_level(level_num); // load level if (!sok_ctx_set_level(&ctx, level->data)) { die("Couldn't load level %zu", level_num); } warp_buf = 0; break; case ACTION_WARP_BUF_PUSH: warp_buf = 10 * warp_buf + action.data; break; case ACTION_WARP_BUF_POP: warp_buf /= 10; break; case ACTION_SOLVE: { // get current number of moves const size_t old_num_moves = ctx.num_moves; if (sok_solve(&ctx, solve_on_error)) { // found solution, print it draw_moves(&ctx, old_num_moves); } else { warn("Couldn't solve level"); } } default: // ignore break; } } break; default: // ignore break; } } draw(renderer, &ctx, level_num, level); } // fini renderer, window SDL_DestroyRenderer(renderer); SDL_DestroyWindow(win); // return success return EXIT_SUCCESS; }