#include #include // debugging #include // memset() #include "sok.h" #define UNUSED(a) ((void) (a)) #define VALID_POS(pos) ( \ (pos).x < SOK_LEVEL_MAX_WIDTH && \ (pos).y < SOK_LEVEL_MAX_HEIGHT \ ) #define POINTS_EQUAL(a, b) ( \ ((a).x == (b).x) && \ ((a).y == (b).y) \ ) static bool on_size( const sok_level_parser_t * const parser, const sok_pos_t pos ) { sok_ctx_t *ctx = (sok_ctx_t*) parser->user_data; if (!VALID_POS(pos)) { // size out of bounds return false; } // save width and height ctx->level.size = pos; // return success return true; } static bool on_home( const sok_level_parser_t * const parser, const sok_pos_t pos ) { sok_ctx_t *ctx = (sok_ctx_t*) parser->user_data; if (!VALID_POS(pos)) { // position out of bounds return false; } // save start position ctx->level.home = pos; // return success return true; } static bool on_wall( const sok_level_parser_t * const parser, const sok_pos_t pos ) { sok_ctx_t *ctx = (sok_ctx_t*) parser->user_data; if (!VALID_POS(pos)) { // position out of bounds return false; } // save wall ctx->level.walls[pos.y * ctx->level.size.x + pos.x] = true; // return sucess return true; } static bool on_goal( const sok_level_parser_t * const parser, const sok_pos_t pos ) { sok_ctx_t *ctx = (sok_ctx_t*) parser->user_data; if (!VALID_POS(pos)) { // position out of bounds return false; } if (ctx->level.num_goals >= SOK_LEVEL_MAX_GOALS - 1) { // too many goals return false; } // save goal ctx->level.goals[ctx->level.num_goals++] = pos; // return sucess return true; } static bool on_box( const sok_level_parser_t * const parser, const sok_pos_t pos ) { sok_ctx_t *ctx = (sok_ctx_t*) parser->user_data; if (!VALID_POS(pos)) { // position out of bounds return false; } if (ctx->level.num_boxes >= SOK_LEVEL_MAX_BOXES - 1) { // too many boxes return false; } // save box ctx->level.boxes[ctx->level.num_boxes++] = pos; // return sucess return true; } static bool on_junk( const sok_level_parser_t * const parser, const size_t ofs, const char tile ) { UNUSED(parser); UNUSED(ofs); UNUSED(tile); // TODO // return sucess return true; } static const sok_level_parser_cbs_t LEVEL_PARSER_CBS = { .on_size = on_size, .on_home = on_home, .on_wall = on_wall, .on_goal = on_goal, .on_box = on_box, .on_junk = on_junk, }; void sok_ctx_init(sok_ctx_t * const ctx, void *user_data) { memset(ctx, 0, sizeof(sok_ctx_t)); ctx->user_data = user_data; } bool sok_ctx_set_level( sok_ctx_t * const ctx, const char * const str ) { // clear level ctx->level.num_goals = 0; ctx->level.num_boxes = 0; memset(ctx->level.walls, 0, SOK_LEVEL_MAX_WIDTH * SOK_LEVEL_MAX_HEIGHT); // parse level sok_level_parser_t parser; sok_level_parser_init(&parser, &LEVEL_PARSER_CBS, ctx); if (!sok_level_parser_parse(&parser, str, strlen(str))) { // return failure return false; } // init moves ctx->num_moves = 0; // init player ctx->home = ctx->level.home; // init boxes memcpy(ctx->boxes, ctx->level.boxes, ctx->level.num_boxes * sizeof(sok_pos_t)); // return success return true; } static bool tile_is_floor( const sok_ctx_t * const ctx, const sok_pos_t pos ) { return ( (pos.x < SOK_LEVEL_MAX_WIDTH - 1) && (pos.y < SOK_LEVEL_MAX_HEIGHT - 1) && !(ctx->level.walls[pos.y * ctx->level.size.x + pos.x]) ); } static bool tile_is_box( const sok_ctx_t * const ctx, const sok_pos_t pos ) { for (size_t i = 0; i < ctx->level.num_boxes; i++) { // fprintf(stderr, "D: ctx->boxes[%zu] = {%u, %u}\n", i, ctx->boxes[i].x, ctx->boxes[i].y); if (POINTS_EQUAL(ctx->boxes[i], pos)) { return true; } } return false; } static bool tile_is_goal( const sok_ctx_t * const ctx, const sok_pos_t pos ) { for (size_t i = 0; i < ctx->level.num_goals; i++) { if (POINTS_EQUAL(pos, ctx->level.goals[i])) { return true; } } return false; } static bool sok_can_move( sok_ctx_t * const ctx, const sok_dir_t dir ) { if (ctx->num_moves >= SOK_CTX_MAX_MOVES - 1) { // no more move slots available return false; } if (dir == SOK_DIR_UP) { const sok_pos_t ps[2] = { { .x = ctx->home.x, .y = ctx->home.y - 1 }, { .x = ctx->home.x, .y = ctx->home.y - 2 }, }; fprintf( stderr, "D: ctx->home = {%u, %u}, ps[] = {{%u, %u}, {%u, %u}};\n" "D: tile_is_floor(ctx, ps[0]) = %c, tile_is_box(ctx, ps[0]) = %c\n" "D: tile_is_floor(ctx, ps[1]) = %c, tile_is_box(ctx, ps[1]) = %c\n" "", ctx->home.x, ctx->home.y, ps[0].x, ps[0].y, ps[1].x, ps[1].y, tile_is_floor(ctx, ps[0]) ? 't' : 'f', tile_is_box(ctx, ps[0]) ? 't' : 'f', tile_is_floor(ctx, ps[1]) ? 't' : 'f', tile_is_box(ctx, ps[1]) ? 't' : 'f' ); return (( (ctx->home.y > 0) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) || ( (ctx->home.y > 1) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) )); } else if (dir == SOK_DIR_DOWN) { const sok_pos_t ps[2] = { { .x = ctx->home.x, .y = ctx->home.y + 1 }, { .x = ctx->home.x, .y = ctx->home.y + 2 }, }; return (( (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 1) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) || ( (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 2) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) )); } else if (dir == SOK_DIR_LEFT) { const sok_pos_t ps[2] = { { .x = ctx->home.x - 1, .y = ctx->home.y }, { .x = ctx->home.x - 2, .y = ctx->home.y }, }; return (( (ctx->home.x > 0) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) || ( (ctx->home.x > 1) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) )); } else if (dir == SOK_DIR_RIGHT) { const sok_pos_t ps[2] = { { .x = ctx->home.x + 1, .y = ctx->home.y }, { .x = ctx->home.x + 2, .y = ctx->home.y }, }; return (( (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 1) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) || ( (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 2) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) )); } // return failure return false; } static bool sok_ctx_push_move( sok_ctx_t * const ctx, const sok_dir_t dir, const bool is_push ) { if (ctx->num_moves >= SOK_CTX_MAX_MOVES - 1) { return false; } ctx->moves[ctx->num_moves].pos = ctx->home; ctx->moves[ctx->num_moves].dir = dir; ctx->moves[ctx->num_moves].is_push = is_push; ctx->num_moves++; return true; } static bool sok_ctx_pop_move( sok_ctx_t * const ctx, sok_move_t * const ret ) { if (!ctx->num_moves) { // return failure return false; } // decriment moves ctx->num_moves--; if (ret) { // save popped move *ret = ctx->moves[ctx->num_moves]; } // return success return true; } static bool sok_ctx_move_box( sok_ctx_t * const ctx, const sok_pos_t old_pos, const sok_pos_t new_pos ) { for (size_t i = 0; i < ctx->level.num_boxes; i++) { if (POINTS_EQUAL(ctx->boxes[i], old_pos)) { ctx->boxes[i] = new_pos; return true; } } return false; } bool sok_ctx_is_done( sok_ctx_t * const ctx ) { for (size_t i = 0; i < ctx->level.num_goals; i++) { bool on_goal = false; for (size_t j = 0; !on_goal && j < ctx->level.num_boxes; j++) { if (POINTS_EQUAL(ctx->level.goals[i], ctx->boxes[j])) { // found box on this goal on_goal = true; } } if (!on_goal) { // no box on this goal, return false return false; } } // return success return true; } bool sok_ctx_move( sok_ctx_t * const ctx, const sok_dir_t dir ) { if (!sok_can_move(ctx, dir)) { fprintf(stderr, "can_move failed\n"); return false; } if (dir == SOK_DIR_UP) { const sok_pos_t ps[2] = { { .x = ctx->home.x, .y = ctx->home.y - 1 }, { .x = ctx->home.x, .y = ctx->home.y - 2 }, }; if ( (ctx->home.y > 0) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) { // push move if (!sok_ctx_push_move(ctx, dir, false)) { return false; } // update position ctx->home.y--; // return success return true; } else if ( (ctx->home.y > 1) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) ) { // push move if (!sok_ctx_push_move(ctx, dir, true)) { return false; } // move box if (!sok_ctx_move_box(ctx, ps[0], ps[1])) { return false; } // update position ctx->home.y--; // return success return true; } } else if (dir == SOK_DIR_DOWN) { const sok_pos_t ps[2] = { { .x = ctx->home.x, .y = ctx->home.y + 1 }, { .x = ctx->home.x, .y = ctx->home.y + 2 }, }; if ( (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 1) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) { // push move if (!sok_ctx_push_move(ctx, dir, false)) { return false; } // update position ctx->home.y++; // return success return true; } else if ( (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 2) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) ) { // push move if (!sok_ctx_push_move(ctx, dir, true)) { return false; } // move box if (!sok_ctx_move_box(ctx, ps[0], ps[1])) { return false; } // update position ctx->home.y++; // return success return true; } } else if (dir == SOK_DIR_LEFT) { const sok_pos_t ps[2] = { { .x = ctx->home.x - 1, .y = ctx->home.y }, { .x = ctx->home.x - 2, .y = ctx->home.y }, }; if ( (ctx->home.x > 1) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) { // push move if (!sok_ctx_push_move(ctx, dir, false)) { return false; } // update position ctx->home.x--; // return success return true; } else if ( (ctx->home.x > 1) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) ) { // push move if (!sok_ctx_push_move(ctx, dir, true)) { return false; } // move box if (!sok_ctx_move_box(ctx, ps[0], ps[1])) { return false; } // update position ctx->home.x--; // return success return true; } } else if (dir == SOK_DIR_RIGHT) { const sok_pos_t ps[2] = { { .x = ctx->home.x + 1, .y = ctx->home.y }, { .x = ctx->home.x + 2, .y = ctx->home.y }, }; if ( (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 1) && tile_is_floor(ctx, ps[0]) && !tile_is_box(ctx, ps[0]) ) { // push move if (!sok_ctx_push_move(ctx, dir, false)) { return false; } // update position ctx->home.x++; // return success return true; } else if ( (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 2) && tile_is_floor(ctx, ps[0]) && tile_is_box(ctx, ps[0]) && tile_is_floor(ctx, ps[1]) && !tile_is_box(ctx, ps[1]) ) { // push move if (!sok_ctx_push_move(ctx, dir, true)) { return false; } // move box if (!sok_ctx_move_box(ctx, ps[0], ps[1])) { return false; } // update position ctx->home.x++; // return success return true; } } // return failure return false; } bool sok_ctx_undo( sok_ctx_t * const ctx ) { sok_move_t move; if (!sok_ctx_pop_move(ctx, &move)) { // return failure return false; } if (move.is_push) { switch (move.dir) { case SOK_DIR_UP: { const sok_pos_t box_pos = { move.pos.x, move.pos.y - 2 }; sok_ctx_move_box(ctx, box_pos, ctx->home); } break; case SOK_DIR_DOWN: { const sok_pos_t box_pos = { move.pos.x, move.pos.y + 2 }; sok_ctx_move_box(ctx, box_pos, ctx->home); } break; case SOK_DIR_LEFT: { const sok_pos_t box_pos = { move.pos.x - 2, move.pos.y }; sok_ctx_move_box(ctx, box_pos, ctx->home); } break; case SOK_DIR_RIGHT: { const sok_pos_t box_pos = { move.pos.x + 2, move.pos.y }; sok_ctx_move_box(ctx, box_pos, ctx->home); } break; default: // never reached return false; } } // update position ctx->home = move.pos; // return success return true; } bool sok_ctx_walk( const sok_ctx_t * const ctx, const sok_ctx_walk_cbs_t * const cbs, void * const data ) { if (!cbs) { return false; } if (cbs->on_size) { // emit size if (!cbs->on_size(ctx, ctx->level.size, data)) { return false; } } if (cbs->on_home) { const bool has_goal = tile_is_goal(ctx, ctx->home); // emit home if (!cbs->on_home(ctx, ctx->home, has_goal, data)) { return false; } } if (cbs->on_wall) { // walk walls for (size_t i = 0; i < (ctx->level.size.x * ctx->level.size.y); i++) { if (ctx->level.walls[i]) { const sok_pos_t pos = { .y = i / ctx->level.size.x, .x = i % ctx->level.size.x, }; // emit wall if (!cbs->on_wall(ctx, pos, data)) { return false; } } } } if (cbs->on_goal) { // walk goals for (size_t i = 0; i < ctx->level.num_goals; i++) { const bool has_player = POINTS_EQUAL(ctx->level.goals[i], ctx->home); const bool has_box = tile_is_box(ctx, ctx->level.goals[i]); // emit goal if (!cbs->on_goal(ctx, ctx->level.goals[i], has_player, has_box, data)) { return false; } } } if (cbs->on_box) { // walk boxes for (size_t i = 0; i < ctx->level.num_boxes; i++) { const bool has_goal = tile_is_goal(ctx, ctx->boxes[i]); // emit box if (!cbs->on_box(ctx, ctx->boxes[i], has_goal, data)) { return false; } } } if (cbs->on_move) { // walk moves for (size_t i = 0; i < ctx->num_moves; i++) { // emit move if (!cbs->on_move(ctx, ctx->moves[i], data)) { return false; } } } // return success return true; }