implement tunnels
This commit is contained in:
@@ -660,3 +660,257 @@ TEST_CASE("BeltSystem: splitter alternates between two unregistered outputs (bui
|
||||
bs.tick();
|
||||
REQUIRE(bs.tryTakeItem(Port{tileSpl, Rotation::North}).has_value());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tunnel — pairing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BeltSystem: tunnel pairing — basic pair within max distance", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
const QPoint entry(0, 0);
|
||||
const QPoint exit(3, 0);
|
||||
|
||||
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||
bs.placeTunnelExit(exit, Rotation::East);
|
||||
|
||||
bs.tryPutItem(entry, makeItem("iron_ore"));
|
||||
|
||||
// With kFastBeltSpeed, items cross one tile per tick.
|
||||
// entry tile: 1 tick to reach front progress 1.0
|
||||
// transit: 3 tiles distance → 3 ticks
|
||||
// exit tile: 1 tick to reach front progress 1.0
|
||||
// Total: 1 (entry) + 1 (entry→transit) + 3 (transit) + 1 (transit→exit) + 1 (exit advance) = ~5-7 ticks
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
REQUIRE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
const std::optional<Item> taken = bs.tryTakeItem(Port{exit, Rotation::East});
|
||||
REQUIRE(taken.has_value());
|
||||
REQUIRE(taken->type.id == "iron_ore");
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tunnel pairing — wrong direction prevents pair", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
bs.placeTunnelEntry(QPoint(0, 0), Rotation::East, 10);
|
||||
bs.placeTunnelExit(QPoint(3, 0), Rotation::North);
|
||||
|
||||
bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore"));
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
// Exit faces North, not East — no pair formed, item stuck in entry.
|
||||
REQUIRE_FALSE(bs.peekItem(Port{QPoint(3, 0), Rotation::North}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tunnel pairing — beyond max distance prevents pair", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
bs.placeTunnelEntry(QPoint(0, 0), Rotation::East, 2);
|
||||
bs.placeTunnelExit(QPoint(3, 0), Rotation::East);
|
||||
|
||||
bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore"));
|
||||
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
REQUIRE_FALSE(bs.peekItem(Port{QPoint(3, 0), Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tunnel pairing — same-dir entry between blocks pairing", "[belt]")
|
||||
{
|
||||
// Entry1 at (0,0) East, Entry2 at (2,0) East, Exit at (4,0) East.
|
||||
// Entry2 is closer to Exit → Entry2 pairs with Exit; Entry1 is blocked by Entry2.
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
bs.placeTunnelEntry(QPoint(0, 0), Rotation::East, 10);
|
||||
bs.placeTunnelEntry(QPoint(2, 0), Rotation::East, 10);
|
||||
bs.placeTunnelExit(QPoint(4, 0), Rotation::East);
|
||||
|
||||
// Put item on Entry2 — should reach exit.
|
||||
bs.tryPutItem(QPoint(2, 0), makeItem("copper_ore"));
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE(bs.peekItem(Port{QPoint(4, 0), Rotation::East}).has_value());
|
||||
bs.tryTakeItem(Port{QPoint(4, 0), Rotation::East});
|
||||
|
||||
// Put item on Entry1 — should NOT reach exit (Entry1 is unpaired).
|
||||
bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore"));
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE_FALSE(bs.peekItem(Port{QPoint(4, 0), Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: tunnel pairing — cross-dir entry between is ignored", "[belt]")
|
||||
{
|
||||
// Entry1 at (0,0) East, Entry2 at (2,0) North (different dir), Exit at (4,0) East.
|
||||
// Entry2 faces North → ignored → Entry1 pairs with Exit.
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
bs.placeTunnelEntry(QPoint(0, 0), Rotation::East, 10);
|
||||
bs.placeTunnelEntry(QPoint(2, 0), Rotation::North, 10);
|
||||
bs.placeTunnelExit(QPoint(4, 0), Rotation::East);
|
||||
|
||||
bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore"));
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE(bs.peekItem(Port{QPoint(4, 0), Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tunnel — item transit
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BeltSystem: unpaired entry blocks items at front", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
bs.placeTunnelEntry(QPoint(0, 0), Rotation::East, 10);
|
||||
// No exit placed — entry is unpaired.
|
||||
|
||||
bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore"));
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
// Item should not vanish — it stays in the entry.
|
||||
// We can verify by placing an exit and seeing item eventually arrive.
|
||||
bs.placeTunnelExit(QPoint(3, 0), Rotation::East);
|
||||
for (int i = 0; i < 20; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE(bs.peekItem(Port{QPoint(3, 0), Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: demolish entry discards transit items", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
const QPoint entry(0, 0);
|
||||
const QPoint exit(5, 0);
|
||||
|
||||
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||
bs.placeTunnelExit(exit, Rotation::East);
|
||||
|
||||
bs.tryPutItem(entry, makeItem("iron_ore"));
|
||||
|
||||
// Advance just enough for item to enter transit but not reach exit.
|
||||
bs.tick(); // item enters entry front
|
||||
bs.tick(); // entry front → transit (progress 0)
|
||||
|
||||
bs.removeTile(entry);
|
||||
|
||||
// Even with many more ticks, nothing arrives at exit.
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: clearTiles discards tunnel transit items", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
const QPoint entry(0, 0);
|
||||
const QPoint exit(5, 0);
|
||||
|
||||
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||
bs.placeTunnelExit(exit, Rotation::East);
|
||||
|
||||
bs.tryPutItem(entry, makeItem("iron_ore"));
|
||||
bs.tick();
|
||||
bs.tick();
|
||||
|
||||
bs.clearTiles({entry});
|
||||
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: belt to entry to transit to exit to belt full chain", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
const QPoint beltIn(0, 0);
|
||||
const QPoint entry(1, 0);
|
||||
const QPoint exit(4, 0);
|
||||
const QPoint beltOut(5, 0);
|
||||
|
||||
bs.placeBelt(beltIn, Rotation::East);
|
||||
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||
bs.placeTunnelExit(exit, Rotation::East);
|
||||
bs.placeBelt(beltOut, Rotation::East);
|
||||
|
||||
bs.tryPutItem(beltIn, makeItem("iron_ore"));
|
||||
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
// Item should have arrived on beltOut.
|
||||
REQUIRE(bs.peekItem(eastPort(beltOut)).has_value());
|
||||
const std::optional<Item> taken = bs.tryTakeItem(eastPort(beltOut));
|
||||
REQUIRE(taken.has_value());
|
||||
REQUIRE(taken->type.id == "iron_ore");
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: multiple items transit tunnel in order", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
const QPoint entry(0, 0);
|
||||
const QPoint exit(3, 0);
|
||||
|
||||
bs.placeTunnelEntry(entry, Rotation::East, 10);
|
||||
bs.placeTunnelExit(exit, Rotation::East);
|
||||
|
||||
bs.tryPutItem(entry, makeItem("item1"));
|
||||
bs.tick();
|
||||
bs.tick(); // item1 enters transit
|
||||
|
||||
bs.tryPutItem(entry, makeItem("item2"));
|
||||
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
// item1 should arrive first.
|
||||
const std::optional<Item> taken1 = bs.tryTakeItem(Port{exit, Rotation::East});
|
||||
REQUIRE(taken1.has_value());
|
||||
REQUIRE(taken1->type.id == "item1");
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
|
||||
const std::optional<Item> taken2 = bs.tryTakeItem(Port{exit, Rotation::East});
|
||||
REQUIRE(taken2.has_value());
|
||||
REQUIRE(taken2->type.id == "item2");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user