dont require output belts to be aligned with output ports

This commit is contained in:
2026-04-22 21:15:39 +02:00
parent 36d6842f71
commit f29dc9862a
6 changed files with 45 additions and 61 deletions

View File

@@ -92,18 +92,14 @@ void BeltSystem::setSplitterFilters(QPoint tile,
// Port interface
// ---------------------------------------------------------------------------
bool BeltSystem::tryPutItem(Port port, Item item)
bool BeltSystem::tryPutItem(QPoint tile, Item item)
{
const std::map<std::pair<int, int>, BeltTile>::iterator it = m_belts.find(key(port.tile));
const std::map<std::pair<int, int>, BeltTile>::iterator it = m_belts.find(key(tile));
if (it == m_belts.end())
{
return false;
}
if (it->second.direction != port.direction)
{
return false;
}
return tryPlaceOnBelt(port.tile, item);
return tryPlaceOnBelt(tile, item);
}
std::optional<Item> BeltSystem::tryTakeItem(Port port)

View File

@@ -56,9 +56,9 @@ public:
// port.tile = the belt tile adjacent to the building
// port.direction = direction items flow on that tile
//
// tryPutItem: place item onto port.tile entering from the opposite side.
// Returns false if the tile is not a belt, direction mismatches, or tile full.
bool tryPutItem(Port port, Item item);
// tryPutItem: place item onto tile.
// Returns false if the tile is not a belt, or tile full.
bool tryPutItem(QPoint tile, Item item);
// tryTakeItem: remove and return the leading item from port.tile.
// Returns nullopt if tile is not a belt, direction mismatches, or tile empty.
@@ -119,3 +119,4 @@ private:
std::map<std::pair<int, int>, BeltTile> m_belts;
std::map<std::pair<int, int>, SplitterTile> m_splitters;
};

View File

@@ -726,7 +726,7 @@ void BuildingSystem::tickBeltPush()
break;
}
const Item item = building.outputBuffer.items.front();
if (m_belts.tryPutItem(outputPort, item))
if (m_belts.tryPutItem(outputPort.tile, item))
{
building.outputBuffer.items.erase(building.outputBuffer.items.begin());
}

View File

