diff --git a/tests/testDispenser.cpp b/tests/testDispenser.cpp new file mode 100644 index 0000000..5ecc3ff --- /dev/null +++ b/tests/testDispenser.cpp @@ -0,0 +1,128 @@ +#include "testHarness.h" +#include "vf/Dispenser.h" + +using namespace vf; + +VF_TEST(dispenserFiresOnlyOnRisingEdge) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::Stone, 5); + store.ensure(pos + Vec3i{1, 0, 0}, dispenserSlots); // container in front + + DispenserState state; + CHECK(tickDropper(world, pos, state, store, 0).action == DispenseAction::None); + CHECK(tickDropper(world, pos, state, store, 15).action == DispenseAction::InsertedToContainer); + CHECK(tickDropper(world, pos, state, store, 15).action == DispenseAction::None); + CHECK(tickDropper(world, pos, state, store, 0).action == DispenseAction::None); + CHECK(tickDropper(world, pos, state, store, 15).action == DispenseAction::InsertedToContainer); + + CHECK_EQ(store.at(pos + Vec3i{1, 0, 0})->totalItems(), 2); + CHECK_EQ(store.at(pos)->totalItems(), 3); +} + +VF_TEST(dispenserPlacesBlockItemIntoAirInFront) { + World world; + Vec3i pos{5, 5, 5}; + Vec3i front{6, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::Stone, 1); + + DispenserState state; + DispenseResult res = tickDispenser(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::PlacedBlock); + CHECK(world.blockAt(front.x, front.y, front.z) == Block::Stone); + CHECK_EQ(store.at(pos)->totalItems(), 0); +} + +VF_TEST(dispenserEjectsNonBlockItemIntoFrontContainer) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::IronIngot, 2); + store.ensure(pos + Vec3i{1, 0, 0}, dispenserSlots); + + DispenserState state; + DispenseResult res = tickDispenser(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::InsertedToContainer); + CHECK_EQ(store.at(pos + Vec3i{1, 0, 0})->totalItems(), 1); + CHECK_EQ(store.at(pos)->totalItems(), 1); +} + +VF_TEST(dispenserEjectsToWorldWhenItCannotPlaceOrDeposit) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::IronIngot, 1); + + DispenserState state; + DispenseResult res = tickDispenser(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::EjectedToWorld); + CHECK(static_cast(res.item.item) == static_cast(ItemId::IronIngot)); + CHECK(world.blockAt(6, 5, 5) == Block::Air); +} + +VF_TEST(dispenserThatCannotPlaceIntoOccupiedCellEjectsInstead) { + World world; + Vec3i pos{5, 5, 5}; + world.setBlock(6, 5, 5, Block::Stone); // front already occupied + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::Dirt, 1); + + DispenserState state; + DispenseResult res = tickDispenser(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::EjectedToWorld); + CHECK(world.blockAt(6, 5, 5) == Block::Stone); // unchanged +} + +VF_TEST(dropperNeverPlacesBlocksEvenForBlockItems) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::Stone, 1); + + DispenserState state; + DispenseResult res = tickDropper(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::EjectedToWorld); + CHECK(world.blockAt(6, 5, 5) == Block::Air); +} + +VF_TEST(dropperDepositsBlockItemIntoFrontContainer) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots).insert(ItemId::Stone, 1); + store.ensure(pos + Vec3i{1, 0, 0}, dispenserSlots); + + DispenserState state; + DispenseResult res = tickDropper(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::InsertedToContainer); + CHECK_EQ(store.at(pos + Vec3i{1, 0, 0})->totalItems(), 1); +} + +VF_TEST(emptyDispenserDoesNothingOnEdge) { + World world; + Vec3i pos{5, 5, 5}; + ContainerStore store; + store.ensure(pos, dispenserSlots); + + DispenserState state; + DispenseResult res = tickDispenser(world, pos, state, store, 15); + CHECK(res.action == DispenseAction::None); +} + +VF_TEST(slotSelectionIsDeterministicAndAlwaysHitsAFilledSlot) { + Container inventory(9); + inventory.setSlot(0, ItemSlot{ItemId::Stone, 10, 0}); + inventory.setSlot(3, ItemSlot{ItemId::Dirt, 10, 0}); + inventory.setSlot(7, ItemSlot{ItemId::Sand, 10, 0}); + + std::uint32_t rngA = 0x1234abcdu; + std::uint32_t rngB = 0x1234abcdu; + for (int i = 0; i < 8; ++i) { + int a = chooseNonEmptySlot(inventory, rngA); + int b = chooseNonEmptySlot(inventory, rngB); + CHECK_EQ(a, b); + CHECK(!inventory.slot(a).empty()); + } +}