Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy

main
Bryson Zimmerman 12 months ago
parent a5bced1cb1
commit fa654e4ce8

@ -4,10 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="741ceb24-e493-47c1-9f25-1dbd8b151381" name="Changes" comment="Filled out World class necessities, created appropriate data holders. Should be ready for Mazefinder">
<change afterPath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" afterDir="false" />
<list default="true" id="741ceb24-e493-47c1-9f25-1dbd8b151381" name="Changes" comment="Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/Mazefinder.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/Mazefinder.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/World.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/World.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Directions.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/Tile.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/TileProperties.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/main/kotlin/data/WorldData.kt" beforeDir="false" afterPath="$PROJECT_DIR$/src/main/kotlin/data/WorldData.kt" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
@ -33,6 +36,9 @@
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="ProblemsViewState">
<option name="selectedTabId" value="CurrentFile" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 2
}</component>
@ -174,7 +180,15 @@
<option name="project" value="LOCAL" />
<updated>1729358883715</updated>
</task>
<option name="localTasksCounter" value="6" />
<task id="LOCAL-00006" summary="Partial refactor of World into an 'object' and delegating functions to their appropriate objects.">
<option name="closed" value="true" />
<created>1729362135731</created>
<option name="number" value="00006" />
<option name="presentableId" value="LOCAL-00006" />
<option name="project" value="LOCAL" />
<updated>1729362135731</updated>
</task>
<option name="localTasksCounter" value="7" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
@ -219,6 +233,8 @@
<MESSAGE value="Added intellij files... for better or for worse" />
<MESSAGE value="Initial work on data structures. Saving for move to another computer." />
<MESSAGE value="Filled out World class necessities, created appropriate data holders. Should be ready for Mazefinder" />
<option name="LAST_COMMIT_MESSAGE" value="Filled out World class necessities, created appropriate data holders. Should be ready for Mazefinder" />
<MESSAGE value="Partial refactor of World into an 'object' and delegating functions to their appropriate objects." />
<MESSAGE value="Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy" />
<option name="LAST_COMMIT_MESSAGE" value="Implemented MazeFinder, significant changes to supporting data structures to match developed understanding of Kotlin and Object-Oriented philosophy" />
</component>
</project>