@@ -36,29 +36,20 @@ static Port eastPort(QPoint tile)
// Placement
// ---------------------------------------------------------------------------
TEST_CASE("BeltSystem: tryPutItem succeeds on registered belt with matching direction", "[belt]")
TEST_CASE("BeltSystem: tryPutItem succeeds on registered belt", "[belt]")
{
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
REQUIRE(bs.tryPutItem(tile, makeItem("iron_ore")));
}
TEST_CASE("BeltSystem: tryPutItem fails on unregistered tile", "[belt]")
{
BeltSystem bs(kFastBeltSpeed);
REQUIRE_FALSE(bs.tryPutItem(eastPort(QPoint(0, 0)), makeItem("iron_ore")));
}
TEST_CASE("BeltSystem: tryPutItem fails on direction mismatch", "[belt]")
{
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::North);
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
REQUIRE_FALSE(bs.tryPutItem(QPoint(0, 0), makeItem("iron_ore")));
}
TEST_CASE("BeltSystem: tryPutItem fails after removeTile", "[belt]")
@@ -68,7 +59,7 @@ TEST_CASE("BeltSystem: tryPutItem fails after removeTile", "[belt]")
bs.placeBelt(tile, Rotation::East);
bs.removeTile(tile);
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("iron_ore")));
}
// ---------------------------------------------------------------------------
@@ -81,8 +72,8 @@ TEST_CASE("BeltSystem: two items fit in one tile", "[belt]")
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("iron_ore")));
REQUIRE(bs.tryPutItem(eastPort(tile), makeItem("copper_ore")));
REQUIRE(bs.tryPutItem(tile, makeItem("iron_ore")));
REQUIRE(bs.tryPutItem(tile, makeItem("copper_ore")));
}
TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
@@ -91,10 +82,10 @@ TEST_CASE("BeltSystem: third tryPutItem on full tile returns false", "[belt]")
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("a"));
bs.tryPutItem(eastPort(tile), makeItem("b"));
bs.tryPutItem(tile, makeItem("a"));
bs.tryPutItem(tile, makeItem("b"));
REQUIRE_FALSE(bs.tryPutItem(eastPort(tile), makeItem("c")));
REQUIRE_FALSE(bs.tryPutItem(tile, makeItem("c")));
}
// ---------------------------------------------------------------------------
@@ -106,7 +97,7 @@ TEST_CASE("BeltSystem: tryTakeItem returns placed item after reaching output edg
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
bs.tryPutItem(tile, makeItem("iron_ore"));
bs.tick(); // advance to output edge
const std::optional<Item> taken = bs.tryTakeItem(eastPort(tile));
@@ -120,7 +111,7 @@ TEST_CASE("BeltSystem: tryTakeItem requires item to reach output edge before yie
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
bs.tryPutItem(tile, makeItem("iron_ore"));
// Item placed but not yet at output edge — must not be available.
REQUIRE_FALSE(bs.tryTakeItem(eastPort(tile)).has_value());
@@ -136,8 +127,8 @@ TEST_CASE("BeltSystem: tryTakeItem with two items returns both after each reache
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("first"));
bs.tryPutItem(eastPort(tile), makeItem("second"));
bs.tryPutItem(tile, makeItem("first"));
bs.tryPutItem(tile, makeItem("second"));
// Front item reaches output edge after one tick.
bs.tick();
@@ -160,16 +151,6 @@ TEST_CASE("BeltSystem: tryTakeItem returns nullopt on empty tile", "[belt]")
REQUIRE_FALSE(bs.tryTakeItem(eastPort(QPoint(0, 0))).has_value());
}
TEST_CASE("BeltSystem: tryTakeItem returns nullopt on direction mismatch", "[belt]")
{
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::North);
bs.tryPutItem(Port{tile, Rotation::North}, makeItem("x"));
REQUIRE_FALSE(bs.tryTakeItem(eastPort(tile)).has_value());
}
// ---------------------------------------------------------------------------
// tick() — item advancement
// ---------------------------------------------------------------------------
@@ -182,7 +163,7 @@ TEST_CASE("BeltSystem: item transfers from tile A to tile B and becomes availabl
bs.placeBelt(tileA, Rotation::East);
bs.placeBelt(tileB, Rotation::East);
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
bs.tryPutItem(tileA, makeItem("iron_ore"));
bs.tick(); // item reaches output edge of A, moves to B at progress 0
bs.tick(); // item reaches output edge of B
@@ -198,7 +179,7 @@ TEST_CASE("BeltSystem: item stays at progress 1.0 when next tile is absent", "[b
const QPoint tileA(0, 0);
bs.placeBelt(tileA, Rotation::East);
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
bs.tryPutItem(tileA, makeItem("iron_ore"));
bs.tick();
// Item should still be on tileA (no registered tile to the east).
@@ -215,7 +196,7 @@ TEST_CASE("BeltSystem: item traverses 3-tile chain in 3 ticks (one per tile)", "
bs.placeBelt(tileB, Rotation::East);
bs.placeBelt(tileC, Rotation::East);
bs.tryPutItem(eastPort(tileA), makeItem("iron_ore"));
bs.tryPutItem(tileA, makeItem("iron_ore"));
bs.tick(); // A output edge → moves to B at progress 0
bs.tick(); // B output edge → moves to C at progress 0
bs.tick(); // C output edge → available for pickup
@@ -234,11 +215,11 @@ TEST_CASE("BeltSystem: item stays blocked when next tile is full", "[belt]")
bs.placeBelt(tileB, Rotation::East);
// Fill tileB to capacity.
bs.tryPutItem(eastPort(tileB), makeItem("b1"));
bs.tryPutItem(eastPort(tileB), makeItem("b2"));
bs.tryPutItem(tileB, makeItem("b1"));
bs.tryPutItem(tileB, makeItem("b2"));
// Place item in tileA — should be blocked.
bs.tryPutItem(eastPort(tileA), makeItem("a1"));
bs.tryPutItem(tileA, makeItem("a1"));
bs.tick();
// Item in tileA must still be there.
@@ -254,8 +235,8 @@ TEST_CASE("BeltSystem: clearTiles removes all items from specified tiles", "[bel
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
bs.tryPutItem(eastPort(tile), makeItem("copper_ore"));
bs.tryPutItem(tile, makeItem("iron_ore"));
bs.tryPutItem(tile, makeItem("copper_ore"));
bs.clearTiles({tile});
@@ -271,7 +252,7 @@ TEST_CASE("BeltSystem: forEachVisualItem visits items inside viewport", "[belt]"
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(5, 5);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
bs.tryPutItem(tile, makeItem("iron_ore"));
int count = 0;
bs.forEachVisualItem(QRect(0, 0, 20, 20), [&count](VisualItem) { ++count; });
@@ -284,7 +265,7 @@ TEST_CASE("BeltSystem: forEachVisualItem skips items outside viewport", "[belt]"
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(50, 50);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("iron_ore"));
bs.tryPutItem(tile, makeItem("iron_ore"));
int count = 0;
bs.forEachVisualItem(QRect(0, 0, 20, 20), [&count](VisualItem) { ++count; });
@@ -297,7 +278,7 @@ TEST_CASE("BeltSystem: forEachVisualItem reports correct ItemType", "[belt]")
BeltSystem bs(kFastBeltSpeed);
const QPoint tile(0, 0);
bs.placeBelt(tile, Rotation::East);
bs.tryPutItem(eastPort(tile), makeItem("copper_ingot"));
bs.tryPutItem(tile, makeItem("copper_ingot"));
std::vector<ItemType> seen;
bs.forEachVisualItem(QRect(-1, -1, 10, 10), [&seen](VisualItem vi)
@@ -329,10 +310,10 @@ TEST_CASE("BeltSystem: splitter alternates between outputA and outputB", "[belt]
bs.placeBelt(tileA, Rotation::North);
bs.placeBelt(tileB, Rotation::South);
bs.tryPutItem(eastPort(tileIn), makeItem("item1"));
bs.tryPutItem(tileIn, makeItem("item1"));
bs.tick(); // item moves: tileIn -> splitter held
bs.tryPutItem(eastPort(tileIn), makeItem("item2"));
bs.tryPutItem(tileIn, makeItem("item2"));
bs.tick(); // item1 routes to outputA (North=tileA); item2 moves to splitter
bs.tick(); // item2 routes to outputB (South=tileB)
@@ -366,7 +347,7 @@ TEST_CASE("BeltSystem: splitter routes filtered item to matching output", "[belt
// Filter: outputA = iron_ore only; outputB = accept all.
bs.setSplitterFilters(tileSpl, {ItemType{"iron_ore"}}, {});
bs.tryPutItem(eastPort(tileIn), makeItem("iron_ore"));
bs.tryPutItem(tileIn, makeItem("iron_ore"));
bs.tick(); // tileIn -> splitter held
bs.tick(); // routed to outputA (filter match)

View File

@@ -107,7 +107,7 @@ TEST_CASE("BuildingSystem: placing a belt registers it with BeltSystem", "[build
bs.place(BuildingType::Belt, QPoint(5, 5), Rotation::East, 0);
REQUIRE(belts.tryPutItem(eastPort(QPoint(5, 5)), makeItem("iron_ore")));
REQUIRE(belts.tryPutItem(QPoint(5, 5), makeItem("iron_ore")));
REQUIRE(bs.allBuildings().empty()); // belts do not create Building instances
}
@@ -145,6 +145,12 @@ TEST_CASE("BuildingSystem: demolish frees tiles and returns refund", "[building]
rng);
const EntityId id = bs.place(BuildingType::Miner, QPoint(0, 0), Rotation::East, 0);
// Miner construction_time_seconds = 10. completesAt = secondsToTicks(10) = 300.
// We need to process tick 300 itself, so run 301 ticks (ticks 0..300).
Tick tick = 0;
runTicks(bs, belts, static_cast<int>(secondsToTicks(10.0)) + 1, tick);
const int refund = bs.demolish(id);
// Miner cost = 15, refund = floor(15 * 75 / 100) = 11.
@@ -341,7 +347,7 @@ TEST_CASE("BuildingSystem: smelter input buffer fills from adjacent west-flowing
// Place west-flowing belt at (2,0): belt flows West, delivers to smelter.
belts.placeBelt(QPoint(2, 0), Rotation::West);
belts.tryPutItem(westPort(QPoint(2, 0)), makeItem("iron_ore"));
belts.tryPutItem(QPoint(2, 0), makeItem("iron_ore"));
bs.tickBeltPull();
@@ -493,7 +499,7 @@ TEST_CASE("BuildingSystem: reprocessing plant produces one cycle output then sta
belts.placeBelt(QPoint(-1, 0), Rotation::East);
for (int i = 0; i < 5; ++i)
{
belts.tryPutItem(eastPort(QPoint(-1, 0)), makeItem("scrap"));
belts.tryPutItem(QPoint(-1, 0), makeItem("scrap"));
bs.tickBeltPull();
}