Added experimental movment which assumes unknown tiles are impassible - hopefully will resolve #3009

This commit is contained in:
Yair Morgenstern 2021-01-27 17:59:07 +02:00
parent aa944bad87
commit 30e5722c24
4 changed files with 62 additions and 3 deletions

View File

@ -2,6 +2,7 @@ package com.unciv.logic.map
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
class UnitMovementAlgorithms(val unit:MapUnit) { class UnitMovementAlgorithms(val unit:MapUnit) {
@ -57,7 +58,12 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
class ParentTileAndTotalDistance(val parentTile: TileInfo, val totalDistance: Float) class ParentTileAndTotalDistance(val parentTile: TileInfo, val totalDistance: Float)
val MOVEMENT_TESTING = UncivGame.Current.settings.unitMovementIncludesImpassibles
fun isUnknownTileWeShouldAssumeToBePassable(tileInfo: TileInfo) = MOVEMENT_TESTING && !unit.civInfo.exploredTiles.contains(tileInfo.position)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
if(MOVEMENT_TESTING) return getDistanceToTilesWithinTurnIncludingUnknownImpassibles(origin, unitMovement)
val distanceToTiles = PathsToTilesWithinTurn() val distanceToTiles = PathsToTilesWithinTurn()
if (unitMovement == 0f) return distanceToTiles if (unitMovement == 0f) return distanceToTiles
@ -102,6 +108,53 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return distanceToTiles return distanceToTiles
} }
fun getDistanceToTilesWithinTurnIncludingUnknownImpassibles(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
val distanceToTiles = PathsToTilesWithinTurn()
if (unitMovement == 0f) return distanceToTiles
val currentUnitTile = unit.currentTile
// This is for performance, because this is called all the time
val unitTile = if(origin==currentUnitTile.position) currentUnitTile else currentUnitTile.tileMap[origin]
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile, 0f)
var tilesToCheck = listOf(unitTile)
while (tilesToCheck.isNotEmpty()) {
val updatedTiles = ArrayList<TileInfo>()
for (tileToCheck in tilesToCheck)
for (neighbor in tileToCheck.neighbors) {
var totalDistanceToTile: Float
if (unit.civInfo.exploredTiles.contains(neighbor.position)) {
if (!canPassThrough(neighbor))
totalDistanceToTile = unitMovement // Can't go here.
// The reason that we don't just "return" is so that when calculating how to reach an enemy,
// You need to assume his tile is reachable, otherwise all movement algs on reaching enemy
// cities and units goes kaput.
else {
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor, unit.civInfo)
totalDistanceToTile = distanceToTiles[tileToCheck]!!.totalDistance + distanceBetweenTiles
}
} else totalDistanceToTile = distanceToTiles[tileToCheck]!!.totalDistance + 1f // If we don't know then we just guess it to be 1.
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!!.totalDistance > totalDistanceToTile) { // this is the new best path
if (totalDistanceToTile < unitMovement) // We can still keep moving from here!
updatedTiles += neighbor
else
totalDistanceToTile = unitMovement
// In Civ V, you can always travel between adjacent tiles, even if you don't technically
// have enough movement points - it simple depletes what you have
distanceToTiles[neighbor] = ParentTileAndTotalDistance(tileToCheck, totalDistanceToTile)
}
}
tilesToCheck = updatedTiles
}
return distanceToTiles
}
/** Returns an empty list if there's no way to get there */ /** Returns an empty list if there's no way to get there */
fun getShortestPath(destination: TileInfo): List<TileInfo> { fun getShortestPath(destination: TileInfo): List<TileInfo> {
val currentTile = unit.getTile() val currentTile = unit.getTile()
@ -127,7 +180,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!.totalDistance distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!.totalDistance
else { else {
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing... if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
if (!canMoveTo(reachableTile)) continue // This is a tile that we can't actually enter - either an intermediary tile containing our unit, or an enemy unit/city if (!isUnknownTileWeShouldAssumeToBePassable(reachableTile) &&
!canMoveTo(reachableTile)) continue // This is a tile that we can't actually enter - either an intermediary tile containing our unit, or an enemy unit/city
movementTreeParents[reachableTile] = tileToCheck movementTreeParents[reachableTile] = tileToCheck
newTilesToCheck.add(reachableTile) newTilesToCheck.add(reachableTile)
} }

View File

@ -12,6 +12,7 @@ class GameSettings {
var showTileYields: Boolean = false var showTileYields: Boolean = false
var checkForDueUnits: Boolean = true var checkForDueUnits: Boolean = true
var singleTapMove: Boolean = false var singleTapMove: Boolean = false
var unitMovementIncludesImpassibles: Boolean = false
var language: String = "English" var language: String = "English"
var resolution: String = "900x600" // Auto-detecting resolution was a BAD IDEA since it needs to be based on DPI AND resolution. var resolution: String = "900x600" // Auto-detecting resolution was a BAD IDEA since it needs to be based on DPI AND resolution.
var tutorialsShown = HashSet<String>() var tutorialsShown = HashSet<String>()

View File

@ -112,7 +112,9 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
if (previousSelectedUnits.isNotEmpty() && previousSelectedUnits.any { it.getTile() != tileInfo } if (previousSelectedUnits.isNotEmpty() && previousSelectedUnits.any { it.getTile() != tileInfo }
&& worldScreen.isPlayersTurn && worldScreen.isPlayersTurn
&& previousSelectedUnits.any { it.movement.canMoveTo(tileInfo) }) { && previousSelectedUnits.any { it.movement.canMoveTo(tileInfo) ||
it.movement.isUnknownTileWeShouldAssumeToBePassable(tileInfo)
}) {
// this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread
addTileOverlaysWithUnitMovement(previousSelectedUnits, tileInfo) addTileOverlaysWithUnitMovement(previousSelectedUnits, tileInfo)
} else addTileOverlays(tileInfo) // no unit movement but display the units in the tile etc. } else addTileOverlays(tileInfo) // no unit movement but display the units in the tile etc.
@ -376,7 +378,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
// The tile is within move range // The tile is within move range
tileToColor.showCircle(Color.BLUE, 0.3f) tileToColor.showCircle(Color.BLUE, 0.3f)
} }
if (unit.movement.canMoveTo(tile)) if (unit.movement.canMoveTo(tile) || unit.movement.isUnknownTileWeShouldAssumeToBePassable(tile))
tileToColor.showCircle(Color.WHITE, tileToColor.showCircle(Color.WHITE,
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f) if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
} }

View File

@ -108,6 +108,8 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr
addYesNoRow ("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it } addYesNoRow ("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it }
addYesNoRow ("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it } addYesNoRow ("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it }
addYesNoRow ("Movement assumes unknown tiles to be passable", settings.unitMovementIncludesImpassibles)
{ settings.unitMovementIncludesImpassibles = it }
addYesNoRow ("Auto-assign city production", settings.autoAssignCityProduction, true) { addYesNoRow ("Auto-assign city production", settings.autoAssignCityProduction, true) {
settings.autoAssignCityProduction = it settings.autoAssignCityProduction = it
if (it && previousScreen is WorldScreen && if (it && previousScreen is WorldScreen &&