Text-Adventure/main.cpp
2023-12-06 10:11:10 +08:00

421 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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>
#include <time.h>
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;
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<uint8_t>(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 <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();
}
}
}
int main() {
initWordWrap();
initRooms();
initState();
currentState->announceLoc();
gameLoop();
return 0;
}