From c31caa18f969199a3fb91995ae08b29368cf010d Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Mon, 7 Jan 2019 22:05:19 -0500 Subject: reorganize src/, add support for RLE --- meson.build | 18 ++ src/cli-main.c | 190 ----------- src/libsok/sok-ctx.c | 731 ++++++++++++++++++++++++++++++++++++++++++ src/libsok/sok-level-parser.c | 192 +++++++++++ src/libsok/sok.h | 181 +++++++++++ src/sdl/main.c | 3 + src/sok-ctx.c | 731 ------------------------------------------ src/sok-level-parser.c | 159 --------- src/sok.h | 181 ----------- src/text/main.c | 190 +++++++++++ 10 files changed, 1315 insertions(+), 1261 deletions(-) create mode 100644 meson.build delete mode 100644 src/cli-main.c create mode 100644 src/libsok/sok-ctx.c create mode 100644 src/libsok/sok-level-parser.c create mode 100644 src/libsok/sok.h create mode 100644 src/sdl/main.c delete mode 100644 src/sok-ctx.c delete mode 100644 src/sok-level-parser.c delete mode 100644 src/sok.h create mode 100644 src/text/main.c diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..9ae6cb6 --- /dev/null +++ b/meson.build @@ -0,0 +1,18 @@ +project('sok', 'c', default_options: ['c_std=c11']) + +sources = [ + 'src/libsok/sok-level-parser.c', + 'src/libsok/sok-ctx.c', +] + +# text interface +executable('sok-text', sources + [ + 'src/text/main.c', +], dependencies: []) + +# text interface +executable('sok-sdl', sources + [ + 'src/sdl/main.c', +], dependencies: [ + # 'sdl2', +]) diff --git a/src/cli-main.c b/src/cli-main.c deleted file mode 100644 index c68908c..0000000 --- a/src/cli-main.c +++ /dev/null @@ -1,190 +0,0 @@ -#include // bool -#include // atoi() -#include // EXIT_{FAILURE,SUCCESS} -#include -#include "sok.h" - -#define UNUSED(a) ((void) (a)) - -static const char * const -LEVELS[] = { - "#####|#@$.#|#####|", - "#######|###.###|###$###|#.$@$.#|###$###|###.###|#######", -}; -#define NUM_LEVELS (sizeof(LEVELS) / sizeof(char*)) - -static char print_buf[(SOK_LEVEL_MAX_WIDTH + 1) * SOK_LEVEL_MAX_HEIGHT + 1]; - -static bool -on_size( - const sok_ctx_t * const ctx, - const sok_pos_t size, - void * const data -) { - UNUSED(ctx); - UNUSED(data); - - // fprintf(stderr, "size: x = %u, y = %u\n", size.x, size.y); - - memset(print_buf, ' ', sizeof(print_buf)); - print_buf[(size.x + 1) * size.y + 1] = '\0'; - for (size_t i = 0; i < size.y; i++) { - print_buf[(i + 1) * (size.x + 1) - 1] = '\n'; - } - - return true; -} - -static bool -on_home( - const sok_ctx_t * const ctx, - const sok_pos_t pos, - const bool has_goal, - void * const data -) { - UNUSED(data); - print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = has_goal ? '+' : '@'; - return true; -} - -static bool -on_wall( - const sok_ctx_t * const ctx, - const sok_pos_t pos, - void * const data -) { - // fprintf(stderr, "wall: x = %u, y = %u\n", pos.x, pos.y); - UNUSED(data); - print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = '#'; - return true; -} - -static bool -on_goal( - const sok_ctx_t * const ctx, - const sok_pos_t pos, - const bool has_player, - const bool has_box, - void * const data -) { - UNUSED(data); - const char c = has_player ? '+' : (has_box ? '*' : '.'); - print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = c; - return true; -} - -static bool -on_box( - const sok_ctx_t * const ctx, - const sok_pos_t pos, - const bool has_goal, - void * const data -) { - UNUSED(data); - print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = has_goal ? '*' : '$'; - return true; -} - -static sok_ctx_walk_cbs_t PRINT_CBS = { - .on_size = on_size, - .on_home = on_home, - .on_wall = on_wall, - .on_goal = on_goal, - .on_box = on_box, -}; - -static void -print_level( - const sok_ctx_t * const ctx -) { - // fill buffer - if (!sok_ctx_walk(ctx, &PRINT_CBS, NULL)) { - fprintf(stderr, "Couldn't print level\n"); - exit(EXIT_FAILURE); - } - - // print level - printf("%s\n", print_buf); -} - -int main(int argc, char *argv[]) { - size_t level = (argc > 1) ? atoi(argv[1]) : 0; - - // init context - sok_ctx_t ctx; - sok_ctx_init(&ctx, NULL); - - if (!sok_ctx_set_level(&ctx, LEVELS[level])) { - fprintf(stderr, "Couldn't load level %zu\n", level); - return EXIT_FAILURE; - } - - char buf[1024]; - bool done = false; - while (!done) { - print_level(&ctx); - printf("%zu%s> ", ctx.num_moves, sok_ctx_is_done(&ctx) ? " (won!)" : ""); - - if (!fgets(buf, sizeof(buf), stdin)) { - done = true; - break; - } - - switch (buf[0]) { - case EOF: - case 'q': - done = true; - break; - case 'k': - if (!sok_ctx_move(&ctx, SOK_DIR_UP)) { - fprintf(stderr, "W: move up failed\n"); - } - - break; - case 'h': - if (!sok_ctx_move(&ctx, SOK_DIR_LEFT)) { - fprintf(stderr, "W: move left failed\n"); - } - - break; - case 'j': - if (!sok_ctx_move(&ctx, SOK_DIR_DOWN)) { - fprintf(stderr, "W: move down failed\n"); - } - - break; - case 'l': - if (!sok_ctx_move(&ctx, SOK_DIR_RIGHT)) { - fprintf(stderr, "W: move right failed\n"); - } - - break; - case 'u': - if (!sok_ctx_undo(&ctx)) { - fprintf(stderr, "W: undo failed\n"); - } - - break; - case 'n': - if (sok_ctx_is_done(&ctx)) { - // advance level - level = (level + 1) % NUM_LEVELS; - - // load next level - if (!sok_ctx_set_level(&ctx, LEVELS[level])) { - fprintf(stderr, "Couldn't load level %zu\n", level); - return EXIT_FAILURE; - } - } else { - fprintf(stderr, "W: cannot advance to next level\n"); - } - - break; - default: - // ignore - break; - } - } - - return 0; -} diff --git a/src/libsok/sok-ctx.c b/src/libsok/sok-ctx.c new file mode 100644 index 0000000..de22d1e --- /dev/null +++ b/src/libsok/sok-ctx.c @@ -0,0 +1,731 @@ +#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; +} diff --git a/src/libsok/sok-level-parser.c b/src/libsok/sok-level-parser.c new file mode 100644 index 0000000..75ac133 --- /dev/null +++ b/src/libsok/sok-level-parser.c @@ -0,0 +1,192 @@ +#include +#include // size_t +#include "sok.h" + +#define UNUSED(a) ((void) (a)) + +#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') +#define CASE_DIGIT \ + case '0': \ + case '1': \ + case '2': \ + case '3': \ + case '4': \ + case '5': \ + case '6': \ + case '7': \ + case '8': \ + case '9': + +void +sok_level_parser_init( + sok_level_parser_t * const parser, + const sok_level_parser_cbs_t * const cbs, + void * const user_data +) { + parser->cbs = cbs; + parser->user_data = user_data; +} + +bool +sok_level_parser_parse( + sok_level_parser_t * const parser, + const char * const buf, + const size_t buf_len +) { + sok_pos_t size = { .x = 0, .y = 0 }; + + for (size_t i = 0, ofs = 0, w = 0; i < buf_len; i++) { + if (buf[i] == '|') { + if (w > size.x) { + size.x = w; + } + + // reset column position, increment row count + w = 0; + size.y++; + } else if (IS_DIGIT(buf[i])) { + ofs = 10 * ofs + (buf[i] - '0'); + } else if (ofs > 0) { + w += ofs; + ofs = 0; + } else { + w++; + } + } + + // increment row count + size.y++; + + // emit level size + if ( + parser->cbs->on_size && + !parser->cbs->on_size(parser, size) + ) { + // return failure + return false; + } + + sok_pos_t pos = { 0, 0 }; + for (size_t i = 0, ofs = 0; i < buf_len; i++) { + switch (buf[i]) { + case '|': + // new line + pos.x = 0; + pos.y++; + + break; + CASE_DIGIT + ofs = 10 * ofs + (buf[i] - '0'); + break; + case '-': + case '_': + case ' ': + // advance + pos.x += (ofs > 0) ? ofs : 1; + + break; + case '+': + // emit goal + if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { + // return failure + return false; + } + + // emit home + if (parser->cbs->on_home && !parser->cbs->on_home(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + + break; + case '@': + // emit home + if (parser->cbs->on_home && !parser->cbs->on_home(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + + break; + case '#': + for (size_t j = (ofs > 0) ? ofs : 1; j; j--) { + // emit wall + if (parser->cbs->on_wall && !parser->cbs->on_wall(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + } + ofs = 0; + + break; + case '*': + for (size_t j = (ofs > 0) ? ofs : 1; j--;) { + // emit goal + if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { + // return failure + return false; + } + + // emit box + if (parser->cbs->on_box && !parser->cbs->on_box(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + } + ofs = 0; + + break; + case '.': + for (size_t j = (ofs > 0) ? ofs : 1; j--;) { + // emit goal + if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + } + ofs = 0; + + break; + case '$': + for (size_t j = (ofs > 0) ? ofs : 1; j--;) { + // emit box + if (parser->cbs->on_box && !parser->cbs->on_box(parser, pos)) { + // return failure + return false; + } + + // advance + pos.x++; + } + ofs = 0; + + break; + default: + // emit junk + if (parser->cbs->on_junk && !parser->cbs->on_junk(parser, i, buf[i])) { + // return failure + return false; + } + + // return failure + return false; + } + } + + // return success + return true; +} diff --git a/src/libsok/sok.h b/src/libsok/sok.h new file mode 100644 index 0000000..7635ead --- /dev/null +++ b/src/libsok/sok.h @@ -0,0 +1,181 @@ +#ifndef SOK_H +#define SOK_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include // uint16_t + +/*********/ +/* types */ +/*********/ + +typedef struct { + uint16_t x, y; +} sok_pos_t; + +typedef enum { + SOK_DIR_RIGHT, + SOK_DIR_UP, + SOK_DIR_LEFT, + SOK_DIR_DOWN, + SOK_DIR_LAST, +} sok_dir_t; + +typedef struct { + sok_pos_t pos; + sok_dir_t dir; + _Bool is_push; +} sok_move_t; + +/****************/ +/* level parser */ +/****************/ + +typedef struct sok_level_parser_t_ sok_level_parser_t; + +typedef bool (*sok_level_parser_pos_cb_t)( + const sok_level_parser_t * const, + const sok_pos_t +); + +typedef bool (*sok_level_parser_junk_cb_t)( + const sok_level_parser_t * const, + const size_t, + const char +); + +typedef struct { + sok_level_parser_pos_cb_t on_size, + on_home, + on_wall, + on_goal, + on_box; + sok_level_parser_junk_cb_t on_junk; +} sok_level_parser_cbs_t; + +struct sok_level_parser_t_ { + const sok_level_parser_cbs_t *cbs; + void *user_data; +}; + +void sok_level_parser_init( + sok_level_parser_t * const parser, + const sok_level_parser_cbs_t * const cbs, + void * const user_data +); + +bool sok_level_parser_parse( + sok_level_parser_t * const parser, + const char * const buf, + const size_t buf_len +); + +/*********/ +/* level */ +/*********/ + +#define SOK_LEVEL_MAX_WIDTH (1 << 8) +#define SOK_LEVEL_MAX_HEIGHT (1 << 8) +#define SOK_LEVEL_MAX_BOXES 64 +#define SOK_LEVEL_MAX_GOALS 64 + +typedef struct { + sok_pos_t size; + _Bool walls[SOK_LEVEL_MAX_WIDTH * SOK_LEVEL_MAX_HEIGHT]; + + // player home position + sok_pos_t home; + + // boxes + sok_pos_t boxes[SOK_LEVEL_MAX_BOXES]; + size_t num_boxes; + + // goals + sok_pos_t goals[SOK_LEVEL_MAX_GOALS]; + size_t num_goals; +} sok_level_t; + +/***********/ +/* context */ +/***********/ + +#define SOK_CTX_MAX_MOVES 1024 + +typedef struct { + sok_level_t level; + + sok_move_t moves[SOK_CTX_MAX_MOVES]; + size_t num_moves; + + // player position + sok_pos_t home; + + // box positions + sok_pos_t boxes[SOK_LEVEL_MAX_BOXES]; + + // user data + void *user_data; +} sok_ctx_t; + +void sok_ctx_init(sok_ctx_t * const ctx, void *user_data); + +_Bool sok_ctx_set_level(sok_ctx_t * const ctx, const char * const level); + +_Bool sok_ctx_is_done(sok_ctx_t * const); + +_Bool sok_ctx_move(sok_ctx_t * const, const sok_dir_t); +_Bool sok_ctx_undo(sok_ctx_t * const); + +/******************/ +/* context walker */ +/******************/ + +typedef _Bool (*sok_ctx_walk_pos_cb_t)( + const sok_ctx_t * const, + const sok_pos_t, + void * const +); + +typedef _Bool (*sok_ctx_walk_tile_cb_t)( + const sok_ctx_t * const, + const sok_pos_t, + const _Bool, + void * const +); + +typedef _Bool (*sok_ctx_walk_goal_cb_t)( + const sok_ctx_t * const, + const sok_pos_t, + const _Bool has_player, + const _Bool has_box, + void * const +); + +typedef _Bool (*sok_ctx_walk_move_cb_t)( + const sok_ctx_t * const, + const sok_move_t, + void * const +); + +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 +); + +#ifdef __cplusplus +}; +#endif /* __cplusplus */ + +#endif /* SOK_H */ diff --git a/src/sdl/main.c b/src/sdl/main.c new file mode 100644 index 0000000..0fb4389 --- /dev/null +++ b/src/sdl/main.c @@ -0,0 +1,3 @@ +int main(int argc, char *argv[]) { + return 0; +} diff --git a/src/sok-ctx.c b/src/sok-ctx.c deleted file mode 100644 index de22d1e..0000000 --- a/src/sok-ctx.c +++ /dev/null @@ -1,731 +0,0 @@ -#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; -} diff --git a/src/sok-level-parser.c b/src/sok-level-parser.c deleted file mode 100644 index eec08c3..0000000 --- a/src/sok-level-parser.c +++ /dev/null @@ -1,159 +0,0 @@ -#include -#include // size_t -#include "sok.h" - -#define UNUSED(a) ((void) (a)) - -void -sok_level_parser_init( - sok_level_parser_t * const parser, - const sok_level_parser_cbs_t * const cbs, - void * const user_data -) { - parser->cbs = cbs; - parser->user_data = user_data; -} - -bool -sok_level_parser_parse( - sok_level_parser_t * const parser, - const char * const buf, - const size_t buf_len -) { - sok_pos_t size = { .x = 0, .y = 0 }; - - for (size_t i = 0, w = 0; i < buf_len; i++) { - if (buf[i] == '|') { - if (w > size.x) { - size.x = w; - } - - // reset column position, increment row count - w = 0; - size.y++; - } else { - w++; - } - } - - // increment row count - size.y++; - - // emit level size - if ( - parser->cbs->on_size && - !parser->cbs->on_size(parser, size) - ) { - // return failure - return false; - } - - sok_pos_t pos = { 0, 0 }; - for (size_t i = 0; i < buf_len; i++) { - switch (buf[i]) { - case '|': - // new line - pos.x = 0; - pos.y++; - - break; - case '-': - case '_': - case ' ': - // advance - pos.x++; - - break; - case '+': - // emit goal - if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { - // return failure - return false; - } - - // emit home - if (parser->cbs->on_home && !parser->cbs->on_home(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - case '@': - // emit home - if (parser->cbs->on_home && !parser->cbs->on_home(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - case '#': - // emit wall - if (parser->cbs->on_wall && !parser->cbs->on_wall(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - case '*': - // emit goal - if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { - // return failure - return false; - } - - // emit box - if (parser->cbs->on_box && !parser->cbs->on_box(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - case '.': - // emit goal - if (parser->cbs->on_goal && !parser->cbs->on_goal(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - case '$': - // emit box - if (parser->cbs->on_box && !parser->cbs->on_box(parser, pos)) { - // return failure - return false; - } - - // advance - pos.x++; - - break; - default: - // emit junk - if (parser->cbs->on_junk && !parser->cbs->on_junk(parser, i, buf[i])) { - // return failure - return false; - } - - // return failure - return false; - } - } - - // return success - return true; -} diff --git a/src/sok.h b/src/sok.h deleted file mode 100644 index 7635ead..0000000 --- a/src/sok.h +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef SOK_H -#define SOK_H - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#include // uint16_t - -/*********/ -/* types */ -/*********/ - -typedef struct { - uint16_t x, y; -} sok_pos_t; - -typedef enum { - SOK_DIR_RIGHT, - SOK_DIR_UP, - SOK_DIR_LEFT, - SOK_DIR_DOWN, - SOK_DIR_LAST, -} sok_dir_t; - -typedef struct { - sok_pos_t pos; - sok_dir_t dir; - _Bool is_push; -} sok_move_t; - -/****************/ -/* level parser */ -/****************/ - -typedef struct sok_level_parser_t_ sok_level_parser_t; - -typedef bool (*sok_level_parser_pos_cb_t)( - const sok_level_parser_t * const, - const sok_pos_t -); - -typedef bool (*sok_level_parser_junk_cb_t)( - const sok_level_parser_t * const, - const size_t, - const char -); - -typedef struct { - sok_level_parser_pos_cb_t on_size, - on_home, - on_wall, - on_goal, - on_box; - sok_level_parser_junk_cb_t on_junk; -} sok_level_parser_cbs_t; - -struct sok_level_parser_t_ { - const sok_level_parser_cbs_t *cbs; - void *user_data; -}; - -void sok_level_parser_init( - sok_level_parser_t * const parser, - const sok_level_parser_cbs_t * const cbs, - void * const user_data -); - -bool sok_level_parser_parse( - sok_level_parser_t * const parser, - const char * const buf, - const size_t buf_len -); - -/*********/ -/* level */ -/*********/ - -#define SOK_LEVEL_MAX_WIDTH (1 << 8) -#define SOK_LEVEL_MAX_HEIGHT (1 << 8) -#define SOK_LEVEL_MAX_BOXES 64 -#define SOK_LEVEL_MAX_GOALS 64 - -typedef struct { - sok_pos_t size; - _Bool walls[SOK_LEVEL_MAX_WIDTH * SOK_LEVEL_MAX_HEIGHT]; - - // player home position - sok_pos_t home; - - // boxes - sok_pos_t boxes[SOK_LEVEL_MAX_BOXES]; - size_t num_boxes; - - // goals - sok_pos_t goals[SOK_LEVEL_MAX_GOALS]; - size_t num_goals; -} sok_level_t; - -/***********/ -/* context */ -/***********/ - -#define SOK_CTX_MAX_MOVES 1024 - -typedef struct { - sok_level_t level; - - sok_move_t moves[SOK_CTX_MAX_MOVES]; - size_t num_moves; - - // player position - sok_pos_t home; - - // box positions - sok_pos_t boxes[SOK_LEVEL_MAX_BOXES]; - - // user data - void *user_data; -} sok_ctx_t; - -void sok_ctx_init(sok_ctx_t * const ctx, void *user_data); - -_Bool sok_ctx_set_level(sok_ctx_t * const ctx, const char * const level); - -_Bool sok_ctx_is_done(sok_ctx_t * const); - -_Bool sok_ctx_move(sok_ctx_t * const, const sok_dir_t); -_Bool sok_ctx_undo(sok_ctx_t * const); - -/******************/ -/* context walker */ -/******************/ - -typedef _Bool (*sok_ctx_walk_pos_cb_t)( - const sok_ctx_t * const, - const sok_pos_t, - void * const -); - -typedef _Bool (*sok_ctx_walk_tile_cb_t)( - const sok_ctx_t * const, - const sok_pos_t, - const _Bool, - void * const -); - -typedef _Bool (*sok_ctx_walk_goal_cb_t)( - const sok_ctx_t * const, - const sok_pos_t, - const _Bool has_player, - const _Bool has_box, - void * const -); - -typedef _Bool (*sok_ctx_walk_move_cb_t)( - const sok_ctx_t * const, - const sok_move_t, - void * const -); - -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 -); - -#ifdef __cplusplus -}; -#endif /* __cplusplus */ - -#endif /* SOK_H */ diff --git a/src/text/main.c b/src/text/main.c new file mode 100644 index 0000000..bcf53a1 --- /dev/null +++ b/src/text/main.c @@ -0,0 +1,190 @@ +#include // bool +#include // atoi() +#include // EXIT_{FAILURE,SUCCESS} +#include +#include "../libsok/sok.h" + +#define UNUSED(a) ((void) (a)) + +static const char * const +LEVELS[] = { + "5#|#@$.#|5#", + "7#|3#.3#|3#$3#|#.$@$.#|3#$3#|3#.3#|7#", +}; +#define NUM_LEVELS (sizeof(LEVELS) / sizeof(char*)) + +static char print_buf[(SOK_LEVEL_MAX_WIDTH + 1) * SOK_LEVEL_MAX_HEIGHT + 1]; + +static bool +on_size( + const sok_ctx_t * const ctx, + const sok_pos_t size, + void * const data +) { + UNUSED(ctx); + UNUSED(data); + + // fprintf(stderr, "size: x = %u, y = %u\n", size.x, size.y); + + memset(print_buf, ' ', sizeof(print_buf)); + print_buf[(size.x + 1) * size.y + 1] = '\0'; + for (size_t i = 0; i < size.y; i++) { + print_buf[(i + 1) * (size.x + 1) - 1] = '\n'; + } + + return true; +} + +static bool +on_home( + const sok_ctx_t * const ctx, + const sok_pos_t pos, + const bool has_goal, + void * const data +) { + UNUSED(data); + print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = has_goal ? '+' : '@'; + return true; +} + +static bool +on_wall( + const sok_ctx_t * const ctx, + const sok_pos_t pos, + void * const data +) { + // fprintf(stderr, "wall: x = %u, y = %u\n", pos.x, pos.y); + UNUSED(data); + print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = '#'; + return true; +} + +static bool +on_goal( + const sok_ctx_t * const ctx, + const sok_pos_t pos, + const bool has_player, + const bool has_box, + void * const data +) { + UNUSED(data); + const char c = has_player ? '+' : (has_box ? '*' : '.'); + print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = c; + return true; +} + +static bool +on_box( + const sok_ctx_t * const ctx, + const sok_pos_t pos, + const bool has_goal, + void * const data +) { + UNUSED(data); + print_buf[pos.y * (ctx->level.size.x + 1) + pos.x] = has_goal ? '*' : '$'; + return true; +} + +static sok_ctx_walk_cbs_t PRINT_CBS = { + .on_size = on_size, + .on_home = on_home, + .on_wall = on_wall, + .on_goal = on_goal, + .on_box = on_box, +}; + +static void +print_level( + const sok_ctx_t * const ctx +) { + // fill buffer + if (!sok_ctx_walk(ctx, &PRINT_CBS, NULL)) { + fprintf(stderr, "Couldn't print level\n"); + exit(EXIT_FAILURE); + } + + // print level + printf("%s\n", print_buf); +} + +int main(int argc, char *argv[]) { + size_t level = (argc > 1) ? atoi(argv[1]) : 0; + + // init context + sok_ctx_t ctx; + sok_ctx_init(&ctx, NULL); + + if (!sok_ctx_set_level(&ctx, LEVELS[level])) { + fprintf(stderr, "Couldn't load level %zu\n", level); + return EXIT_FAILURE; + } + + char buf[1024]; + bool done = false; + while (!done) { + print_level(&ctx); + printf("%zu%s> ", ctx.num_moves, sok_ctx_is_done(&ctx) ? " (won!)" : ""); + + if (!fgets(buf, sizeof(buf), stdin)) { + done = true; + break; + } + + switch (buf[0]) { + case EOF: + case 'q': + done = true; + break; + case 'k': + if (!sok_ctx_move(&ctx, SOK_DIR_UP)) { + fprintf(stderr, "W: move up failed\n"); + } + + break; + case 'h': + if (!sok_ctx_move(&ctx, SOK_DIR_LEFT)) { + fprintf(stderr, "W: move left failed\n"); + } + + break; + case 'j': + if (!sok_ctx_move(&ctx, SOK_DIR_DOWN)) { + fprintf(stderr, "W: move down failed\n"); + } + + break; + case 'l': + if (!sok_ctx_move(&ctx, SOK_DIR_RIGHT)) { + fprintf(stderr, "W: move right failed\n"); + } + + break; + case 'u': + if (!sok_ctx_undo(&ctx)) { + fprintf(stderr, "W: undo failed\n"); + } + + break; + case 'n': + if (sok_ctx_is_done(&ctx)) { + // advance level + level = (level + 1) % NUM_LEVELS; + + // load next level + if (!sok_ctx_set_level(&ctx, LEVELS[level])) { + fprintf(stderr, "Couldn't load level %zu\n", level); + return EXIT_FAILURE; + } + } else { + fprintf(stderr, "W: cannot advance to next level\n"); + } + + break; + default: + // ignore + break; + } + } + + return 0; +} -- cgit v1.2.3