首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++库存系统

C++库存系统
EN

Code Review用户
提问于 2018-05-16 19:54:17
回答 1查看 5K关注 0票数 10

最近,我在一个系统的项目上做了很多工作,这个系统代表了一个游戏中的库存和物品使用情况,其中一个重要的特性就是它应该在多个项目中被重用。我想我做得很好,尽可能少地保持所需的界面。我已经到了一个阶段,我认为这个项目已经完成了,但是我想得到关于我仍然可以改进代码的反馈。它真的很长,所以我不确定我是否应该把所有的东西都贴在这里。我将在这里张贴主目录头,其余的代码可以在github上找到。如果我需要复制这里的所有代码,请告诉我。

Inventory.h

代码语言:javascript
复制
#pragma once

#include "IItem.h"
#include "ItemDispatcher.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 



template
class Inventory
{
private:

    class Traits //class to simulate namespace inside class
    {
    public:

        /*HasUseMethod type trait*/

        template>
        struct HasUseMethodHelper : std::false_type 
        {
        };

        template
        struct HasUseMethodHelper<_Ty, std::void_t().use(std::declval&>()))>> : std::true_type
        {
        };

        template
        struct HasUseMethodT : HasUseMethodHelper<_Ty>::type
        {
        };

        template using HasUseMethod = typename HasUseMethodT<_Ty>::type;
        template static constexpr bool HasUseMethodV = HasUseMethod<_Ty>::value;

        /*HasEquippabmeMethod type trait*/

        template>
        struct HasEquippableMethodHelper : std::false_type
        {
        };

        template
        struct HasEquippableMethodHelper<_Ty, 
            std::void_t().equippable()), bool >)>> : std::true_type
        {
        };

        template
        struct HasEquippableMethodT : HasEquippableMethodHelper<_Ty>::type
        {
        };

        template using HasEquippableMethod = typename HasEquippableMethodT<_Ty>::type;
        template static constexpr bool HasEquippableMethodV = HasEquippableMethod<_Ty>::value;

        /*HasIsEquipped type trait*/

        template>
        struct HasIsEquippedMethodHelper : std::false_type
        {
        };

        template
        struct HasIsEquippedMethodHelper<_Ty, 
            std::void_t().is_equipped()), bool >)>> : std::true_type
        {
        };

        template
        struct HasIsEquippedMethodT : HasIsEquippedMethodHelper<_Ty>::type
        {
        };

        template using HasIsEquippedMethod = typename HasIsEquippedMethodT<_Ty>::type;
        template static constexpr bool HasIsEquippedMethodV = HasIsEquippedMethod<_Ty>::value;

        /*HasSetEquip type trait*/

        template>
        struct HasSetEquipMethodHelper : std::false_type
        {
        };

        template
        struct HasSetEquipMethodHelper<_Ty, 
            std::void_t().set_equip(std::declval())), void >)>> 
                : std::true_type
        {
        };

        template
        struct HasSetEquipMethodT : HasSetEquipMethodHelper<_Ty>::type
        {
        };

        template using HasSetEquipMethod = typename HasSetEquipMethodT<_Ty>::type;
        template static constexpr bool HasSetEquipMethodV = HasSetEquipMethod<_Ty>::value;

        /*HasUnequipMethod type trait*/

        template>
        struct HasUnequipMethodHelper : std::false_type
        {
        };

        template
        struct HasUnequipMethodHelper<_Ty, 
            std::void_t().unequip(std::declval())), void >)>> 
                : std::true_type
        {
        };

        template
        struct HasUnequipMethodT : HasUnequipMethodHelper<_Ty>::type
        {
        };

        template using HasUnequipMethod = typename HasUnequipMethodT<_Ty>::type;
        template static constexpr bool HasUnequipMethodV = HasUnequipMethod<_Ty>::value;

        /*HasReusableMethod type trait*/

        template>
        struct HasReusableMethodHelper : std::false_type
        {
        };

        template
        struct HasReusableMethodHelper<_Ty, 
            std::void_t().reusable()), bool >)>> 
                : std::true_type
        {
        };

        template
        struct HasReusableMethodT : HasReusableMethodHelper<_Ty>::type
        {
        };

        template using HasReusableMethod = typename HasReusableMethodT<_Ty>::type;
        template static constexpr bool HasReusableMethodV = HasReusableMethod<_Ty>::value;

        template>
        struct HasStackableMethodHelper : std::false_type
        {
        };

        template
        struct HasStackableMethodHelper<_Ty, 
            std::void_t().stackable()), bool>)>>
                : std::true_type
        {
        };

        template
        struct HasStackableMethodT : HasStackableMethodHelper<_Ty>::type
        {
        };

        template using HasStackableMethod = typename HasStackableMethodT<_Ty>::type;
        template static constexpr bool HasStackableMethodV = HasStackableMethod<_Ty>::value;

        template
        struct IsValidItemT
        {
            static constexpr bool value =
                HasEquippableMethodV<_Ty>
                && HasUseMethodV<_Ty>
                && HasIsEquippedMethodV<_Ty>
                && HasSetEquipMethodV<_Ty>
                && HasEquippableMethodV<_Ty>
                && HasReusableMethodV<_Ty>
                && HasStackableMethodV<_Ty>;
        };

        template using IsValidItem = typename IsValidItemT<_Ty>::type;
        template static constexpr bool IsValidItemV = IsValidItemT<_Ty>::value;
    };

