#include #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 ctx_is_wall( const sok_ctx_t * const ctx, const sok_pos_t pos ) { return ctx->level.walls[pos.y * ctx->level.size.x + pos.x]; } static void ctx_set_wall( sok_ctx_t * const ctx, const sok_pos_t pos ) { ctx->level.walls[pos.y * ctx->level.size.x + pos.x] = true; } static void ctx_clear_walls( sok_ctx_t * const ctx ) { memset(ctx->level.walls, 0, SOK_LEVEL_MAX_WIDTH * SOK_LEVEL_MAX_HEIGHT); } 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_set_wall(ctx, pos); // 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, }; static size_t pos_find( const sok_pos_t pos, const sok_pos_t * const set, const size_t len ) { for (size_t i = 0; i < len; i++) { if (POINTS_EQUAL(pos, set[i])) { return i; } } return len; } static size_t sok_ctx_count_goals_left( sok_ctx_t * const ctx ) { const size_t num_boxes = ctx->level.num_boxes; size_t r = 0; for (size_t i = 0; i < ctx->level.num_goals; i++) { r += pos_find( ctx->level.goals[i], ctx->boxes, num_boxes ) < num_boxes ? 0 : 1; } return r; } /* * static bool * sok_ctx_has_corner_boxes( * const sok_ctx_t * const ctx * ) { * for (size_t i = 0; i < ctx->level.num_boxes; i++) { * // right * const bool r = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x + 1, * .y = ctx->boxes[i].y, * }); * * // top-right * const bool tr = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x + 1, * .y = ctx->boxes[i].y - 1, * }); * * // top * const bool t = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x, * .y = ctx->boxes[i].y - 1, * }); * * // top-left * const bool tl = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x - 1, * .y = ctx->boxes[i].y - 1, * }); * * // left * const bool l = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x - 1, * .y = ctx->boxes[i].y, * }); * * // bottom-left * const bool bl = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x - 1, * .y = ctx->boxes[i].y + 1, * }); * * // bottom * const bool b = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x, * .y = ctx->boxes[i].y + 1, * }); * * const bool br = ctx_is_wall(ctx, (sok_pos_t) { * .x = ctx->boxes[i].x + 1, * .y = ctx->boxes[i].y + 1, * }); * * if ( * (r && tr && t) || // top-right * (t && tl && l) || // top-left * (l && bl && b) || // bottom-left * (b && br && r) // bottom-right * ) { * // return * return true; * } * } * * return false; * } */ 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; ctx_clear_walls(ctx); // 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, player, and boxes ctx->num_moves = 0; ctx->home = ctx->level.home; memcpy(ctx->boxes, ctx->level.boxes, ctx->level.num_boxes * sizeof(sok_pos_t)); // count number of goals left and check whether there are ctx->num_goals_left = sok_ctx_count_goals_left(ctx); // ctx->is_lost = sok_ctx_is_lost(ctx); // 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_is_wall(ctx, pos) ); } static bool tile_is_box( const sok_ctx_t * const ctx, const sok_pos_t pos ) { const size_t num_boxes = ctx->level.num_boxes; return pos_find(pos, ctx->boxes, num_boxes) < num_boxes; } static bool tile_is_goal( const sok_ctx_t * const ctx, const sok_pos_t pos ) { const size_t num_goals = ctx->level.num_goals; return pos_find(pos, ctx->level.goals, num_goals) < num_goals; } 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 ) { const size_t num_boxes = ctx->level.num_boxes, box_ofs = pos_find(old_pos, ctx->boxes, num_boxes); if (box_ofs < num_boxes) { // update box and goal count ctx->boxes[box_ofs] = new_pos; ctx->num_goals_left = sok_ctx_count_goals_left(ctx); // ctx->is_lost = sok_ctx_has_corner_boxes(ctx); // return success return true; } // return failure return false; } bool sok_ctx_is_done( const sok_ctx_t * const ctx ) { return ctx->num_goals_left == 0; } /* * bool * sok_ctx_is_lost( * const sok_ctx_t * const ctx * ) { * return ctx->is_lost; * } */ bool sok_ctx_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; } const sok_pos_t ps[2] = {{ .x = ctx->home.x + ((dir == SOK_DIR_LEFT) ? -1 : ((dir == SOK_DIR_RIGHT) ? 1 : 0)), .y = ctx->home.y + ((dir == SOK_DIR_UP) ? -1 : ((dir == SOK_DIR_DOWN) ? 1 : 0)) }, { .x = ctx->home.x + ((dir == SOK_DIR_LEFT) ? -2 : ((dir == SOK_DIR_RIGHT) ? 2 : 0)), .y = ctx->home.y + ((dir == SOK_DIR_UP) ? -2 : ((dir == SOK_DIR_DOWN) ? 2 : 0)) }}; const bool can_move = ( (dir == SOK_DIR_UP) ? (ctx->home.y > 0) : ((dir == SOK_DIR_DOWN) ? (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 1) : ((dir == SOK_DIR_LEFT) ? (ctx->home.x > 0) : ((dir == SOK_DIR_RIGHT) ? (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 1) : false )))); const bool can_push = ( (dir == SOK_DIR_UP) ? (ctx->home.y > 1) : ((dir == SOK_DIR_DOWN) ? (ctx->home.y < SOK_LEVEL_MAX_HEIGHT - 2) : ((dir == SOK_DIR_LEFT) ? (ctx->home.x > 1) : ((dir == SOK_DIR_RIGHT) ? (ctx->home.x < SOK_LEVEL_MAX_WIDTH - 2) : false )))); if ( can_move && 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 += (dir == SOK_DIR_LEFT) ? -1 : ((dir == SOK_DIR_RIGHT) ? 1 : 0); ctx->home.y += (dir == SOK_DIR_UP) ? -1 : ((dir == SOK_DIR_DOWN) ? 1 : 0); // return success return true; } else if ( can_push && 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 += (dir == SOK_DIR_LEFT) ? -1 : ((dir == SOK_DIR_RIGHT) ? 1 : 0); ctx->home.y += (dir == SOK_DIR_UP) ? -1 : ((dir == SOK_DIR_DOWN) ? 1 : 0); // 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) { const sok_pos_t box_pos = { .x = move.pos.x + ((move.dir == SOK_DIR_LEFT) ? -2 : ((move.dir == SOK_DIR_RIGHT) ? 2 : 0)), .y = move.pos.y + ((move.dir == SOK_DIR_UP) ? -2 : ((move.dir == SOK_DIR_DOWN) ? 2 : 0)), }; sok_ctx_move_box(ctx, box_pos, ctx->home); } // 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_walls_start && !cbs->on_walls_start(ctx, data)) { return false; } if (cbs->on_wall) { // walk walls for (size_t i = 0; i < (ctx->level.size.x * ctx->level.size.y); i++) { const sok_pos_t pos = { .y = i / ctx->level.size.x, .x = i % ctx->level.size.x, }; if (ctx_is_wall(ctx, pos)) { // emit wall if (!cbs->on_wall(ctx, pos, data)) { return false; } } } } if (cbs->on_walls_end && !cbs->on_walls_end(ctx, data)) { return false; } if (cbs->on_goals_start && !cbs->on_goals_start(ctx, 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_goals_end && !cbs->on_goals_end(ctx, data)) { return false; } if (cbs->on_boxes_start && !cbs->on_boxes_start(ctx, 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_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_boxes_end && !cbs->on_boxes_end(ctx, data)) { return false; } if (cbs->on_moves_start && !cbs->on_moves_start(ctx, 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; } } } if (cbs->on_moves_end && !cbs->on_moves_end(ctx, data)) { return false; } // return success return true; }