diff --git a/src/main/java/baritone/builder/Bounds.java b/src/main/java/baritone/builder/Bounds.java index 8121f25dc..241a09a69 100644 --- a/src/main/java/baritone/builder/Bounds.java +++ b/src/main/java/baritone/builder/Bounds.java @@ -63,6 +63,7 @@ public interface Bounds { int volume(); + // this must be implemented EXTREMELY efficiently. no integer division allowed! even a hashmap lookup is borderline. int toIndex(int x, int y, int z); // easy to implement for cuboid, harder for more complicated shapes default int toIndex(long pos) { diff --git a/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java b/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java index 6b82e35ad..1c0afd590 100644 --- a/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java +++ b/src/main/java/baritone/builder/DependencyGraphScaffoldingOverlay.java @@ -112,6 +112,10 @@ public class DependencyGraphScaffoldingOverlay { } } + public LongSets.UnmodifiableSet scaffolding() { + return (LongSets.UnmodifiableSet) LongSets.unmodifiable(scaffoldingAdded); + } + public BlockStateCachedData data(long pos) { if (Main.DEBUG && !real(pos)) { throw new IllegalStateException(); diff --git a/src/main/java/baritone/builder/GreedySolver.java b/src/main/java/baritone/builder/GreedySolver.java index 9f8ec7327..40d2c7e92 100644 --- a/src/main/java/baritone/builder/GreedySolver.java +++ b/src/main/java/baritone/builder/GreedySolver.java @@ -23,27 +23,28 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; public class GreedySolver { SolverEngineInput engineInput; - - public GreedySolver(SolverEngineInput input) { - this.engineInput = input; - - } - NodeBinaryHeap heap = new NodeBinaryHeap(); Long2ObjectOpenHashMap nodes = new Long2ObjectOpenHashMap<>(); Long2ObjectOpenHashMap zobristWorldStateCache = new Long2ObjectOpenHashMap<>(); - synchronized SolverEngineOutput search() { - while (!heap.isEmpty()) { - Node node = heap.removeLowest(); + public GreedySolver(SolverEngineInput input) { + this.engineInput = input; + } + synchronized SolverEngineOutput search() { + Node root = new Node(engineInput.player, 0L, -1L, this, 0); + nodes.put(root.nodeMapKey(), root); + heap.insert(root); + zobristWorldStateCache.put(0L, new WorldState.WorldStateWrappedSubstrate(engineInput)); + while (!heap.isEmpty()) { + expandNode(heap.removeLowest()); } throw new UnsupportedOperationException(); } private void expandNode(Node node) { WorldState worldState = node.coalesceState(this); - long pos = node.pos(); + long pos = node.pos; BlockStateCachedData above = at(BetterBlockPos.offsetBy(pos, 0, 2, 0), worldState); BlockStateCachedData head = at(Face.UP.offset(pos), worldState); if (Main.DEBUG && head.collidesWithPlayer) { @@ -56,7 +57,7 @@ public class GreedySolver { throw new IllegalStateException(); } boolean stickingUpIntoThirdBlock = blipsWithinBlock > Blip.TWO_BLOCKS - Blip.PLAYER_HEIGHT_SLIGHT_OVERESTIMATE; // exactly equal means not sticking up, since overestimate means overestimate - int blips = node.y * Blip.PER_BLOCK + blipsWithinBlock; + int blips = BetterBlockPos.YfromLong(pos) * Blip.PER_BLOCK + blipsWithinBlock; mid: for (Face travel : Face.HORIZONTALS) { @@ -141,25 +142,46 @@ public class GreedySolver { } } - int calculateHeuristicModifier(WorldState previous, long blockPlacedAt) { + private int calculateHeuristicModifier(WorldState previous, long blockPlacedAt) { if (Main.DEBUG && previous.blockExists(blockPlacedAt)) { throw new IllegalStateException(); } - if (engineInput.desiredToBePlaced(blockPlacedAt)) { - return -100; + if (true) { + throw new UnsupportedOperationException("tune the values first lol"); + } + switch (engineInput.desiredToBePlaced(blockPlacedAt)) { + case PART_OF_CURRENT_GOAL: + case SCAFFOLDING_OF_CURRENT_GOAL: + return -100; // keep kitten on task + case PART_OF_FUTURE_GOAL: + return -10; // smaller kitten treat for working ahead + case SCAFFOLDING_OF_FUTURE_GOAL: + return -5; // smallest kitten treat for working ahead on scaffolding + case ANCILLARY: + return 0; // no kitten treat for placing a random extra block + default: + throw new IllegalStateException(); } - return 0; } - Node getNode(long playerPosition, Node prev, WorldState prevWorld, long blockPlacement) { - long worldStateZobristHash = blockPlacement == -1 ? prev.worldStateZobristHash : WorldState.updateZobrist(prev.worldStateZobristHash, blockPlacement); + private Node getNode(long playerPosition, Node prev, WorldState prevWorld, long blockPlacement) { + if (Main.DEBUG && blockPlacement != -1 && prev.coalesceState(this).blockExists(blockPlacement)) { + throw new IllegalStateException(); + } + long worldStateZobristHash = prev.worldStateZobristHash; + if (blockPlacement != -1) { + worldStateZobristHash = WorldState.updateZobrist(worldStateZobristHash, blockPlacement); + } long code = playerPosition ^ worldStateZobristHash; Node existing = nodes.get(code); if (existing != null) { return existing; } - int newHeuristic = prev.heuristic + blockPlacement == -1 ? 0 : calculateHeuristicModifier(prevWorld, blockPlacement); - Node node = new Node(BetterBlockPos.XfromLong(playerPosition), BetterBlockPos.YfromLong(playerPosition), BetterBlockPos.ZfromLong(playerPosition), worldStateZobristHash, blockPlacement, this, newHeuristic); + int newHeuristic = prev.heuristic; + if (blockPlacement != -1) { + newHeuristic += calculateHeuristicModifier(prevWorld, blockPlacement); + } + Node node = new Node(playerPosition, worldStateZobristHash, blockPlacement, this, newHeuristic); if (Main.DEBUG && node.nodeMapKey() != code) { throw new IllegalStateException(); } diff --git a/src/main/java/baritone/builder/Node.java b/src/main/java/baritone/builder/Node.java index 34fd6f94c..d22ab06c1 100644 --- a/src/main/java/baritone/builder/Node.java +++ b/src/main/java/baritone/builder/Node.java @@ -17,31 +17,24 @@ package baritone.builder; -import baritone.api.utils.BetterBlockPos; - public class Node { - public int x; - public int y; - public int z; + public final long pos; + public final long worldStateZobristHash; - public long worldStateZobristHash; - - public int heuristic; + public final int heuristic; public int cost; public int combinedCost; public Node previous; public int heapPosition; // boolean unrealizedZobristBlockChange; // no longer needed since presence in the overall GreedySolver zobristWorldStateCache indicates if this is a yet-unrealized branch of the zobrist space - long packedUnrealizedCoordinate; + private long packedUnrealizedCoordinate; // int unrealizedState; // no longer needed now that world state is binarized with scaffolding/build versus air // long unrealizedZobristParentHash; // no longer needed since we can compute it backwards with XOR - public Node(int x, int y, int z, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) { - this.x = x; - this.y = y; - this.z = z; + public Node(long pos, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) { + this.pos = pos; this.heapPosition = -1; this.cost = Integer.MAX_VALUE; this.heuristic = heuristic; @@ -55,25 +48,27 @@ public class Node { public WorldState coalesceState(GreedySolver solver) { WorldState alr = solver.zobristWorldStateCache.get(worldStateZobristHash); if (alr != null) { - // packedUnrealizedCoordinate can be -1 if the cache did not include our zobrist state on a previous call to coalesceState + if (Main.DEBUG && alr.zobristHash != worldStateZobristHash) { + throw new IllegalStateException(); + } + // don't check packedUnrealizedCoordinate here because it could exist (not -1) if a different route was taken to a zobrist-equivalent node (such as at a different player position) which was then expanded and coalesced return alr; } + if (Main.DEBUG && packedUnrealizedCoordinate == -1) { + throw new IllegalStateException(); + } long parent = WorldState.updateZobrist(worldStateZobristHash, packedUnrealizedCoordinate); // updateZobrist is symmetric because XOR, so the same operation can do child->parent as parent->child WorldState myState = solver.zobristWorldStateCache.get(parent).withChild(packedUnrealizedCoordinate); if (Main.DEBUG && myState.zobristHash != worldStateZobristHash) { throw new IllegalStateException(); } solver.zobristWorldStateCache.put(worldStateZobristHash, myState); - // TODO set packedUnrealizedCoordinate to -1 here perhaps? + packedUnrealizedCoordinate = -1; return myState; } - public long pos() { - return BetterBlockPos.toLong(x, y, z); - } - public long nodeMapKey() { - return pos() ^ worldStateZobristHash; + return pos ^ worldStateZobristHash; } public boolean inHeap() { diff --git a/src/main/java/baritone/builder/Scaffolder.java b/src/main/java/baritone/builder/Scaffolder.java index ed3f046db..61414f2de 100644 --- a/src/main/java/baritone/builder/Scaffolder.java +++ b/src/main/java/baritone/builder/Scaffolder.java @@ -20,9 +20,7 @@ package baritone.builder; import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph; import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongIterator; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.*; @@ -55,6 +53,14 @@ public class Scaffolder { this.rootComponents = calcRoots(); } + public static Scaffolder run(DependencyGraphScaffoldingOverlay overlayGraph) { + Scaffolder scaffolder = new Scaffolder(overlayGraph); + while (scaffolder.rootComponents.size() > 1) { + scaffolder.loop(); + } + return scaffolder; + } + private List calcRoots() { // since the components form a DAG (because all strongly connected components, and therefore all cycles, have been collapsed) // we can locate all root components by simply finding the ones with no incoming edges @@ -66,7 +72,9 @@ public class Scaffolder { } private void loop() { - int cid = collapsedGraph.lastComponentID().getAsInt(); + if (rootComponents.size() <= 1) { + throw new IllegalStateException(); + } CollapsedDependencyGraphComponent root = rootComponents.remove(rootComponents.size() - 1); if (!root.getIncoming().isEmpty()) { throw new IllegalStateException(); @@ -83,15 +91,23 @@ public class Scaffolder { if (!componentLocations.containsKey(path.get(0).pos)) { throw new IllegalStateException(); } - for (int i = 1; i < path.size() - 1; i++) { - if (componentLocations.containsKey(path.get(i).pos)) { + LongList toEnable = path + .subList(1, path.size() - 1) + .stream() + .map(node -> node.pos) + .collect(Collectors.toCollection(LongArrayList::new)); + enable(toEnable); + } + + private void enable(LongList positions) { + positions.forEach(pos -> { + if (componentLocations.containsKey(pos)) { throw new IllegalStateException(); } - } + }); + int cid = collapsedGraph.lastComponentID().getAsInt(); - for (int i = 1; i < path.size() - 1; i++) { - overlayGraph.enable(path.get(i).pos); - } + positions.forEach(overlayGraph::enable); int newCID = collapsedGraph.lastComponentID().getAsInt(); for (int i = cid + 1; i <= newCID; i++) { @@ -106,7 +122,23 @@ public class Scaffolder { throw new IllegalStateException(); } } + } + public void enableAncillaryScaffoldingAndRecomputeRoot(LongList positions) { + getRoot(); + enable(positions); + getRoot(); + } + + public CollapsedDependencyGraphComponent getRoot() { + if (rootComponents.size() != 1) { + throw new IllegalStateException(); // this is okay because this can only possibly be called after Scaffolder.run is completed + } + CollapsedDependencyGraphComponent root = rootComponents.get(0); + if (!root.getIncoming().isEmpty()) { + throw new IllegalStateException(); + } + return root; } private void walkAllDescendents(CollapsedDependencyGraphComponent root, Set set) { @@ -155,7 +187,7 @@ public class Scaffolder { // any position in the initial frontier is clearly in the node map, but also any node that has already been considered // this prevents useless cycling of equivalent paths // this is okay because all paths are equivalent, so there is no possible way to find a better path (because currently it's a fixed value for horizontal / vertical movements) - if (existingNode.costSoFar < newCost) { + if (existingNode.costSoFar != newCost) { throw new IllegalStateException(); } continue; // nothing to do - we already have an equal-or-better path to this location @@ -194,23 +226,4 @@ public class Scaffolder { this.pos = pos; } } - - private void sanityCheck() { - // we will trust DependencyGraphScaffoldingOverlay that there are no cycles of any kind in the components - they form a DAG - // - } - - private void enableBlock(long pos) { - // first, before everything gets destroyed by updating the overlay graph, let's chill out our subgraphs - // i really would rather not write a whole new thing for incrementally recomputing an overlay graph! - - } - - public static void run(DependencyGraphScaffoldingOverlay overlay) { - - - CollapsedDependencyGraph collapsed = overlay.getCollapsedGraph(); - Map components = collapsed.getComponents(); - - } } diff --git a/src/main/java/baritone/builder/SolverEngineHarness.java b/src/main/java/baritone/builder/SolverEngineHarness.java new file mode 100644 index 000000000..b8db95ed5 --- /dev/null +++ b/src/main/java/baritone/builder/SolverEngineHarness.java @@ -0,0 +1,141 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.builder; + +import baritone.builder.DependencyGraphScaffoldingOverlay.CollapsedDependencyGraph.CollapsedDependencyGraphComponent; +import it.unimi.dsi.fastutil.longs.*; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalLong; +import java.util.Set; +import java.util.stream.Collectors; + +public class SolverEngineHarness { + + private final ISolverEngine engine; + private final PackedBlockStateCuboid blocks; + private final PlaceOrderDependencyGraph graph; + private final DependencyGraphScaffoldingOverlay overlay; + private final Scaffolder scaffolder; + + public SolverEngineHarness(ISolverEngine engine, PackedBlockStateCuboid blocks) { + this.engine = engine; + this.blocks = blocks; + this.graph = new PlaceOrderDependencyGraph(blocks); + this.overlay = new DependencyGraphScaffoldingOverlay(graph); + this.scaffolder = Scaffolder.run(overlay); + } + + public List solve(long playerStartPos) { + LongOpenHashSet alreadyPlacedSoFar = new LongOpenHashSet(); + List steps = new ArrayList<>(); + while (true) { + Set frontier = calculateCurrentSolverFrontier(alreadyPlacedSoFar); + if (frontier.isEmpty()) { + // nothing on the table! + break; + } + List goals = expandAndSubtract(frontier, alreadyPlacedSoFar); + long playerPos = steps.isEmpty() ? playerStartPos : steps.get(steps.size() - 1).playerMovesTo(); + SolverEngineInput inp = new SolverEngineInput(graph, overlay.scaffolding(), alreadyPlacedSoFar, goals, playerPos); + SolverEngineOutput out = engine.solve(inp); + if (Main.DEBUG) { + out.sanityCheck(inp); + } + steps.addAll(out.getSteps()); + LongList ancillaryScaffolding = new LongArrayList(); + for (SolvedActionStep step : out.getSteps()) { + OptionalLong blockPlace = step.placeAt(); + if (blockPlace.isPresent()) { + long pos = blockPlace.getAsLong(); + if (!alreadyPlacedSoFar.add(pos)) { + throw new IllegalStateException(); + } + if (overlay.air(pos)) { // not part of the schematic, nor intended scaffolding + ancillaryScaffolding.add(pos); // therefore it must be ancillary scaffolding, some throwaway block we needed to place in order to achieve something else, maybe to get a needed vantage point on some particularly tricky placement + } + } + } + scaffolder.enableAncillaryScaffoldingAndRecomputeRoot(ancillaryScaffolding); + } + if (Main.DEBUG) { + overlay.forEachReal(pos -> { + if (!alreadyPlacedSoFar.contains(pos)) { + throw new IllegalStateException(); + } + }); + alreadyPlacedSoFar.forEach(pos -> { + if (!overlay.real(pos)) { + throw new IllegalStateException(); + } + }); + } + return steps; + } + + private List expandAndSubtract(Set frontier, LongSet already) { + return frontier.stream() + .map(component -> { + LongOpenHashSet remainingPositionsInComponent = new LongOpenHashSet(component.getPositions().size()); + LongIterator it = component.getPositions().iterator(); + while (it.hasNext()) { + long pos = it.nextLong(); + if (!already.contains(pos)) { + remainingPositionsInComponent.add(pos); + } + } + return remainingPositionsInComponent; + }).collect(Collectors.toList()); + } + + private Set calculateCurrentSolverFrontier(LongSet alreadyPlacedSoFar) { + Set currentFrontier = new ObjectOpenHashSet<>(); + Set confirmedFullyCompleted = new ObjectOpenHashSet<>(); + ObjectArrayFIFOQueue toExplore = new ObjectArrayFIFOQueue<>(); + toExplore.enqueue(scaffolder.getRoot()); + outer: + while (!toExplore.isEmpty()) { + CollapsedDependencyGraphComponent component = toExplore.dequeue(); + for (CollapsedDependencyGraphComponent parent : component.getIncoming()) { + if (!confirmedFullyCompleted.contains(parent)) { + // to be here, one parent must have been fully completed, but it's possible a different parent is not yet fully completed + // this is because while the collapsed block placement dependency graph is a directed acyclic graph, it can still have a diamond shape + // imagine the dependency is A->B, A->C, C->D, B->E, D->E (so, A is root, it splits in two, then converges at E) + // it might explore it in order A, B, C, E (because B, yet canceled as D is not completed yet), D (because C), E (now succeeds since B and D are confirmed) + continue outer; + } + } + LongIterator it = component.getPositions().iterator(); + while (it.hasNext()) { + if (!alreadyPlacedSoFar.contains(it.nextLong())) { + currentFrontier.add(component); + continue outer; + } + } + if (confirmedFullyCompleted.add(component)) { + for (CollapsedDependencyGraphComponent child : component.getOutgoing()) { + toExplore.enqueue(child); // always reenqueue children because we added ourselves to confirmedFullyCompleted, meaning this time they may well have all parents completed + } + } + } + return currentFrontier; + } +} diff --git a/src/main/java/baritone/builder/SolverEngineInput.java b/src/main/java/baritone/builder/SolverEngineInput.java index 518d5e596..85d1d95fd 100644 --- a/src/main/java/baritone/builder/SolverEngineInput.java +++ b/src/main/java/baritone/builder/SolverEngineInput.java @@ -17,16 +17,21 @@ package baritone.builder; +import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; +import java.util.Collection; +import java.util.List; + public class SolverEngineInput { public final PlaceOrderDependencyGraph graph; public final LongSet intendedScaffolding; public final LongSet alreadyPlaced; - private final LongOpenHashSet toPlaceNow; + public final LongSet allToPlaceNow; + private final List toPlaceNow; public final long player; /** @@ -36,12 +41,13 @@ public class SolverEngineInput { * @param toPlaceNow Locations that are currently top of mind and must be placed. For example, to place the rest of the graph, this would be intendedScaffolding|(graph.allNonAir&~alreadyPlaced) * @param player Last but not least, where is the player standing? */ - public SolverEngineInput(PlaceOrderDependencyGraph graph, LongOpenHashSet intendedScaffolding, LongOpenHashSet alreadyPlaced, LongOpenHashSet toPlaceNow, long player) { + public SolverEngineInput(PlaceOrderDependencyGraph graph, LongSets.UnmodifiableSet intendedScaffolding, LongOpenHashSet alreadyPlaced, List toPlaceNow, long player) { this.graph = graph; - this.intendedScaffolding = LongSets.unmodifiable(intendedScaffolding); + this.intendedScaffolding = intendedScaffolding; this.alreadyPlaced = LongSets.unmodifiable(alreadyPlaced); this.toPlaceNow = toPlaceNow; this.player = player; + this.allToPlaceNow = combine(toPlaceNow); if (Main.DEBUG) { sanityCheck(); } @@ -51,24 +57,68 @@ public class SolverEngineInput { if (!graph.bounds().inRangePos(player)) { throw new IllegalStateException(); } - for (LongSet toVerify : new LongSet[]{intendedScaffolding, alreadyPlaced, toPlaceNow}) { + for (LongSet toVerify : new LongSet[]{intendedScaffolding, alreadyPlaced}) { for (long pos : toVerify) { if (!graph.bounds().inRangePos(pos)) { throw new IllegalStateException(); } } } - for (long pos : toPlaceNow) { - if (alreadyPlaced.contains(pos)) { - throw new IllegalStateException(); + for (LongSet toPlace : toPlaceNow) { + for (long pos : toPlace) { + if (alreadyPlaced.contains(pos)) { + throw new IllegalStateException(); + } + if (!graph.bounds().inRangePos(pos)) { + throw new IllegalStateException(); + } + if (intendedScaffolding.contains(pos) ^ graph.airTreatedAsScaffolding(pos)) { + throw new IllegalStateException(); + } } } } - public boolean desiredToBePlaced(long pos) { + public PlacementDesire desiredToBePlaced(long pos) { if (Main.DEBUG && !graph.bounds().inRangePos(pos)) { throw new IllegalStateException(); } - return !graph.data(pos).isAir || intendedScaffolding.contains(pos); + if (allToPlaceNow.contains(pos)) { + if (graph.airTreatedAsScaffolding(pos)) { + return PlacementDesire.SCAFFOLDING_OF_CURRENT_GOAL; + } else { + return PlacementDesire.PART_OF_CURRENT_GOAL; + } + } else { + // for positions NOT in allToPlaceNow, intendedScaffolding is not guaranteed to be equivalent to airTreatedAsScaffolding + if (graph.airTreatedAsScaffolding(pos)) { + if (intendedScaffolding.contains(pos)) { + return PlacementDesire.SCAFFOLDING_OF_FUTURE_GOAL; + } else { + return PlacementDesire.ANCILLARY; + } + } else { + return PlacementDesire.PART_OF_FUTURE_GOAL; + } + } + } + + public enum PlacementDesire { + PART_OF_CURRENT_GOAL, + PART_OF_FUTURE_GOAL, + SCAFFOLDING_OF_CURRENT_GOAL, + SCAFFOLDING_OF_FUTURE_GOAL, + ANCILLARY + } + + private static LongOpenHashSet combine(List entries) { + LongOpenHashSet ret = new LongOpenHashSet(entries.stream().mapToInt(Collection::size).sum()); + for (LongOpenHashSet set : entries) { + LongIterator it = set.iterator(); + while (it.hasNext()) { + ret.add(it.nextLong()); + } + } + return ret; } } diff --git a/src/main/java/baritone/builder/SolverEngineOutput.java b/src/main/java/baritone/builder/SolverEngineOutput.java index 1d5cc8f8a..786e24219 100644 --- a/src/main/java/baritone/builder/SolverEngineOutput.java +++ b/src/main/java/baritone/builder/SolverEngineOutput.java @@ -19,6 +19,7 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; +import java.util.Collections; import java.util.List; public class SolverEngineOutput { @@ -48,6 +49,10 @@ public class SolverEngineOutput { } } + public List getSteps() { + return Collections.unmodifiableList(steps); + } + private static void sanityCheckMovement(long from, long to) { int dx = BetterBlockPos.XfromLong(from) - BetterBlockPos.XfromLong(to); int dz = BetterBlockPos.ZfromLong(from) - BetterBlockPos.ZfromLong(to); diff --git a/src/main/java/baritone/builder/TarjansAlgorithm.java b/src/main/java/baritone/builder/TarjansAlgorithm.java index 5e92a5f68..52e510a38 100644 --- a/src/main/java/baritone/builder/TarjansAlgorithm.java +++ b/src/main/java/baritone/builder/TarjansAlgorithm.java @@ -69,7 +69,7 @@ public class TarjansAlgorithm { } private void strongConnect(long vpos) { - if (!graph.real(vpos)) { + if (Main.DEBUG && !graph.real(vpos)) { throw new IllegalStateException(); } TarjanVertexInfo info = infoMap.get(vpos); diff --git a/src/main/java/baritone/builder/Testing.java b/src/main/java/baritone/builder/Testing.java index e47ae8f08..e2f998fcb 100644 --- a/src/main/java/baritone/builder/Testing.java +++ b/src/main/java/baritone/builder/Testing.java @@ -18,9 +18,7 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; -import baritone.pathing.calc.openset.BinaryHeapOpenSet; import baritone.utils.BlockStateInterface; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -39,76 +37,11 @@ public class Testing { public static class AbstractNodeCostSearch { - Long2ObjectOpenHashMap nodeMap; - BinaryHeapOpenSet openSet; - Long2ObjectOpenHashMap zobristMap; - long[] KEYS_CACHE = new long[MAX_LEAF_LEVEL]; int[] VALS_CACHE = new int[MAX_LEAF_LEVEL]; - - protected void search() { - while (!openSet.isEmpty()) { - PathNode node = null; - BlockStateInterfaceAbstractWrapper bsi = node.get(this); - - // consider actions: - // traverse - // pillar - // place blocks beneath feet - } - } - - /*protected PathNode getNodeAtPosition(int x, int y, int z, long hashCode) { - baritone.pathing.calc.PathNode node = map.get(hashCode); - if (node == null) { - node = new baritone.pathing.calc.PathNode(x, y, z, goal); - map.put(hashCode, node); - } - return node; - }*/ } - public static final class PathNode { - - public int x; - public int y; - public int z; - - public long zobristBlocks; - - public int heuristic; - public int cost; - public int combinedCost; - public PathNode previous; - public int heapPosition; - - boolean unrealizedZobristBlockChange; - long packedUnrealizedCoordinate; - int unrealizedState; - long unrealizedZobristParentHash; - - public long nodeMapKey() { - return BetterBlockPos.longHash(x, y, z) ^ zobristBlocks; - } - - public BlockStateInterfaceAbstractWrapper get(AbstractNodeCostSearch ref) { - BlockStateInterfaceAbstractWrapper alr = ref.zobristMap.get(zobristBlocks); - if (alr != null) { - return alr; - } - if (!unrealizedZobristBlockChange || (BetterBlockPos.murmur64(BetterBlockPos.longHash(packedUnrealizedCoordinate) ^ unrealizedState) ^ zobristBlocks) != unrealizedZobristParentHash) { - throw new IllegalStateException(); - } - alr = ref.zobristMap.get(unrealizedZobristParentHash).with(packedUnrealizedCoordinate, unrealizedState, ref); - if (alr.zobrist != zobristBlocks) { - throw new IllegalStateException(); - } - ref.zobristMap.put(zobristBlocks, alr); - return alr; - } - } - public static abstract class BlockStateInterfaceAbstractWrapper { protected long zobrist; @@ -196,7 +129,7 @@ public class Testing { } } BlockStateInterfaceMappedDiff coalesced = new BlockStateInterfaceMappedDiff(ancestor, keys, vals, startIdx, zobrist); - ref.zobristMap.put(zobrist, coalesced); + //ref.zobristMap.put(zobrist, coalesced); return coalesced; } } @@ -271,6 +204,8 @@ public class Testing { public static class BlockStateLookupHelper { + // this falls back from System.identityHashCode to == so it's safe even in the case of a 32-bit collision + // but that would never have happened anyway: https://www.wolframalpha.com/input/?i=%28%282%5E32-1%29%2F%282%5E32%29%29%5E2000 private static Reference2IntOpenHashMap states = new Reference2IntOpenHashMap<>(); static { @@ -286,14 +221,5 @@ public class Testing { states.put(state, realState); return realState; } - - static { - IntOpenHashSet taken = new IntOpenHashSet(); - for (IBlockState state : Block.BLOCK_STATE_IDS) { - if (!taken.add(System.identityHashCode(state))) { - throw new IllegalStateException("Duplicate hashcode among IBlockStates"); - } - } - } } } diff --git a/src/main/java/baritone/builder/WorldState.java b/src/main/java/baritone/builder/WorldState.java index 80dcf180b..4e39a9a5a 100644 --- a/src/main/java/baritone/builder/WorldState.java +++ b/src/main/java/baritone/builder/WorldState.java @@ -18,22 +18,84 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; +import it.unimi.dsi.fastutil.longs.LongIterator; + +import java.util.BitSet; public abstract class WorldState { - protected long zobristHash; + public final long zobristHash; - public abstract boolean blockExists(long coord); - - public WorldState withChild(long coord) { - throw new UnsupportedOperationException(); + protected WorldState(long zobristHash) { + this.zobristHash = zobristHash; } - public static class WorldStateWrappedSubstrate { - // refers to GreedySolver.SolverEngineInput.PlaceOrderDependencyGraph.PackedBlockStateCuboid + public abstract boolean blockExists(long pos); + + public WorldState withChild(long pos) { + return new WorldStateLeafDiff(this, pos); } public static long updateZobrist(long worldStateZobristHash, long changedPosition) { return BetterBlockPos.zobrist(changedPosition) ^ worldStateZobristHash; } + + public static class WorldStateWrappedSubstrate extends WorldState { + + private final Bounds bounds; + private final BitSet placed; // won't be copied, since alreadyPlaced is going to be **far** larger than toPlaceNow + private final int offset; + + public WorldStateWrappedSubstrate(SolverEngineInput inp) { + super(0L); + this.bounds = inp.graph.bounds(); + int min; + int max; + { + LongIterator positions = inp.alreadyPlaced.iterator(); + if (!positions.hasNext()) { + throw new IllegalStateException(); + } + min = bounds.toIndex(positions.nextLong()); + max = min; + while (positions.hasNext()) { + int val = bounds.toIndex(positions.nextLong()); + min = Math.min(min, val); + max = Math.max(max, val); + } + } + this.offset = min; + this.placed = new BitSet(max - min + 1); + LongIterator it = inp.alreadyPlaced.iterator(); + while (it.hasNext()) { + placed.set(bounds.toIndex(it.nextLong()) - offset); + } + } + + @Override + public boolean blockExists(long pos) { + int storedAt = bounds.toIndex(pos) - offset; + if (storedAt < 0) { + return false; + } + return placed.get(storedAt); + } + } + + public static class WorldStateLeafDiff extends WorldState { + + private WorldState delegate; + private final long pos; + + private WorldStateLeafDiff(WorldState delegate, long pos) { + super(updateZobrist(delegate.zobristHash, pos)); + this.delegate = delegate; + this.pos = pos; + } + + @Override + public boolean blockExists(long pos) { + return this.pos == pos || delegate.blockExists(pos); + } + } }