Belts are no longer placed on hover during a drag. Left-press anchors the drag; as the cursor moves, a rectilinear (L-shaped) path of belt ghosts is previewed from the anchor to the cursor — first leg parallel to the belt's current orientation, then orthogonal — with each tile auto-oriented to follow the path. Construction sites are placed on release: new valid tiles are placed (subject to cumulative affordability), tiles holding only a belt are re-oriented in place, and occupied/invalid tiles are skipped. Unaffordable tiles show no ghost. Right-click during a drag cancels it without leaving belt build mode. Path geometry is factored into a pure computeBeltDragPath() in lib/core with Catch2 coverage; GameWorldView owns the classification, affordability, and rendering against live sim state. Implements REQ-BLD-BELT-DRAG. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
158 lines
6.5 KiB
C++
158 lines
6.5 KiB
C++
#include "catch.hpp"
|
|
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
|
|
#include "BeltDragPath.h"
|
|
#include "Rotation.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static std::vector<QPoint> tilesOf(const std::vector<BeltPathTile>& path)
|
|
{
|
|
std::vector<QPoint> tiles;
|
|
for (const BeltPathTile& entry : path) { tiles.push_back(entry.tile); }
|
|
return tiles;
|
|
}
|
|
|
|
static std::vector<Rotation> rotationsOf(const std::vector<BeltPathTile>& path)
|
|
{
|
|
std::vector<Rotation> rotations;
|
|
for (const BeltPathTile& entry : path) { rotations.push_back(entry.rotation); }
|
|
return rotations;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Single tile
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("Single-tile path keeps the belt orientation")
|
|
{
|
|
for (Rotation orientation : { Rotation::North, Rotation::East,
|
|
Rotation::South, Rotation::West })
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(3, 4), QPoint(3, 4), orientation);
|
|
REQUIRE(path.size() == 1);
|
|
REQUIRE(path[0].tile == QPoint(3, 4));
|
|
REQUIRE(path[0].rotation == orientation);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Straight runs
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("Straight horizontal run faces along the row toward the cursor")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(3, 0), Rotation::East);
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(3, 0) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::East, Rotation::East, Rotation::East, Rotation::East });
|
|
}
|
|
|
|
TEST_CASE("Straight horizontal run toward the left faces West")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(-2, 0), Rotation::East);
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(-1, 0), QPoint(-2, 0) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::West, Rotation::West, Rotation::West });
|
|
}
|
|
|
|
TEST_CASE("Straight vertical run faces along the column toward the cursor")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(0, 3), Rotation::South);
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(0, 3) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::South, Rotation::South, Rotation::South, Rotation::South });
|
|
}
|
|
|
|
// A vertical target with a horizontal orientation still yields a straight vertical
|
|
// line (the parallel-axis leg is zero-length).
|
|
TEST_CASE("Vertical target with horizontal orientation is a straight vertical line")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(2, 0), QPoint(2, 2), Rotation::East);
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(2, 0), QPoint(2, 1), QPoint(2, 2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::South, Rotation::South, Rotation::South });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// L-shaped paths — horizontal-first (East/West orientation)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("East orientation goes horizontal then vertical (down-right)")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(2, 2), Rotation::East);
|
|
// Leg 1 East to the corner (2,0), then Leg 2 South to the cursor (2,2).
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(2, 1), QPoint(2, 2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::East, Rotation::East, Rotation::South, Rotation::South,
|
|
Rotation::South });
|
|
}
|
|
|
|
TEST_CASE("East orientation with cursor up-left goes horizontal (West) then vertical (North)")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(-2, -2), Rotation::East);
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(-1, 0), QPoint(-2, 0), QPoint(-2, -1),
|
|
QPoint(-2, -2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::West, Rotation::West, Rotation::North, Rotation::North,
|
|
Rotation::North });
|
|
}
|
|
|
|
TEST_CASE("West orientation is horizontal-first as well (up-right cursor)")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(2, -2), Rotation::West);
|
|
// Horizontal axis first: East toward the cursor to the corner (2,0), then North.
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(1, 0), QPoint(2, 0), QPoint(2, -1), QPoint(2, -2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::East, Rotation::East, Rotation::North, Rotation::North,
|
|
Rotation::North });
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// L-shaped paths — vertical-first (North/South orientation)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
TEST_CASE("South orientation goes vertical then horizontal (down-right)")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(2, 2), Rotation::South);
|
|
// Leg 1 South to the corner (0,2), then Leg 2 East to the cursor (2,2).
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(1, 2), QPoint(2, 2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::South, Rotation::South, Rotation::East, Rotation::East,
|
|
Rotation::East });
|
|
}
|
|
|
|
TEST_CASE("North orientation is vertical-first (down-left cursor)")
|
|
{
|
|
const std::vector<BeltPathTile> path =
|
|
computeBeltDragPath(QPoint(0, 0), QPoint(-2, 2), Rotation::North);
|
|
// Vertical axis first: South toward the cursor to the corner (0,2), then West.
|
|
REQUIRE(tilesOf(path) == std::vector<QPoint>{
|
|
QPoint(0, 0), QPoint(0, 1), QPoint(0, 2), QPoint(-1, 2), QPoint(-2, 2) });
|
|
REQUIRE(rotationsOf(path) == std::vector<Rotation>{
|
|
Rotation::South, Rotation::South, Rotation::West, Rotation::West,
|
|
Rotation::West });
|
|
}
|