405 lines
13 KiB
C++
405 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>
|
|
#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);
|
|
|
|
/* 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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
int main() {
|
|
initWordWrap();
|
|
initRooms();
|
|
initState();
|
|
currentState->announceLoc();
|
|
gameLoop();
|
|
return 0;
|
|
}
|