public:
    static_assert(Traits::IsValidItemV, "Item type is invalid. It should provide methods listed in documentation");

    class Exception
    {
    private:
        std::string msg;
    public:
        explicit inline Exception(std::string_view error) : msg {error} {}
        inline std::string_view what() { return msg; }
    };

    using game_object_type = GameObjTy; 
    using item_type = ItemTy; 
    using item_pointer = std::unique_ptr;

    using game_object_pointer = game_object_type*;
    using inventory_type = std::unordered_map>;
    using iterator = typename inventory_type::iterator;
    using const_iterator = typename inventory_type::const_iterator;
    using size_type = typename inventory_type::size_type;

    explicit Inventory(game_object_pointer owner);
    Inventory(Inventory const& other) = delete;
    Inventory(Inventory&& other);

    Inventory& operator=(Inventory const& other) = delete;
    Inventory& operator=(Inventory&& other);

    inventory_type const& contents() const;
    inventory_type& contents();

    /*Adds a new item, stacked on top of an item with the same ID. The id parameter will be used to access the item*/
    template
    void addItem(std::string_view id);

    /*constructs a new item and adds it to the inventory. The id parameter will be used to access the item*/
    template
    void emplaceItem(std::string_view id, Args... args);

    void useItem(std::string_view name, game_object_pointer target = nullptr);

    /*The iterators invalidate when a new Item is added to the inventory*/
    iterator getItem(std::string_view name);
    /*The iterators invalidate when a new Item is added to the inventory*/
    const_iterator getItem(std::string_view name) const;

    void removeItem(std::string_view name);

    iterator begin();
    iterator end();

    const_iterator cbegin() const;
    const_iterator cend() const;

    size_type max_size() const;
    size_type size() const;

    /*Merges inventory A with this inventory. Leaves other empty, unless this inventory is full, in which case the leftover
      elements will be deleted*/ //#TODO: leftover elements are left in old inventory?
    template
    void merge(Inventory& other);

    template
    bool merge_fits(Inventory const& other);

    /*Transfers item with name parameter into the inventory specified in destination, unless destination does not have enough
     *space left*/
    template
    void transfer(Inventory& destination, std::string_view name);

    bool empty() const;
    bool full() const;

    void setOwner(game_object_pointer owner);
    game_object_pointer getOwner() const;

    void clear();
    unsigned int getItemCount(std::string_view id) const;

