Updated MazeFinder and supporting structures to improve performance and implement tools for the PathFinder

main
Bryson Zimmerman 11 months ago
parent 8f274458a9
commit 6a06ba20d3

@ -19,7 +19,12 @@ class HierarchicalPathfinding {
World.setSize(n, n) World.setSize(n, n)
println("Start") println("Start")
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
MazeFinder.primsAlgorithm() try {
MazeFinder.primsAlgorithm()
} catch(e: Exception) {
println(World.toString())
println(e.message)
}
val endTime = System.currentTimeMillis() val endTime = System.currentTimeMillis()
println(World.toString()) println(World.toString())

@ -3,7 +3,6 @@ package technology.zim
import it.unimi.dsi.util.XoRoShiRo128PlusRandom import it.unimi.dsi.util.XoRoShiRo128PlusRandom
import technology.zim.data.Directions import technology.zim.data.Directions
import technology.zim.data.Tile import technology.zim.data.Tile
import technology.zim.util.TrimmableArrayList
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -18,60 +17,49 @@ import kotlin.collections.ArrayList
//http://weblog.jamisbuck.org/2011/1/3/maze-generation-kruskal-s-algorithm //http://weblog.jamisbuck.org/2011/1/3/maze-generation-kruskal-s-algorithm
object MazeFinder { object MazeFinder {
val frontier = TrimmableArrayList<Tile>() val frontier = ArrayList<Tile>()
var startingTile = World.getRandomLocation()
val randGen = XoRoShiRo128PlusRandom() val randGen = XoRoShiRo128PlusRandom()
val tempInGraph = ArrayList<Tile>() fun <E> ArrayList<E>.swapRemove(index: Int) {
this[index] = this[this.lastIndex]
this.removeLast()
}
fun primsAlgorithm() { fun primsAlgorithm() {
cleanUp()
//val randomGen = Random
//Prime the graph with the first connection, which marks the first visited Tiles //Prime the graph with the first connection, which marks the first visited Tiles
startingTile = World.getRandomLocation() val startingTile = World.getRandomLocation()
val tmpIndex = randGen.nextInt(Directions.ALL.size) val tmpIndex = randGen.nextInt(Directions.ALL.size)
val connectorTile = startingTile + Directions.ALL[tmpIndex] val connectorTile = startingTile + Directions.ALL[tmpIndex]
//Connect the first two tiles so they're recognized as in the graph
startingTile.connect(connectorTile) startingTile.connect(connectorTile)
tempInGraph.add(startingTile) //Add their unexplored adjacent tiles to the frontier
tempInGraph.add(connectorTile)
//Choose an arbitrary vertex from G (the graph), and add it to some (initially empty) set V.
frontier.addAll(startingTile.getAdjacentTiles(false)) frontier.addAll(startingTile.getAdjacentTiles(false))
frontier.addAll((connectorTile.getAdjacentTiles(false))) frontier.addAll((connectorTile.getAdjacentTiles(false)))
//Choose a random edge from G, that connects a vertex in V with another vertex not in V.
var current: Tile var current: Tile
var inGraph: Tile var inGraph: Tile
var adjacentExplored: Set<Tile> var adjacentExplored: Set<Tile>
while(frontier.isNotEmpty()) { while(frontier.isNotEmpty()) {
//Grab a random tile from the frontier, mark it as visited //Grab a random tile from the frontier
val random = randGen.nextInt(frontier.size()) val random = randGen.nextInt(frontier.size)
current = frontier.data.get(random) current = frontier.get(random)
frontier.removeAt(random) frontier.swapRemove(random)
//Find adjacent tiles that are in the graph //Find adjacent tiles that are in the graph
adjacentExplored = current.getAdjacentTiles(true) adjacentExplored = current.getAdjacentTiles(true)
if(adjacentExplored.isEmpty()) //Select a random explored tile
println("")
if(adjacentExplored.isEmpty()) {
val world = World.tiles
println ("No explored adjacent tiles found")
}
//Select a random tile from possibleTiles
inGraph = adjacentExplored.elementAt(randGen.nextInt(adjacentExplored.size)) inGraph = adjacentExplored.elementAt(randGen.nextInt(adjacentExplored.size))
//Connect the frontier tile to the graph //Connect the frontier tile to the explored tile
current.connect(inGraph) current.connect(inGraph)
tempInGraph.add(current)
//Add current's unexplored tiles to the frontier, if not already in frontier
frontier.addAll(current.getAdjacentTiles(false))
//Look around the frontier tile for unexplored tiles, add them to the frontier
manifestDestiny(current)
//print(World.toString()) //print(World.toString())
//println("--------------------------------------------") //println("--------------------------------------------")
@ -80,9 +68,15 @@ object MazeFinder {
println("prim") println("prim")
} }
fun cleanUp() { private fun manifestDestiny(current: Tile) {
//Clean up the frontier current.getAdjacentTiles(false).forEach {
frontier.clear() tile ->
val tileprops = World.get(tile)
if(!tileprops.isManifested()) {
World.update(tile, tileprops + Directions.MANIFEST)
frontier.add(tile)
}
}
} }
//Todo: Consider growing World with null values, then use Mazefinder to instantiate as the maze is developed //Todo: Consider growing World with null values, then use Mazefinder to instantiate as the maze is developed
//Consequence: World becomes nullable, requiring null checks to be added in order to avoid... issues //Consequence: World becomes nullable, requiring null checks to be added in order to avoid... issues

@ -5,9 +5,15 @@ enum class Directions(val dir: Int) {
UP(1), UP(1),
DOWN(2), DOWN(2),
LEFT(4), LEFT(4),
RIGHT(8); RIGHT(8),
INPATH(16), //Chosen by the pathfinder
CHECKED(32), //Checked by the pathfinder
MANIFEST(64), //Checked by MazeFinder
;
companion object { companion object {
//Todo: This breaks the connect() function when used
fun opposite(dir: Directions): Directions { fun opposite(dir: Directions): Directions {
return when(dir) { return when(dir) {
UP -> DOWN UP -> DOWN
@ -48,4 +54,5 @@ enum class Directions(val dir: Int) {
fun inv(): Int { fun inv(): Int {
return dir.inv() return dir.inv()
} }
} }

@ -48,7 +48,6 @@ value class Tile(private val value: ULong) {
dirs.forEach { dir -> dirs.forEach { dir ->
val candidateTile = this + dir val candidateTile = this + dir
//Ensure that the tile is within bounds //Ensure that the tile is within bounds
if(candidateTile.isInBounds() && World.get(candidateTile).visited() == explored) if(candidateTile.isInBounds() && World.get(candidateTile).visited() == explored)
{ {

@ -15,7 +15,7 @@ import technology.zim.data.Directions.*
value class TileProperties(val connections: Int) { value class TileProperties(val connections: Int) {
fun visited(): Boolean { fun visited(): Boolean {
return connections != 0 return connections and(UP.dir+DOWN.dir+LEFT.dir+RIGHT.dir )!= 0
} }
//Remove a direction from the list of connections //Remove a direction from the list of connections
@ -53,6 +53,10 @@ value class TileProperties(val connections: Int) {
return connections and(DOWN.dir) == DOWN.dir return connections and(DOWN.dir) == DOWN.dir
} }
fun isManifested(): Boolean {
return connections and(MANIFEST.dir) == MANIFEST.dir
}
override fun toString():String { override fun toString():String {
val ret = StringBuilder() val ret = StringBuilder()
if(isWest()) { if(isWest()) {

@ -1,49 +0,0 @@
package technology.zim.util
import kotlin.random.Random
class TrimmableArrayList<E> {
val data = ArrayList<E>()
fun addAll(c: Collection<E>) {
data.addAll(c)
}
fun isNotEmpty(): Boolean {
return data.isNotEmpty()
}
fun isEmpty(): Boolean {
return data.isEmpty()
}
fun add(item: E) {
data.add(item)
}
fun size():Int {
return data.size
}
fun clear() {
data.clear()
}
fun removeAt(index: Int) {
data[index] = data[data.size - 1]
data.removeLast()
}
//O(n) remove operation
fun remove(item: E) {
data[data.indexOf(item)] = data[data.size - 1]
data.removeLast()
}
fun random(): E {
return data[Random.nextInt(data.size)]
}
}

@ -1,28 +0,0 @@
import technology.zim.data.Tile
import technology.zim.util.TrimmableArrayList
import kotlin.test.BeforeTest
import kotlin.test.Test
class TrimmableArrayListTest {
@Test
fun emptyTest() {
val empty = TrimmableArrayList<Tile>()
assert(empty.isEmpty())
empty.add(Tile(0, 0))
empty.remove(Tile(0, 0))
assert(empty.isEmpty())
empty.addAll(listOf(Tile(0, 0), Tile(1, 1), Tile(2, 2)))
assert(empty.isNotEmpty())
empty.remove(Tile(0, 0))
println("realSize: " + empty.size() + " Size: " + empty.data.size)
assert(empty.size() == 2)
}
}
Loading…
Cancel
Save