mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-10 23:37:31 +07:00
BFS cleanup (#4859)
* BFS cleanup - Kotlin ArrayDeque is no longer experimental - Client code sidestepped class API even where API existed -> closed it down - nextStep when already ended now no-op instead of crash, suits usecases - predicate test very likely to be more expensive than tilesReached lookup - getPathTo only used asSequence() - why not a Sequence in the first place - Compleat documentation. This is very low level, so better help future coders to understand. - Lint: One annoying hardcoded conditional breakpoint, one unhelpful exception * BFS cleanup - patch1
This commit is contained in:
parent
5d249ee673
commit
4daa22ddea
@ -347,7 +347,7 @@ object GameStarter {
|
|||||||
while (landTiles.any()) {
|
while (landTiles.any()) {
|
||||||
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
|
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
|
||||||
bfs.stepToEnd()
|
bfs.stepToEnd()
|
||||||
val tilesInGroup = bfs.tilesReached.keys
|
val tilesInGroup = bfs.getReachedTiles()
|
||||||
landTiles = landTiles.filter { it !in tilesInGroup }
|
landTiles = landTiles.filter { it !in tilesInGroup }
|
||||||
if (tilesInGroup.size > 20) // is this a good number? I dunno, but it's easy enough to change later on
|
if (tilesInGroup.size > 20) // is this a good number? I dunno, but it's easy enough to change later on
|
||||||
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
||||||
@ -463,4 +463,4 @@ object GameStarter {
|
|||||||
val distanceFromCenter = HexMath.getDistance(vector, Vector2.Zero)
|
val distanceFromCenter = HexMath.getDistance(vector, Vector2.Zero)
|
||||||
return hexagonalRadius - distanceFromCenter >= n
|
return hexagonalRadius - distanceFromCenter >= n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ object Automation {
|
|||||||
val findWaterConnectedCitiesAndEnemies =
|
val findWaterConnectedCitiesAndEnemies =
|
||||||
BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() }
|
BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() }
|
||||||
findWaterConnectedCitiesAndEnemies.stepToEnd()
|
findWaterConnectedCitiesAndEnemies.stepToEnd()
|
||||||
if (findWaterConnectedCitiesAndEnemies.tilesReached.keys.none {
|
if (findWaterConnectedCitiesAndEnemies.getReachedTiles().none {
|
||||||
(it.isCityCenter() && it.getOwner() != city.civInfo)
|
(it.isCityCenter() && it.getOwner() != city.civInfo)
|
||||||
|| (it.militaryUnit != null && it.militaryUnit!!.civInfo != city.civInfo)
|
|| (it.militaryUnit != null && it.militaryUnit!!.civInfo != city.civInfo)
|
||||||
}) // there is absolutely no reason for you to make water units on this body of water.
|
}) // there is absolutely no reason for you to make water units on this body of water.
|
||||||
@ -218,4 +218,4 @@ enum class ThreatLevel{
|
|||||||
Medium,
|
Medium,
|
||||||
High,
|
High,
|
||||||
VeryHigh
|
VeryHigh
|
||||||
}
|
}
|
||||||
|
@ -85,10 +85,10 @@ class WorkerAutomation(val unit: MapUnit) {
|
|||||||
// we order cities by their closeness to the worker first, and then check for each one whether there's a viable path
|
// we order cities by their closeness to the worker first, and then check for each one whether there's a viable path
|
||||||
// it can take to an existing connected city.
|
// it can take to an existing connected city.
|
||||||
for (bfs in citiesThatNeedConnectingBfs) {
|
for (bfs in citiesThatNeedConnectingBfs) {
|
||||||
while (bfs.tilesToCheck.isNotEmpty()) {
|
while (!bfs.hasEnded()) {
|
||||||
bfs.nextStep()
|
bfs.nextStep()
|
||||||
for (city in connectedCities)
|
for (city in connectedCities)
|
||||||
if (bfs.tilesToCheck.contains(city)) { // we have a winner!
|
if (bfs.hasReachedTile(city)) { // we have a winner!
|
||||||
val pathToCity = bfs.getPathTo(city).asSequence()
|
val pathToCity = bfs.getPathTo(city).asSequence()
|
||||||
val roadableTiles = pathToCity.filter { it.roadStatus < targetRoad }
|
val roadableTiles = pathToCity.filter { it.roadStatus < targetRoad }
|
||||||
val tileToConstructRoadOn: TileInfo
|
val tileToConstructRoadOn: TileInfo
|
||||||
|
@ -1,59 +1,80 @@
|
|||||||
package com.unciv.logic.map
|
package com.unciv.logic.map
|
||||||
|
|
||||||
// Kotlin's ArrayDeque is experimental
|
import kotlin.collections.ArrayDeque
|
||||||
import java.util.ArrayDeque
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines intermediate steps of a breadth-first search, for use in either get shortest path or get onnected tiles.
|
* Defines intermediate steps of a breadth-first search, for use in either get shortest path or get connected tiles.
|
||||||
*/
|
*/
|
||||||
class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean) {
|
class BFS(
|
||||||
var tilesToCheck = ArrayDeque<TileInfo>()
|
val startingPoint: TileInfo,
|
||||||
|
private val predicate : (TileInfo) -> Boolean
|
||||||
|
) {
|
||||||
|
/** remaining tiles to check */
|
||||||
|
private val tilesToCheck = ArrayDeque<TileInfo>(37) // needs resize at distance 4
|
||||||
|
|
||||||
/** each tile reached points to its parent tile, where we got to it from */
|
/** each tile reached points to its parent tile, where we got to it from */
|
||||||
val tilesReached = HashMap<TileInfo, TileInfo>()
|
private val tilesReached = HashMap<TileInfo, TileInfo>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
tilesToCheck.add(startingPoint)
|
tilesToCheck.add(startingPoint)
|
||||||
tilesReached[startingPoint] = startingPoint
|
tilesReached[startingPoint] = startingPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Process fully until there's nowhere left to check */
|
||||||
fun stepToEnd() {
|
fun stepToEnd() {
|
||||||
while (!hasEnded())
|
while (!hasEnded())
|
||||||
nextStep()
|
nextStep()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process until either [destination] is reached or there's nowhere left to check
|
||||||
|
* @return `this` instance for chaining
|
||||||
|
*/
|
||||||
fun stepUntilDestination(destination: TileInfo): BFS {
|
fun stepUntilDestination(destination: TileInfo): BFS {
|
||||||
while (!tilesReached.containsKey(destination) && !hasEnded())
|
while (!tilesReached.containsKey(destination) && !hasEnded())
|
||||||
nextStep()
|
nextStep()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process one tile-to-search, fetching all neighbors not yet touched
|
||||||
|
* and adding those that fulfill the [predicate] to the reached set
|
||||||
|
* and to the yet-to-be-processed set.
|
||||||
|
*
|
||||||
|
* Will do nothing when [hasEnded] returns `true`
|
||||||
|
*/
|
||||||
fun nextStep() {
|
fun nextStep() {
|
||||||
val current = tilesToCheck.remove()
|
val current = tilesToCheck.removeFirstOrNull() ?: return
|
||||||
for (neighbor in current.neighbors) {
|
for (neighbor in current.neighbors) {
|
||||||
if (predicate(neighbor) && !tilesReached.containsKey(neighbor)) {
|
if (neighbor !in tilesReached && predicate(neighbor)) {
|
||||||
tilesReached[neighbor] = current
|
tilesReached[neighbor] = current
|
||||||
tilesToCheck.add(neighbor)
|
tilesToCheck.add(neighbor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPathTo(destination: TileInfo): ArrayList<TileInfo> {
|
/**
|
||||||
val path = ArrayList<TileInfo>()
|
* @return a Sequence from the [destination] back to the [startingPoint], including both, or empty if [destination] has not been reached
|
||||||
path.add(destination)
|
*/
|
||||||
|
fun getPathTo(destination: TileInfo): Sequence<TileInfo> = sequence {
|
||||||
var currentNode = destination
|
var currentNode = destination
|
||||||
while (currentNode != startingPoint) {
|
while (true) {
|
||||||
val parent = tilesReached[currentNode]
|
val parent = tilesReached[currentNode] ?: break // destination is not in our path
|
||||||
if (parent == null) return ArrayList()// destination is not in our path
|
yield(currentNode)
|
||||||
|
if (currentNode == startingPoint) break
|
||||||
currentNode = parent
|
currentNode = parent
|
||||||
path.add(currentNode)
|
|
||||||
}
|
}
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return true if there are no more tiles to check */
|
||||||
fun hasEnded() = tilesToCheck.isEmpty()
|
fun hasEnded() = tilesToCheck.isEmpty()
|
||||||
|
|
||||||
fun hasReachedTile(tile: TileInfo) =
|
/** @return true if the [tile] has been reached */
|
||||||
tilesReached.containsKey(tile)
|
fun hasReachedTile(tile: TileInfo) = tilesReached.containsKey(tile)
|
||||||
}
|
|
||||||
|
/** @return all tiles reached so far */
|
||||||
|
fun getReachedTiles(): MutableSet<TileInfo> = tilesReached.keys
|
||||||
|
|
||||||
|
/** @return number of tiles reached so far */
|
||||||
|
fun size() = tilesReached.size
|
||||||
|
}
|
||||||
|
@ -254,10 +254,6 @@ class TileMap {
|
|||||||
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any {
|
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any {
|
||||||
bNeighbor: TileInfo ->
|
bNeighbor: TileInfo ->
|
||||||
val bNeighborHeight = bNeighbor.getHeight()
|
val bNeighborHeight = bNeighbor.getHeight()
|
||||||
if(cTile.resource=="Marble"
|
|
||||||
&& bNeighbor.terrainFeatures.contains("Forest")
|
|
||||||
)
|
|
||||||
println()
|
|
||||||
viewableTiles.contains(bNeighbor) && (
|
viewableTiles.contains(bNeighbor) && (
|
||||||
currentTileHeight > bNeighborHeight // a>b
|
currentTileHeight > bNeighborHeight // a>b
|
||||||
|| cTileHeight > bNeighborHeight // c>b
|
|| cTileHeight > bNeighborHeight // c>b
|
||||||
|
@ -183,12 +183,12 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
|||||||
|
|
||||||
val distanceToTiles = getDistanceToTiles()
|
val distanceToTiles = getDistanceToTiles()
|
||||||
|
|
||||||
class UnreachableDestinationException : Exception()
|
class UnreachableDestinationException(msg: String) : Exception(msg)
|
||||||
|
|
||||||
// If the tile is far away, we need to build a path how to get there, and then take the first step
|
// If the tile is far away, we need to build a path how to get there, and then take the first step
|
||||||
if (!distanceToTiles.containsKey(finalDestination))
|
if (!distanceToTiles.containsKey(finalDestination))
|
||||||
return getShortestPath(finalDestination).firstOrNull()
|
return getShortestPath(finalDestination).firstOrNull()
|
||||||
?: throw UnreachableDestinationException()
|
?: throw UnreachableDestinationException("$unit ${unit.currentTile.position} cannot reach $finalDestination")
|
||||||
|
|
||||||
// we should be able to get there this turn
|
// we should be able to get there this turn
|
||||||
if (canMoveTo(finalDestination))
|
if (canMoveTo(finalDestination))
|
||||||
@ -565,4 +565,4 @@ class PathsToTilesWithinTurn : LinkedHashMap<TileInfo, UnitMovementAlgorithms.Pa
|
|||||||
}
|
}
|
||||||
return reversePathList.reversed()
|
return reversePathList.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user