src/Game.cc | src/Game.cc | ||||
src/gfxutil.cc | src/gfxutil.cc | ||||
src/Item.cc | src/Item.cc | ||||
src/ItemStack.cc | |||||
src/Mod.cc | src/Mod.cc | ||||
src/OS.cc | src/OS.cc | ||||
src/Resource.cc | src/Resource.cc |
struct Builder { | struct Builder { | ||||
std::string name; | std::string name; | ||||
std::string image; | std::string image; | ||||
int max_stack = 64; | |||||
}; | }; | ||||
Item(const ResourceManager &resources, const Builder &builder): | Item(const ResourceManager &resources, const Builder &builder): | ||||
name_(builder.name), image_(resources.getImage(builder.image)) {} | |||||
name_(builder.name), image_(resources.getImage(builder.image)), | |||||
max_stack_(builder.max_stack) {} | |||||
const std::string name_; | const std::string name_; | ||||
const ImageResource &image_; | const ImageResource &image_; | ||||
const int max_stack_; | |||||
static std::unique_ptr<Item> createInvalid(Context &ctx); | static std::unique_ptr<Item> createInvalid(Context &ctx); | ||||
// For testing, we want to be able to create Items without an actual ImageResource. | |||||
// Tests can create a MockItem class which inherits from Item and uses this ctor, | |||||
// as long as the test never does anything which tries to follow the image_ member. | |||||
// Eventually, this should become unnecessary, because we don't need to require | |||||
// a complete ImageResource for a headless server, but for now, this will suffice. | |||||
protected: | |||||
Item(const Builder &builder): | |||||
name_(builder.name), image_(*(ImageResource *)this), | |||||
max_stack_(builder.max_stack) {} | |||||
}; | }; | ||||
} | } |
#pragma once | #pragma once | ||||
#include <optional> | |||||
#include "log.h" | |||||
namespace Swan { | namespace Swan { | ||||
class Item; | class Item; | ||||
class ItemStack { | class ItemStack { | ||||
public: | public: | ||||
static constexpr int MAX_COUNT = 64; | |||||
ItemStack(Item *item, int count = 0): item_(item), count_(count) { | ItemStack(Item *item, int count = 0): item_(item), count_(count) { | ||||
// We don't want a "partially empty" state. | // We don't want a "partially empty" state. | ||||
bool empty() { return item_ == nullptr; } | bool empty() { return item_ == nullptr; } | ||||
// Insert as much of 'st' as possible, returning the leftovers | // Insert as much of 'st' as possible, returning the leftovers | ||||
ItemStack insert(ItemStack st) { | |||||
// If this is an empty item stack, just copy over st | |||||
if (empty()) { | |||||
item_ = st.item_; | |||||
count_ = st.count_; | |||||
st.item_ = nullptr; | |||||
st.count_ = 0; | |||||
return st; | |||||
} | |||||
// If st is a stack of a different kind of item, we don't want it | |||||
if (st.item_ != item_) | |||||
return st; | |||||
// Merge | |||||
count_ += st.count_; | |||||
if (count_ > MAX_COUNT) { | |||||
st.count_ = count_ - MAX_COUNT; | |||||
count_ = MAX_COUNT; | |||||
} else { | |||||
st.count_ = 0; | |||||
st.item_ = nullptr; | |||||
} | |||||
return st; | |||||
} | |||||
ItemStack insert(ItemStack st); | |||||
private: | private: | ||||
Item *item_; | Item *item_; |
#include "ItemStack.h" | |||||
#include "Item.h" | |||||
namespace Swan { | |||||
ItemStack ItemStack::insert(ItemStack st) { | |||||
// If this is an empty item stack, just copy over st | |||||
if (empty()) { | |||||
item_ = st.item_; | |||||
count_ = st.count_; | |||||
st.item_ = nullptr; | |||||
st.count_ = 0; | |||||
return st; | |||||
} | |||||
// If st is a stack of a different kind of item, we don't want it | |||||
if (st.item_ != item_) | |||||
return st; | |||||
// Merge | |||||
count_ += st.count_; | |||||
if (count_ > item_->max_stack_) { | |||||
st.count_ = count_ - item_->max_stack_; | |||||
count_ = item_->max_stack_; | |||||
} else { | |||||
st.count_ = 0; | |||||
st.item_ = nullptr; | |||||
} | |||||
return st; | |||||
} | |||||
} |
#include "ItemStack.h" | #include "ItemStack.h" | ||||
#include "Item.h" | |||||
#include "lib/test.h" | #include "lib/test.h" | ||||
// Most of these tests need two ItemStacks. | |||||
// ItemStack requires an Item pointer, and only ever looks at the pointer. | |||||
// Therefore, we'll just give the ItemStacks pointers to ints. | |||||
static int itint1, itint2; | |||||
static Swan::Item *item1 = (Swan::Item *)&itint1; | |||||
static Swan::Item *item2 = (Swan::Item *)&itint2; | |||||
class MockItem: public Swan::Item { | |||||
public: | |||||
MockItem(const Builder &builder): Item(builder) {} | |||||
}; | |||||
test("Basic insert") { | test("Basic insert") { | ||||
Swan::ItemStack s1(item1, 0); | |||||
Swan::ItemStack s2(item1, 10); | |||||
MockItem item1({ .name = "item1", .image = "no" }); | |||||
Swan::ItemStack s1(&item1, 0); | |||||
Swan::ItemStack s2(&item1, 10); | |||||
s2 = s1.insert(s2); | s2 = s1.insert(s2); | ||||
expecteq(s1.count(), 10); | expecteq(s1.count(), 10); | ||||
} | } | ||||
test("Insert rejects different items") { | test("Insert rejects different items") { | ||||
Swan::ItemStack s1(item1, 5); | |||||
Swan::ItemStack s2(item2, 10); | |||||
MockItem item1({ .name = "item1", .image = "no" }); | |||||
MockItem item2({ .name = "itemm2", .image = "no" }); | |||||
Swan::ItemStack s1(&item1, 5); | |||||
Swan::ItemStack s2(&item2, 10); | |||||
Swan::ItemStack ret = s1.insert(s2); | Swan::ItemStack ret = s1.insert(s2); | ||||
expecteq(s1.count(), 5); | expecteq(s1.count(), 5); | ||||
} | } | ||||
test("Insert never overflows") { | test("Insert never overflows") { | ||||
Swan::ItemStack s1(item1, 40); | |||||
Swan::ItemStack s2(item1, 40); | |||||
MockItem item1({ .name = "item1", .image = "no" }); | |||||
Swan::ItemStack s1(&item1, 40); | |||||
Swan::ItemStack s2(&item1, 40); | |||||
s2 = s1.insert(s2); | s2 = s1.insert(s2); | ||||
expecteq(s1.count(), Swan::ItemStack::MAX_COUNT); | |||||
expecteq(s2.count(), 80 - Swan::ItemStack::MAX_COUNT); | |||||
expecteq(s1.count(), item1.max_stack_); | |||||
expecteq(s2.count(), 80 - item1.max_stack_); | |||||
} | } | ||||
test("Insert respects max_stack_") { | |||||
MockItem item1({ .name = "item1", .image = "no", .max_stack = 20 }); | |||||
Swan::ItemStack s1(&item1, 15); | |||||
Swan::ItemStack s2(&item1, 19); | |||||
s2 = s1.insert(s2); | |||||
expecteq(s1.count(), 20); | |||||
expecteq(s2.count(), 14); | |||||
} | |||||
test("When insert empties an ItemStack, it should have its item nulled out") { | test("When insert empties an ItemStack, it should have its item nulled out") { | ||||
Swan::ItemStack s1(item1, 10); | |||||
Swan::ItemStack s2(item1, 10); | |||||
MockItem item1({ .name = "item1", .image = "no" }); | |||||
MockItem item2({ .name = "itemm2", .image = "no" }); | |||||
Swan::ItemStack s1(&item1, 10); | |||||
Swan::ItemStack s2(&item1, 10); | |||||
s2 = s1.insert(s2); | s2 = s1.insert(s2); | ||||
expecteq(s1.count(), 20); | expecteq(s1.count(), 20); | ||||
} | } | ||||
test("Insert on an empty item stack") { | test("Insert on an empty item stack") { | ||||
MockItem item1({ .name = "item1", .image = "no" }); | |||||
Swan::ItemStack s1(nullptr, 0); | Swan::ItemStack s1(nullptr, 0); | ||||
Swan::ItemStack s2(item1, 10); | |||||
Swan::ItemStack s2(&item1, 10); | |||||
s2 = s1.insert(s2); | s2 = s1.insert(s2); | ||||
expecteq(s1.count(), 10); | expecteq(s1.count(), 10); | ||||
expecteq(s1.item(), item1); | |||||
expecteq(s1.item(), &item1); | |||||
expecteq(s2.count(), 0); | expecteq(s2.count(), 0); | ||||
expect(s2.empty()); | expect(s2.empty()); | ||||
} | } |