list what a belt is carrying, and clear it by name
The belt card's only content was a button reading "Clear stuck items", which assumed a state the items need not be in. It now reads "Clear items", and above it the card lists what the selected tiles hold -- one item chip per type, the same chip the buffer sections and the HQ's block stock draw -- so a line's contents can be read before they are removed, and can be read at all: items on a moving belt are too small and too transient to count by eye, and items inside a tunnel are drawn nowhere. BeltSystem gains countItems(tiles), a query of the same kind as forEachVisualItem: a method rather than exposed tile containers, so the per-tile representation stays swappable. It walks the same five containers as clearTiles, in the same order, so the list and the button cannot drift apart. The tunnel's two ends are now told apart. Items in transit are counted on the exit they are travelling toward, and a clear removes exactly what the panel listed for the tile it acts on: the exit discards them, the entry leaves them travelling. BeltSystemTest's tunnel case splits in two accordingly. Splitters now aggregate with belts and tunnel ends. Their output filters are per-object configuration, which an aggregate simply does not show -- a splitter selected alone still gets them. Both cards share one BeltItemList widget, and both derive their tiles from collectBeltTiles, so nothing is stated twice. The mixed count summary loses the clear action it carried: a button acting on part of a selection is worse than no button, and the tiles can be selected by themselves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include "catch.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -327,6 +328,87 @@ TEST_CASE("BeltSystem: forEachVisualItem reports correct ItemType", "[belt]")
|
||||
REQUIRE(seen[0].id == "copper_ingot");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// countItems (REQ-UI-BELT-ITEMS)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
TEST_CASE("BeltSystem: countItems sums one item type over several tiles", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint first(0, 0);
|
||||
const QPoint second(1, 0);
|
||||
bs.placeBelt(first, Rotation::East);
|
||||
bs.placeBelt(second, Rotation::East);
|
||||
bs.tryPutItem(first, makeItem("iron_ore"), Rotation::East);
|
||||
bs.tryPutItem(second, makeItem("iron_ore"), Rotation::East);
|
||||
bs.tryPutItem(second, makeItem("copper_ore"), Rotation::East);
|
||||
|
||||
const std::map<ItemType, int> counts = bs.countItems({first, second});
|
||||
|
||||
REQUIRE(counts.size() == 2);
|
||||
REQUIRE(counts.at(ItemType{"iron_ore"}) == 2);
|
||||
REQUIRE(counts.at(ItemType{"copper_ore"}) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: countItems ignores tiles outside the given selection", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint selected(0, 0);
|
||||
const QPoint other(1, 0);
|
||||
bs.placeBelt(selected, Rotation::East);
|
||||
bs.placeBelt(other, Rotation::East);
|
||||
bs.tryPutItem(other, makeItem("iron_ore"), Rotation::East);
|
||||
|
||||
REQUIRE(bs.countItems({selected}).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: countItems counts a splitter's held items", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint feed(0, 0);
|
||||
const QPoint splitter(1, 0);
|
||||
bs.placeBelt(feed, Rotation::East);
|
||||
bs.placeSplitter(splitter, Rotation::North, Rotation::South);
|
||||
|
||||
// One item routed onto an output slot, one still unassigned in the back.
|
||||
bs.tryPutItem(splitter, makeItem("iron_ore"), Rotation::East);
|
||||
bs.tick();
|
||||
bs.tryPutItem(splitter, makeItem("iron_ore"), Rotation::East);
|
||||
|
||||
REQUIRE(bs.countItems({splitter}).at(ItemType{"iron_ore"}) == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: countItems counts transit items on the exit, not the entry",
|
||||
"[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"), Rotation::East);
|
||||
bs.tick(); // item enters the entry's front slot
|
||||
bs.tick(); // entry front -> transit
|
||||
|
||||
// Invisible in the world and drawn nowhere, so the exit's count is the only place
|
||||
// the player can see it (REQ-BLD-TUNNEL-TRANSIT).
|
||||
REQUIRE(bs.countItems({entry}).empty());
|
||||
REQUIRE(bs.countItems({exit}).at(ItemType{"iron_ore"}) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: countItems on empty tiles reports nothing", "[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
const QPoint tile(0, 0);
|
||||
bs.placeBelt(tile, Rotation::East);
|
||||
|
||||
REQUIRE(bs.countItems({tile}).empty());
|
||||
REQUIRE(bs.countItems({}).empty());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Splitter — basic alternation (no filters)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1000,7 +1082,34 @@ TEST_CASE("BeltSystem: deconstruct entry discards transit items", "[belt]")
|
||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: clearTiles discards tunnel transit items", "[belt]")
|
||||
// A clear removes exactly what countItems reports for the tile it acts on
|
||||
// (REQ-UI-BELT-CLEAR, REQ-UI-BELT-ITEMS): the transit items belong to the exit, so only
|
||||
// the exit's clear discards them.
|
||||
TEST_CASE("BeltSystem: clearTiles on a tunnel exit 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"), Rotation::East);
|
||||
bs.tick();
|
||||
bs.tick();
|
||||
|
||||
bs.clearTiles({exit});
|
||||
|
||||
for (int i = 0; i < 30; ++i)
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: clearTiles on a tunnel entry leaves transit items travelling",
|
||||
"[belt]")
|
||||
{
|
||||
BeltSystem bs(kFastBeltSpeed);
|
||||
|
||||
@@ -1020,7 +1129,7 @@ TEST_CASE("BeltSystem: clearTiles discards tunnel transit items", "[belt]")
|
||||
{
|
||||
bs.tick();
|
||||
}
|
||||
REQUIRE_FALSE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
REQUIRE(bs.peekItem(Port{exit, Rotation::East}).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE("BeltSystem: belt to entry to transit to exit to belt full chain", "[belt]")
|
||||
|
||||
Reference in New Issue
Block a user