diff options
| -rw-r--r-- | README.md | 17 | ||||
| -rw-r--r-- | dnglib/algs.lua | 12 | ||||
| -rw-r--r-- | dnglib/constants.lua | 2 | ||||
| -rw-r--r-- | dnglib/defaults.lua | 33 | ||||
| -rw-r--r-- | maps/lvl3/dng.map | 14 | ||||
| -rw-r--r-- | src/CApi.h | 104 | ||||
| -rw-r--r-- | src/Level.cpp | 53 | ||||
| -rw-r--r-- | src/Level.h | 11 | ||||
| -rw-r--r-- | src/SfmlUtils.h | 38 | ||||
| -rw-r--r-- | src/main.cpp | 7 | 
10 files changed, 241 insertions, 50 deletions
| @@ -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 @@ -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) { |