@ -1,5 +1,8 @@
package technology.zim
import technology.zim.data.Directions
import technology.zim.data.Tile
//Build the maze
//Options:
@ -8,13 +11,69 @@ package technology.zim
//Wall-builder?
//https://emmilco.github.io/path_finder/
//Needs https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/PriorityQueue.html
//and https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Comparator.html
//http://weblog.jamisbuck.org/2011/1/10/maze-generation-prim-s-algorithm
//http://weblog.jamisbuck.org/2011/1/3/maze-generation-kruskal-s-algorithm
class Mazefinder {
class MazeFinder {
val visited = ArrayList<ArrayList<Boolean>>(World.tiles.data.size).apply {
this.forEach() { element -> element.fill(false) }
}
val frontier = mutableSetOf<Tile>()
fun primsAlgorithm() {
//Choose an arbitrary vertex from G (the graph), and add it to some (initially empty) set V.
frontier.add(World.getRandomLocation())
//Choose a random edge from G, that connects a vertex in V with another vertex not in V.
var current: Tile
var prospect: Pair<Tile, Directions>
var possibleTiles: Set<Pair<Tile, Directions>>
while(frontier.isNotEmpty()) {
//Grab a random tile from the frontier, mark it as visited
current = frontier.random()
frontier.remove(current)
visited[current.value.first][current.value.second] = true
//Find all adjacent tiles to that tile
possibleTiles = getUnexploredAdjacent(current)
//It's possible that this frontier tile has no unexplored neighbors, in which case removing it from the frontier is enough
if(possibleTiles.isNotEmpty()) {
//Grab a random tile from the possible set
prospect = possibleTiles.random()
//Connect it to the current tile
current.connect(prospect.second)
//Add its adjacent unexplored tiles to frontier
frontier.addAll(getUnexploredAdjacentWithoutDirection(prospect.first))
}
}
}
fun getUnexploredAdjacentWithoutDirection(tile: Tile): Set<Tile> {
val result = mutableSetOf<Tile>()
getUnexploredAdjacent(tile).forEach { pair -> result.add(pair.first)}
return result
}
fun getUnexploredAdjacent(tile: Tile): Set<Pair<Tile, Directions>> {
val adj = mutableSetOf<Pair<Tile, Directions>>()
val dirs = Directions.NORTH.all()
dirs.forEach { dir ->
val candidateTile = tile + dir
//Ensure that the tile is within bounds
if(candidateTile.isInBounds() && !visited[candidateTile.value.first][candidateTile.value.second])
{
adj.add(Pair(candidateTile, dir))
}
}
return adj
}
//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
//It's probable that this will only be needed at large scales
}

@ -1,11 +1,13 @@
package technology.zim
import technology.zim.data.Directions
import technology.zim.data.Tile
import technology.zim.data.TileProperties
import technology.zim.data.WorldData
import java.util.*
//Singleton object containing a set of tiles
//Has helper functions included
//For now, keep it small with uncompresed tile representation
//In the future, this could be stored in a gzipped file and memory mapped
@ -15,37 +17,22 @@ import java.util.*
// Which currently only contains the edges of the "graph", stored as directions
object World {
val tiles = WorldData(ArrayList<ArrayList<TileProperties>>())
//Default size should be 20
val tiles = WorldData(ArrayList<ArrayList<TileProperties>>(20))
//Returns a coordinate pair
fun getRandomLocation(): Tile {
return Tile(Pair((0..tiles.data.size).random(), (0..tiles.data[0].size).random()))
}
//Get the properties of the tile at the given coordinates
fun getProperties(tile: Tile): TileProperties {
return tiles.data.elementAt(tile.loc.first).elementAt(tile.loc.second)
fun setSize(x: Int, y: Int) {
tiles.setSize(x, y)
}
//fun addConnection(at: Pair<Int, Int>, dir: Directions)
//Sort out what cells are connected. Induces some CPU overhead compared to storing a simple list
//Benefit is smaller memory footprint by using references to singleton enums
fun getConnections(at: Tile): Set<Tile> {
val listTiles = ArrayList<Tile>()
for (dir: Directions in getProperties(at).connections) {
//Use the ghost of linear algebra to identify potential neighbor tiles
val candidateTile = Tile(Pair(at.loc.first + dir.dif.first, at.loc.second + dir.dif.second))
//Ensure that the tile is within bounds
if(candidateTile.loc.first > 0 &&
candidateTile.loc.second > 0 &&
candidateTile.loc.first < tiles.data.size &&
candidateTile.loc.second < tiles.data[candidateTile.loc.first].size) {
listTiles.add(candidateTile)
}
}
return listTiles.toSet()
}
//TODO: toString method
//Reads array left to right, top to bottom
//Only looks at SOUTH and EAST connections
//Either connection exists or it does not, whitespace character for exists, some block-appearing char for not
//Needs one monowidth char space between each column of array
//Needs one line between each row, line containing vertical connections
}

@ -1,5 +1,32 @@
package technology.zim.data
enum class Directions(val dif: Pair<Int, Int>) {
NORTH(Pair(0, 1)), SOUTH(Pair(0, -1)), EAST(Pair(1, 0)), WEST(Pair(-1, 0))
enum class Directions(val value: Pair<Int, Int>) {
NORTH(Pair(0, 1)) {
override fun opposite(): Directions {
return SOUTH
}
},
SOUTH(Pair(0, -1)) {
override fun opposite(): Directions {
return NORTH
}
},
EAST(Pair(1, 0)) {
override fun opposite(): Directions {
return WEST
}
},
WEST(Pair(-1, 0)) {
override fun opposite(): Directions {
return EAST
}
};
//Return the opposite direction
abstract fun opposite(): Directions
fun all(): MutableSet<Directions> {
return mutableSetOf(NORTH, SOUTH, EAST, WEST)
}
}

@ -1,7 +1,63 @@
package technology.zim.data
import technology.zim.World
@JvmInline
value class Tile(val loc: Pair<Int, Int>) {
//Todo: Function for cell to add a connection
//Todo: Refactor so Tiles know how to get their connections, not the world
value class Tile(val value: Pair<Int, Int>) {
constructor(x: Int, y: Int): this(Pair(x, y))
//Connect two tiles together.
//Calls utility function on the connected cell
fun connect(dir: Directions) {
val candidateTile = this+dir
//Ensure that the tile is within bounds
if(candidateTile.isInBounds())
{
this.getProperties().add(dir)
(this + dir).getProperties().add(dir.opposite())
}
else {
//TODO: Consider just silently not connecting out-of-bounds values
println("Attempted to connect to outside bounds: <" +
candidateTile.value.first + ", " + candidateTile.value.second
+ "> From Tile: <" + this.value.first + ", " +
this.value.second + ">")
return
}
}
//Gets Tile objects for all connected directions
//Used for finding a path through the maze
fun getConnections(): MutableSet<Tile> {
val listTiles = mutableSetOf<Tile>()
for (dir: Directions in getProperties().connections) {
//Use the ghost of linear algebra to identify potential neighbor tiles
listTiles.add(this+dir)
}
return listTiles
}
fun isInBounds(): Boolean {
return value.first > 0 &&
value.second > 0 &&
value.first < World.tiles.data.size &&
value.second < World.tiles.data[value.second].size
}
//Get the properties of the tile at the given coordinates
fun getProperties(): TileProperties {
return World.tiles.data.elementAt(value.first).elementAt(value.second)
}
//Get tile at given direction
operator fun plus(dir: Directions): Tile {
return Tile(value.first + dir.value.first, value.second + dir.value.second)
}
//Get tile at direction opposite of given direction
operator fun minus(dir: Directions): Tile {
return Tile(value.first - dir.value.first, value.second - dir.value.second)
}
}

@ -1,11 +1,28 @@
package technology.zim.data
import technology.zim.World
import technology.zim.World.tiles
//Data holder for a Tile
//Should contain a mutable set of connected directions
//Later, can hold jumps to other locations other such fancy features
//For now, a simple inline class to mitigate memory usage
@JvmInline
value class TileProperties(val connections:MutableSet<Directions> = mutableSetOf<Directions>()) {
//Remove a direction from the list of connections
fun remove(dir: Directions) {
connections.remove(dir)
}
//Add a direction to the list of connections
//Should only be accessed by the Tile class
fun add(dir: Directions) {
connections.add(dir)
}
}

@ -1,16 +1,18 @@
package technology.zim.data
//Data structure wrapper for more easily readable code
//Data structure wrapper for a set of tiles
@JvmInline
value class WorldData constructor(val data: ArrayList<ArrayList<TileProperties>>) {
value class WorldData(val data: ArrayList<ArrayList<TileProperties>>) {
fun setSize(xmin : Int, ymin : Int) {
//Fill every column with an ArrayList of TileProperties
with(data) {
this.ensureCapacity(xmin)
this.fill(ArrayList<TileProperties>())
}
//Fill every row with TileProperties
for(y in data) {
with(y) {
this.ensureCapacity((ymin))

Loading…
Cancel
Save