commit ee8cc69a8b1e9ba93ca43e06a9ffff1f50b809aa Author: tqcq <99722391+tqcq@users.noreply.github.com> Date: Thu Nov 16 00:44:12 2023 +0800 init repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..59b0c4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cmake-* +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d3f20a8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.8) +project(textadv) + +set(CMAKE_CXX_STANDARD 11) + +set(SOURCE_FILES main.cpp Room.cpp Room.h wordwrap.h wordwrap.cpp State.cpp State.h strings.h + GameObject.cpp + FoodObject.h + Serializable.cpp + FoodObject.cpp) +add_executable(textadv ${SOURCE_FILES}) \ No newline at end of file diff --git a/CMakeLists_1.txt b/CMakeLists_1.txt new file mode 100644 index 0000000..f6a7ecf --- /dev/null +++ b/CMakeLists_1.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.8) +project(textadv) + +set(CMAKE_CXX_STANDARD 11) + +set(SOURCE_FILES main.cpp Room.cpp Room.h wordwrap.h wordwrap.cpp State.cpp State.h strings.h) +add_executable(textadv ${SOURCE_FILES}) \ No newline at end of file diff --git a/FoodObject.cpp b/FoodObject.cpp new file mode 100644 index 0000000..e2a9fee --- /dev/null +++ b/FoodObject.cpp @@ -0,0 +1,53 @@ +// +// Created by tqcq on 2023/11/14. +// + +#include "FoodObject.h" + + +namespace tqcq { +} // namespace tqcq +FoodObject::FoodObject(const std::string &name, const std::string &keyword, const std::string &description, + int energy) + : GameObject(name, keyword, description) { + object_type = Food; + setEnergy(energy); +} + +int FoodObject::getEnergy() const { + return energy; +} + +std::string FoodObject::ToString() const { + Packer packer; + packer.pack(GameObject::ToString()); + packer.pack(energy); + return packer.ToHexStr(); +} + +void FoodObject::FromString(const std::string &str) { + Packer packer; + std::string game_object_hex; + packer.FromHexStr(str); + + packer.unpack(energy); + packer.unpack(game_object_hex); + + GameObject::FromString(game_object_hex); +} + +FoodObject::FoodObject() + : GameObject(), energy(0) +{ + object_type = Food; +} + +void FoodObject::setEnergy(int _energy) { + _energy = std::min(_energy, 10); + _energy = std::max(_energy, 1); + energy = _energy; +} + +std::string FoodObject::getInfo() const { + return GameObject::getInfo() + " (" + std::to_string(energy) + ")"; +} diff --git a/FoodObject.h b/FoodObject.h new file mode 100644 index 0000000..1c27992 --- /dev/null +++ b/FoodObject.h @@ -0,0 +1,24 @@ +// +// Created by tqcq on 2023/11/14. +// + +#ifndef TEXTADV_FOODOBJECT_H +#define TEXTADV_FOODOBJECT_H + +#include "GameObject.h" + +class FoodObject : public GameObject { + int energy; +public: + FoodObject(); + FoodObject(const std::string &name, const std::string &keyword, const std::string &description, int energy); + std::string getInfo() const override; + + int getEnergy() const; + void setEnergy(int energy); + + std::string ToString() const override; + void FromString(const std::string &str) override; +}; + +#endif //TEXTADV_FOODOBJECT_H diff --git a/GameObject.cpp b/GameObject.cpp new file mode 100644 index 0000000..83d7482 --- /dev/null +++ b/GameObject.cpp @@ -0,0 +1,105 @@ +// +// Created by tqcq on 2023/11/14. +// + +#include "GameObject.h" +#include "FoodObject.h" + +std::map GameObject::game_object_map; +int GameObject::game_object_id_counter = 1; + +GameObject::GameObject() + : game_object_id(game_object_id_counter++), object_type(Default) { +} + +GameObject::GameObject(const std::string &name, const std::string &keyword, const std::string &description) + : game_object_id(game_object_id_counter++), object_type(Default), name(name), keyword(keyword), + description(description) { +} + +const std::string &GameObject::getName() const { + return name; +} + +const std::string &GameObject::getKeyword() const { + return keyword; +} + +const std::string &GameObject::getDescription() const { + return description; +} + +std::string GameObject::ToString() const { + Packer packer; + packer.pack(game_object_id); + packer.pack(name); + packer.pack(keyword); + packer.pack(description); + + return packer.ToHexStr(); +} + +void GameObject::FromString(const std::string &str) { + Packer packer; + packer.FromHexStr(str); + packer.unpack(description); + packer.unpack(keyword); + packer.unpack(name); + packer.unpack(game_object_id); +} + +GameObject *GameObject::CreateGameObject(GameObject::Type type, bool add_to_map) { + GameObject *object = nullptr; + switch (type) { + case Default: + object = new GameObject(); + break; + case Food: + object = new FoodObject(); + break; + default: + return nullptr; + } + if (add_to_map) AddGameObject(object); + return object; +} + +int GameObject::getGameObjectId() const { + return game_object_id; +} + +GameObject::Type GameObject::getObjectType() const { + return object_type; +} + +GameObject *GameObject::getGameObjectById(int id) { + auto it = game_object_map.find(id); + return it == game_object_map.end() ? nullptr : it->second; +} + +void GameObject::AddGameObject(GameObject *object) { + if (!object) { + return; + } + game_object_map.insert({object->getGameObjectId(), object}); +} + +GameObject::~GameObject() { + game_object_map.erase(game_object_id); +} + +void GameObject::setName(const std::string &_name) { + name = _name; +} + +void GameObject::setKeyword(const std::string &_keyword) { + keyword = _keyword; +} + +void GameObject::setDescription(const std::string &_description) { + description = _description; +} + +std::string GameObject::getInfo() const { + return keyword + ": " + name; +} diff --git a/GameObject.h b/GameObject.h new file mode 100644 index 0000000..c181bfc --- /dev/null +++ b/GameObject.h @@ -0,0 +1,63 @@ +// +// Created by tqcq on 2023/11/14. +// + +#ifndef TEXTADV_GAMEOBJECT_H +#define TEXTADV_GAMEOBJECT_H +#include "Serializable.h" +#include + +class GameObject : public Serializable { +public: + enum Type { + Default, // GameObject + Food, // FoodObject + TYPE_MAX_COUNT, + }; + + + static int game_object_id_counter; + /** + * @brief Create a GameObject object + */ + static std::map game_object_map; + static GameObject* getGameObjectById(int id); + /** + * @brief Create a GameObject object + * @param type + * @return + */ + static GameObject* CreateGameObject(Type type, bool add_to_map = true); + static void AddGameObject(GameObject* object); + + GameObject(); + GameObject(const std::string &name, const std::string &keyword, const std::string &description); + ~GameObject() override; + + virtual std::string getInfo() const; + + const std::string& getName() const; + void setName(const std::string& name); + + const std::string& getKeyword() const; + void setKeyword(const std::string& keyword); + + const std::string& getDescription() const; + void setDescription(const std::string& description); + + int getGameObjectId() const; + virtual Type getObjectType() const; + + std::string ToString() const override; + void FromString(const std::string &str) override; + +protected: + int game_object_id; + Type object_type; + std::string name; + std::string keyword; + std::string description; +}; + + +#endif //TEXTADV_GAMEOBJECT_H diff --git a/Room.cpp b/Room.cpp new file mode 100644 index 0000000..cf99c49 --- /dev/null +++ b/Room.cpp @@ -0,0 +1,204 @@ + +#include "Room.h" +#include "wordwrap.h" +#include + +/** + * Stores a static list of all rooms. + */ +std::map Room::room_map; +int Room::room_id_counter = 1; + +/** + * Room default constructor. + * @param _name Room's name. + * @param _desc Room's description. + */ +Room::Room(const string *_name, const string *_desc) : + name(_name), description(_desc), north_id(-1), south_id(-1), east_id(-1), west_id(-1), + room_id(room_id_counter++), objects() {}; + +/** + * Remove destroyed rooms from the static list. + */ +Room::~Room() { + Room::room_map.erase(room_id); + delete name; + delete description; +} + +/** + * Prints the description of a room (the name and long description) + */ +void Room::describe() const { + wrapOut(this->name); + wrapEndPara(); + wrapOut(this->description); + wrapEndPara(); + for (const auto &obj_id: this->objects) { + auto obj = GameObject::getGameObjectById(obj_id); + std::string obj_desc = obj->getInfo(); + wrapOut(&obj_desc); + wrapEndPara(); + } +} + +/** + * Statically creates a room and then adds it to the global list. + * @param _name Name for the new room. + * @param _desc Description for the new room. + * @return A pointer to the newly created room. + */ +Room *Room::addRoom(const string *_name, const string *_desc) { + auto *newRoom = new Room(_name, _desc); + newRoom->setRoomId(room_id_counter++); + Room::room_map.insert({newRoom->getRoomId(), newRoom}); + return newRoom; +} + +/** + * Adds an existing room to the static list. + * @param room Pointer to the room to add. + * @return + */ +void Room::addRoom(Room *room) { + Room::room_map.insert({room->getRoomId(), room}); +} + + +string Room::ToString() const { + Packer packer; + + for (const auto &obj_id: this->objects) { + packer.pack(obj_id); + } + packer.pack(*name) + .pack(*description) + .pack(room_id) + .pack(north_id) + .pack(south_id) + .pack(east_id) + .pack(west_id) + .pack(this->objects.size()); + return packer.ToHexStr(); +} + +void Room::FromString(const string &str) { + std::string *new_name = new std::string(); + std::string *new_desc = new std::string(); + + Packer packer; + packer.FromHexStr(str); + size_t objects_size; + packer.unpack(objects_size) + .unpack(west_id) + .unpack(east_id) + .unpack(south_id) + .unpack(north_id) + .unpack(room_id) + .unpack(*new_desc) + .unpack(*new_name); + for (int i = 0; i < objects_size; i++) { + int id; + packer.unpack(id); + objects.push_back(id); + } + if (name) delete name; + if (description) delete description; + name = new_name; + description = new_desc; +} + +int Room::getNorthId() const { + return north_id; +} + +void Room::setNorthId(int _north_id) { + north_id = _north_id; +} + +int Room::getSouthId() const { + return south_id; +} + +void Room::setSouthId(int _south_id) { + south_id = _south_id; +} + +int Room::getEastId() const { + return east_id; +} + +void Room::setEastId(int _east_id) { + east_id = _east_id; +} + +int Room::getWestId() const { + return west_id; +} + +void Room::setWestId(int _west_id) { + west_id = _west_id; +} + +int Room::getRoomId() const { + return room_id; +} + +void Room::setRoomId(int _room_id) { + room_id = _room_id; +} + +Room::Room() : Room(nullptr, nullptr) { +} + +Room *Room::getNorth() const { + auto iter = Room::room_map.find(north_id); + return iter == Room::room_map.end() ? nullptr : iter->second; +} + +Room *Room::getSouth() const { + auto iter = Room::room_map.find(south_id); + return iter == Room::room_map.end() ? nullptr : iter->second; +} + +Room *Room::getEast() const { + auto iter = Room::room_map.find(east_id); + return iter == Room::room_map.end() ? nullptr : iter->second; +} + +Room *Room::getWest() const { + auto iter = Room::room_map.find(west_id); + return iter == Room::room_map.end() ? nullptr : iter->second; +} + +Room *Room::getRoomById(int id) { + auto iter = Room::room_map.find(id); + return iter == Room::room_map.end() ? nullptr : iter->second; +} + +void Room::AddGameObject(int object_id) { + objects.push_back(object_id); + objects.sort(); +} + +void Room::RemoveGameObject(int object_id) { + objects.remove(object_id); +} + +int Room::FindGameObject(const string &keyword) const { + for (const auto &obj_id: this->objects) { + auto obj = GameObject::getGameObjectById(obj_id); + if (obj->getKeyword() == keyword) { + return obj_id; + } + } + return -1; +} + +bool Room::IsInRoom(int object_id) const { + return std::find(objects.begin(), objects.end(), object_id) != objects.end(); +} + + + diff --git a/Room.h b/Room.h new file mode 100644 index 0000000..3041a1b --- /dev/null +++ b/Room.h @@ -0,0 +1,125 @@ + + + +#ifndef TEXTADV_ROOM_H +#define TEXTADV_ROOM_H + +#include +#include +#include +#include +#include "GameObject.h" +#include "Serializable.h" + +using std::string; + +/** + * Represents a room (accessible location in the game). + */ +class Room : public Serializable { + /** + * Short name used as a header. + */ + const string* name; + /** + * Full description of the room. + */ + const string* description; + /** + * Room ID + */ + int room_id; + /** + * Pointer to room that is north of this one. + */ + + int north_id; + int south_id; + int east_id; + int west_id; + + // int: game object id + std::list objects; +public: + /** + * for serialization + */ + Room(); + + /** + * Constructs a new Room. + * @param _name Name of the room. + * @param _desc Description of the room. + */ + Room(const string *_name, const string *_desc); + + /** + * Removes a destroyed room from the global list if it's there. + */ + ~Room(); + + /** + * Outputs the name and description of the room + * in standard format. + */ + void describe() const; + + /** + * List storing all rooms that have been registered via addRoom(). + */ + // static std::list rooms; + + static int room_id_counter; + /** + * Map storing all rooms that have been registered via addRoom(). + */ + static std::map room_map; + /** + * Returns a pointer to the room with the given ID. + * @param room_id + * @return + */ + static Room* getRoomById(int room_id); + /** + * Creates a new Room with the given parameters and register it with the static list. + * @param _name Name of the room. + * @param _desc Description of the room. + */ + static Room* addRoom(const string* _name, const string* _desc); + static void addRoom(Room* room); + + int getRoomId() const; + void setRoomId(int _room_id); + + + Room* getNorth() const; + int getNorthId() const; + void setNorthId(int _north_id); + + Room* getSouth() const; + int getSouthId() const; + void setSouthId(int _south_id); + + Room* getEast() const; + int getEastId() const; + void setEastId(int _east_id); + + Room* getWest() const; + int getWestId() const; + void setWestId(int _west_id); + + void AddGameObject(int object_id); + void RemoveGameObject(int object_id); + bool IsInRoom(int object_id) const; + /** + * Find a game object in this room by keyword. + * @param keyword + * @return game object id, or -1 if not found + */ + int FindGameObject(const std::string &keyword) const; + + string ToString() const override; + void FromString(const string &str) override; +}; + +#endif //TEXTADV_ROOM_H diff --git a/Serializable.cpp b/Serializable.cpp new file mode 100644 index 0000000..a15b5b5 --- /dev/null +++ b/Serializable.cpp @@ -0,0 +1,15 @@ +// +// Created by tqcq on 2023/11/14. +// + +#include "Serializable.h" + +void Serializable::serialize(std::ostream &os) const { + os << ToString(); +} + +void Serializable::deserialize(std::istream &is) { + std::string str; + is >> str; + FromString(str); +} diff --git a/Serializable.h b/Serializable.h new file mode 100644 index 0000000..1a12f93 --- /dev/null +++ b/Serializable.h @@ -0,0 +1,228 @@ +// +// Created by tqcq on 2023/11/14. +// + +#ifndef TEXTADV_SERIALIZABLE_H +#define TEXTADV_SERIALIZABLE_H + +#include +#include +#include +#include + + +class Serializable { +public: + + + virtual ~Serializable() = default; + + virtual std::string ToString() const = 0; + virtual void FromString(const std::string &str) = 0; + + virtual void serialize(std::ostream &os) const; + virtual void deserialize(std::istream &is); + friend std::ostream &operator<<(std::ostream &os, const Serializable &obj) { + obj.serialize(os); + return os; + } + friend std::istream &operator>>(std::istream &is, Serializable &obj) { + obj.deserialize(is); + return is; + } +}; + + +class Packer { +public: + enum Type { + CHAR = 0, + INT, + LONG, + SIZE_T, + DOUBLE, + FLOAT, + STRING, + VECTOR, + MAP, + }; + + struct PacketHeader { + Type type; + size_t size; + }; + + struct Packet { + union { + PacketHeader header; + struct { + Type type; + size_t size; + }; + }; + std::string data; + }; + + Packer& pack(char c) { + data.push_back({CHAR, sizeof(char), std::string(sizeof(char), c)}); + return *this; + } + Packer& pack(int i) { + data.push_back({INT, sizeof(int), std::string((char*)&i, sizeof(int))}); + return *this; + } + Packer& pack(long l) { + data.push_back({LONG, sizeof(long), std::string((char*)&l, sizeof(long))}); + return *this; + } + Packer& pack(size_t s) { + data.push_back({SIZE_T, sizeof(size_t), std::string((char*)&s, sizeof(size_t))}); + return *this; + } + Packer& pack(double d) { + data.push_back({DOUBLE, sizeof(double), std::string((char*)&d, sizeof(double))}); + return *this; + } + Packer& pack(float f) { + data.push_back({FLOAT, sizeof(float), std::string((char*)&f, sizeof(float))}); + return *this; + } + Packer& pack(const std::string &str) { + data.push_back({STRING, str.size(), str}); + return *this; + } + Packer& pack(const Serializable &obj) { + pack(obj.ToString()); + return *this; + } + Packer& pack(const Serializable* obj) { + pack(obj->ToString()); + return *this; + } + + Packer& unpack(char &c) { + Packet &d = data.back(); + if (d.type != CHAR) throw std::runtime_error("Type mismatch"); + c = d.data[0]; + data.pop_back(); + return *this; + } + Packer& unpack(int &i) { + Packet &d = data.back(); + if (d.type != INT) throw std::runtime_error("Type mismatch"); + i = *(int*)d.data.data(); + data.pop_back(); + return *this; + } + + Packer& unpack(long &l) { + Packet &d = data.back(); + if (d.type != LONG) throw std::runtime_error("Type mismatch"); + l = *(long*)d.data.data(); + data.pop_back(); + return *this; + } + + Packer& unpack(size_t& s) { + Packet &d = data.back(); + if (d.type != SIZE_T) throw std::runtime_error("Type mismatch"); + s = *(size_t*)d.data.data(); + data.pop_back(); + return *this; + } + + Packer& unpack(double &d) { + Packet &packet = this->data.back(); + if (packet.type != DOUBLE) throw std::runtime_error("Type mismatch"); + d = *(double*)packet.data.data(); + this->data.pop_back(); + return *this; + } + + Packer& unpack(float &f) { + Packet &packet = this->data.back(); + if (packet.type != FLOAT) throw std::runtime_error("Type mismatch"); + f = *(float*)packet.data.data(); + this->data.pop_back(); + return *this; + } + + Packer& unpack(std::string &str) { + Packet &packet= this->data.back(); + if (packet.type != STRING) throw std::runtime_error("Type mismatch"); + str = packet.data; + this->data.pop_back(); + return *this; + } + + Packer& unpack(Serializable &obj) { + std::string str; + unpack(str); + obj.FromString(str); + return *this; + } + + Packer& unpack(Serializable* obj) { + std::string str; + unpack(str); + obj->FromString(str); + return *this; + } + + std::string ToHexStr() const { + static const std::string hex_str = "0123456789ABCDEF"; + std::string str; + for (const auto& d : data) { + // convert header to hex + auto* ptr = (unsigned char*)&d.header; + auto* end = ptr + sizeof(d.header); + for (; ptr != end; ++ptr) { + str.push_back(hex_str[*ptr >> 4]); + str.push_back(hex_str[*ptr & 0x0F]); + } + + // convert data to hex + ptr = (unsigned char*)d.data.data(); + end = ptr + d.data.size(); + for (; ptr != end; ++ptr) { + str.push_back(hex_str[*ptr >> 4]); + str.push_back(hex_str[*ptr & 0x0F]); + } + } + return str; + } + + void FromHexStr(const std::string& str) { + static const std::string hex_str = "0123456789ABCDEF"; + data.clear(); + int idx = 0; + for (;idx < str.size();) { + Packet packet; + // convert header from hex + auto* ptr = (unsigned char*)&packet.header; + auto* end = ptr + sizeof(packet.header); + for (; ptr != end; ++ptr) { + auto hi = hex_str.find(str[idx++]); + auto lo = hex_str.find(str[idx++]); + if (hi == std::string::npos || lo == std::string::npos) throw std::runtime_error("Invalid hex string"); + *ptr = (hi << 4) | lo; + } + + packet.data.resize(packet.header.size); + for (size_t i = 0; i < packet.header.size; ++i) { + auto hi = hex_str.find(str[idx++]); + auto lo = hex_str.find(str[idx++]); + if (hi == std::string::npos || lo == std::string::npos) throw std::runtime_error("Invalid hex string"); + packet.data[i] = (hi << 4) | lo; + } + + data.emplace_back(packet); + } + } +private: + std::vector data; +}; + + + +#endif //TEXTADV_SERIALIZABLE_H diff --git a/State.cpp b/State.cpp new file mode 100644 index 0000000..6eefe32 --- /dev/null +++ b/State.cpp @@ -0,0 +1,108 @@ + + +#include "State.h" +#include +#include + +/** + * Current state of the game. + */ + +/** + * Display the description of the room the player is in. */ + +void State::announceLoc() const { + Room::getRoomById(currentRoomId)->describe(); +} + +int State::seed = time(nullptr); + +/** + * Constructor. + * @param startRoom Pointer to the room to start in. + */ +State::State(int room_id) : currentRoomId(room_id),inventory_sorted(true),inventory(),HP(100) {}; + +/** + * Move to a specified room and print its description. + * @param target Pointer to the room to move to. + */ +void State::goTo(Room *target) { + if (target != nullptr) { + this->currentRoomId = target->getRoomId(); + this->announceLoc(); + } +} + +/** + * Return a pointer to the current room. + * @return Pointer to the current room. + */ +Room *State::getCurrentRoom() const { + return Room::room_map.find(currentRoomId)->second; +} + +int State::getHP() const { + return HP; +} + +void State::setHP(int HP) { + HP = std::min(HP, 100); + HP = std::max(HP, 0); + this->HP = HP; +} + +string State::ToString() const { + Packer packer; + for (auto it = inventory.begin(); it != inventory.end(); it++) { + packer.pack(*it); + } + packer.pack(inventory.size()) + .pack(HP) + .pack(currentRoomId); + return packer.ToHexStr(); +} + +void State::FromString(const string &str) { + inventory.clear(); + Packer packer; + packer.FromHexStr(str); + size_t inventory_size; + + packer.unpack(currentRoomId) + .unpack(HP) + .unpack(inventory_size); + for (int i = 0; i < inventory_size; i++) { + int id; + packer.unpack(id); + inventory.push_back(id); + } +} + +std::list State::getInventory() { + inventory_sorted = true; + inventory.sort(); + return inventory; +} + +bool State::IsInInventory(int object_id) const { + return std::find(inventory.begin(), inventory.end(), object_id) != inventory.end(); +} + +void State::addToInventory(int object_id) { + inventory.push_back(object_id); + inventory_sorted = false; +} + +void State::removeFromInventory(int object_id) { + inventory.remove(object_id); + inventory_sorted = false; +} + +int State::NextRandom() { + const int a = 1103515245; + const int c = 12345; + const int m = 1 << 31; + seed = (a * seed + c) % m; + return (int)((unsigned int)seed >> 1); +} diff --git a/State.h b/State.h new file mode 100644 index 0000000..deeeee2 --- /dev/null +++ b/State.h @@ -0,0 +1,38 @@ + + +#ifndef TEXTADV_STATE_H +#define TEXTADV_STATE_H + + +#include "Room.h" +#include "GameObject.h" + +class State : public Serializable { + int currentRoomId; + // int: game object id + bool inventory_sorted; + std::list inventory; + int HP; +public: + static int seed; + explicit State(int room_id); + void goTo(Room* target); + void announceLoc() const; + Room* getCurrentRoom() const; + + int getHP() const; + void setHP(int HP); + std::list getInventory(); + void addToInventory(int object_id); + void removeFromInventory(int object_id); + bool IsInInventory(int object_id) const; + + static int NextRandom(); + + string ToString() const override; + + void FromString(const string &str) override; +}; + + +#endif //TEXTADV_STATE_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..3c985a2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,398 @@ + +#include +#include +#include +#include +#include +#include +#include "Room.h" +#include "wordwrap.h" +#include "State.h" +#include "strings.h" +#include +#include "FoodObject.h" +#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; + 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(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 , 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(); + } else { + currentState->setHP(currentState->getHP() - 1); + gameOver = gameOver && (currentState->getHP() <= 0); + } + + } +} + + +int main() { + initWordWrap(); + initRooms(); + initState(); + currentState->announceLoc(); + gameLoop(); + return 0; +} \ No newline at end of file diff --git a/strings.h b/strings.h new file mode 100644 index 0000000..efa2053 --- /dev/null +++ b/strings.h @@ -0,0 +1,36 @@ + + +#ifndef TEXTADV_STRINGS_H +#define TEXTADV_STRINGS_H + + +#include + +const std::string r1name = "Room 1"; +const std::string r1desc = "You are in room 1. It's really quite boring, but then, it's just for testing really. There's a passage to the north."; +const std::string r2name = "Blue Room"; +const std::string r2desc = "You are the blue room. You know because it's blue. That's about all though. There's a passage to the south."; +const std::string r3name = "Secret Chamber"; +const std::string r3desc = "You've discovered a secret chamber. It's filled with ancient artifacts and mysterious symbols."; + +const std::string r4name = "Library"; +const std::string r4desc = "You find yourself in a vast library, with shelves full of dusty old books."; + +const std::string badExit = "You can't go that way."; +const std::string badCommand = "I don't understand that."; + +const std::string itemInInventory = "You already have that."; +const std::string itemInRoom = "You can't drop that."; +const std::string itemNotFound = "You don't see that here."; +const std::string itemGot = "You pick up the "; +const std::string itemDropped = "You drop the "; +const std::string itemEaten = "You eat the "; + +const std::string loadGameSuccess = "Game loaded successfully."; +const std::string loadGameFail = "Failed to load game."; +const std::string saveGameSuccess = "Game saved successfully."; +const std::string saveGameFail = "Failed to save game."; + + + +#endif //TEXTADV_STRINGS_H diff --git a/wordwrap.cpp b/wordwrap.cpp new file mode 100644 index 0000000..6b6f206 --- /dev/null +++ b/wordwrap.cpp @@ -0,0 +1,81 @@ + + + +#include +// #include +#include +#include + + +/* Library routines to output text and "word wrap" so that words are not broken across lines. */ + +uint16_t consoleWidth = 80; +uint16_t currentConsoleOffset = 5; + +/** + * Set up the word wrap library. + */ +void initWordWrap() { + /* Find the width of the console window. */ + // CONSOLE_SCREEN_BUFFER_INFO csbi{}; + // GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),&csbi); + /* We subtract 2 because Windows doesn't allow for the right hand scroll bar when reporting + * width of command prompt windows. */ + // consoleWidth = static_cast(csbi.srWindow.Right - csbi.srWindow.Left - 2); + currentConsoleOffset = 0; +} + + +/** + * Output a string with word wrap. + * @param text Pointer to the string to output. + */ +void wrapOut(const std::string *text) { + size_t len = text->length(); // Length of the string + size_t position = 0; // How much of the string we've printed + std::ostream_iterator iout(std::cout); // Iterators for the string and display + std::string::const_iterator strInt = text->begin(); + const char *rawString = text->data(); + while (position < len) { + // How much space do we have left on the current console line? + size_t left = (consoleWidth - currentConsoleOffset); + if ((len - position) < left) { // Can we fit the whole string on this line? + std::cout << &rawString[position] << " "; // Yes, just do it. + currentConsoleOffset += len + 1; // Update our position on this line. + break; + } else { // We can't fit the whole string on this line. + size_t lastSpace = left; // This would be the last character we did fit on this line + // Move this pointer back until we find a space + while ((rawString[position + lastSpace] != ' ') && (lastSpace > 0)) { + lastSpace--; + } + // Print up to that space using the output iterator + std::copy(strInt + position, strInt + position + lastSpace, iout); + // Print an end-of-line, unless the string ended exactly at the end of the line in which case the + // cursor has automatically moved to the next line + if (lastSpace < left) { + std::cout << std::endl; + } + position += lastSpace + 1; // Mark how much of the string we've printed + currentConsoleOffset = 0; // Since we just created a new line, we're now at the start of it + } + } +} + +void wrapOut(const std::string& text) { + wrapOut(&text); +} + +/** + * Ends a paragraph of word wrapped output. + */ +void wrapEndPara() { + // If we aren't at the end of a line already, end the line + if (currentConsoleOffset != 0) { + std::cout << std::endl; + } + // And print a blank line + std::cout << std::endl; + // Since we just started a new line, we're now at the start of it + currentConsoleOffset = 0; +} diff --git a/wordwrap.h b/wordwrap.h new file mode 100644 index 0000000..9b4d741 --- /dev/null +++ b/wordwrap.h @@ -0,0 +1,14 @@ + + +#ifndef TEXTADV_WORDWRAP_H +#define TEXTADV_WORDWRAP_H + +#include + +void initWordWrap(); +void wrapOut(const std::string *text); +void wrapOut(const std::string& text); +void wrapEndPara(); + + +#endif //TEXTADV_WORDWRAP_H