From 8e101f8bfd995321ac08a16fea9a171b549a0ae4 Mon Sep 17 00:00:00 2001 From: Steph Enders Date: Sat, 17 Dec 2022 11:04:20 -0500 Subject: Support doors with keys Add initial support for doors and keys via pre-defined mappings: k || d ------ 1 -> a 2 -> b 3 -> c 4 -> d Any key can open any door of its mapping, but is spent once used. May require additional testing --- README.md | 17 +++++++++ dnglib/algs.lua | 12 +++++- dnglib/constants.lua | 2 +- dnglib/defaults.lua | 33 +++++++++++++--- maps/lvl3/dng.map | 14 +++---- src/CApi.h | 104 +++++++++++++++++++++++++++++++++++++-------------- src/Level.cpp | 53 ++++++++++++++++++++++++-- src/Level.h | 11 ++++++ src/SfmlUtils.h | 38 ++++++++++++++++++- src/main.cpp | 7 +++- 10 files changed, 241 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 3c17630..8158279 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,23 @@ The map format is just a text file with the following key tokens: | t | Treasure (0 or more) | | 0 | Empty space | +Doors and Keys: + +Doors and keys are single use and map to specific doors. +Each key (1-4) maps to a door type (a-d) + +| Token | Description | +|:------|:-------------------------| +| 1 | Key 1, for Door a | +| 2 | Key 2, for Door b | +| 3 | Key 3, for Door c | +| 4 | Key 4, for Door d | +| a | Door a, opened by key 1 | +| a | Door b, opened by key 2 | +| a | Door c, opened by key 3 | +| d | Door d, opened by key 4 | + + #### Tips Space your map out using whitespace between every token (`w w w w` instead of `wwww`) for better readability. diff --git a/dnglib/algs.lua b/dnglib/algs.lua index d2e6252..b5f331f 100644 --- a/dnglib/algs.lua +++ b/dnglib/algs.lua @@ -150,10 +150,12 @@ end ---@param target_pos table [x, y] -- @param enemies list of enemy positions (cannot pass thru enemy) -- @param treasures list of treasure positions (cannot pass thru treasure) +-- @param door_keys list of key positions (cannot pass thru treasure) +-- @param doors list of door positions (cannot pass thru treasure) ---@param map table 2D map array ---@return table best move to target [x, y] --- -local function pathfind(start_pos, target_pos, enemies, treasures, map) +local function pathfind(start_pos, target_pos, enemies, treasures, door_keys, doors, map) local queue = Queue:new() local visit_map = {} @@ -173,7 +175,13 @@ local function pathfind(start_pos, target_pos, enemies, treasures, map) for _, t in ipairs(treasures) do visit_map[t.y][t.x] = MAP_WALL -- use wall value for impass end - + for _, k in ipairs(door_keys) do + visit_map[k.y][k.x] = MAP_WALL -- use wall value for impass + end + for _, d in ipairs(doors) do + visit_map[d.y][d.x] = MAP_WALL -- use wall value for impass + end + -- since we mutate the visit_map let's calc this early if need be local best_effort = best_effort_move(start_pos, target_pos, visit_map) diff --git a/dnglib/constants.lua b/dnglib/constants.lua index 098f308..2e1fa52 100644 --- a/dnglib/constants.lua +++ b/dnglib/constants.lua @@ -77,7 +77,7 @@ KEY_UP = 73 KEY_DOWN = 74 -- map values -MAP_WALL = 1 +MAP_WALL = 9 MAP_SPACE = 0 MAP_VISITED = -1 diff --git a/dnglib/defaults.lua b/dnglib/defaults.lua index f33e603..f2251a2 100644 --- a/dnglib/defaults.lua +++ b/dnglib/defaults.lua @@ -60,6 +60,12 @@ keys = { --- setup random --math.randomseed(os.time()) +-- Checks if x,y equals for both objects +local function is_collision(a, b) + return a.x == b.x and a.y == b.y +end + + ---@param pressedKey number function onKeyPress(pressedKey) scene = c_get_scene() @@ -102,35 +108,52 @@ function onUpdate(dt) treasures = c_get_treasures() assert(type(treasures) == "table", "treasures is not a table") + door_keys = c_get_keys() + assert(type(door_keys) == "table", "keys is not a table") + + doors = c_get_doors() + assert(type(doors) == "table", "doors is not a table") + map = c_get_map(); assert(type(map) == "table", "map is not a table") for i, v in ipairs(enemies) do local next; if diff_time >= MOV_TIME then - next = algs.pathfind(v, player, enemies, treasures, map) + next = algs.pathfind(v, player, enemies, treasures, door_keys, doors, map) else next = { dx = 0, dy = 0 } end new_pos = c_move_enemy(v.id, next.dx, next.dy) assert(type(new_pos) == "table", "new_pos is not a table") - if new_pos.x == player.x and new_pos.y == player.y then + if is_collision(new_pos, player) then c_trigger_loss() end enemies[i] = new_pos -- update new position for pathfinding end - treasures = c_get_treasures() - assert(type(treasures) == "table", "treasures is not a table") for _, t in ipairs(treasures) do - if t.x == player.x and t.y == player.y then + if is_collision(t, player) then c_score_treasure(t.id) if #treasures == 1 then c_trigger_win() end end end + + for _, k in ipairs(door_keys) do + if is_collision(k, player) then + c_take_key(k.id) + end + end + + for _, d in ipairs(doors) do + if is_collision(d, player) then + c_open_door(d.id) + end + end + if diff_time > MOV_TIME then diff_time = 0 end diff --git a/maps/lvl3/dng.map b/maps/lvl3/dng.map index d250f9f..3905121 100644 --- a/maps/lvl3/dng.map +++ b/maps/lvl3/dng.map @@ -2,22 +2,22 @@ w w w w w w w w w w w w w w w w w w w w w w 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 w w 0 w w w w w w w w w w w w w w w w w 0 w w p w 0 0 0 0 0 0 0 w 0 0 0 0 0 0 w w 0 w -w 0 w e t 0 0 0 0 0 w 0 0 0 0 t 0 w w 0 w -w 0 w 0 0 0 0 0 0 0 w 0 0 0 0 0 0 w w 0 w +w 0 w 0 t 0 0 0 0 0 w 0 0 0 0 t 0 w w 0 w +w 0 w 0 e 0 0 0 0 0 w 0 0 0 0 0 0 w w 0 w w 0 w 0 t 0 0 0 0 0 w 0 0 0 0 0 0 w w 0 w w 0 w 0 0 0 0 w w w w w w 0 0 0 0 w t 0 w -w 0 w 0 0 0 0 w t t t t w 0 0 0 0 0 w 0 w -w 0 w 0 0 0 0 0 t t t t w e 0 0 0 0 w 0 w -w 0 w 0 0 0 0 w t t t t 0 0 0 0 0 0 w 0 w +w 0 w 0 0 0 0 w t t t t w 0 e 0 0 0 w 0 w +w 0 w 0 0 0 0 b t t t t w 0 0 0 0 0 w 0 w +w 0 a 0 0 0 0 w t t t t 0 0 0 0 0 0 w 0 w w 0 w 0 0 0 0 w t t t t w 0 0 0 0 0 w 0 w w 0 w 0 0 0 0 w w w w w w 0 0 0 0 0 w 0 w w 0 w 0 t 0 0 0 0 0 w 0 0 0 0 0 0 0 w 0 w w 0 w 0 0 0 0 0 0 0 w 0 0 0 0 0 0 0 w 0 w -w 0 w 0 0 0 0 0 0 0 w 0 0 0 0 0 0 0 w 0 w +w 0 w 0 0 0 2 0 0 0 w 0 0 0 0 0 0 0 w 0 w w 0 w 0 t 0 0 0 0 0 w 0 0 0 0 t 0 0 w 0 w w 0 w 0 0 0 0 0 0 0 w 0 0 0 0 0 0 0 w 0 w w 0 w t t t t t t t w t t t t t t t t 0 w w 0 w w w w w w w w t w w w w w w w 0 0 w -w 0 t 0 0 0 0 0 0 0 t 0 0 0 0 0 0 0 0 0 w +w 0 t 0 0 0 0 0 1 0 t 0 0 0 0 0 0 0 0 0 w w 0 t 0 0 0 0 0 0 0 w 0 0 0 0 0 0 0 0 0 w w w w w w w w w w w w w w w w w w w w w w diff --git a/src/CApi.h b/src/CApi.h index 4d35939..c6ffea3 100644 --- a/src/CApi.h +++ b/src/CApi.h @@ -35,6 +35,25 @@ extern std::shared_ptr lvl; extern Scene scene; +void push_position_table(lua_State *L, std::vector positions) { + lua_createtable(L, int(positions.size()), 0); + int idx = 0; + + for (auto &pos : positions) { + lua_pushnumber(L, ++idx); + lua_createtable(L, 0, 3); + lua_pushnumber(L, pos.token); + lua_setfield(L, -2, "token"); + lua_pushnumber(L, pos.id); + lua_setfield(L, -2, "id"); + lua_pushnumber(L, pos.x + 1); + lua_setfield(L, -2, "x"); + lua_pushnumber(L, pos.y + 1); + lua_setfield(L, -2, "y"); + lua_settable(L, -3); + } +} + /* * c_get_player_position(int x, int y) */ @@ -103,22 +122,7 @@ static int c_move_enemy(lua_State *L) { * c_get_enemies() */ static int c_get_enemies(lua_State *L) { - lua_createtable(L, int(lvl->enemyPositions.size()), 0); - - int idx = 0; - - for (auto &pos : lvl->enemyPositions) { - lua_pushnumber(L, ++idx); - lua_createtable(L, 0, 3); - lua_pushnumber(L, pos.id); - lua_setfield(L, -2, "id"); - lua_pushnumber(L, pos.x + 1); - lua_setfield(L, -2, "x"); - lua_pushnumber(L, pos.y + 1); - lua_setfield(L, -2, "y"); - lua_settable(L, -3); - } - + push_position_table(L, lvl->enemyPositions); return 1; } @@ -184,19 +188,7 @@ static int c_get_scene(lua_State *L) { } static int c_get_treasures(lua_State *L) { - lua_createtable(L, static_cast(lvl->treasurePositions.size()), 0); - int idx = 0; - for (auto &t : lvl->treasurePositions) { - lua_pushnumber(L, ++idx); - lua_createtable(L, 0, 3); - lua_pushnumber(L, t.y + 1); - lua_setfield(L, -2, "y"); - lua_pushnumber(L, t.x + 1); - lua_setfield(L, -2, "x"); - lua_pushnumber(L, t.id); - lua_setfield(L, -2, "id"); - lua_settable(L, -3); - } + push_position_table(L, lvl->treasurePositions); return 1; } @@ -223,6 +215,56 @@ static int c_trigger_restart(lua_State *L) { return 1; } +static int c_get_doors(lua_State *L) { + push_position_table(L, lvl->doorPositions); + return 1; +} + +/** + * c_open_door(id) + * if you have a key it will open the door and use the key + */ +static int c_open_door(lua_State *L) { + int id = static_cast(lua_tonumber(L, -1)); + bool can_open = false; + for (int i = 0; i < lvl->doorPositions.size(); i++) { + if (lvl->doorPositions[i].id == id) { + char c = lvl->doorPositions[i].token; + for (int k = lvl->heldKeys.size() - 1; k >= 0; k--) { + char mapped_door = KEY_DOOR_MAPPING[lvl->heldKeys[k] - KEY_TKN_START]; + if (mapped_door == c) { + can_open = true; + // erase key + lvl->heldKeys.erase(lvl->heldKeys.begin() + k); + lvl->doorPositions.erase(lvl->doorPositions.begin() + i); + lvl->map[lvl->doorPositions[i].y][lvl->doorPositions[i].x] = + BLANK_SPACE; + break; + } + } + } + } + + return 1; +} + +static int c_get_keys(lua_State *L) { + push_position_table(L, lvl->keyPositions); + return 1; +} + +static int c_take_key(lua_State *L) { + int id = static_cast(lua_tonumber(L, -1)); + for (int i = 0; i < lvl->keyPositions.size(); i++) { + if (lvl->keyPositions[i].id == id) { + lvl->heldKeys.push_back(lvl->keyPositions[i].token); + lvl->keyPositions.erase(lvl->keyPositions.begin() + i); + break; + } + } + return 1; +} + // not for lua use void init_c_api(lua_State *L) { lua_register(L, "c_move_player", c_move_player); @@ -239,6 +281,10 @@ void init_c_api(lua_State *L) { lua_register(L, "c_score_treasure", c_score_treasure); lua_register(L, "c_get_treasures", c_get_treasures); lua_register(L, "c_trigger_restart", c_trigger_restart); + lua_register(L, "c_get_doors", c_get_doors); + lua_register(L, "c_open_door", c_open_door); + lua_register(L, "c_get_keys", c_get_keys); + lua_register(L, "c_take_key", c_take_key); } #endif // DNG_CAPI_H diff --git a/src/Level.cpp b/src/Level.cpp index 5c8fbe0..3a013b7 100644 --- a/src/Level.cpp +++ b/src/Level.cpp @@ -63,17 +63,28 @@ void Level::load() { } else if (c == ENEMY_TKN) { auto e = create_enemy(x, y); this->enemyPositions.push_back( - {.id = this->nextId(), .x = x, .y = y, .sprite = e}); + {.token = c, .id = this->nextId(), .x = x, .y = y, .sprite = e}); this->map[y].push_back(BLANK_SPACE); } else if (c == PLAYER_TKN) { auto p = create_player(x, y); - this->player = {.id = playerId, .x = x, .y = y, .sprite = p}; + this->player = { + .token = c, .id = playerId, .x = x, .y = y, .sprite = p}; this->map[y].push_back(BLANK_SPACE); } else if (c == TREASURE_TKN) { auto t = create_treasure(x, y); this->treasurePositions.push_back( - {.id = this->nextId(), .x = x, .y = y, .sprite = t}); + {.token = c, .id = this->nextId(), .x = x, .y = y, .sprite = t}); this->map[y].push_back(BLANK_SPACE); + } else if (c >= KEY_TKN_START && c <= KEY_TKN_END) { + auto k = create_key(c, x, y); + this->keyPositions.push_back( + {.token = c, .id = this->nextId(), .x = x, .y = y, .sprite = k}); + this->map[y].push_back(BLANK_SPACE); + } else if (c >= DOOR_TKN_START && c <= DOOR_TKN_END) { + auto d = create_door(c, x, y); + this->doorPositions.push_back( + {.token = c, .id = this->nextId(), .x = x, .y = y, .sprite = d}); + this->map[y].push_back(WALL_SPACE); } else { continue; } @@ -99,11 +110,45 @@ void Level::reset() { this->treasurePositions.clear(); this->displayMap.clear(); this->enemyPositions.clear(); + this->keyPositions.clear(); + this->doorPositions.clear(); this->load(); } bool Level::playerCanStep(int dx, int dy) const { - return canStep(player, dx, dy, map); + bool check_wall = canStep(player, dx, dy, map); + + auto new_pos_x = player.x + dx; + auto new_pos_y = player.y + dy; + return check_wall || + (isDoor(new_pos_x, new_pos_y) && tryDoor(new_pos_x, new_pos_y)); +} + +bool Level::isDoor(int x, int y) const { + for (auto &d : doorPositions) { + if (d.x == x && d.y == y) { + return true; + } + } + return false; +} + +bool Level::tryDoor(int x, int y) const { + + for (auto &d : doorPositions) { + if (d.x == x && d.y == y) { + char door_token = d.token; + for (auto &k : heldKeys) { + if (KEY_DOOR_MAPPING[k - KEY_TKN_START] == door_token) { + return true; + } + } + // matched door pos but not openable + return false; + } + } + // not a door? + return false; } int Level::nextId() { return idCounter++; } diff --git a/src/Level.h b/src/Level.h index 7b25d0b..7c2c74b 100644 --- a/src/Level.h +++ b/src/Level.h @@ -39,8 +39,14 @@ static const char TREASURE_TKN = 't'; static const char ENEMY_TKN = 'e'; static const char BLANK_SPACE = '\0'; static const char WALL_SPACE = '#'; +static const char KEY_TKN_START = '1'; // inclusive (1, 2, 3, 4) +static const char KEY_TKN_END = '4'; // inclusive (1, 2, 3, 4) +static const char DOOR_TKN_START = 'a'; // inclusive (a, b, c, d) +static const char DOOR_TKN_END = 'd'; // inclusive (a, b, c, d) +static const char KEY_DOOR_MAPPING[4] = {'a', 'b', 'c', 'd'}; struct Pos { + char token; int id; int x; int y; @@ -54,6 +60,8 @@ public: ~Level() = default; void load(); bool playerCanStep(int dx, int dy) const; + bool tryDoor(int x, int y) const; + bool isDoor(int x, int y) const; int getEnemyIndex(int id); bool enemyCanStep(const Pos &pos, int dx, int dy) const; void reset(); @@ -64,8 +72,11 @@ public: std::vector> map; // source copy of map std::vector displayMap; Pos player; + std::vector heldKeys; std::vector enemyPositions; std::vector treasurePositions; + std::vector doorPositions; + std::vector keyPositions; private: int idCounter = 1; // defaults at 1 (player always 0) diff --git a/src/SfmlUtils.h b/src/SfmlUtils.h index dd63cd6..86d5f62 100644 --- a/src/SfmlUtils.h +++ b/src/SfmlUtils.h @@ -63,6 +63,14 @@ inline sf::RectangleShape create_square(sf::Color color, int x, int y) { return rect; } +inline sf::RectangleShape create_small_square(sf::Color color, int x, int y) { + sf::RectangleShape rect({SPRITE_SIZE - 4.f, SPRITE_SIZE - 4.f}); + rect.setFillColor(color); + auto pos = to_position_xy(x, y); + rect.setPosition({pos.x + 2.f, pos.y + 2.f}); + return rect; +} + inline sf::RectangleShape create_wall(int x, int y) { return create_square(WALL_COLOR, x, y); } @@ -79,6 +87,34 @@ inline sf::RectangleShape create_treasure(int x, int y) { return create_square(sf::Color::Yellow, x, y); } +inline sf::RectangleShape create_key(char t, int x, int y) { + switch (t) { + case '1': + return create_small_square(sf::Color::Blue, x, y); + case '2': + return create_small_square(sf::Color::Green, x, y); + case '3': + return create_small_square(sf::Color::Black, x, y); + case '4': + default: + return create_small_square(sf::Color::White, x, y); + } +} + +inline sf::RectangleShape create_door(char t, int x, int y) { + switch (t) { + case 'a': + return create_square(sf::Color::Blue, x, y); + case 'b': + return create_square(sf::Color::Green, x, y); + case 'c': + return create_square(sf::Color::Black, x, y); + case 'd': + default: + return create_square(sf::Color::White, x, y); + } +} + inline sf::Vector2f round(const sf::Vector2f vector) { return sf::Vector2f{std::round(vector.x), std::round(vector.y)}; } @@ -107,4 +143,4 @@ inline sf::Text write_text(const char *msg, unsigned int fontSize, return text; } -#endif // DNG_SFML_UTILS_H \ No newline at end of file +#endif // DNG_SFML_UTILS_H diff --git a/src/main.cpp b/src/main.cpp index 38208ec..796c3f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -238,9 +238,14 @@ int main(int argc, char **argv) { window.draw(enemy.sprite); } for (auto &treasure : lvl->treasurePositions) { - treasure.sprite.setPosition(to_position(treasure)); window.draw(treasure.sprite); } + for (auto &key : lvl->keyPositions) { + window.draw(key.sprite); + } + for (auto &door : lvl->doorPositions) { + window.draw(door.sprite); + } } if (scene != Scene::LOSS) { -- cgit v1.2.3-54-g00ecf