private:
    size_type m_size = 0;

    inventory_type m_items { MAX_SIZE };
    game_object_pointer m_owner { nullptr };

    //these functions are private so you cannot accidentally pass an invalid iterator to one of these, causing undefined behavior

    void useItem(iterator pos, game_object_pointer target = nullptr); 
    void removeItem(iterator pos);

    inline Exception InventoryFullException() const { return Exception {"Inventory is full"}; }
    inline Exception InvalidItemTypeException() const { return Exception {"Item type must be derived from Inventory::ItemTy, which defaults to IItem"}; }
    inline Exception InvalidItemException() const { return Exception { "Invalid item name" }; }
    inline Exception InvalidStackException() const { return Exception {"Tried to stack a non-stackable item"}; }
    inline Exception InvalidIDException() const { return Exception {"ID not found in inventory"}; }
};

template
Inventory::Inventory(game_object_pointer owner) : m_owner(owner)
{

}

template
Inventory::Inventory(Inventory&& other) : m_owner(std::move(other.m_owner)), m_items(std::move(other.m_items)), m_size(other.m_size)
{
    other.m_owner = nullptr;
    other.m_items = inventory_type {};
    other.m_size = 0;
}

template
Inventory& Inventory::operator=(Inventory&& other)
{
    // #WARNING: Self assignment check is missing
    m_owner = other.m_owner;
    m_items = std::move(other.m_items);
    m_size = other.m_size;

    other.m_owner = nullptr;
    other.m_items = inventory_type {};
    other.m_size = 0;

    return *this;
}

template
typename Inventory::inventory_type const& Inventory::contents() const
{
    return m_items;
}

template
typename Inventory::inventory_type& Inventory::contents()
{
    return m_items;
}

template
typename Inventory::iterator Inventory::begin() 
{ 
    return m_items.begin(); 
}

template
typename Inventory::iterator Inventory::end()
{
    return m_items.end();
}

template
typename Inventory::const_iterator Inventory::cbegin() const 
{ 
    return m_items.cbegin(); 
}
template
typename Inventory::const_iterator Inventory::cend() const 
{ 
    return m_items.cend(); 
}

template
typename Inventory::iterator Inventory::getItem(std::string_view name)
{
    return m_items.find(name.data());
}

template
typename Inventory::const_iterator Inventory::getItem(std::string_view name) const
{
    return m_items.find(name.data());
}

template
void Inventory::useItem(std::string_view name, game_object_pointer target)
{
    useItem(getItem(name), target);
}

template
template
void Inventory::addItem(std::string_view id)
{
    if constexpr (!std::is_base_of_v)
        throw InvalidItemTypeException();

    if (size() >= MAX_SIZE)
    {
        throw InventoryFullException();
    }

    if (m_items.find(id.data()) != m_items.end()) //if we already own this item, increment the count
    {
        if (!m_items[id.data()].first->stackable())
            throw InvalidStackException();
        m_items[id.data()].second += 1; //increment count
        m_size += 1;
    }
    else
    {
        throw InvalidIDException();
    }
}

template
template
void Inventory::emplaceItem(std::string_view id, Args... args)
{
    if constexpr (!std::is_base_of_v)
        throw InvalidItemTypeException();

    if (size() >= MAX_SIZE)
    {
        throw InventoryFullException();
    }

    m_items[id.data()] = std::make_pair(std::make_unique(std::forward(args)...), 1);
    m_size += 1;
}

template
void Inventory::useItem(iterator pos, game_object_pointer target)
{
    if (pos == m_items.end()) throw InvalidItemException();

    //use the item

    ItemDispatcher dispatcher { target };

    auto& it = *pos;
    auto& itemPair = it.second;
    auto& item = itemPair.first;

    if (item->equippable())
    {
        dispatcher.setTarget(m_owner);
        if (!item->is_equipped())
        {
            item->set_equip(true);
            item->use(dispatcher);
        }
        else
        {
            item->set_equip(false);
            item->use(dispatcher);
        }
        return;
    }
    else
    {
        dispatcher.setTarget(target);
        item->use(dispatcher); //dispatcher.target == target, see construction above
    }

    if (!item->reusable())
    {
        if (!item->stackable())
            removeItem(pos);
        else
        {
            if (itemPair.second > 1) itemPair.second -= 1; //decrement count if we have more than 1
            else removeItem(pos);
        }
        m_size -= 1;
    }
}

template
void Inventory::removeItem(iterator pos)
{
    m_items.erase(pos);
}

