#include // bool #include // EXIT_{FAILURE,SUCCESS} #include // strerror #include // errno #include #include #include "../levels/levels.h" #include "../core/sok.h" #include "util.h" #include "action.h" #include "warp-buf.h" #include "sprites.h" #include "draw.h" #include "log-renderer-info.h" #include "assets.h" #include "solve.h" #include "sounds.h" #define WINDOW_TITLE "Pablotron Sokoban" static TTF_Font * load_font( const asset_id_t id ) { // get asset const asset_t * const asset = asset_get(id); if (!asset) { die("asset_get()"); } // create io SDL_RWops *rw = SDL_RWFromConstMem(asset->buf, asset->len); if (!rw) { die("SDL_RWFromConstMem(): %s", SDL_GetError()); } // load font TTF_Font *font = TTF_OpenFontIndexRW(rw, 1, 16, 0); if (!font) { die("TTF_OpenFontIndex(): %s", TTF_GetError()); } // return font return font; } static void set_level( draw_ctx_t * const draw_ctx, sok_ctx_t * const ctx, const size_t level_num ) { // get level data draw_ctx->level = levels_get_level(level_num); // load level if (!sok_ctx_set_level(ctx, draw_ctx->level->data)) { die("Couldn't load level %d", (int) level_num); } // log level title SDL_Log( "Loaded level \"%s: %s\" (#%d)", draw_ctx->level->pack, draw_ctx->level->name, (int) level_num ); } static void log_moves( const sok_ctx_t * const ctx ) { char buf[1024] = { 0 }; size_t ofs = 0; for (size_t i = 0; i < ctx->num_moves; i++) { if (ctx->moves[i].dir >= SOK_DIR_LAST) { die("invalid move: %u", ctx->moves[i].dir); } buf[ofs++] = SOK_DIR_TO_CHAR(ctx->moves[i].dir); } SDL_Log("Solution (%d moves): %s", (int) ctx->num_moves, buf); } static void solve_on_done( const bool result, const sok_ctx_t * const solved_ctx, const size_t num_steps, void *user_data ) { if (result) { // copy context sok_ctx_t *ctx = user_data; *ctx = *solved_ctx; // log moves log_moves(ctx); } } static void bump( draw_ctx_t * const draw_ctx, Mix_Chunk ** sounds, const Uint32 ticks ) { draw_ctx->bump_ticks = ticks; sound_play(sounds, ASSET_SOUND_HIT_1_WAV); } int main(int argc, char *argv[]) { size_t level_num = (argc > 1) ? atoi(argv[1]) : 0, zoom = 0; // init warp buffer warp_buf_t warp_buf; warp_buf_clear(&warp_buf); // init sok context sok_ctx_t ctx; sok_ctx_init(&ctx, NULL); // init sdl if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) { die("SDL_Init(): %s", SDL_GetError()); } // register SDL exit handler if (atexit(SDL_Quit)) { die("atexit(SDL_Init): %s", strerror(errno)); exit(EXIT_FAILURE); } // init SDL_mixer if (Mix_Init(0)) { die("Mix_Init(): %s", Mix_GetError()); } // register SDL_mixer exit handler if (atexit(Mix_Quit)) { die("atexit(Mix_Quit): %s", strerror(errno)); exit(EXIT_FAILURE); } // open audio if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024)) { die("Mix_OpenAudio(): %s", Mix_GetError()); } // init TTF if (TTF_Init()) { die("TTF_Init(): %s", TTF_GetError()); } // register TTF exit handler if (atexit(TTF_Quit)) { die("atexit(TTF_Quit): %s", strerror(errno)); exit(EXIT_FAILURE); } // create window SDL_Window *win = SDL_CreateWindow( WINDOW_TITLE, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_RESIZABLE ); // check for error if (!win) { die("SDL_CreateWindow(): %s", SDL_GetError()); } // load sounds Mix_Chunk *sounds[ASSET_SOUND_LAST]; sounds_init(sounds); // create renderer SDL_Renderer *renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); if (!renderer) { die("SDL_CreateRenderer(): %s", SDL_GetError()); } log_renderer_info(renderer); // init draw context draw_ctx_t draw_ctx = { .state = GAME_STATE_PLAY, .level_num = &level_num, .ctx = &ctx, .renderer = renderer, .zoom = &zoom, .theme = theme_get_default(), .bump_ticks = SDL_GetTicks() - 5000, }; // set level set_level(&draw_ctx, &ctx, level_num); // init sprites, set window icon, load font sprites_init(renderer, draw_ctx.sprites); sprites_set_window_icon(win, SPRITE_HOME); draw_ctx.font = load_font(ASSET_ROBOTO_TTF); // register solve event const Uint32 solve_event_type = SDL_RegisterEvents(1); if (solve_event_type == ((Uint32)-1)) { die("SDL_RegisterEvents(): %s", SDL_GetError()); } bool is_fullscreen = false; bool done = false; while (!done) { const Uint32 ticks = SDL_GetTicks(); SDL_Event ev; // read events while (SDL_PollEvent(&ev)) { // get action const action_t action = get_action(draw_ctx.state, &ev, solve_event_type); // handle action switch (action.type) { case ACTION_NONE: // do nothing break; case ACTION_QUIT: done = true; break; case ACTION_MOVE: if (sok_ctx_move(&ctx, (sok_dir_t) action.data)) { if (sok_ctx_is_done(&ctx)) { // play sound sound_play(sounds, ASSET_SOUND_POWERUP_3_WAV); } } else { // move failed bump(&draw_ctx, sounds, ticks); warn("move %s failed", SOK_DIR_TO_STR((sok_dir_t) action.data)); } break; case ACTION_UNDO: if (sok_ctx_undo(&ctx)) { // play sound sound_play(sounds, ASSET_SOUND_HIT_1_WAV); } else { bump(&draw_ctx, sounds, ticks); warn("undo failed"); } break; case ACTION_NEXT: if (sok_ctx_is_done(&ctx)) { // play sound sound_play(sounds, ASSET_SOUND_JUMP_0_WAV); // advance level level_num++; // set level set_level(&draw_ctx, &ctx, level_num); } else { bump(&draw_ctx, sounds, ticks); warn("cannot advance to next level"); } break; case ACTION_RESET: // play sound sound_play(sounds, ASSET_SOUND_UNDO_0_WAV); // reset level if (!sok_ctx_set_level(&ctx, draw_ctx.level->data)) { die("Couldn't load level %d", (int) level_num); } break; case ACTION_WARP: if (warp_buf_get(&warp_buf, &level_num)) { // play sound sound_play(sounds, ASSET_SOUND_JUMP_0_WAV); // load level set_level(&draw_ctx, &ctx, level_num); // clear warp buf warp_buf_clear(&warp_buf); } break; case ACTION_WARP_BUF_PUSH: warp_buf_push_num(&warp_buf, action.data); break; case ACTION_WARP_BUF_POP: warp_buf_pop_num(&warp_buf); break; case ACTION_SOLVE: draw_ctx.state = GAME_STATE_SOLVE; draw_ctx.solve_num_steps = 0; draw_ctx.solve = solve(&ctx, solve_event_type); break; case ACTION_ZOOM_IN: if (zoom < 30) { zoom++; } break; case ACTION_ZOOM_OUT: if (zoom > 0) { zoom--; } break; case ACTION_FULLSCREEN: if (SDL_SetWindowFullscreen(win, is_fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP)) { die("SDL_SetWindowFullscreen(): %s", SDL_GetError()); } // toggle flag is_fullscreen = !is_fullscreen; break; case ACTION_SOLVE_CANCEL: SDL_Log("solve cancelled by user"); sound_play(sounds, ASSET_SOUND_HIT_2_WAV); draw_ctx.state = GAME_STATE_PLAY; solve_cancel(draw_ctx.solve); break; case ACTION_SOLVE_EVENT_STEP: draw_ctx.solve_num_steps = action.data; break; case ACTION_SOLVE_EVENT_DONE: SDL_Log("solve done"); sound_play(sounds, ASSET_SOUND_POWERUP_4_WAV); draw_ctx.state = GAME_STATE_PLAY; // TODO: handle success solve_fini(draw_ctx.solve, solve_on_done, &ctx); draw_ctx.solve = NULL; break; case ACTION_SOLVE_EVENT_FAIL: SDL_Log("solve fail"); draw_ctx.state = GAME_STATE_PLAY; solve_fini(draw_ctx.solve, NULL, NULL); draw_ctx.solve = NULL; break; default: // ignore break; } } // draw draw(&draw_ctx); } // fini renderer, window SDL_DestroyRenderer(renderer); SDL_DestroyWindow(win); // return success return EXIT_SUCCESS; }