#include "testHarness.h" #include "vf/SlotAction.h" #include #include #include using namespace vf; namespace { const CraftingRegistry& sharedRegistry() { static CraftingRegistry registry; return registry; } struct Session { ContainerMenu menu; CursorStack cursor; DragState drag; explicit Session(MenuKind kind) : menu(kind, &sharedRegistry()) {} SlotActionResult click(ClickKind kind, int slotIndex) { return applySlotAction(menu, cursor, drag, clickAt(kind, slotIndex)); } SlotActionResult hotbar(int slotIndex, int button) { return applySlotAction(menu, cursor, drag, hotbarSwapAt(slotIndex, button)); } }; // The seven items the random walk shuffles around. Tools are in the mix so the // walk also has stacks that refuse to merge. const std::array walkItems = {ItemId::Stone, ItemId::Planks, ItemId::Sand, ItemId::CoalLump, ItemId::Apple, ItemId::IronPickaxe, ItemId::RawBeef}; struct Ledger { std::array counts{}; void snapshot(const ContainerMenu& menu, const CursorStack& cursor) { for (std::size_t i = 0; i < walkItems.size(); ++i) { counts[i] = totalAcrossMenuAndCursor(menu, cursor, walkItems[i]); } } }; void seedMenu(ContainerMenu& menu, uint32_t& rng) { for (int i = 0; i < menu.slotCount(); ++i) { if (menu.slot(i).takeOnly) continue; if (stackRandom(rng) % 3u == 0u) continue; ItemId item = walkItems[stackRandom(rng) % walkItems.size()]; MetaStack stack = makeStack(item, 1 + static_cast(stackRandom(rng) % 40u)); if (!menu.canPlace(i, stack)) continue; menu.slot(i).cell.insert(stack); } } SlotAction randomAction(const ContainerMenu& menu, uint32_t& rng) { ClickKind kind = static_cast(stackRandom(rng) % static_cast(ClickKind::Count)); int slotIndex = static_cast(stackRandom(rng) % static_cast(menu.slotCount())); // Every eighth action aims outside the window, which is the only way to // reach the throw-away path with a full cursor. if (stackRandom(rng) % 8u == 0u) slotIndex = -1; if (kind == ClickKind::HotbarSwap) { return hotbarSwapAt(slotIndex, static_cast(stackRandom(rng) % 9u)); } return clickAt(kind, slotIndex); } struct WalkCoverage { int applied = 0; int dropped = 0; int distinctKindsApplied = 0; }; WalkCoverage runConservationWalk(MenuKind kind, uint32_t seed, int steps) { Session session(kind); uint32_t rng = seed; seedMenu(session.menu, rng); Ledger before; before.snapshot(session.menu, session.cursor); std::array thrown{}; std::array(ClickKind::Count)> perKind{}; WalkCoverage coverage; for (int step = 0; step < steps; ++step) { SlotAction action = randomAction(session.menu, rng); SlotActionResult outcome = applySlotAction(session.menu, session.cursor, session.drag, action); if (outcome.applied) { ++coverage.applied; ++perKind[static_cast(action.kind)]; } coverage.dropped += outcome.dropped; CHECK_EQ(outcome.created, 0); CHECK_EQ(outcome.dropped, static_cast([&] { int sum = 0; for (const MetaStack& lost : outcome.droppedStacks) sum += lost.count; return sum; }())); for (const MetaStack& lost : outcome.droppedStacks) { for (std::size_t i = 0; i < walkItems.size(); ++i) { if (lost.item == walkItems[i]) thrown[i] += lost.count; } } } Ledger after; after.snapshot(session.menu, session.cursor); for (std::size_t i = 0; i < walkItems.size(); ++i) { CHECK_EQ(after.counts[i] + thrown[i], before.counts[i]); } for (int hits : perKind) { if (hits > 0) ++coverage.distinctKindsApplied; } return coverage; } } // namespace VF_TEST(everyClickKindHasAName) { for (int i = 0; i < static_cast(ClickKind::Count); ++i) { CHECK(std::string(clickKindName(static_cast(i))) != std::string("unknown")); } } VF_TEST(aLeftClickPicksUpTheWholeStackAndPutsItDownAgain) { Session session(MenuKind::Chest); session.menu.slot(0).cell.stack = makeStack(ItemId::Stone, 37); SlotActionResult pickup = session.click(ClickKind::Left, 0); CHECK(pickup.applied); CHECK_EQ(pickup.moved, 37); CHECK(pickup.cursorChanged); CHECK_EQ(session.cursor.held.count, 37); CHECK(session.menu.stackAt(0).empty()); SlotActionResult drop = session.click(ClickKind::Left, 4); CHECK(drop.applied); CHECK_EQ(drop.moved, 37); CHECK(session.cursor.empty()); CHECK_EQ(session.menu.stackAt(4).count, 37); CHECK(!session.click(ClickKind::Left, 9).applied); CHECK(!session.click(ClickKind::Left, 999).applied); } VF_TEST(aLeftClickMergesIntoAMatchingStackAndSwapsWithAForeignOne) { Session session(MenuKind::Chest); session.menu.slot(0).cell.stack = makeStack(ItemId::Sand, 50); session.menu.slot(1).cell.stack = makeStack(ItemId::Sand, 60); session.menu.slot(2).cell.stack = makeStack(ItemId::Planks, 8);