#include #include #include #include #include #include #include "Room.h" #include "wordwrap.h" #include "State.h" #include "strings.h" #include #include "FoodObject.h" #include #include #include using std::string; using std::unique_ptr; string commandBuffer; State *currentState; /** * Print out the command prompt then read a command into the provided string buffer. * @param buffer Pointer to the string buffer to use. */ void inputCommand(string *buffer) { buffer->clear(); std::cout << "> "; std::getline(std::cin, *buffer); } bool LoadFromFile(const std::string& file) { std::ifstream ifs(file); if (!ifs.is_open()) { return false; } std::string str; ifs >> str; ifs.close(); GameObject::game_object_map.clear(); Room::room_map.clear(); Packer packer; packer.FromHexStr(str); { size_t objects_size; packer.unpack(objects_size); for (int i = 0; i < objects_size; i++) { int type; int id; GameObject *obj = nullptr; packer.unpack(type); packer.unpack(id); obj = GameObject::CreateGameObject(static_cast(type), false); packer.unpack(*obj); GameObject::AddGameObject(obj); } } { size_t rooms_size; packer.unpack(rooms_size); for (int i = 0; i < rooms_size; i++) { int id; Room *room = new Room(); packer.unpack(id); packer.unpack(*room); Room::room_map.insert({id, room}); } } packer.unpack(GameObject::game_object_id_counter) .unpack(Room::room_id_counter) .unpack(State::seed) .unpack(*currentState); return true; } bool SaveToFile(const std::string& file) { Packer packer; packer.pack(*currentState) .pack(State::seed) .pack(Room::room_id_counter) .pack(GameObject::game_object_id_counter); for (const auto& item : Room::room_map) { packer.pack(item.second); packer.pack(item.first); } packer.pack(Room::room_map.size()); for (const auto& item : GameObject::game_object_map) { packer.pack(item.second); packer.pack(item.first); packer.pack(item.second->getObjectType()); } packer.pack(GameObject::game_object_map.size()); std::ofstream ofs(file, std::ios::trunc | std::ios::out); if (!ofs.is_open()) { return false; } ofs << packer.ToHexStr(); ofs.flush(); ofs.close(); return true; } void CreateRandomGameObject(Room *room) { int cnt = (currentState->NextRandom() % 5) + 1; while (cnt--) { GameObject::Type type = static_cast(rand() % GameObject::TYPE_MAX_COUNT); GameObject *obj = GameObject::CreateGameObject(type); GameObject::AddGameObject(obj); // use id as keyword obj->setKeyword(std::to_string(obj->getGameObjectId())); room->AddGameObject(obj->getGameObjectId()); switch (type) { case GameObject::Food: { FoodObject *food = dynamic_cast(obj); food->setName("food"); food->setEnergy((currentState->NextRandom() % 10 + 1)); food->setDescription("This is a food. energy: " + std::to_string(food->getEnergy()) + "."); break; } case GameObject::Default: { obj->setName("object"); obj->setDescription("This is an object."); break; } default: break; } } } /** * Sets up the map. */ void initRooms() { // Create a map that associates rooms with their corresponding exit directions and adjacent rooms std::map> roomConnections; // Helper function to create a room and add it to the list of rooms in the Room class auto createRoom = [&](Room *room, const string &name, const string &desc) { room = new Room(&name, &desc); Room::addRoom(room); // add random game object CreateRandomGameObject(room); return room; }; Room *r1, *r2, *r3, *r4; // Create and initialize room objects r1 = createRoom(r1, r1name, r1desc); r2 = createRoom(r2, r2name, r2desc); r3 = createRoom(r3, r3name, r3desc); r4 = createRoom(r4, r4name, r4desc); // Associate rooms with adjacent rooms and their corresponding directions roomConnections[r1] = {{"north", r2}}; roomConnections[r2] = {{"south", r1}, {"east", r3}}; roomConnections[r3] = {{"west", r2}, {"south", r4}}; roomConnections[r4] = {{"north", r3}}; // Iterate through the map to set exits for each room for (const auto &entry: roomConnections) { Room *currentRoom = entry.first; const auto &exits = entry.second; for (const auto &exit: exits) { const string &direction = exit.first; Room *connectedRoom = exit.second; // Set the respective exit based on the direction if (direction == "north") currentRoom->setNorthId(connectedRoom->getRoomId()); else if (direction == "east") currentRoom->setEastId(connectedRoom->getRoomId()); else if (direction == "south") currentRoom->setSouthId(connectedRoom->getRoomId()); else if (direction == "west") currentRoom->setWestId(connectedRoom->getRoomId()); } } } /** * Sets up the game state. */ void initState() { currentState = new State(Room::room_map.begin()->first); } std::vector split(const std::string &str) { std::vector result; std::istringstream iss(str); for (std::string s; iss >> s;) result.push_back(s); return result; } /** * The main game loop. */ void gameLoop() { bool gameOver = false; auto prev_time = time(nullptr); while (!gameOver) { /* Ask for a command. */ bool commandOk = false; inputCommand(&commandBuffer); auto now = time(nullptr); if (now - prev_time > 60) { int hp_offset = (now - prev_time) / 60; prev_time += hp_offset * 60; currentState->setHP(currentState->getHP() - hp_offset); } gameOver = gameOver || (currentState->getHP() <= 0); if (gameOver) { wrapOut("HP is 0, GameOver!!!"); wrapEndPara(); break; } /* The first word of a command would normally be the verb. The first word is the text before the first * space, or if there is no space, the whole string. */ auto endOfVerb = static_cast(commandBuffer.find(' ')); if (commandBuffer.compare(0, endOfVerb, "hp") == 0) { wrapOut("Current HP: " + std::to_string(currentState->getHP())); wrapEndPara(); commandOk = true; } else { wrapOut("Current HP: " + std::to_string(currentState->getHP())); wrapEndPara(); } /* We could copy the verb to another string but there's no reason to, we'll just compare it in place. */ /* Command to go north. */ if ((commandBuffer.compare(0, endOfVerb, "north") == 0) || (commandBuffer.compare(0, endOfVerb, "n") == 0)) { commandOk = true; /* Confirm command has been handled */ /* See if there's a north exit */ Room *northRoom = currentState->getCurrentRoom()->getNorth(); if (northRoom == nullptr) { /* there isn't */ wrapOut(&badExit); /* Output the "can't go there" message */ wrapEndPara(); } else { /* There is */ currentState->goTo(northRoom); /* Update state to that room - this will also describe it */ } } /* Command to go east. */ if ((commandBuffer.compare(0, endOfVerb, "east") == 0) || (commandBuffer.compare(0, endOfVerb, "e") == 0)) { commandOk = true; Room *eastRoom = currentState->getCurrentRoom()->getEast(); if (eastRoom == nullptr) { wrapOut(&badExit); wrapEndPara(); } else { currentState->goTo(eastRoom); } } /* Command to go south. */ if ((commandBuffer.compare(0, endOfVerb, "south") == 0) || (commandBuffer.compare(0, endOfVerb, "s") == 0)) { commandOk = true; Room *southRoom = currentState->getCurrentRoom()->getSouth(); if (southRoom == nullptr) { wrapOut(&badExit); wrapEndPara(); } else { currentState->goTo(southRoom); } } /* Command to go west. */ if ((commandBuffer.compare(0, endOfVerb, "west") == 0) || (commandBuffer.compare(0, endOfVerb, "w") == 0)) { commandOk = true; Room *westRoom = currentState->getCurrentRoom()->getWest(); if (westRoom == nullptr) { wrapOut(&badExit); wrapEndPara(); } else { currentState->goTo(westRoom); } } if (commandBuffer.compare(0, endOfVerb, "inventory") == 0) { commandOk = true; for (auto obj_id : currentState->getInventory()) { auto obj = GameObject::getGameObjectById(obj_id); wrapOut(obj->getKeyword() + ": " + obj->getName()); wrapEndPara(); } } // check get , drop , eat if (!commandOk) { std::vector words = split(commandBuffer); int id = words.size() >= 2 ? std::strtol(words[1].c_str(), nullptr, 10) : 0; if (words.size() >= 2 && words[0].compare("load") == 0) { commandOk = true; if (!LoadFromFile(words[1])) { wrapOut(&loadGameFail); } else { wrapOut(&loadGameSuccess); } wrapEndPara(); } if (words.size() >= 2 && words[0].compare("save") == 0) { commandOk = true; if (!SaveToFile(words[1])) { wrapOut(&saveGameFail); } else { wrapOut(&saveGameSuccess); } wrapEndPara(); } if (id > 0 && words[0].compare("get") == 0) { commandOk = true; if (currentState->IsInInventory(id)) { wrapOut(&itemInInventory); wrapEndPara(); } else if (!currentState->getCurrentRoom()->IsInRoom(id)) { wrapOut(&itemNotFound); wrapEndPara(); } else { currentState->getCurrentRoom()->RemoveGameObject(id); currentState->addToInventory(id); wrapOut(itemGot + GameObject::getGameObjectById(id)->getName()); wrapEndPara(); } } if (id > 0 && words[0].compare("drop") == 0) { commandOk = true; if (currentState->getCurrentRoom()->IsInRoom(id)) { wrapOut(&itemInRoom); wrapEndPara(); } else if (!currentState->IsInInventory(id)) { wrapOut(&itemNotFound); wrapEndPara(); } else { currentState->removeFromInventory(id); currentState->getCurrentRoom()->AddGameObject(id); wrapOut(itemDropped + GameObject::getGameObjectById(id)->getName()); wrapEndPara(); } } if (id > 0 && words[0].compare("eat") == 0) { commandOk = true; if (!currentState->IsInInventory(id)) { wrapOut(&itemNotFound); wrapEndPara(); } else { GameObject *obj = GameObject::getGameObjectById(id); if (obj->getObjectType() != GameObject::Food) { wrapOut(&itemNotFound); wrapEndPara(); } else { FoodObject *food = dynamic_cast(obj); currentState->setHP(currentState->getHP() + food->getEnergy()); currentState->removeFromInventory(id); wrapOut(itemEaten + GameObject::getGameObjectById(id)->getName()); wrapOut("Current HP: " + std::to_string(currentState->getHP())); wrapEndPara(); } } } if (id > 0 && words[0].compare("examine") == 0) { commandOk = true; if (!currentState->IsInInventory(id) && !currentState->getCurrentRoom()->IsInRoom(id)) { wrapOut(&itemNotFound); wrapEndPara(); } else { wrapOut(GameObject::getGameObjectById(id)->getDescription()); wrapEndPara(); } } } /* Quit command */ if ((commandBuffer.compare(0, endOfVerb, "quit") == 0)) { commandOk = true; gameOver = true; } /* If commandOk hasn't been set, command wasn't understood, display error message */ if (!commandOk) { wrapOut(&badCommand); wrapEndPara(); } } } int main() { initWordWrap(); initRooms(); initState(); currentState->announceLoc(); gameLoop(); return 0; }