template
void Inventory::removeItem(std::string_view name)
{
    removeItem(getItem(name));
}

template
void Inventory::setOwner(game_object_pointer owner)
{
    m_owner = owner;
}

template
typename Inventory::game_object_pointer Inventory::getOwner() const
{
    return m_owner;
}

template
typename Inventory::size_type Inventory::max_size() const
{
    return MAX_SIZE;
}

template
typename Inventory::size_type Inventory::size() const
{
    return m_size;
}

template
template
void Inventory::merge(Inventory& other)
{
    if (!merge_fits(other))
        throw InventoryFullException();

    for (auto& it = other.begin(); it != other.end(); std::advance(it, 1))
    {

        this->m_items[it->first] = std::move(it->second);
    }
    other.clear();
}

template
void Inventory::clear()
{
    m_size = 0;
    m_items.clear();
}

template
template
bool Inventory::merge_fits(Inventory const& other)
{
    return !(full() || other.size() + this->size() >= max_size());
}

template
template
void Inventory::transfer(Inventory& destination, std::string_view name)
{   
    if (destination.full())
        return;

    auto& it = getItem(name);
    auto& item = (*it).second;

    destination.contents()[name.data()] = std::move(item);

    m_items.erase(it);
    m_size -= 1;
}

template
bool Inventory::empty() const
{
    return size() <= 0;
}

template
bool Inventory::full() const
{
    return max_size() <= size();
}

template
unsigned int Inventory::getItemCount(std::string_view id) const
{
    if (m_items.find(id.data()) == m_items.end()) throw InvalidItemException();
    return m_items.at(id.data()).second;
}

我还将添加一个main.cpp测试文件,以演示如何使用它。

main.cpp

代码语言:javascript
复制
#include "DamagePotion.h"
#include "Inventory.h"
#include "HealPotion.h"
#include "Sword.h"
#include "ItemUtil.h"

std::ostream& operator<<(std::ostream& out, ItemID const& id)
{
    if (id == ItemID::DAMAGE_POTION)
    {
        out << "ID_DAMAGE_POTION";
    }
    else if (id == ItemID::DEFAULT_ITEM) out << "ID_DEFAULT_ITEM";
    else if (id == ItemID::HEAL_POTION) out << "ID_HEAL_POTION";
    else if (id == ItemID::SWORD) out << "ID_SWORD";
    return out;
}

//Replace temp::GameObject class with the GameObject class used by your game

