Skip to content

File SecondWorld.hpp

File List > source > Worlds > SecondWorld.hpp

Go to the documentation of this file



#pragma once
#include <algorithm>
#include <nlohmann/json.hpp>

#include "../Agents/PacingAgent.hpp"
#include "../core/WorldBase.hpp"
#include "ProgramExecutor.hpp"  //< for attack, item scripts

using cse491::CellType;

namespace group4 {
const std::string FIRST_FLOOR_FILENAME = "../assets/grids/group4_maze.grid";

const std::string SECOND_FLOOR_FILENAME = "../assets/grids/second_floor.grid";

const std::string FINAL_FLOOR_FILENAME = "../assets/grids/third_floor.grid";

const std::string ITEM_PICKUP_SCRIPT = "../assets/scripts/g4_item_pickup.ws";

const std::string COMBAT_SCRIPT = "../assets/scripts/g4_agent_attack.ws";

const std::string WORLD_LOAD_SCRIPT = "../assets/scripts/g4_world_load.ws";

const std::string WORLD_LOAD_SCRIPT_2 = "../assets/scripts/g4_world_2_load.ws";

const std::string WORLD_LOAD_SCRIPT_3 = "../assets/scripts/g4_world_3_load.ws";

const size_t MAX_INVENTORY_SIZE = 30;

const cse491::GridPosition OffGrid = {-1, -1};

class SecondWorld : public cse491::WorldBase {
 private:
  std::string world_filename = "";

  std::string agents_filename = "";


  void LoadWorldScript(std::string source){
    // Clear items on the current grid, unless held by a player
    for (auto it = item_map.begin(); it != item_map.end();) {
      auto& item = it->second;
      if (item->IsOnGrid()) {
        // clear this item
        it = item_map.erase(it);
      } else if (item->IsOwnedByItem()) {
        // it's owned by a chest: this is currently the only item containing
        // items
        it = item_map.erase(it);
      } else {
        ++it;
      }
    }

    // Clear agents, unless they are the player (an interface)
    std::vector<size_t> agents_to_remove = {};
    for (auto& [agent_id, agent_ptr] : agent_map) {
      if (!agent_ptr->IsInterface()) {
        agents_to_remove.push_back(agent_id);
      }
    }

    for (auto agent_id : agents_to_remove) {
      RemoveAgent(agent_id);
    }

    // Initialize level
    pe.runFile(source);
  }


  void SwitchGrid(cse491::AgentBase& agent) {

    // This value initialized by each world load script to point to the next level
    auto next_world_script = pe.var<std::string>("next_world");

    agent.Notify("Going to " + world_filename, "world_switched");
    if (next_world_script == "GAME_END") {
      agent.Notify("Congrats, you won the game!", "congrats_msg");
      run_over = true;
      return;
    }

    LoadWorldScript(next_world_script);
  }

 protected:
  enum ActionType {
    REMAIN_STILL = 0,
    MOVE_UP,
    MOVE_DOWN,
    MOVE_LEFT,
    MOVE_RIGHT,
    DROP_ITEM,
//    WARP_TO_FLOOR_3  // New action for hidden warp tile
  };

  worldlang::ProgramExecutor pe;

  std::map<size_t, std::unique_ptr<cse491::ItemBase>> inventory;

  void ConfigAgent(cse491::AgentBase& agent) override {
    agent.AddAction("up", MOVE_UP);
    agent.AddAction("down", MOVE_DOWN);
    agent.AddAction("left", MOVE_LEFT);
    agent.AddAction("right", MOVE_RIGHT);
    agent.AddAction("drop", DROP_ITEM);
//    agent.AddAction("warp", WARP_TO_FLOOR_3);
  }

 public:
  SecondWorld() : world_filename(FIRST_FLOOR_FILENAME), pe{*this} {
    pe.registerFunction("loadAgents", [this](worldlang::ProgramExecutor& pe){
      auto args = pe.popArgs();
      if (args.size() != 1) { pe.error("Wrong number of arguments!"); return; }
      auto path = pe.as<std::string>(args[0]);

      this->LoadFromFile(path);
    });

    LoadWorldScript(WORLD_LOAD_SCRIPT);
  }

  SecondWorld(std::string grid_filename, std::string agent_filename)
      : world_filename(grid_filename),
        agents_filename(agent_filename),
        pe{*this} {
    AddCellType("floor", "Floor that you can easily walk over.", ' ');
    AddCellType("flag", "Goal flag for a game end state", 'g');
    AddCellType("wall", "Impenetrable wall that you must find a way around.", '#');
    AddCellType("hidden_warp", "Hidden warp tile that warps to floor 3.", 'u');
    AddCellType("water", "Water that distinguishes fire.", 'w');

    main_grid.Read(grid_filename, type_options);
    LoadFromFile(agent_filename);
  }

