diff --git a/tests/testBrewing.cpp b/tests/testBrewing.cpp new file mode 100644 index 0000000..14e8047 --- /dev/null +++ b/tests/testBrewing.cpp @@ -0,0 +1,159 @@ +#include + +#include "testHarness.h" +#include "vf/Brewing.h" + +using namespace vf; + +namespace { + +using Ingredient = BrewRecipe::Ingredient; + +BrewingStandState fueledStand(const PotionItem& bottle, int blazePowder = 5) { + BrewingStandState stand; + setBottle(stand, 0, bottle); + addFuel(stand, blazePowder); + return stand; +} + +// Loads one ingredient and runs the stand until the brew lands. Returns the +// bottle in the first slot afterwards. +PotionItem brewOnce(BrewingStandState& stand, Ingredient ingredient) { + setIngredient(stand, ingredient, 1); + for (int tick = 0; tick < brewDurationTicks; ++tick) tickBrewing(stand); + return stand.bottles[0].potion; +} + +// Walks a whole chain of ingredients through a single bottle. +PotionItem brewChain(const PotionItem& start, const std::vector& chain) { + BrewingStandState stand = fueledStand(start, 10); + PotionItem bottle = start; + for (Ingredient ingredient : chain) bottle = brewOnce(stand, ingredient); + return bottle; +} + +PotionItem potionOf(PotionKind kind) { + PotionItem potion; + potion.kind = kind; + return potion; +} + +int durationOf(PotionKind kind, bool upgraded, bool extended) { + PotionItem potion = potionOf(kind); + potion.upgraded = upgraded; + potion.extended = extended; + return potionEffect(potion).durationTicks; +} + +int levelOf(PotionKind kind, bool upgraded, bool extended) { + PotionItem potion = potionOf(kind); + potion.upgraded = upgraded; + potion.extended = extended; + return potionEffect(potion).level(); +} + +} // namespace + +VF_TEST(theStandsPublishedConstantsAreTheOnesTheRestOfTheEngineWasBuiltAgainst) { + CHECK_EQ(brewingBottleSlots, 3); + CHECK_EQ(brewDurationTicks, 400); + CHECK_EQ(brewDurationTicks, 20 * ticksPerSecond); + CHECK_EQ(brewsPerBlazePowder, 20); + CHECK_EQ(static_cast(PotionKind::Count), 17); + CHECK_EQ(static_cast(BrewRecipe::Ingredient::None), 0); + + // A stand with one blaze powder in it runs a literal twenty second brew and + // has nineteen brews of fuel left afterwards. + BrewingStandState stand; + setBottle(stand, 0, PotionItem{}); + addFuel(stand, 1); + setIngredient(stand, Ingredient::NetherWart, 1); + CHECK_EQ(stand.fuel, 20); + + int completedOn = 0; + for (int tick = 1; tick <= 500; ++tick) { + if (tickBrewing(stand) && completedOn == 0) completedOn = tick; + } + CHECK_EQ(completedOn, 400); + CHECK_EQ(stand.fuel, 19); +} + +VF_TEST(recipeTableHasNoDuplicateInputAndIngredientPairs) { + const std::vector& recipes = brewRecipes(); + CHECK(recipes.size() >= 19); + + int collisions = 0; + int selfLoops = 0; + int emptyIngredients = 0; + for (std::size_t i = 0; i < recipes.size(); ++i) { + if (recipes[i].ingredient == Ingredient::None) ++emptyIngredients; + if (recipes[i].input == recipes[i].output) ++selfLoops; + for (std::size_t j = i + 1; j < recipes.size(); ++j) { + if (recipes[i].input == recipes[j].input && + recipes[i].ingredient == recipes[j].ingredient) { + ++collisions; + } + } + } + CHECK_EQ(collisions, 0); + CHECK_EQ(selfLoops, 0); + CHECK_EQ(emptyIngredients, 0); +} + +VF_TEST(netherWartTurnsWaterIntoAwkwardAndAwkwardOpensEveryBasePotion) { + bool changed = false; + CHECK_EQ(brewResult(PotionItem{}, Ingredient::NetherWart, changed).kind, PotionKind::Awkward); + CHECK(changed); + + const struct { Ingredient ingredient; PotionKind expected; } branches[] = { + {Ingredient::Sugar, PotionKind::Swiftness}, + {Ingredient::GlisteringMelon, PotionKind::Healing}, + {Ingredient::SpiderEye, PotionKind::Poison}, + {Ingredient::BlazePowder, PotionKind::Strength}, + {Ingredient::MagmaCream, PotionKind::FireResistance}, + {Ingredient::PufferFish, PotionKind::WaterBreathing}, + {Ingredient::GoldenCarrot, PotionKind::NightVision}, + {Ingredient::GhastTear, PotionKind::Regeneration}, + {Ingredient::RabbitFoot, PotionKind::Leaping}, + }; + for (const auto& branch : branches) { + bool branched = false; + PotionItem brewed = brewResult(potionOf(PotionKind::Awkward), branch.ingredient, branched); + CHECK(branched); + CHECK_EQ(brewed.kind, branch.expected); + } +} + +VF_TEST(aStandTurnsWaterIntoAwkwardOnTheFourHundredthTickAndNotOneTickEarlier) { + BrewingStandState stand = fueledStand(PotionItem{}, 1); + CHECK_EQ(stand.fuel, brewsPerBlazePowder); + CHECK(setIngredient(stand, Ingredient::NetherWart, 1)); + + bool completedEarly = false; + for (int tick = 1; tick < brewDurationTicks; ++tick) { + if (tickBrewing(stand)) completedEarly = true; + } + CHECK(!completedEarly); + CHECK(stand.brewing); + CHECK_EQ(stand.progressTicks, brewDurationTicks - 1); + CHECK_EQ(stand.bottles[0].potion.kind, PotionKind::Water); + + CHECK(tickBrewing(stand)); + CHECK_EQ(stand.bottles[0].potion.kind, PotionKind::Awkward); + CHECK_EQ(stand.progressTicks, 0); + CHECK(!stand.brewing); + CHECK_EQ(stand.fuel, brewsPerBlazePowder - 1); + CHECK_EQ(stand.ingredientCount, 0); + CHECK_EQ(stand.ingredient, Ingredient::None); +} + +VF_TEST(theFullWaterToUpgradedSplashStrengthCycleRunsOnOneStand) { + BrewingStandState stand = fueledStand(PotionItem{}, 1); + + CHECK_EQ(brewOnce(stand, Ingredient::NetherWart).kind, PotionKind::Awkward); + CHECK_EQ(brewOnce(stand, Ingredient::BlazePowder).kind, PotionKind::Strength); + + PotionItem upgraded = brewOnce(stand, Ingredient::Glowstone); + CHECK(upgraded.upgraded); + CHECK(!upgraded.extended); +