From 3e658ed87b5795b2be8f50d683dc19241aba0111 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Tue, 15 Jan 2019 09:16:56 -0500 Subject: s/libsok/core/g --- src/core/sok-ctx.c | 636 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 src/core/sok-ctx.c (limited to 'src/core/sok-ctx.c') diff --git a/src/core/sok-ctx.c b/src/core/sok-ctx.c new file mode 100644 index 0000000..e278af1 --- /dev/null +++ b/src/core/sok-ctx.c @@ -0,0 +1,636 @@ +#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; +} -- cgit v1.2.3