diff --git a/core/src/Rail.cpp b/core/src/Rail.cpp new file mode 100644 index 0000000..1cd5005 --- /dev/null +++ b/core/src/Rail.cpp @@ -0,0 +1,279 @@ +#include "vf/Rail.h" + +namespace vf { + +namespace { + +bool railAt(const World& world, const Vec3i& cell) { + return isRailBlock(world.blockAt(cell.x, cell.y, cell.z)); +} + +bool poweredRailAt(const World& world, const Vec3i& cell) { + return world.blockAt(cell.x, cell.y, cell.z) == Block::PoweredRail; +} + +struct Neighbours { + bool n = false, s = false, e = false, w = false; + bool nUp = false, sUp = false, eUp = false, wUp = false; +}; + +void probe(const World& world, const Vec3i& pos, RailDir dir, bool& conn, bool& ascend) { + Vec3i flat = pos + railDelta(dir); + Vec3i up = flat + Vec3i{0, 1, 0}; + Vec3i down = flat + Vec3i{0, -1, 0}; + bool flatRail = railAt(world, flat); + bool upRail = railAt(world, up); + bool downRail = railAt(world, down); + conn = flatRail || upRail || downRail; + ascend = upRail && !flatRail; +} + +Neighbours gatherNeighbours(const World& world, const Vec3i& pos) { + Neighbours out; + probe(world, pos, RailDir::North, out.n, out.nUp); + probe(world, pos, RailDir::South, out.s, out.sUp); + probe(world, pos, RailDir::East, out.e, out.eUp); + probe(world, pos, RailDir::West, out.w, out.wUp); + return out; +} + +bool nextPoweredRail(const World& world, const Vec3i& cur, RailDir dir, Vec3i& out) { + Vec3i candidate = cur + railDelta(dir); + for (int dy : {0, 1, -1}) { + Vec3i cell = candidate + Vec3i{0, dy, 0}; + if (poweredRailAt(world, cell)) { + out = cell; + return true; + } + } + return false; +} + +bool railEndNeighbour(const World& world, const Vec3i& pos, RailShape shape, + RailDir endDir, Vec3i& out) { + Vec3i base = pos + railDelta(endDir); + if (isAscending(shape) && endDir == ascendUpDir(shape)) base.y += 1; + Vec3i lower = base + Vec3i{0, -1, 0}; + if (railAt(world, base)) { + out = base; + return true; + } + if (railAt(world, lower)) { + out = lower; + return true; + } + return false; +} + +} // namespace + +RailDir opposite(RailDir dir) { + switch (dir) { + case RailDir::North: return RailDir::South; + case RailDir::South: return RailDir::North; + case RailDir::East: return RailDir::West; + case RailDir::West: return RailDir::East; + } + return RailDir::North; +} + +Vec3i railDelta(RailDir dir) { + switch (dir) { + case RailDir::North: return {0, 0, -1}; + case RailDir::South: return {0, 0, 1}; + case RailDir::East: return {1, 0, 0}; + case RailDir::West: return {-1, 0, 0}; + } + return {0, 0, 0}; +} + +bool isAscending(RailShape shape) { + return shape == RailShape::AscendNorth || shape == RailShape::AscendSouth || + shape == RailShape::AscendEast || shape == RailShape::AscendWest; +} + +RailDir ascendUpDir(RailShape shape) { + switch (shape) { + case RailShape::AscendNorth: return RailDir::North; + case RailShape::AscendSouth: return RailDir::South; + case RailShape::AscendEast: return RailDir::East; + case RailShape::AscendWest: return RailDir::West; + default: return RailDir::North; + } +} + +std::pair railEnds(RailShape shape) { + switch (shape) { + case RailShape::NorthSouth: + case RailShape::AscendNorth: + case RailShape::AscendSouth: + return {RailDir::North, RailDir::South}; + case RailShape::EastWest: + case RailShape::AscendEast: + case RailShape::AscendWest: + return {RailDir::East, RailDir::West}; + case RailShape::CurveSouthEast: return {RailDir::South, RailDir::East}; + case RailShape::CurveSouthWest: return {RailDir::South, RailDir::West}; + case RailShape::CurveNorthWest: return {RailDir::North, RailDir::West}; + case RailShape::CurveNorthEast: return {RailDir::North, RailDir::East}; + } + return {RailDir::North, RailDir::South}; +} + +RailShape inferRailShape(const World& world, const Vec3i& pos, bool switched) { + Neighbours nb = gatherNeighbours(world, pos); + bool canCurve = world.blockAt(pos.x, pos.y, pos.z) == Block::Rail; + int count = int(nb.n) + int(nb.s) + int(nb.e) + int(nb.w); + + RailShape shape = RailShape::NorthSouth; + if (nb.n && nb.s) shape = RailShape::NorthSouth; + else if (nb.e && nb.w) shape = RailShape::EastWest; + else if (canCurve && nb.s && nb.e) shape = RailShape::CurveSouthEast; + else if (canCurve && nb.s && nb.w) shape = RailShape::CurveSouthWest; + else if (canCurve && nb.n && nb.w) shape = RailShape::CurveNorthWest; + else if (canCurve && nb.n && nb.e) shape = RailShape::CurveNorthEast; + else if (nb.n || nb.s) shape = RailShape::NorthSouth; + else if (nb.e || nb.w) shape = RailShape::EastWest; + + if (switched && canCurve && count >= 3) { + if (nb.n && nb.s && nb.e) shape = RailShape::CurveSouthEast; + else if (nb.n && nb.s && nb.w) shape = RailShape::CurveSouthWest; + else if (nb.e && nb.w && nb.n) shape = RailShape::CurveNorthEast; + else if (nb.e && nb.w && nb.s) shape = RailShape::CurveSouthEast; + } + + if (shape == RailShape::NorthSouth) { + if (nb.nUp) shape = RailShape::AscendNorth; + else if (nb.sUp) shape = RailShape::AscendSouth; + } else if (shape == RailShape::EastWest) { + if (nb.eUp) shape = RailShape::AscendEast; + else if (nb.wUp) shape = RailShape::AscendWest; + } + + return shape; +} + +RailStep advanceOnRail(const World& world, const Vec3i& pos, RailDir moveDir, bool switched) { + RailStep step; + if (!railAt(world, pos)) return step; + + RailShape shape = inferRailShape(world, pos, switched); + std::pair ends = railEnds(shape); + RailDir cameFrom = opposite(moveDir); + + RailDir exitDir; + if (cameFrom == ends.first) exitDir = ends.second; + else if (cameFrom == ends.second) exitDir = ends.first; + else return step; + + Vec3i out; + if (!railEndNeighbour(world, pos, shape, exitDir, out)) return step; + step.nextPos = out; + step.exitDir = exitDir; + step.valid = true; + return step; +} + +std::unordered_set activePoweredRails( + const World& world, + const std::vector& poweredRails, + const std::function& directlyPowered) { + std::unordered_set active; + std::vector sources; + for (const Vec3i& rail : poweredRails) { + if (directlyPowered(rail)) { + active.insert(rail); + sources.push_back(rail); + } + } + + for (const Vec3i& source : sources) { + RailShape shape = inferRailShape(world, source); + std::pair ends = railEnds(shape); + for (RailDir dir : {ends.first, ends.second}) { + Vec3i cur = source; + for (int step = 1; step <= poweredRailReach; ++step) { + Vec3i nxt; + if (!nextPoweredRail(world, cur, dir, nxt)) break; + active.insert(nxt); + cur = nxt; + } + } + } + return active; +} + +std::unordered_set activePoweredRails( + const World& world, + const std::vector& poweredRails, + const RedstoneCircuit& circuit) { + return activePoweredRails(world, poweredRails, [&](const Vec3i& p) { + return circuit.poweredAt(p.x, p.y, p.z) || circuit.poweredAt(p.x, p.y - 1, p.z); + }); +} + +void RailGraph::build(const World& world, const std::vector& railCells, bool switched) { + adjacency.clear(); + for (const Vec3i& cell : railCells) { + if (!railAt(world, cell)) continue; + RailShape shape = inferRailShape(world, cell, switched); + std::pair ends = railEnds(shape); + std::vector links; + for (RailDir end : {ends.first, ends.second}) { + Vec3i out; + if (railEndNeighbour(world, cell, shape, end, out)) links.push_back(out); + } + adjacency.emplace(cell, std::move(links)); + } +} + +std::vector RailGraph::neighbours(const Vec3i& pos) const { + auto it = adjacency.find(pos); + if (it == adjacency.end()) return {}; + return it->second; +} + +std::vector RailGraph::trace(const World& world, const Vec3i& start, + RailDir dir, int maxSteps) const { + std::vector path; + std::unordered_set seen; + Vec3i cur = start; + RailDir heading = dir; + for (int step = 0; step < maxSteps; ++step) { + if (seen.count(cur)) break; + path.push_back(cur); + seen.insert(cur); + RailStep next = advanceOnRail(world, cur, heading); + if (!next.valid) break; + cur = next.nextPos; + heading = next.exitDir; + } + return path; +} + +int RailGraph::distance(const Vec3i& a, const Vec3i& b) const { + if (!contains(a) || !contains(b)) return -1; + if (a == b) return 0; + + std::unordered_map depth; + std::vector frontier{a}; + depth[a] = 0; + while (!frontier.empty()) { + std::vector nextFrontier; + for (const Vec3i& node : frontier) { + int nodeDepth = depth[node]; + auto it = adjacency.find(node); + if (it == adjacency.end()) continue; + for (const Vec3i& neighbour : it->second) { + if (depth.find(neighbour) != depth.end()) continue; + depth[neighbour] = nodeDepth + 1; + if (neighbour == b) return nodeDepth + 1; + nextFrontier.push_back(neighbour); + } + } + frontier.swap(nextFrontier); + } + return -1; +} + +} // namespace vf