init repo

This commit is contained in:
tqcq 2023-11-16 00:44:12 +08:00
commit ee8cc69a8b
17 changed files with 1512 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cmake-*
.idea/

11
CMakeLists.txt Normal file
View File

@ -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})

7
CMakeLists_1.txt Normal file
View File

@ -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})

53
FoodObject.cpp Normal file
View File

@ -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) + ")";
}

24
FoodObject.h Normal file
View File

@ -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

105
GameObject.cpp Normal file
View File

@ -0,0 +1,105 @@
//
// Created by tqcq on 2023/11/14.
//
#include "GameObject.h"
#include "FoodObject.h"
std::map<int, GameObject *> 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;
}

63
GameObject.h Normal file
View File

@ -0,0 +1,63 @@
//
// Created by tqcq on 2023/11/14.
//
#ifndef TEXTADV_GAMEOBJECT_H
#define TEXTADV_GAMEOBJECT_H
#include "Serializable.h"
#include <string>
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<int, GameObject*> 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

204
Room.cpp Normal file
View File

@ -0,0 +1,204 @@
#include "Room.h"
#include "wordwrap.h"
#include <algorithm>
/**
* Stores a static list of all rooms.
*/
std::map<int, Room *> 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();
}

125
Room.h Normal file
View File

@ -0,0 +1,125 @@
#ifndef TEXTADV_ROOM_H
#define TEXTADV_ROOM_H
#include <string>
#include <forward_list>
#include <list>
#include <map>
#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<int> 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<Room*> rooms;
static int room_id_counter;
/**
* Map storing all rooms that have been registered via addRoom().
*/
static std::map<int, Room *> 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

15
Serializable.cpp Normal file
View File

@ -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);
}

228
Serializable.h Normal file
View File

@ -0,0 +1,228 @@
//
// Created by tqcq on 2023/11/14.
//
#ifndef TEXTADV_SERIALIZABLE_H
#define TEXTADV_SERIALIZABLE_H
#include <map>
#include <vector>
#include <string>
#include <sstream>
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<Packet> data;
};
#endif //TEXTADV_SERIALIZABLE_H

108
State.cpp Normal file
View File

@ -0,0 +1,108 @@
#include "State.h"
#include <algorithm>
#include <ctime>
/**
* 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<int> 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);
}

38
State.h Normal file
View File

@ -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<int> 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<int> 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

398
main.cpp Normal file
View File

@ -0,0 +1,398 @@
#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>
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;
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();
} else {
currentState->setHP(currentState->getHP() - 1);
gameOver = gameOver && (currentState->getHP() <= 0);
}
}
}
int main() {
initWordWrap();
initRooms();
initState();
currentState->announceLoc();
gameLoop();
return 0;
}

36
strings.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef TEXTADV_STRINGS_H
#define TEXTADV_STRINGS_H
#include <string>
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

81
wordwrap.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <cstdint>
// #include <afxres.h>
#include <iterator>
#include <iostream>
/* 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<uint16_t>(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<char> 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;
}

14
wordwrap.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef TEXTADV_WORDWRAP_H
#define TEXTADV_WORDWRAP_H
#include <string>
void initWordWrap();
void wrapOut(const std::string *text);
void wrapOut(const std::string& text);
void wrapEndPara();
#endif //TEXTADV_WORDWRAP_H