From ee8cc69a8b1e9ba93ca43e06a9ffff1f50b809aa Mon Sep 17 00:00:00 2001 From: tqcq <99722391+tqcq@users.noreply.github.com> Date: Thu, 16 Nov 2023 00:44:12 +0800 Subject: [PATCH] init repo --- .gitignore | 2 + CMakeLists.txt | 11 ++ CMakeLists_1.txt | 7 + FoodObject.cpp | 53 +++++++ FoodObject.h | 24 +++ GameObject.cpp | 105 +++++++++++++ GameObject.h | 63 ++++++++ Room.cpp | 204 ++++++++++++++++++++++++ Room.h | 125 +++++++++++++++ Serializable.cpp | 15 ++ Serializable.h | 228 +++++++++++++++++++++++++++ State.cpp | 108 +++++++++++++ State.h | 38 +++++ main.cpp | 398 +++++++++++++++++++++++++++++++++++++++++++++++ strings.h | 36 +++++ wordwrap.cpp | 81 ++++++++++ wordwrap.h | 14 ++ 17 files changed, 1512 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CMakeLists_1.txt create mode 100644 FoodObject.cpp create mode 100644 FoodObject.h create mode 100644 GameObject.cpp create mode 100644 GameObject.h create mode 100644 Room.cpp create mode 100644 Room.h create mode 100644 Serializable.cpp create mode 100644 Serializable.h create mode 100644 State.cpp create mode 100644 State.h create mode 100644 main.cpp create mode 100644 strings.h create mode 100644 wordwrap.cpp create mode 100644 wordwrap.h 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