class Player : public temp::GameObject
{
public:
    Player() : temp::GameObject(200)
    {
        try
        {
            m_inventory.emplaceItem("Damage Potion I", 50);
            m_inventory.emplaceItem("Heal Potion I", 70);
            m_inventory.emplaceItem("Sword I", 20);

            std::cout << "Inventory contents after adding base items:\n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            m_inventory.useItem("Damage Potion I", this);
            m_inventory.useItem("Heal Potion I", this);
            m_inventory.useItem("Sword I");

            std::cout << "Inventory contents after using base items:\n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            m_inventory.useItem("Sword I"); // will unequip Sword I

            std::cout << "Inventory contents after unequipping Sword I:\n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            chest.emplaceItem("CDmgPot", 100);
            chest.emplaceItem("CHealPot", 200);

            std::cout << "Chest contents after adding base items:\n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            m_inventory.merge(chest);

            std::cout << "Chest contents after merging with inventory:\n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";
            std::cout << "Inventory contents after merging with chest:\n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            chest.emplaceItem("CSword", 50);

            std::cout << "Chest contents after adding CSword:\n";
            for (auto const& it : chest.contents()) std::cout << it.second.first ->id() << "\n";
            std::cout << "\n";

            chest.transfer(m_inventory, "CSword");

            std::cout << "Inventory contents after transferring CSword from chest:\n";
            for (auto const& it : m_inventory.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            chest.emplaceItem("CSword", 20);

            std::cout << "Chest contents after adding a CSword:\n";
            for (auto const& it : chest.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";

            chest.removeItem("CSword");

            std::cout << "Chest contents after removing a CSword:\n";
            for (auto const& it :chest.contents()) std::cout << it.second.first->id() << "\n";
            std::cout << "\n";
        }
        catch (std::runtime_error e)
        {
            std::cerr << e.what();
        }
    }

private:
    Inventory<200> m_inventory { this };

    Inventory<5> chest { this };
};


struct InvalidItem
{

};

struct InvalidEquippable
{
    void equippable()
    {

    }
};

class TestClass : public temp::GameObject
{
public:
    TestClass() : temp::GameObject(50)
    {
        try
        {

            Inventory<100> inv { this };

            inv.emplaceItem::type>("P", 20);
            inv.addItem("P");

            std::cout << ItemName(ItemID::HEAL_POTION) << ' ' << ItemId() << '\n';

            std::cout << inv.getItemCount("P") << '\n';

            inv.useItem("P", this);
            inv.useItem("P", this);

            std::cout << getHealth() << '\n';

            std::cout << inv.getItemCount("P") << '\n';
        }
        catch (Inventory<100>::Exception e)
        {
            std::cout << e.what() << "\n";
        }
    }
};



int main()
{

    Player p;


//  IItem* base_ptr;

//  Inventory<200> {nullptr};

    //Fails to compile due to traits
//  Inventory<100, temp::GameObject, InvalidItem> {nullptr};
//  Inventory<100, temp::GameObject, InvalidEquippable> {nullptr};

//  base_ptr = new DamagePotion(20);

//  temp::GameObject target { 100 };
/*
    std::cout << "Using Item: \n";
    std::cout << "Name:\t" << base_ptr->name() << "\n";
    std::cout << "ID:\t" << base_ptr->id() << "\n";
    base_ptr->use(&target);
*/


    std::cin.get();
}

注意:我刚读到一篇元文章,上面说它不允许发布GitHub链接。我不知道在这种情况下我应该做什么,因为我主要是希望库存头审查。github中的文件只适用于真正需要更多信息的时候。

编辑:根据评论中的要求,以下是一些进一步的解释:

架构

当我创建这个项目时,考虑到其他项目的可移植性,因此作为一种库,该体系结构设计得尽可能简单。所有项都将从基类IItem类或任何其他支持库存::特征类中的函数的类继承。库存负责管理每个项目的资源,并创建它们。因此,当添加您调用的项时

代码语言:javascript
复制
inventory.emplaceItem("Potion", 50); //the 50 is passed to SomePotion's constructor

MAX_SIZE

MAX_SIZE是一个模板参数,因为在一开始就有一个不吉利的设计选择。因为我不想为项目做大量的分配,所以我想要固定大小。这可能是完全不相关的,但现在令人讨厌的删除。

Item使用率

由于清单负责使用项目(通过调用useItem("name", target_ptr);),所以我需要某种方法。我选择了访问者模式,我用ItemDispatcher类实现了这个模式。

Data结构

这些项目存储在一个std::unordered_map, unsigned>>;中,我将详细分析我为什么选择这样做。首先,我希望能以某种方式获取这些物品。迭代器很笨拙,因为当一个项目被添加到库存中时,迭代器就会失效。我仍然保留了begin()和end()函数,以便在循环中迭代库存。因此,我选择了一个未完成的映射,其中包含了字符串索引,这样您就可以为每个项目提供自己的字符串ID。这些项作为一对存储,因为我希望能够在可能的情况下堆叠项目(Item.stackable() == true)。

可重用性

关于多个项目中的可重用性。我的意思是,如果我,在某一时刻决定制作一个RPG游戏,而角色需要一个清单,我可以使用这个精确的库存类而不作任何修改。如果我以后想要制作另一个完全不同的游戏,它也具有库存,或者其他存储物品的方式,我可以再次使用它。

技术

属性类用作SFINAE类型特征的包装器,用于检测最终的自定义项类型是否支持所有必需的函数,例如:

代码语言:javascript
复制
Inventory<200, MyCoolGameObject, MyCoolItem> inventory;

特质是一门课因为我做不到

代码语言:javascript
复制
class X
{
    namespace Y
    {

    };
};
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-05-28 00:22:58

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/194561

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档