Text-Adventure/main.cpp
2023-11-16 00:44:12 +08:00

398 lines
13 KiB
C++

#include <iostream>
#include <iomanip>
#include <memory>
#include <iterator>
#include <vector>
#include <forward_list>
#include "Room.h"
#include "wordwrap.h"
#include "State.h"
#include "strings.h"
#include <map>
#include "FoodObject.h"
#include <fstream>
#include <sstream>
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<GameObject::Type>(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<GameObject::Type>(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<FoodObject *>(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<Room *, std::map<std::string, Room *>> 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<std::string> split(const std::string &str) {
std::vector<std::string> 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;
while (!gameOver) {
/* Ask for a command. */
bool commandOk = false;
inputCommand(&commandBuffer);
/* 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<uint8_t>(commandBuffer.find(' '));
/* 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 <keyword>, drop <keyword>, eat <keyword>
if (!commandOk) {
std::vector<std::string> 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<FoodObject *>(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();
} else {
currentState->setHP(currentState->getHP() - 1);
gameOver = gameOver && (currentState->getHP() <= 0);
}
}
}
int main() {
initWordWrap();
initRooms();
initState();
currentState->announceLoc();
gameLoop();
return 0;
}