  ~SecondWorld() = default;

  void LoadFromFile(const std::string& input_filename) {
    std::ifstream input_file(input_filename);

    if (!input_file.is_open()) {
      std::cerr << "Error: could not open file " << input_filename << std::endl;
      return;
    }

    nlohmann::json data;
    try {
      input_file >> data;
    } catch (const nlohmann::json::parse_error& err) {
      std::cerr << "JSON parsing error: " << err.what() << std::endl;
      return;
    }

//    const int BASE_MAX_HEALTH = 100;
    for (const auto& agent : data) {
      // May get a json.exception.type_error here if you assign to the wrong C++
      // type, so make sure to nail down what types things are in JSON first! My
      // intuition is that each agent object will have: name: string (C++
      // std::string) x: number (C++ int) y: number (C++ int) entities:
      // array<string> (C++ std::vector<std::string>)
      std::string agent_name = agent.at("name");
      int x_pos = agent.at("x");
      int y_pos = agent.at("y");

//      int additional_max_health = 0;
//      std::vector<std::string> entities = agent.at("entities");


//      for (const auto& entity : entities) {
        // TODO: How should we set the entity properties here?
        // Just adding to MaxHealth now, but this doesn't seem very scalable.
/*        if (entity == "chocolate_bar") {
          additional_max_health = 10;
        }*/
//      }

      auto& a = AddAgent<cse491::PacingAgent>(agent_name)
          .SetPosition(x_pos, y_pos);
//          .SetProperty("MaxHealth", BASE_MAX_HEALTH + additional_max_health);

      auto properties = agent.at("properties");
      for (const auto& p : properties.items()){
        std::cout << p.value().is_number() << std::endl;
        a.SetProperty(p.key(), p.value().get<double>());
      }

    }
  }

  virtual void Run() override {
    while (!run_over) {
      RunAgents();
    }

    SaveToFile();
  }

  void SaveToFile() {
    nlohmann::json output_data;  // json to store the data being outputted

    for (const auto& [agent_id, agent_ptr] : agent_map) {
      auto new_position = agent_ptr->GetPosition();
      std::string agent_name = agent_ptr->GetName();
      double x_pos = new_position.GetX();
      double y_pos = new_position.GetY();

      nlohmann::json agent_data;  // json for each agent
      agent_data["name"] = agent_name;
      agent_data["x"] = x_pos;
      agent_data["y"] = y_pos;

      output_data.push_back(agent_data);  // add it to the json array
    }

    std::ofstream ofs("output.json");  // save it to a file called output.json
    ofs << output_data.dump(2);        // indentation
  }

  void DropItem(cse491::AgentBase& agent, cse491::GridPosition& pos) {
    // Cannot drop
    if (agent.GetInventory().empty()) {
      agent.Notify("Cannot drop any items, inventory is empty.", "item_alert");
      return;
    }

    auto items_found = FindItemsAt(pos, 0);
    auto& item_drop = GetItem(agent.GetInventory().at(0));

    // Transfer ownership to chest
    if (!items_found.empty()) {
      auto& target_item = GetItem(items_found.at(0));
      if (target_item.HasProperty("Chest")) {
        item_drop.SetPosition(OffGrid);
        target_item.AddItem(item_drop);

        // Set the position and remove item from agent's inventory
        agent.RemoveItem(item_drop.GetID());

        // Must set the grid back because RemoveItem() doesn't account for that
        item_drop.SetGrid();
        agent.Notify("Dropping " + item_drop.GetName() + " into the chest!",
                     "item_alert");
        return;

        // Item already on player's position
      } else {
        agent.Notify(
            "Cannot drop the item, there is already an item on this cell.",
            "item_alert");
        return;
      }
    }
    // Transfer ownership to grid
    agent.RemoveItem(item_drop.GetID());
    item_drop.SetGrid();
    item_drop.SetPosition(pos);

    agent.Notify("Dropping " + item_drop.GetName() + " onto the ground!",
                 "item_alert");
  }

