From 2a433508c73988c000e98656a4ee1baa7773bdde Mon Sep 17 00:00:00 2001 From: Bryson Zimmerman Date: Fri, 15 Nov 2024 09:58:37 -0500 Subject: [PATCH] Added BFS --- src/main/kotlin/BFSPathFinder.kt | 46 +++++++++++++++++++++----- src/main/kotlin/Main.kt | 12 +++++-- src/main/kotlin/World.kt | 5 ++- src/main/kotlin/data/Tile.kt | 4 +++ src/main/kotlin/data/TileProperties.kt | 4 +++ 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/BFSPathFinder.kt b/src/main/kotlin/BFSPathFinder.kt index ad3d7bd..f93d4de 100644 --- a/src/main/kotlin/BFSPathFinder.kt +++ b/src/main/kotlin/BFSPathFinder.kt @@ -1,10 +1,13 @@ package technology.zim +import technology.zim.data.Directions import technology.zim.data.Tile +import technology.zim.data.TileNavigatedArray object BFSPathFinder { //In this particular situation, only a single outcome is likely. Thus, BFS always finds the perfect (and only) path //Skipping the Heap data structure allows better utilization of on-die cache + var gVals:TileNavigatedArray = TileNavigatedArray() fun generatePath(start: Tile, end: Tile) { if (!start.isInBounds() || !end.isInBounds()) { @@ -15,21 +18,48 @@ object BFSPathFinder { println("Ouroboros detected") return } + gVals = TileNavigatedArray(World.sizeX, World.sizeY, false) //Queue for tiles to be checked val frontier = ArrayDeque() frontier.addLast(start) + gVals.set(start, 0) + World.update(start, Directions.BFSFRONTIER) + var current:Tile while (frontier.isNotEmpty()) { - //Grab the next tile - - // Mark it explored + //Grab the next tile and its gValue + current = frontier.removeFirst() + val currentG = gVals.get(current)?:Int.MAX_VALUE-1.also{throw IndexOutOfBoundsException("Couldn't get gValue in BFS")} - //record its distance-cost - - // add its unexplored neighbors + // add its unexplored neighbors and update their gVals + current.getConnections().forEach { + tile -> + if(!World.get(tile).isBFSFrontier()) { + World.update(tile, Directions.BFSFRONTIER) + frontier.addLast(tile) + gVals.set(tile, currentG + 1) + } + } } - + markPath(start, end) + } + fun markPath(start: Tile, end:Tile) { + //Step through the path from end until start + var current = end + var lowestG = Int.MAX_VALUE + var lowestCost = end + while(current != start) { + World.update(current, current.getProperties() + Directions.BFSINPATH) + current.getConnections().forEach { candidateTile -> + val candidateG = gVals.get(candidateTile) ?: -1 + if(candidateTile.isInBounds() && candidateG != -1 && candidateG < lowestG ) { + lowestG = candidateG + lowestCost = candidateTile + } + } + current = lowestCost + } + World.update(start, start.getProperties() + Directions.BFSINPATH) } - } \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 3954055..1d38094 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -16,19 +16,27 @@ class HierarchicalPathfinding { } println("Pathfinding") + + val BFSPathFinderTime = measureTime { + BFSPathFinder.generatePath(Tile(0, 0), Tile(n-1, n-1)) + } + + val ArrayBackedPathfinderTime = measureTime { ArrayBackedPathfinder.generatePath(Tile(0, 0), Tile(n - 1, (n - 1))) } - +/* val MapBackedPathfinderTime = measureTime { MapBackedPathfinder.generatePath(Tile(0, 0), Tile(n - 1, (n - 1))) } + */ println(World.toString()) println(n*n) println("Maze build time: ${buildMazeTime.inWholeMilliseconds} ms") - println("HashMap-Backed Pathfinder time: ${MapBackedPathfinderTime.inWholeMilliseconds}ms") + //println("HashMap-Backed Pathfinder time: ${MapBackedPathfinderTime.inWholeMilliseconds}ms") println("Array-Backed Pathfinder time: ${ArrayBackedPathfinderTime.inWholeMilliseconds}ms") + println("BFS Pathfinder time: ${BFSPathFinderTime.inWholeMilliseconds}ms") } //Clear the maze of pathfinding markers before running another pathfinding algorithm diff --git a/src/main/kotlin/World.kt b/src/main/kotlin/World.kt index 4eadfe8..49e5c36 100644 --- a/src/main/kotlin/World.kt +++ b/src/main/kotlin/World.kt @@ -34,6 +34,9 @@ object World { const val ANSI_CYAN = "\u001B[36m" const val ANSI_WHITE = "\u001B[37m" + fun update(tile: Tile, dir: Directions) { + update(tile, get(tile) + dir) + } fun update(tile: Tile, to: TileProperties) { tiles.set(tile, to) @@ -74,7 +77,7 @@ object World { for (y in 0..sizeY - 1) { //Upper line: Print each tile, print right-hand connections for (x in 0..sizeX - 1) { - if(get(Tile(x, y)).connections and(INPATH.dir) != 0) + if(get(Tile(x, y)).connections and(INPATH.dir+BFSINPATH.dir) != 0) inPath = true else if (get(Tile(x, y)).connections and(FRONTIER.dir) != 0) checked = true diff --git a/src/main/kotlin/data/Tile.kt b/src/main/kotlin/data/Tile.kt index 60511e2..b46dce9 100644 --- a/src/main/kotlin/data/Tile.kt +++ b/src/main/kotlin/data/Tile.kt @@ -5,6 +5,8 @@ import technology.zim.World //Tile is a ULong that represents the X,Y coordinates of a Tile //Contains functions necessary for accessing and manipulating Tiles +//TODO: Untangle visited maths + @JvmInline value class Tile(private val value: ULong) { @@ -40,6 +42,7 @@ value class Tile(private val value: ULong) { return Directions.convertModifier(Tile(otherTile.x() - this.x(), otherTile.y() - this.y()).value) } + //Get adjacent tiles for Prim's Algorithm fun getAdjacentTiles(explored:Boolean): Set { val adj = mutableSetOf() val dirs = Directions.ALL @@ -111,6 +114,7 @@ value class Tile(private val value: ULong) { return "<" + x() + ", " + y() + ">" } + //Get connections for pathfinding algorithms fun getConnections(): Set { val connections = mutableSetOf() val properties = getProperties() diff --git a/src/main/kotlin/data/TileProperties.kt b/src/main/kotlin/data/TileProperties.kt index 20994e4..b8dadfd 100644 --- a/src/main/kotlin/data/TileProperties.kt +++ b/src/main/kotlin/data/TileProperties.kt @@ -58,6 +58,10 @@ value class TileProperties(val connections: Int) { return connections and(MANIFEST.dir) == MANIFEST.dir } + fun isBFSFrontier(): Boolean { + return connections and(BFSFRONTIER.dir) == BFSFRONTIER.dir + } + override fun toString():String { val ret = StringBuilder() if(isWest()) {