diff --git a/tests/testPotionCatalog.cpp b/tests/testPotionCatalog.cpp index 1b6de3d..83718be 100644 --- a/tests/testPotionCatalog.cpp +++ b/tests/testPotionCatalog.cpp @@ -155,3 +155,163 @@ VF_TEST(lingeringBottlesDoseAQuarterAndLeaveACloudBehindThem) { CHECK_EQ(catalog.variantTicks(lingeringStrong), 450); CHECK_EQ(catalog.variantLevel(lingeringStrong), 2); + PotionItem lingeringWater = bottleOf(PotionKind::Water, false, false, + PotionItem::Form::Lingering); + CHECK_EQ(catalog.variantTicks(lingeringWater), 0); + CHECK_EQ(catalog.cloudTicks(lingeringWater), 0); +} + +VF_TEST(instantPotionsStayAtOneTickInEveryFormAndModifier) { + const PotionCatalog& catalog = potionCatalog(); + const PotionKind instants[] = {PotionKind::Healing, PotionKind::Harming}; + const PotionItem::Form forms[] = {PotionItem::Form::Drinkable, PotionItem::Form::Splash, + PotionItem::Form::Lingering}; + for (PotionKind kind : instants) { + CHECK(catalog.entry(kind).instant); + CHECK(!catalog.entry(kind).acceptsRedstone()); + CHECK(catalog.entry(kind).acceptsGlowstone()); + for (PotionItem::Form form : forms) { + CHECK_EQ(catalog.variantTicks(bottleOf(kind, false, false, form)), 1); + CHECK_EQ(catalog.variantTicks(bottleOf(kind, true, false, form)), 1); + CHECK_EQ(catalog.variantLevel(bottleOf(kind, true, false, form)), 2); + } + } +} + +VF_TEST(everyCatalogRouteIsARealRecipeAndAlsoTheShortestOne) { + const PotionCatalog& catalog = potionCatalog(); + const BrewGraph& graph = brewGraph(); + + int routesChecked = 0; + for (const PotionEntry& row : catalog.entries()) { + if (row.ingredient == Ingredient::None) { + CHECK_EQ(row.kind, PotionKind::Water); + continue; + } + bool changed = false; + PotionItem brewed = brewResult(bottleOf(row.base), row.ingredient, changed); + CHECK(changed); + CHECK_EQ(brewed.kind, row.kind); + CHECK_EQ(graph.depthOf(bottleOf(row.kind)), graph.depthOf(bottleOf(row.base)) + 1); + ++routesChecked; + } + CHECK_EQ(routesChecked, static_cast(PotionKind::Count) - 1); +} + +VF_TEST(harmfulFlagsAndEffectLookupsAgreeWithTheEffectModule) { + const PotionCatalog& catalog = potionCatalog(); + for (const PotionEntry& row : catalog.entries()) { + if (!row.carriesEffect()) { + CHECK(!row.harmful); + CHECK(!row.instant); + continue; + } + CHECK_EQ(row.harmful, isHarmfulEffect(row.effect)); + CHECK_EQ(row.instant, isInstantEffect(row.effect)); + CHECK_EQ(catalog.kindForEffect(row.effect), row.kind); + } + + const EffectKind uncovered[] = {EffectKind::Haste, EffectKind::MiningFatigue, + EffectKind::Nausea, EffectKind::Resistance, + EffectKind::Wither, EffectKind::HealthBoost, + EffectKind::Absorption, EffectKind::Saturation, + EffectKind::Levitation, EffectKind::Count}; + for (EffectKind effect : uncovered) CHECK_EQ(catalog.kindForEffect(effect), PotionKind::Count); +} + +VF_TEST(invertedKindReadsTheFermentedSpiderEyeBranchesStraightOffTheRecipeTable) { + const PotionCatalog& catalog = potionCatalog(); + CHECK_EQ(catalog.invertedKind(PotionKind::Water), PotionKind::Weakness); + CHECK_EQ(catalog.invertedKind(PotionKind::NightVision), PotionKind::Invisibility); + CHECK_EQ(catalog.invertedKind(PotionKind::Healing), PotionKind::Harming); + CHECK_EQ(catalog.invertedKind(PotionKind::Poison), PotionKind::Harming); + CHECK_EQ(catalog.invertedKind(PotionKind::Swiftness), PotionKind::Slowness); + CHECK_EQ(catalog.invertedKind(PotionKind::Leaping), PotionKind::Slowness); + CHECK_EQ(catalog.invertedKind(PotionKind::Strength), PotionKind::Weakness); + + const PotionKind untouched[] = {PotionKind::Awkward, PotionKind::Regeneration, + PotionKind::FireResistance, PotionKind::WaterBreathing, + PotionKind::Harming, PotionKind::Slowness}; + for (PotionKind kind : untouched) CHECK_EQ(catalog.invertedKind(kind), PotionKind::Count); + + // Every inversion the catalog reports has to survive a real brew. + for (int index = 0; index < static_cast(PotionKind::Count); ++index) { + PotionKind kind = static_cast(index); + PotionKind inverted = catalog.invertedKind(kind); + bool changed = false; + PotionItem brewed = brewResult(bottleOf(kind), Ingredient::FermentedSpiderEye, changed); + CHECK_EQ(changed, inverted != PotionKind::Count); + if (changed) CHECK_EQ(brewed.kind, inverted); + } +} + +VF_TEST(everyPotionInTheCatalogHasABrewingPathThatARealStandCanReplay) { + const PotionCatalog& catalog = potionCatalog(); + const BrewGraph& graph = brewGraph(); + + std::vector paths = graph.drinkablePaths(); + CHECK_EQ(paths.size(), catalog.size()); + + for (std::size_t i = 0; i < paths.size(); ++i) { + const BrewPath& path = paths[i]; + const PotionEntry& row = catalog.entries()[i]; + CHECK(path.reachable); + CHECK_EQ(path.target.kind, row.kind); + CHECK_EQ(path.totalTicks, path.stepCount() * brewDurationTicks); + CHECK_EQ(path.stepCount(), graph.depthOf(path.target)); + + PotionItem brewed = replayOnAStand(path.steps); + CHECK_EQ(brewed.kind, row.kind); + CHECK_EQ(brewed.form, PotionItem::Form::Drinkable); + CHECK(!brewed.upgraded); + CHECK(!brewed.extended); + } +} + +VF_TEST(everyReachableVariantReplaysExactlyThroughItsOwnShortestPath) { + const BrewGraph& graph = brewGraph(); + int replayed = 0; + for (int index = 0; index < BrewGraph::stateCount(); ++index) { + PotionItem state = BrewGraph::stateAt(index); + BrewPath path = graph.shortestPath(state); + if (!path.reachable) continue; + + CHECK(replayThroughSteps(path.steps) == state); + CHECK_EQ(path.stepCount(), graph.depthOf(state)); + ++replayed; + } + CHECK_EQ(replayed, graph.reachableStateCount()); +} + +VF_TEST(theGraphReachesExactlyTheVariantsTheCatalogDeclaresAndNothingElse) { + const PotionCatalog& catalog = potionCatalog(); + const BrewGraph& graph = brewGraph(); + + int expected = 0; + for (const PotionEntry& row : catalog.entries()) expected += row.variantCount() * 3; + CHECK_EQ(graph.reachableStateCount(), expected); + CHECK(graph.reachableStateCount() < BrewGraph::stateCount()); + + int fromDepths = 0; + for (int depth = 0; depth <= BrewGraph::stateCount(); ++depth) { + fromDepths += static_cast(graph.statesAtDepth(depth).size()); + } + CHECK_EQ(fromDepths, graph.reachableStateCount()); + CHECK_EQ(graph.statesAtDepth(0).size(), std::size_t{1}); + CHECK(graph.statesAtDepth(0)[0] == PotionItem{}); +} + +VF_TEST(modifiersAPotionRefusesLeaveThatVariantWithNoPathAtAll) { + const BrewGraph& graph = brewGraph(); + const PotionCatalog& catalog = potionCatalog(); + + const PotionKind noGlowstone[] = {PotionKind::Weakness, PotionKind::FireResistance, + PotionKind::WaterBreathing, PotionKind::NightVision, + PotionKind::Invisibility}; + for (PotionKind kind : noGlowstone) { + CHECK(!catalog.entry(kind).acceptsGlowstone()); + CHECK(!graph.reachable(bottleOf(kind, true, false))); + CHECK_EQ(graph.depthOf(bottleOf(kind, true, false)), -1); + CHECK(graph.reachable(bottleOf(kind, false, true))); + } +