#include "testHarness.h" #include "vf/Crafting.h" using namespace vf; namespace { CraftGrid emptyGrid() { CraftGrid grid; grid.fill(ItemId::None); return grid; } void place(CraftGrid& grid, int row, int col, ItemId item) { grid[static_cast(row * craftGridWidth + col)] = item; } } // namespace VF_TEST(defaultRegistryLoadsExpectedRecipeCounts) { CraftingRegistry registry; CHECK_EQ(registry.shapedRecipes().size(), static_cast(31)); CHECK_EQ(registry.shapelessRecipes().size(), static_cast(1)); CHECK_EQ(registry.smeltingRecipes().size(), static_cast(11)); } VF_TEST(shapelessPlanksMatchesFromAnySingleCell) { CraftingRegistry registry; for (int i = 0; i < craftGridCells; ++i) { CraftGrid grid = emptyGrid(); grid[static_cast(i)] = ItemId::Wood; CraftResult result = registry.craftMatch(grid); CHECK(result.valid()); CHECK(result.item == ItemId::Planks); CHECK_EQ(result.count, 4); } } VF_TEST(shapedRecipeMatchesRegardlessOfGridOffset) { CraftingRegistry registry; // 2x2 crafting-table recipe: try all four corners of the 3x3 grid. int origins[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}}; for (auto& origin : origins) { CraftGrid grid = emptyGrid(); place(grid, origin[0], origin[1], ItemId::Planks); place(grid, origin[0], origin[1] + 1, ItemId::Planks); place(grid, origin[0] + 1, origin[1], ItemId::Planks); place(grid, origin[0] + 1, origin[1] + 1, ItemId::Planks); CraftResult result = registry.craftMatch(grid); CHECK(result.valid()); CHECK(result.item == ItemId::CraftingTable); } } VF_TEST(shapedRecipeMatchesMirroredOrientation) { CraftingRegistry registry; CraftGrid direct = emptyGrid(); place(direct, 0, 0, ItemId::Planks); place(direct, 0, 1, ItemId::Planks); place(direct, 1, 0, ItemId::Planks); place(direct, 1, 1, ItemId::Stick); place(direct, 2, 1, ItemId::Stick); CraftResult directResult = registry.craftMatch(direct); CHECK(directResult.valid()); CHECK(directResult.item == ItemId::WoodAxe); CraftGrid mirrored = emptyGrid(); place(mirrored, 0, 1, ItemId::Planks); place(mirrored, 0, 2, ItemId::Planks); place(mirrored, 1, 1, ItemId::Stick); place(mirrored, 1, 2, ItemId::Planks); place(mirrored, 2, 1, ItemId::Stick); CraftResult mirroredResult = registry.craftMatch(mirrored); CHECK(mirroredResult.valid()); CHECK(mirroredResult.item == ItemId::WoodAxe); } VF_TEST(shapedPickaxeBreaksWhenAStickIsMissing) { CraftingRegistry registry; CraftGrid grid = emptyGrid(); place(grid, 0, 0, ItemId::Cobblestone); place(grid, 0, 1, ItemId::Cobblestone); place(grid, 0, 2, ItemId::Cobblestone); place(grid, 1, 1, ItemId::Stick); place(grid, 2, 1, ItemId::Stick); CraftResult good = registry.craftMatch(grid); CHECK(good.valid()); CHECK(good.item == ItemId::StonePickaxe); place(grid, 2, 1, ItemId::None); // break the pattern: only one stick left CraftResult broken = registry.craftMatch(grid); CHECK(!broken.valid()); } VF_TEST(incoherentGridProducesNoMatch) { CraftingRegistry registry; CraftGrid grid = emptyGrid(); place(grid, 0, 0, ItemId::Stone); place(grid, 2, 2, ItemId::Leaves); CraftResult result = registry.craftMatch(grid); CHECK(!result.valid()); } VF_TEST(shapelessRecipeMatchesIngredientMultisetIgnoringPosition) { CraftingRegistry registry; registry.addShapeless({{ItemId::Stick, ItemId::Stick, ItemId::CoalLump}, {ItemId::Torch, 8}}); CraftGrid grid = emptyGrid(); place(grid, 0, 0, ItemId::Stick); place(grid, 0, 2, ItemId::Stick); place(grid, 2, 1, ItemId::CoalLump); CraftResult result = registry.craftMatch(grid); CHECK(result.valid()); CHECK(result.item == ItemId::Torch); CHECK_EQ(result.count, 8); CraftGrid tooFew = emptyGrid(); place(tooFew, 0, 0, ItemId::Stick); place(tooFew, 2, 1, ItemId::CoalLump); CraftResult resultTooFew = registry.craftMatch(tooFew); CHECK(!resultTooFew.valid()); } VF_TEST(smeltingRegistryFindsRecipeByInput) { CraftingRegistry registry; const SmeltingRecipe* ironRecipe = registry.smeltingFor(ItemId::IronOre); CHECK(ironRecipe != nullptr); CHECK(ironRecipe->result.item == ItemId::IronIngot); const SmeltingRecipe* none = registry.smeltingFor(ItemId::Stick); CHECK(none == nullptr); } VF_TEST(fuelBurnSecondsRanksCoalAboveWood) { CHECK(isFuel(ItemId::CoalLump)); CHECK(isFuel(ItemId::Planks)); CHECK(!isFuel(ItemId::Stone)); CHECK(fuelBurnSeconds(ItemId::CoalLump) > fuelBurnSeconds(ItemId::Planks)); } VF_TEST(furnaceDoesNothingWithoutFuel) { CraftingRegistry registry; Furnace furnace(registry); furnace.input = {ItemId::IronOre, 4, 0}; for (int i = 0; i < 200; ++i) furnace.tick(1.0f); CHECK(!furnace.isLit()); CHECK(furnace.output.empty()); CHECK_EQ(furnace.input.count, 4); } VF_TEST(furnaceSmeltsOreIntoIngotsOverTime) { CraftingRegistry registry; Furnace furnace(registry); furnace.input = {ItemId::IronOre, 3, 0}; furnace.fuel = {ItemId::CoalLump, 1, 0}; bool sawLit = false; for (int i = 0; i < 200; ++i) { furnace.tick(0.5f); if (furnace.isLit()) sawLit = true; } CHECK(sawLit); CHECK(furnace.output.item == ItemId::IronIngot); CHECK(furnace.output.count >= 1); CHECK(furnace.input.count < 3); } VF_TEST(furnaceStopsProgressingWhenOutputHoldsADifferentItem) { CraftingRegistry registry; Furnace furnace(registry); furnace.input = {ItemId::IronOre, 1, 0}; furnace.fuel = {ItemId::CoalLump, 1, 0}; furnace.output = {ItemId::GoldIngot, 5, 0}; // blocks the mismatched output for (int i = 0; i < 60; ++i) furnace.tick(0.5f); CHECK(furnace.output.item == ItemId::GoldIngot); CHECK_EQ(furnace.output.count, 5); CHECK_EQ(furnace.input.count, 1); // never consumed }