421 lines
14 KiB
C++
421 lines
14 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>
|
||
#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;
|
||
}
|