diff --git a/tests/testFarming.cpp b/tests/testFarming.cpp new file mode 100644 index 0000000..953ca6d --- /dev/null +++ b/tests/testFarming.cpp @@ -0,0 +1,294 @@ +#include "testHarness.h" +#include "vf/Farming.h" + +using namespace vf; + +namespace { + +int countLeaves(World& world, const Vec3i& centre, int radius, int yLow, int yHigh) { + int count = 0; + for (int x = centre.x - radius; x <= centre.x + radius; ++x) + for (int y = yLow; y <= yHigh; ++y) + for (int z = centre.z - radius; z <= centre.z + radius; ++z) + if (world.blockAt(x, y, z) == Block::Leaves) ++count; + return count; +} + +} // namespace + +VF_TEST(groundCropRipensThroughStagesInLight) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(1u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + CHECK_EQ(farm.stateAt(crop)->stage, 0); + + for (int i = 0; i < 4000 && !farm.stateAt(crop)->mature(); ++i) farm.growStep(world, 15); + CHECK(farm.stateAt(crop)->mature()); + CHECK_EQ(farm.stateAt(crop)->stage, 7); + CHECK(world.blockAt(0, 64, 0) == Block::Wheat); +} + +VF_TEST(cropDoesNotGrowInDarkness) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(1u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + + for (int i = 0; i < 1000; ++i) farm.growStep(world, 0); + CHECK_EQ(farm.stateAt(crop)->stage, 0); +} + +VF_TEST(hydrationAcceleratesGrowth) { + Vec3i crop{0, 64, 0}; + + World dryWorld; + dryWorld.setBlock(0, 63, 0, Block::Farmland); + Farming dryFarm(50u); + dryFarm.plant(dryWorld, crop, CropType::Wheat); + CHECK(!dryFarm.isHydrated(dryWorld, Vec3i{0, 63, 0})); + int drySteps = 0; + while (!dryFarm.stateAt(crop)->mature() && drySteps < 100000) { + dryFarm.growStep(dryWorld, 15); + ++drySteps; + } + + World wetWorld; + wetWorld.setBlock(0, 63, 0, Block::Farmland); + wetWorld.setBlock(2, 63, 0, Block::Water); + Farming wetFarm(50u); + wetFarm.plant(wetWorld, crop, CropType::Wheat); + CHECK(wetFarm.isHydrated(wetWorld, Vec3i{0, 63, 0})); + int wetSteps = 0; + while (!wetFarm.stateAt(crop)->mature() && wetSteps < 100000) { + wetFarm.growStep(wetWorld, 15); + ++wetSteps; + } + + CHECK(wetFarm.stateAt(crop)->mature()); + CHECK(dryFarm.stateAt(crop)->mature()); + CHECK(wetSteps < drySteps); +} + +VF_TEST(sugarCaneGrowsBesideWaterUpToThree) { + World world; + world.setBlock(0, 63, 0, Block::Dirt); + world.setBlock(1, 63, 0, Block::Water); + Farming farm(2u); + Vec3i base{0, 64, 0}; + farm.plant(world, base, CropType::SugarCane); + + for (int i = 0; i < 4000 && farm.stateAt(base)->stage < 2; ++i) farm.growStep(world, 15); + CHECK(world.blockAt(0, 65, 0) == Block::SugarCane); + CHECK(world.blockAt(0, 66, 0) == Block::SugarCane); + CHECK(world.blockAt(0, 67, 0) == Block::Air); + CHECK_EQ(farm.stateAt(base)->stage, 2); +} + +VF_TEST(sugarCaneWillNotGrowWithoutWater) { + World world; + world.setBlock(0, 63, 0, Block::Dirt); + Farming farm(2u); + Vec3i base{0, 64, 0}; + farm.plant(world, base, CropType::SugarCane); + + for (int i = 0; i < 800; ++i) farm.growStep(world, 15); + CHECK_EQ(farm.stateAt(base)->stage, 0); + CHECK(world.blockAt(0, 65, 0) == Block::Air); +} + +VF_TEST(cactusGrowsAloneButNotBesideSolids) { + World open; + open.setBlock(0, 63, 0, Block::Sand); + Farming lonely(3u); + Vec3i base{0, 64, 0}; + lonely.plant(open, base, CropType::Cactus); + for (int i = 0; i < 4000 && lonely.stateAt(base)->stage < 2; ++i) lonely.growStep(open, 15); + CHECK(open.blockAt(0, 65, 0) == Block::Cactus); + CHECK(open.blockAt(0, 66, 0) == Block::Cactus); + CHECK_EQ(lonely.stateAt(base)->stage, 2); + + World crowded; + crowded.setBlock(0, 63, 0, Block::Sand); + crowded.setBlock(1, 65, 0, Block::Stone); + Farming blocked(3u); + blocked.plant(crowded, base, CropType::Cactus); + for (int i = 0; i < 800; ++i) blocked.growStep(crowded, 15); + CHECK_EQ(blocked.stateAt(base)->stage, 0); + CHECK(crowded.blockAt(0, 65, 0) == Block::Air); +} + +VF_TEST(stemMaturesAndSetsFruitOnFreeTile) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + world.setBlock(1, 63, 0, Block::Dirt); + Farming farm(4u); + Vec3i stem{0, 64, 0}; + farm.plant(world, stem, CropType::PumpkinStem); + + bool fruited = false; + for (int i = 0; i < 8000 && !fruited; ++i) { + farm.growStep(world, 15); + fruited = world.blockAt(1, 64, 0) == Block::Pumpkin; + } + CHECK(fruited); + CHECK(world.blockAt(1, 64, 0) == Block::Pumpkin); +} + +VF_TEST(saplingBecomesTree) { + World world; + world.setBlock(0, 63, 0, Block::Dirt); + Farming farm(5u); + Vec3i sapling{0, 64, 0}; + farm.plant(world, sapling, CropType::Sapling); + + for (int i = 0; i < 8000 && farm.stateAt(sapling) != nullptr; ++i) farm.growStep(world, 15); + CHECK(farm.stateAt(sapling) == nullptr); + CHECK(world.blockAt(0, 64, 0) == Block::Wood); + CHECK(world.blockAt(0, 65, 0) == Block::Wood); + CHECK(countLeaves(world, Vec3i{0, 68, 0}, 2, 64, 72) > 0); +} + +VF_TEST(harvestMatureWheatYieldsCropAndClears) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(6u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + for (int i = 0; i < 4000 && !farm.stateAt(crop)->mature(); ++i) farm.growStep(world, 15); + + std::vector drops = farm.harvest(world, crop); + CHECK(!drops.empty()); + CHECK(drops[0].item == Block::Wheat); + CHECK(drops[0].count >= 1); + CHECK(world.blockAt(0, 64, 0) == Block::Air); + CHECK(farm.stateAt(crop) == nullptr); + CHECK_EQ(farm.trackedCount(), static_cast(0)); +} + +VF_TEST(harvestImmatureWheatDropsNothing) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(6u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + + std::vector drops = farm.harvest(world, crop); + CHECK(drops.empty()); + CHECK(world.blockAt(0, 64, 0) == Block::Air); + CHECK(farm.stateAt(crop) == nullptr); +} + +VF_TEST(harvestSugarCaneYieldsFullHeight) { + World world; + world.setBlock(0, 63, 0, Block::Dirt); + world.setBlock(1, 63, 0, Block::Water); + Farming farm(7u); + Vec3i base{0, 64, 0}; + farm.plant(world, base, CropType::SugarCane); + for (int i = 0; i < 4000 && farm.stateAt(base)->stage < 2; ++i) farm.growStep(world, 15); + + std::vector drops = farm.harvest(world, base); + CHECK_EQ(drops.size(), static_cast(1)); + CHECK(drops[0].item == Block::SugarCane); + CHECK_EQ(drops[0].count, 3); + CHECK(world.blockAt(0, 64, 0) == Block::Air); + CHECK(world.blockAt(0, 65, 0) == Block::Air); + CHECK(world.blockAt(0, 66, 0) == Block::Air); +} + +VF_TEST(bonemealShovesGroundCropForward) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(1u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + + CHECK(farm.applyBonemeal(world, crop)); + CHECK(farm.stateAt(crop)->stage >= 2); + + World sandWorld; + sandWorld.setBlock(0, 63, 0, Block::Sand); + Farming cactusFarm(1u); + cactusFarm.plant(sandWorld, crop, CropType::Cactus); + CHECK(!cactusFarm.applyBonemeal(sandWorld, crop)); +} + +VF_TEST(bonemealCanRipenAndHarvest) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(8u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Carrot); + + for (int i = 0; i < 20 && !farm.stateAt(crop)->mature(); ++i) farm.applyBonemeal(world, crop); + CHECK(farm.stateAt(crop)->mature()); + std::vector drops = farm.harvest(world, crop); + CHECK(!drops.empty()); + CHECK(drops[0].item == Block::Carrot); +} + +VF_TEST(bonemealEventuallyGrowsSaplingIntoTree) { + World world; + world.setBlock(0, 63, 0, Block::Dirt); + Farming farm(9u); + Vec3i sapling{0, 64, 0}; + farm.plant(world, sapling, CropType::Sapling); + + for (int i = 0; i < 100 && farm.stateAt(sapling) != nullptr; ++i) farm.applyBonemeal(world, sapling); + CHECK(farm.stateAt(sapling) == nullptr); + CHECK(world.blockAt(0, 64, 0) == Block::Wood); +} + +VF_TEST(tramplingRevertsFarmlandAndKillsCrop) { + World world; + world.setBlock(0, 63, 0, Block::Farmland); + Farming farm(1u); + Vec3i crop{0, 64, 0}; + farm.plant(world, crop, CropType::Wheat); + + CHECK(farm.trample(world, Vec3i{0, 63, 0})); + CHECK(world.blockAt(0, 63, 0) == Block::Dirt); + CHECK(world.blockAt(0, 64, 0) == Block::Air); + CHECK(farm.stateAt(crop) == nullptr); + CHECK(!farm.trample(world, Vec3i{5, 5, 5})); +} + +VF_TEST(vineHangsDownFromAnchorOnly) { + World anchored; + anchored.setBlock(1, 64, 0, Block::Stone); + Farming climber(2u); + Vec3i head{0, 64, 0}; + climber.plant(anchored, head, CropType::Vine); + for (int i = 0; i < 4000 && climber.stateAt(head)->stage < 4; ++i) climber.growStep(anchored, 15); + CHECK(anchored.blockAt(0, 63, 0) == Block::Vine); + CHECK(anchored.blockAt(0, 60, 0) == Block::Vine); + CHECK_EQ(climber.stateAt(head)->stage, 4); + + World floating; + Farming unanchored(2u); + unanchored.plant(floating, head, CropType::Vine); + for (int i = 0; i < 800; ++i) unanchored.growStep(floating, 15); + CHECK_EQ(unanchored.stateAt(head)->stage, 0); + CHECK(floating.blockAt(0, 63, 0) == Block::Air); +} + +VF_TEST(sameSeedGrowsIdentically) { + World left; + left.setBlock(0, 63, 0, Block::Farmland); + World right; + right.setBlock(0, 63, 0, Block::Farmland); + Farming leftFarm(123u); + Farming rightFarm(123u); + Vec3i crop{0, 64, 0}; + leftFarm.plant(left, crop, CropType::Wheat); + rightFarm.plant(right, crop, CropType::Wheat); + + for (int i = 0; i < 50; ++i) { + leftFarm.growStep(left, 15); + rightFarm.growStep(right, 15); + } + CHECK_EQ(leftFarm.stateAt(crop)->stage, rightFarm.stateAt(crop)->stage); +}