  bool CheckPosition(cse491::AgentBase& agent, cse491::GridPosition& pos) {
    // First check to see if agent is on win flag
    if ((type_options[main_grid.At(pos)].HasProperty("Goal")) &&
        (agent.IsInterface())) {
      agent.Notify("Flag found ", "item_alert");

      agent.Notify("Leaving " + world_filename, "world_switched");

      SwitchGrid(agent);

      return false;

      // then checks if agent is on any items
    } else if ((type_options[main_grid.At(pos)].HasProperty("Warp")) &&
               (agent.IsInterface())) {
      // Agent used the hidden warp tile action
      agent.Notify("Hidden warp tile activated! Warping to floor 3.",
                   "hidden_warp");

      agent.Notify("Leaving " + world_filename, "world_switched");

      LoadWorldScript(WORLD_LOAD_SCRIPT_3); // skip right to the end
      return false;
    } else {
      auto items_found = FindItemsAt(pos, 0);
      // If there are items at this position
      if (!items_found.empty() && agent.IsInterface()) {
        auto& item_found = GetItem(items_found.at(0));

        // Item is a chest
        if (item_found.HasProperty("Chest")) {
          // Check to see if the chest owns any items
          if (!item_found.GetInventory().empty()) {
            auto temp_inventory = item_found.GetInventory();
            agent.Notify("This is inside the chest: ", "item_alert");

            // Display the items found
            for (auto x : temp_inventory) {
              agent.Notify("You found the " + GetItem(x).GetName() +
                           " in the " + item_found.GetName());
              agent.AddItem(GetItem(x));
              item_found.RemoveItem(GetItem(x));
            }

            // Check agent's inventory size
            if (agent.GetInventory().size() == MAX_INVENTORY_SIZE) {
              agent.Notify(
                  "It looks like your inventory is full, please drop items or "
                  "place them in chests!",
                  "item_alert");
            }
          } else {
            agent.Notify("The chest is empty! You can store items with 't'!",
                         "item_alert");
          }

          // Item is not a chest
        } else {
          if (agent.GetInventory().size() == MAX_INVENTORY_SIZE) {
            agent.Notify(
                "It looks like your inventory is full, please drop items or "
                "place them in chests!",
                "item_alert");

          } else {
            agent.Notify("You found " + item_found.GetName() + "!",
                         "item_alert");

            // Set the position off the grid, so it doesn't render
            // Note: Setting the position with a GridPosition SETS THE OWNER
            item_found.SetPosition(OffGrid);
            // Add item to the agent's inventory
            agent.AddItem(item_found.GetID());

          }
        }
      }
    }
    return true;
  }

  int DoAction(cse491::AgentBase& agent, size_t action_id) override {
    if (agent.HasProperty("Dead")){
      return false; // Can't move when dead!
    }

    cse491::GridPosition new_position;
    bool IsDropped = false;
    switch (action_id) {
      case REMAIN_STILL:
        new_position = agent.GetPosition();
        break;
      case MOVE_UP:
        new_position = agent.GetPosition().Above();
        break;
      case MOVE_DOWN:
        new_position = agent.GetPosition().Below();
        break;
      case MOVE_LEFT:
        new_position = agent.GetPosition().ToLeft();
        break;
      case MOVE_RIGHT:
        new_position = agent.GetPosition().ToRight();
        break;
      case DROP_ITEM:
        new_position = agent.GetPosition();
        DropItem(agent, new_position);
//        IsDropped = true;
        return true;
        break;
    }

    if (!main_grid.IsValid(new_position)) {
      return false;
    }
    if (!IsTraversable(agent, new_position)) {
      return false;
    }

    // At this point, new_position is valid and not going into a wall.
    // Check if there are any agents on this tile:
    auto res = this->FindAgentsAt(new_position);
    if (res.size() && res[0] != agent.GetID()) {
      // At least one agent was found (and isn't the player)
      // Take the first agent and attack it.
      pe.setVariable("agent", agent.GetID());
      pe.setVariable("opponent", res[0]);
      pe.runFile(COMBAT_SCRIPT);

      auto& opponent = GetAgent(res[0]);
      if (opponent.HasProperty("Dead") && opponent.IsInterface()){
        // game over!
        opponent.Notify("You have died.", "game_over");
        run_over = true;
      } else if (opponent.HasProperty("Dead") && opponent.HasProperty("Boss")){
        // level up: i.e. you can go on water now
        agent.Notify("You have defeated the boss! You now can walk on water.", "message");
        agent.SetProperty("Swimmer", true);
      }

      // The movement was not legal, so we return false.
      // TODO: Should this return a status indicating that an attack occured,
      // to distinguish moves that do nothing from attacks?
      return false;
    }

    if (!IsDropped) {
      if (!CheckPosition(agent, new_position))
        //does not set position if grid changed, but move is considered successful
        return true; 
    }

    IsDropped = false;
    agent.SetPosition(new_position);
    return true;
  }

  void PrintEntities() {
    for (const auto& [id, item_ptr] : item_map) {
      if (!item_ptr) {
        continue;
      }
      std::cout << item_ptr->GetName() << "\n";
    }
    std::cout << std::endl;
  }

  bool IsTraversable(const AgentBase& agent,
                     cse491::GridPosition pos) const override {
    if (GetCellTypes().at(main_grid.At(pos)).HasProperty(CellType::CELL_WALL))
      return false;
    else if (GetCellTypes()
                 .at(main_grid.At(pos))
                 .HasProperty(CellType::CELL_WATER))
      return agent.HasProperty("Swimmer");
    else
      return true;
  }
};
}  // namespace group4