#include #include "testHarness.h" #include "vf/Enchant.h" using namespace vf; namespace { // Places `count` shelves on the ring the table reads, filling the lower layer // before the upper one. void surroundWithShelves(World& world, const Vec3i& table, int count) { int placed = 0; for (int dy = 0; dy <= 1 && placed < count; ++dy) { for (int dx = -2; dx <= 2 && placed < count; ++dx) { for (int dz = -2; dz <= 2 && placed < count; ++dz) { if (std::max(dx < 0 ? -dx : dx, dz < 0 ? -dz : dz) != 2) continue; world.setBlock(table.x + dx, table.y + dy, table.z + dz, Block::Bookshelf); ++placed; } } } } bool sameOffer(const EnchantOffer& a, const EnchantOffer& b) { if (a.requiredLevels != b.requiredLevels || a.lapisCost != b.lapisCost) return false; if (a.enchants.entries.size() != b.enchants.entries.size()) return false; for (std::size_t i = 0; i < a.enchants.entries.size(); ++i) { if (a.enchants.entries[i].kind != b.enchants.entries[i].kind) return false; if (a.enchants.entries[i].level != b.enchants.entries[i].level) return false; } return true; } EnchantSet setOf(EnchantKind kind, int level) { EnchantSet set; addEnchant(set, kind, level); return set; } } // namespace VF_TEST(bookshelvesOnTheRingRaiseTheOfferedLevelsUpToTheCap) { World bare; CHECK_EQ(bookshelfCount(bare, {0, 64, 0}), 0); World lightlyStocked; surroundWithShelves(lightlyStocked, {0, 64, 0}, 6); CHECK_EQ(bookshelfCount(lightlyStocked, {0, 64, 0}), 6); World library; surroundWithShelves(library, {0, 64, 0}, 30); CHECK_EQ(bookshelfCount(library, {0, 64, 0}), maxBookshelfPower); } VF_TEST(shelvesWithSomethingSolidInBetweenDoNotCount) { World world; world.setBlock(2, 64, 0, Block::Bookshelf); CHECK_EQ(bookshelfCount(world, {0, 64, 0}), 1); world.setBlock(1, 64, 0, Block::Stone); CHECK_EQ(bookshelfCount(world, {0, 64, 0}), 0); // A torch in the gap is see-through, so the shelf counts again. world.setBlock(1, 64, 0, Block::Torch); CHECK_EQ(bookshelfCount(world, {0, 64, 0}), 1); } VF_TEST(offersAreDeterministicForTheSameSeedAndMoveOnAfterOneIsTaken) { EnchantTableState first; first.seed = 20250904u; first.bookshelves = 15; first.target = Enchantment::Target::Sword; first.enchantability = 10; refreshOffers(first); EnchantTableState second; second.seed = 20250904u; second.bookshelves = 15; second.target = Enchantment::Target::Sword; second.enchantability = 10; refreshOffers(second); for (int slot = 0; slot < 3; ++slot) { CHECK(sameOffer(first.offers[static_cast(slot)], second.offers[static_cast(slot)])); } uint32_t seedBefore = first.seed; EnchantOffer granted = takeOffer(first, 2, 40, 3); CHECK(granted.valid()); CHECK(first.seed != seedBefore); CHECK_EQ(granted.lapisCost, 3); } VF_TEST(topRowNeedsThirtyLevelsInAFullyShelvedLibrary) { for (uint32_t seed = 1; seed <= 40; ++seed) { EnchantTableState table; table.seed = seed * 2654435761u + 1u; table.bookshelves = 15; table.target = Enchantment::Target::Sword; table.enchantability = 14; refreshOffers(table); CHECK_EQ(table.offers[2].requiredLevels, 30); CHECK_EQ(table.offers[2].lapisCost, 3); CHECK(table.offers[0].requiredLevels <= table.offers[1].requiredLevels); CHECK(table.offers[1].requiredLevels <= table.offers[2].requiredLevels); } } VF_TEST(anEmptyTableOffersFarLessThanAFullyShelvedOne) { int bareTotal = 0; int shelvedTotal = 0; for (uint32_t seed = 1; seed <= 40; ++seed) { EnchantTableState bare; bare.seed = seed * 40503u + 7u; bare.bookshelves = 0; bare.target = Enchantment::Target::Pickaxe; bare.enchantability = 10; refreshOffers(bare); EnchantTableState shelved = bare; shelved.bookshelves = 15; refreshOffers(shelved); for (int slot = 0; slot < 3; ++slot) { bareTotal += bare.offers[static_cast(slot)].requiredLevels; shelvedTotal += shelved.offers[static_cast(slot)].requiredLevels; } } CHECK(shelvedTotal > bareTotal * 2); } VF_TEST(takingAnOfferFailsWithoutEnoughLevelsOrLapis) { EnchantTableState table; table.seed = 987654321u; table.bookshelves = 15; table.target = Enchantment::Target::Chestplate; table.enchantability = 9; refreshOffers(table); int cost = table.offers[2].requiredLevels; CHECK(cost > 0); CHECK(!takeOffer(table, 2, cost - 1, 3).valid()); CHECK(!takeOffer(table, 2, cost, 2).valid()); CHECK(!takeOffer(table, 7, 100, 64).valid()); CHECK(takeOffer(table, 2, cost, 3).valid()); } VF_TEST(rolledEnchantmentsRespectTargetsCapsAndConflicts) { for (uint32_t seed = 1; seed <= 300; ++seed) { uint32_t rng = seed * 2246822519u + 3u; EnchantSet rolled = rollEnchantments(Enchantment::Target::Sword, 14, 30, rng); for (std::size_t i = 0; i < rolled.entries.size(); ++i) { const Enchantment& entry = rolled.entries[i]; CHECK(entry.level >= 1); CHECK(entry.level <= maxEnchantLevel(entry.kind)); CHECK(enchantApplies(entry.kind, Enchantment::Target::Sword)); for (std::size_t j = i + 1; j < rolled.entries.size(); ++j) { CHECK(entry.kind != rolled.entries[j].kind); CHECK(!enchantsConflict(entry.kind, rolled.entries[j].kind)); } } } }