diff --git a/src/api/java/baritone/api/utils/BetterBlockPos.java b/src/api/java/baritone/api/utils/BetterBlockPos.java index bea49f3a8..f529f6062 100644 --- a/src/api/java/baritone/api/utils/BetterBlockPos.java +++ b/src/api/java/baritone/api/utils/BetterBlockPos.java @@ -170,10 +170,6 @@ public final class BetterBlockPos extends BlockPos { return murmur64(HASHCODE_MURMUR_MASK ^ packed); } - public static long zobrist(long packed) { - return murmur64(ZOBRIST_MURMUR_MASK ^ packed); - } - public static long murmur64(long h) { return HashCommon.murmurHash3(h); } diff --git a/src/main/java/baritone/builder/GreedySolver.java b/src/main/java/baritone/builder/GreedySolver.java index 02e6d4fb3..0916accbb 100644 --- a/src/main/java/baritone/builder/GreedySolver.java +++ b/src/main/java/baritone/builder/GreedySolver.java @@ -20,12 +20,16 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.ArrayDeque; +import java.util.ArrayList; + public class GreedySolver { private final SolverEngineInput engineInput; private final NodeBinaryHeap heap = new NodeBinaryHeap(); private final Long2ObjectOpenHashMap nodes = new Long2ObjectOpenHashMap<>(); - final Long2ObjectOpenHashMap zobristWorldStateCache = new Long2ObjectOpenHashMap<>(); + private final ZobristWorldStateCache zobristCache; + private final long allCompleted; private final Bounds bounds; private Column scratchpadExpandNode1 = new Column(); private Column scratchpadExpandNode2 = new Column(); @@ -34,19 +38,41 @@ public class GreedySolver { public GreedySolver(SolverEngineInput input) { this.engineInput = input; this.bounds = engineInput.graph.bounds(); + this.zobristCache = new ZobristWorldStateCache(new WorldState.WorldStateWrappedSubstrate(engineInput)); + Node root = new Node(engineInput.player, null, 0L, -1L, 0); + nodes.put(root.nodeMapKey(), root); + heap.insert(root); + this.allCompleted = WorldState.predetermineGoalZobrist(engineInput.allToPlaceNow); } synchronized SolverEngineOutput search() { - Node root = new Node(engineInput.player, null, 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()); + Node node = heap.removeLowest(); + if (!node.sneaking() && node.worldStateZobristHash == allCompleted) { + return backwards(node); + } + expandNode(node); } throw new UnsupportedOperationException(); } + private SolverEngineOutput backwards(Node node) { + ArrayDeque steps = new ArrayDeque<>(); + while (node.previous != null) { + steps.addFirst(step(node, node.previous)); + node = node.previous; + } + return new SolverEngineOutput(new ArrayList<>(steps)); + } + + private SolvedActionStep step(Node next, Node prev) { + if (next.worldStateZobristHash == prev.worldStateZobristHash) { + return new SolvedActionStep(next.pos()); + } else { + return new SolvedActionStep(next.pos(), WorldState.unzobrist(prev.worldStateZobristHash ^ next.worldStateZobristHash)); + } + } + private boolean wantToPlaceAt(long blockGoesAt, Node vantage, int blipsWithinVoxel, WorldState worldState) { if (worldState.blockExists(blockGoesAt)) { return false; @@ -80,7 +106,7 @@ public class GreedySolver { } private void expandNode(Node node) { - WorldState worldState = node.coalesceState(this); + WorldState worldState = zobristCache.coalesceState(node); long pos = node.pos(); Column within = scratchpadExpandNode1; within.initFrom(pos, worldState, engineInput); @@ -186,7 +212,10 @@ public class GreedySolver { private void upsertEdge(Node node, WorldState worldState, long newPlayerPosition, Face sneakingTowards, long blockPlacement, int edgeCost) { Node neighbor = getNode(newPlayerPosition, sneakingTowards, node, worldState, blockPlacement); - if (Main.SLOW_DEBUG && blockPlacement != -1 && !neighbor.coalesceState(this).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node! + if (Main.SLOW_DEBUG && blockPlacement != -1 && !zobristCache.coalesceState(neighbor).blockExists(blockPlacement)) { // only in slow_debug because this force-allocates a WorldState for every neighbor of every node! + throw new IllegalStateException(); + } + if (Main.DEBUG && node == neighbor) { throw new IllegalStateException(); } updateNeighbor(node, neighbor, edgeCost); @@ -250,7 +279,7 @@ public class GreedySolver { if (blockPlacement != -1) { newHeuristic += calculateHeuristicModifier(prevWorld, blockPlacement); } - Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, this, newHeuristic); + Node node = new Node(playerPosition, null, worldStateZobristHash, blockPlacement, 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 1ed28c6a6..41cd2da14 100644 --- a/src/main/java/baritone/builder/Node.java +++ b/src/main/java/baritone/builder/Node.java @@ -28,45 +28,20 @@ public class Node { public int cost; public int combinedCost; public Node previous; - public int heapPosition; + 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 - private long packedUnrealizedCoordinate; + 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(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, GreedySolver solver, int heuristic) { + public Node(long pos, Face sneakingTowards, long zobristState, long unrealizedBlockPlacement, int heuristic) { this.posAndSneak = encode(pos, sneakingTowards); this.heapPosition = -1; this.cost = Integer.MAX_VALUE; this.heuristic = heuristic; this.worldStateZobristHash = zobristState; this.packedUnrealizedCoordinate = unrealizedBlockPlacement; - if (Main.DEBUG && (solver.zobristWorldStateCache.containsKey(worldStateZobristHash) ^ (unrealizedBlockPlacement == -1))) { - throw new IllegalStateException(); - } - } - - public WorldState coalesceState(GreedySolver solver) { - WorldState alr = solver.zobristWorldStateCache.get(worldStateZobristHash); - if (alr != null) { - 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); - packedUnrealizedCoordinate = -1; - return myState; } public static long encode(long pos, Face sneakingTowards) { diff --git a/src/main/java/baritone/builder/SolvedActionStep.java b/src/main/java/baritone/builder/SolvedActionStep.java index 04d74bedf..b88b6e024 100644 --- a/src/main/java/baritone/builder/SolvedActionStep.java +++ b/src/main/java/baritone/builder/SolvedActionStep.java @@ -31,6 +31,9 @@ public class SolvedActionStep { public SolvedActionStep(long playerMovesTo, long blockPlacedAt) { this.playerEndPosition = playerMovesTo; this.placePosition = blockPlacedAt; + if (Main.DEBUG && blockPlacedAt < -1) { + throw new IllegalStateException(); + } } public OptionalLong placeAt() { diff --git a/src/main/java/baritone/builder/WorldState.java b/src/main/java/baritone/builder/WorldState.java index 8320a6550..b8248eb92 100644 --- a/src/main/java/baritone/builder/WorldState.java +++ b/src/main/java/baritone/builder/WorldState.java @@ -18,6 +18,8 @@ package baritone.builder; import baritone.api.utils.BetterBlockPos; +import it.unimi.dsi.fastutil.HashCommon; +import it.unimi.dsi.fastutil.longs.LongCollection; import it.unimi.dsi.fastutil.longs.LongIterator; import java.util.BitSet; @@ -40,7 +42,24 @@ public abstract class WorldState { } public static long updateZobrist(long worldStateZobristHash, long changedPosition) { - return BetterBlockPos.zobrist(changedPosition) ^ worldStateZobristHash; + return zobrist(changedPosition) ^ worldStateZobristHash; + } + + public static long predetermineGoalZobrist(LongCollection goal) { + LongIterator it = goal.iterator(); + long ret = 0; + while (it.hasNext()) { + ret ^= zobrist(it.nextLong()); + } + return ret; + } + + public static long zobrist(long packed) { + return HashCommon.mix(BetterBlockPos.ZOBRIST_MURMUR_MASK ^ packed); + } + + public static long unzobrist(long zobrist) { + return BetterBlockPos.ZOBRIST_MURMUR_MASK ^ HashCommon.invMix(zobrist); } public static class WorldStateWrappedSubstrate extends WorldState { diff --git a/src/main/java/baritone/builder/ZobristWorldStateCache.java b/src/main/java/baritone/builder/ZobristWorldStateCache.java new file mode 100644 index 000000000..4196a4dc0 --- /dev/null +++ b/src/main/java/baritone/builder/ZobristWorldStateCache.java @@ -0,0 +1,52 @@ +/* + * 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 it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +public class ZobristWorldStateCache { + + private final Long2ObjectOpenHashMap zobristWorldStateCache; + + public ZobristWorldStateCache(WorldState zeroEntry) { + this.zobristWorldStateCache = new Long2ObjectOpenHashMap<>(); + this.zobristWorldStateCache.put(0L, zeroEntry); + } + + public WorldState coalesceState(Node node) { + WorldState alr = zobristWorldStateCache.get(node.worldStateZobristHash); + if (alr != null) { + if (Main.DEBUG && alr.zobristHash != node.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 && node.packedUnrealizedCoordinate == -1) { + throw new IllegalStateException(); + } + long parent = WorldState.updateZobrist(node.worldStateZobristHash, node.packedUnrealizedCoordinate); // updateZobrist is symmetric because XOR, so the same operation can do child->parent as parent->child + WorldState myState = zobristWorldStateCache.get(parent).withChild(node.packedUnrealizedCoordinate); + if (Main.DEBUG && myState.zobristHash != node.worldStateZobristHash) { + throw new IllegalStateException(); + } + zobristWorldStateCache.put(node.worldStateZobristHash, myState); + node.packedUnrealizedCoordinate = -1; + return myState; + } +}