init repo
This commit is contained in:
commit
ee8cc69a8b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
cmake-*
|
||||||
|
.idea/
|
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal 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
7
CMakeLists_1.txt
Normal 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
53
FoodObject.cpp
Normal 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
24
FoodObject.h
Normal 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
105
GameObject.cpp
Normal 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
63
GameObject.h
Normal 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
204
Room.cpp
Normal 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
125
Room.h
Normal 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
15
Serializable.cpp
Normal 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
228
Serializable.h
Normal 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
108
State.cpp
Normal 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
38
State.h
Normal 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
398
main.cpp
Normal 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
36
strings.h
Normal 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
81
wordwrap.cpp
Normal 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
14
wordwrap.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user