diff --git a/tests/testCircuit.cpp b/tests/testCircuit.cpp new file mode 100644 index 0000000..a033b94 --- /dev/null +++ b/tests/testCircuit.cpp @@ -0,0 +1,259 @@ +#include "testHarness.h" +#include "vf/Circuit.h" + +using namespace vf; + +namespace { + +void wireLine(World& world, int x0, int x1, int y, int z) { + for (int x = x0; x <= x1; ++x) world.setBlock(x, y, z, Block::RedstoneWire); +} + +} // namespace + +VF_TEST(wirePropagatesWithFalloffToZeroAfterFifteenBlocks) { + World world; + world.setBlock(0, 0, 0, Block::RedstoneBlock); + wireLine(world, 1, 16, 0, 0); + RedstoneCircuit circuit(world, {0, 0, 0}, {17, 1, 1}); + circuit.tick(); + + for (int x = 1; x <= 15; ++x) CHECK_EQ(circuit.powerAt(x, 0, 0), redstoneMaxPower - (x - 1)); + CHECK_EQ(circuit.powerAt(16, 0, 0), 0); +} + +VF_TEST(wireDoesNotCrossThroughSolidBlocks) { + World world; + world.setBlock(0, 0, 0, Block::RedstoneBlock); + wireLine(world, 1, 3, 0, 0); + world.setBlock(4, 0, 0, Block::Stone); + wireLine(world, 5, 7, 0, 0); + RedstoneCircuit circuit(world, {0, 0, 0}, {8, 1, 1}); + circuit.tick(); + + CHECK(circuit.powerAt(3, 0, 0) > 0); + CHECK_EQ(circuit.powerAt(5, 0, 0), 0); + CHECK_EQ(circuit.powerAt(6, 0, 0), 0); +} + +VF_TEST(leverTogglesWireOnAndOff) { + World world; + world.setBlock(5, 0, 5, Block::Lever); + world.setBlock(6, 0, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 0, 5), 0); + CHECK(!circuit.leverOn({5, 0, 5})); + + circuit.setLever({5, 0, 5}, true); + circuit.tick(); + CHECK(circuit.leverOn({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), redstoneMaxPower); + + circuit.setLever({5, 0, 5}, false); + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 0, 5), 0); +} + +VF_TEST(buttonPowersThenExpires) { + World world; + world.setBlock(5, 0, 5, Block::Button); + world.setBlock(6, 0, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.pressButton({5, 0, 5}, 3); + + circuit.tick(); + CHECK(circuit.buttonActive({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), redstoneMaxPower); + + circuit.tick(); + CHECK(circuit.buttonActive({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), redstoneMaxPower); + + circuit.tick(); + CHECK(!circuit.buttonActive({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), 0); + + circuit.tick(); + CHECK(!circuit.buttonActive({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), 0); +} + +VF_TEST(torchInvertsThePowerStateOfItsMountBlock) { + World world; + world.setBlock(4, 0, 5, Block::RedstoneBlock); + world.setBlock(5, 0, 5, Block::Stone); + world.setBlock(5, 1, 5, Block::RedstoneTorch); + world.setBlock(6, 1, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 1, 5), redstoneMaxPower); // mount not yet observed as powered + + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 1, 5), 0); // mount observed powered, torch inverted off +} + +VF_TEST(torchWithUnpoweredMountStaysLitIndefinitely) { + World world; + world.setBlock(5, 0, 5, Block::Stone); + world.setBlock(5, 1, 5, Block::RedstoneTorch); + world.setBlock(6, 1, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + + for (int i = 0; i < 5; ++i) { + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 1, 5), redstoneMaxPower); + } +} + +VF_TEST(repeaterDelaysOutputByConfiguredTicksAndRefreshesToFull) { + World world; + world.setBlock(5, 0, 5, Block::Lever); + world.setBlock(6, 0, 5, Block::Repeater); + wireLine(world, 7, 20, 0, 5); + RedstoneCircuit circuit(world, {0, 0, 0}, {21, 1, 10}); + circuit.setRepeater({6, 0, 5}, Face::PosX, 4); + circuit.setLever({5, 0, 5}, true); + + circuit.tick(); // edge not visible yet (fresh field) + CHECK(!circuit.repeaterOutputOn({6, 0, 5})); + circuit.tick(); // edge observed, countdown starts at 4 and ticks to 3 + CHECK(!circuit.repeaterOutputOn({6, 0, 5})); + circuit.tick(); + CHECK(!circuit.repeaterOutputOn({6, 0, 5})); + circuit.tick(); + CHECK(!circuit.repeaterOutputOn({6, 0, 5})); + circuit.tick(); + CHECK(circuit.repeaterOutputOn({6, 0, 5})); + CHECK_EQ(circuit.powerAt(7, 0, 5), redstoneMaxPower); // output refreshed to full strength +} + +VF_TEST(repeaterWithMinimumDelayRespondsOneTickAfterTheEdge) { + World world; + world.setBlock(5, 0, 5, Block::Lever); + world.setBlock(6, 0, 5, Block::Repeater); + world.setBlock(7, 0, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.setRepeater({6, 0, 5}, Face::PosX, 1); + circuit.setLever({5, 0, 5}, true); + + circuit.tick(); + CHECK(!circuit.repeaterOutputOn({6, 0, 5})); + circuit.tick(); + CHECK(circuit.repeaterOutputOn({6, 0, 5})); +} + +VF_TEST(repeaterIgnoresInputPerpendicularToItsFacing) { + World world; + world.setBlock(5, 0, 6, Block::Lever); // powers from the side, not behind + world.setBlock(5, 0, 5, Block::Repeater); + world.setBlock(6, 0, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.setRepeater({5, 0, 5}, Face::PosX, 1); + circuit.setLever({5, 0, 6}, true); + + for (int i = 0; i < 4; ++i) circuit.tick(); + CHECK(!circuit.repeaterOutputOn({5, 0, 5})); + CHECK_EQ(circuit.powerAt(6, 0, 5), 0); +} + +VF_TEST(comparatorCompareModePassesBackWhenSideIsLower) { + World world; + world.setBlock(5, 0, 5, Block::RedstoneBlock); + world.setBlock(6, 0, 5, Block::Comparator); + world.setBlock(7, 0, 5, Block::RedstoneWire); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.setComparator({6, 0, 5}, Face::PosX, ComparatorMode::Compare); + + circuit.tick(); + circuit.tick(); + CHECK_EQ(circuit.powerAt(7, 0, 5), redstoneMaxPower); +} + +VF_TEST(comparatorCompareModeBlocksWhenSideExceedsBack) { + World world; + world.setBlock(3, 0, 5, Block::RedstoneBlock); + wireLine(world, 4, 5, 0, 5); // back input falls off to 14 by the comparator + world.setBlock(6, 0, 5, Block::Comparator); + world.setBlock(7, 0, 5, Block::RedstoneWire); + world.setBlock(6, 0, 6, Block::RedstoneBlock); // full-strength side input + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.setComparator({6, 0, 5}, Face::PosX, ComparatorMode::Compare); + + circuit.tick(); + circuit.tick(); + CHECK_EQ(circuit.powerAt(7, 0, 5), 0); +} + +VF_TEST(comparatorSubtractModeSubtractsSideFromBack) { + World world; + world.setBlock(4, 0, 5, Block::RedstoneBlock); + wireLine(world, 5, 5, 0, 5); + world.setBlock(6, 0, 5, Block::Comparator); + world.setBlock(7, 0, 5, Block::RedstoneWire); + world.setBlock(6, 0, 6, Block::Lever); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + circuit.setComparator({6, 0, 5}, Face::PosX, ComparatorMode::Subtract); + circuit.setLever({6, 0, 6}, true); + + circuit.tick(); + circuit.tick(); + CHECK_EQ(circuit.powerAt(7, 0, 5), 0); // full back (15) minus full side (15) = 0 +} + +VF_TEST(redstoneBlockIsAConstantSourceRegardlessOfTicks) { + World world; + world.setBlock(5, 0, 5, Block::RedstoneBlock); + wireLine(world, 6, 8, 0, 5); + RedstoneCircuit circuit(world, {0, 0, 0}, {9, 1, 10}); + + for (int i = 0; i < 4; ++i) { + circuit.tick(); + CHECK_EQ(circuit.powerAt(6, 0, 5), redstoneMaxPower); + CHECK_EQ(circuit.powerAt(7, 0, 5), redstoneMaxPower - 1); + } +} + +VF_TEST(lampLitsWhenAdjacentToPowerAndDarkensWhenNot) { + World world; + world.setBlock(5, 0, 5, Block::Lever); + world.setBlock(6, 0, 5, Block::RedstoneLamp); + RedstoneCircuit circuit(world, {0, 0, 0}, {10, 3, 10}); + + circuit.tick(); + circuit.applyLamps(); + CHECK(world.blockAt(6, 0, 5) == Block::RedstoneLamp); + + circuit.setLever({5, 0, 5}, true); + circuit.tick(); + circuit.applyLamps(); + CHECK(world.blockAt(6, 0, 5) == Block::RedstoneLampLit); + + circuit.setLever({5, 0, 5}, false); + circuit.tick(); + circuit.applyLamps(); + CHECK(world.blockAt(6, 0, 5) == Block::RedstoneLamp); +} + +VF_TEST(circuitIsDeterministicAcrossIdenticalRuns) { + World worldA, worldB; + for (World* w : {&worldA, &worldB}) { + w->setBlock(4, 0, 5, Block::RedstoneBlock); + w->setBlock(5, 0, 5, Block::Stone); + w->setBlock(5, 1, 5, Block::RedstoneTorch); + wireLine(*w, 6, 12, 1, 5); + w->setBlock(6, 1, 6, Block::Lever); + } + RedstoneCircuit a(worldA, {0, 0, 0}, {14, 3, 10}); + RedstoneCircuit b(worldB, {0, 0, 0}, {14, 3, 10}); + a.setLever({6, 1, 6}, true); + b.setLever({6, 1, 6}, true); + + for (int i = 0; i < 6; ++i) { + a.tick(); + b.tick(); + for (int x = 6; x <= 12; ++x) CHECK_EQ(a.powerAt(x, 1, 5), b.powerAt(x, 1, 5)); + } +}