summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteph Enders <smenders@gmail.com>2022-12-17 11:04:20 -0500
committerSteph Enders <smenders@gmail.com>2022-12-17 11:04:20 -0500
commit8e101f8bfd995321ac08a16fea9a171b549a0ae4 (patch)
tree61c25c357c24381e653f2dd104463628fe2875e7
parentf710faa0cc8862a8367dbbf89bf8c3cd44790b5d (diff)
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
-rw-r--r--README.md17
-rw-r--r--dnglib/algs.lua12
-rw-r--r--dnglib/constants.lua2
-rw-r--r--dnglib/defaults.lua33
-rw-r--r--maps/lvl3/dng.map14
-rw-r--r--src/CApi.h104
-rw-r--r--src/Level.cpp53
-rw-r--r--src/Level.h11
-rw-r--r--src/SfmlUtils.h38
-rw-r--r--src/main.cpp7
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<Level> lvl;
extern Scene scene;
+void push_position_table(lua_State *L, std::vector<Pos> 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<int>(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<int>(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<int>(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<std::vector<char>> map; // source copy of map
std::vector<sf::RectangleShape> displayMap;
Pos player;
+ std::vector<char> heldKeys;
std::vector<Pos> enemyPositions;
std::vector<Pos> treasurePositions;
+ std::vector<Pos> doorPositions;
+ std::vector<Pos> 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) {