Organized unit algorithms

Combat units now head towards the closest city with no garrison if exists
This commit is contained in:
Yair Morgenstern 2018-05-09 21:31:34 +03:00
parent afa6478745
commit 4cfbf18178
5 changed files with 125 additions and 121 deletions

View file

@ -62,7 +62,7 @@ class UnitAutomation{
if (unitToAttack.getBaseUnit().unitType == UnitType.Civilian) { // kill
unitToAttack.civInfo.addNotification("Our " + unitToAttack.name + " was destroyed by an enemy " + unit.name + "!", unitTileToAttack.position)
unitTileToAttack.unit = null
unit.headTowards(unitTileToAttack.position)
unit.movementAlgs().headTowards(unitTileToAttack)
return
}
@ -70,7 +70,7 @@ class UnitAutomation{
if (damageToAttacker < unit.health) { // don't attack if we'll die from the attack
if(MapUnitCombatant(unit).isMelee())
unit.headTowards(unitTileToAttack.position)
unit.movementAlgs().headTowards(unitTileToAttack)
Battle(unit.civInfo.gameInfo).attack(MapUnitCombatant(unit), MapUnitCombatant(unitToAttack))
return
}
@ -78,6 +78,13 @@ class UnitAutomation{
if(unit.getTile().isCityCenter()) return // It's always good to have a unit in the city center, so if you havn't found annyonw aroud to attack, forget it.
val reachableCitiesWithoutUnits = unit.civInfo.cities.filter { it.getCenterTile().unit==null
&& unit.movementAlgs().canReach(it.getCenterTile()) }
if(reachableCitiesWithoutUnits.isNotEmpty()){
val closestCityWithoutUnit = reachableCitiesWithoutUnits
.minBy { unit.movementAlgs().getShortestPath(it.getCenterTile()).size }!!
unit.movementAlgs().headTowards(closestCityWithoutUnit.getCenterTile())
}
if (unit.health < 80) {
healUnit(unit)
@ -94,7 +101,7 @@ class UnitAutomation{
.firstOrNull { attackableTiles.contains(it) }
if (closestUnit != null) {
unit.headTowards(closestUnit.position)
unit.movementAlgs().headTowards(closestUnit)
return
}
@ -132,7 +139,7 @@ class UnitAutomation{
if (unit.getTile() == bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
else {
unit.headTowards(bestCityLocation.position)
unit.movementAlgs().headTowards(bestCityLocation)
if (unit.currentMovement > 0 && unit.getTile() == bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
}

View file

@ -3,18 +3,17 @@ package com.unciv.logic.automation
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitMovementAlgorithms
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.TileImprovement
class WorkerAutomation {
fun automateWorkerAction(unit: MapUnit) {
var tile = unit.getTile()
val tile = unit.getTile()
val tileToWork = findTileToWork(unit)
if (tileToWork != tile) {
tile = unit.headTowards(tileToWork.position)
unit.doPreTurnAction(tile)
unit.movementAlgs().headTowards(tileToWork)
unit.doPreTurnAction()
return
}
if (tile.improvementInProgress == null) {
@ -39,8 +38,8 @@ class WorkerAutomation {
// which is why we DON'T calculate this for every possible tile in the radius,
// but only for the tile that's about to be chosen.
val selectedTile = workableTiles.firstOrNull{
UnitMovementAlgorithms(currentTile.tileMap)
.getShortestPath(currentTile.position, workableTiles.first().position,worker)
worker.movementAlgs()
.getShortestPath(workableTiles.first())
.isNotEmpty()}
if (selectedTile != null

View file

@ -26,31 +26,32 @@ class MapUnit {
fun getDistanceToTiles(): HashMap<TileInfo, Float> {
val tile = getTile()
return UnitMovementAlgorithms(tile.tileMap).getDistanceToTilesWithinTurn(tile.position,currentMovement,
this)
return movementAlgs().getDistanceToTilesWithinTurn(tile.position,currentMovement)
}
fun doPreTurnAction(tile: TileInfo) {
fun doPreTurnAction() {
val currentTile = getTile()
if (currentMovement == 0f) return // We've already done stuff this turn, and can't do any more stuff
if (action != null && action!!.startsWith("moveTo")) {
val destination = action!!.replace("moveTo ", "").split(",").dropLastWhile { it.isEmpty() }.toTypedArray()
val destinationVector = Vector2(Integer.parseInt(destination[0]).toFloat(), Integer.parseInt(destination[1]).toFloat())
val gotTo = headTowards(destinationVector)
if(gotTo==tile) // We didn't move at all
val gotTo = movementAlgs().headTowards(currentTile.tileMap[destinationVector])
if(gotTo==currentTile) // We didn't move at all
return
if (gotTo.position == destinationVector) action = null
if (currentMovement != 0f) doPreTurnAction(gotTo)
if (currentMovement != 0f) doPreTurnAction()
return
}
if (action == "automation") WorkerAutomation().automateWorkerAction(this)
}
private fun doPostTurnAction(tile: TileInfo) {
if (name == "Worker" && tile.improvementInProgress != null) workOnImprovement(tile)
private fun doPostTurnAction() {
if (name == "Worker" && getTile().improvementInProgress != null) workOnImprovement()
}
private fun workOnImprovement(tile: TileInfo) {
private fun workOnImprovement() {
val tile=getTile()
tile.turnsToImprovement -= 1
if (tile.turnsToImprovement != 0) return
when {
@ -65,9 +66,6 @@ class MapUnit {
/**
* @return The tile that we reached this turn
*/
fun headTowards(destination: Vector2): TileInfo {
return UnitMovementAlgorithms(getTile().tileMap).headTowards(this,destination)
}
private fun heal(){
val tile = getTile()
@ -93,13 +91,12 @@ class MapUnit {
}
fun nextTurn() {
val tile = getTile()
doPostTurnAction(tile)
doPostTurnAction()
if(currentMovement==maxMovement.toFloat()){ // didn't move this turn
heal()
}
currentMovement = maxMovement.toFloat()
doPreTurnAction(tile)
doPreTurnAction()
}
fun hasUnique(unique:String): Boolean {
@ -107,4 +104,5 @@ class MapUnit {
return baseUnit.uniques!=null && baseUnit.uniques!!.contains(unique)
}
fun movementAlgs() = UnitMovementAlgorithms(this)
}

View file

@ -2,136 +2,136 @@ package com.unciv.logic.map
import com.badlogic.gdx.math.Vector2
class UnitMovementAlgorithms(val tileMap: TileMap){
private fun getMovementCostBetweenAdjacentTiles(from:TileInfo, to:TileInfo, unit:MapUnit): Float {
class UnitMovementAlgorithms(val unit:MapUnit) {
val tileMap = unit.getTile().tileMap
private fun getMovementCostBetweenAdjacentTiles(from: TileInfo, to: TileInfo): Float {
if (from.roadStatus === RoadStatus.Railroad && to.roadStatus === RoadStatus.Railroad)
return 1 / 10f
if (from.roadStatus !== RoadStatus.None && to.roadStatus !== RoadStatus.None) //Road
{
if(unit.civInfo.tech.isResearched("Machinery")) return 1/3f
else return 1/2f
if (unit.civInfo.tech.isResearched("Machinery")) return 1 / 3f
else return 1 / 2f
}
if(unit.hasUnique("Ignores terrain cost")) return 1f
if (unit.hasUnique("Ignores terrain cost")) return 1f
if(unit.hasUnique("Rough terrain penalty")
&& (to.baseTerrain=="Hill" || to.terrainFeature=="Forest" || to.terrainFeature=="Jungle"))
if (unit.hasUnique("Rough terrain penalty")
&& (to.baseTerrain == "Hill" || to.terrainFeature == "Forest" || to.terrainFeature == "Jungle"))
return 4f
return to.lastTerrain.movementCost.toFloat() // no road
}
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement:Float, unit: MapUnit): HashMap<TileInfo, Float> {
val distanceToTiles = HashMap<TileInfo, Float>()
val unitTile = tileMap[origin]
distanceToTiles[unitTile] = 0f
var tilesToCheck = listOf(unitTile)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> {
val distanceToTiles = HashMap<TileInfo, Float>()
val unitTile = tileMap[origin]
distanceToTiles[unitTile] = 0f
var tilesToCheck = listOf(unitTile)
while (!tilesToCheck.isEmpty()) {
val updatedTiles = ArrayList<TileInfo>()
for (tileToCheck in tilesToCheck)
for (neighbor in tileToCheck.neighbors) {
if(neighbor.getOwner() != null && neighbor.getOwner() != unit.civInfo
&& neighbor.isCityCenter())
continue // Enemy city, can't move through it!
while (!tilesToCheck.isEmpty()) {
val updatedTiles = ArrayList<TileInfo>()
for (tileToCheck in tilesToCheck)
for (neighbor in tileToCheck.neighbors) {
if (neighbor.getOwner() != null && neighbor.getOwner() != unit.civInfo
&& neighbor.isCityCenter())
continue // Enemy city, can't move through it!
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck,neighbor,unit)
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor)
var totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) {
if (totalDistanceToTile < unitMovement)
updatedTiles += neighbor
else
totalDistanceToTile = unitMovement
distanceToTiles[neighbor] = totalDistanceToTile
}
}
var totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) {
if (totalDistanceToTile < unitMovement)
updatedTiles += neighbor
else
totalDistanceToTile = unitMovement
distanceToTiles[neighbor] = totalDistanceToTile
}
}
tilesToCheck = updatedTiles
}
tilesToCheck = updatedTiles
}
return distanceToTiles
}
return distanceToTiles
}
fun getShortestPath(origin: Vector2, destination: Vector2, unit:MapUnit): List<TileInfo> {
if(origin.equals(destination)) return listOf(tileMap[origin]) // edge case that's needed, so that workers will know that they can reach their own tile. *sig
fun getShortestPath(destination: TileInfo): List<TileInfo> {
val currentTile = unit.getTile()
if (currentTile.position.equals(destination)) return listOf(currentTile) // edge case that's needed, so that workers will know that they can reach their own tile. *sig
var tilesToCheck: List<TileInfo> = listOf(tileMap[origin])
val movementTreeParents = HashMap<TileInfo, TileInfo?>() // contains a map of "you can get from X to Y in that turn"
movementTreeParents[tileMap[origin]] = null
var tilesToCheck: List<TileInfo> = listOf(currentTile)
val movementTreeParents = HashMap<TileInfo, TileInfo?>() // contains a map of "you can get from X to Y in that turn"
movementTreeParents[currentTile] = null
var distance = 1
while (true) {
val newTilesToCheck = ArrayList<TileInfo>()
val distanceToDestination = HashMap<TileInfo, Float>()
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.maxMovement.toFloat()
for (tileToCheck in tilesToCheck) {
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn, unit)
for (reachableTile in distanceToTilesThisTurn.keys) {
if(reachableTile.position == destination)
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
else {
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
if (reachableTile.position != destination && reachableTile.unit != null) continue // This is an intermediary tile that contains a unit - we can't go there!
movementTreeParents[reachableTile] = tileToCheck
newTilesToCheck.add(reachableTile)
}
}
}
var distance = 1
while (true) {
val newTilesToCheck = ArrayList<TileInfo>()
val distanceToDestination = HashMap<TileInfo, Float>()
val movementThisTurn = if (distance == 1) unit.currentMovement else unit.maxMovement.toFloat()
for (tileToCheck in tilesToCheck) {
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn)
for (reachableTile in distanceToTilesThisTurn.keys) {
if (reachableTile == destination)
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
else {
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
if (reachableTile != destination && reachableTile.unit != null) continue // This is an intermediary tile that contains a unit - we can't go there!
movementTreeParents[reachableTile] = tileToCheck
newTilesToCheck.add(reachableTile)
}
}
}
if (distanceToDestination.isNotEmpty()) {
val path = mutableListOf(tileMap[destination]) // Traverse the tree upwards to get the list of tiles leading to the destination,
var currentTile = distanceToDestination.minBy { it.value }!!.key
while (currentTile.position != origin) {
path.add(currentTile)
currentTile = movementTreeParents[currentTile]!!
}
return path.reversed() // and reverse in order to get the list in chronological order
}
if (distanceToDestination.isNotEmpty()) {
val path = mutableListOf(destination) // Traverse the tree upwards to get the list of tiles leading to the destination,
// Get the tile from which the distance to the final tile in least -
// this is so that when we finally get there, we'll have as many movement points as possible
var intermediateTile = distanceToDestination.minBy { it.value }!!.key
while (intermediateTile != currentTile) {
path.add(intermediateTile)
intermediateTile = movementTreeParents[intermediateTile]!!
}
return path.reversed() // and reverse in order to get the list in chronological order
}
if(newTilesToCheck.isEmpty()) return emptyList() // there is NO PATH (eg blocked by enemy units)
if (newTilesToCheck.isEmpty()) return emptyList() // there is NO PATH (eg blocked by enemy units)
// no need to check tiles that are surrounded by reachable tiles, only need to check the edgemost tiles.
// Because anything we can reach from intermediate tiles, can be more easily reached by the edgemost tiles,
// since we'll have to pass through an edgemost tile in order to reach the diestination anyway
tilesToCheck = newTilesToCheck.filterNot {tile -> tile.neighbors.all{newTilesToCheck.contains(it) || tilesToCheck.contains(it) } }
// no need to check tiles that are surrounded by reachable tiles, only need to check the edgemost tiles.
// Because anything we can reach from intermediate tiles, can be more easily reached by the edgemost tiles,
// since we'll have to pass through an edgemost tile in order to reach the diestination anyway
tilesToCheck = newTilesToCheck.filterNot { tile -> tile.neighbors.all { newTilesToCheck.contains(it) || tilesToCheck.contains(it) } }
distance++
}
}
distance++
}
}
/**
* @param origin
* @param destination
* @return The tile that we reached this turn
*/
fun headTowards(unit:MapUnit,destination: Vector2): TileInfo {
fun headTowards(destination: TileInfo): TileInfo {
val currentTile = unit.getTile()
val tileMap = currentTile.tileMap
val finalDestinationTile = tileMap[destination]
val distanceToTiles = unit.getDistanceToTiles()
val destinationTileThisTurn:TileInfo
if (distanceToTiles.containsKey(finalDestinationTile)) { // we can get there this turn
if (finalDestinationTile.unit == null)
destinationTileThisTurn = finalDestinationTile
val destinationTileThisTurn: TileInfo
if (distanceToTiles.containsKey(destination)) { // we can get there this turn
if (destination.unit == null)
destinationTileThisTurn = destination
else // Someone is blocking to the path to the final tile...
{
val destinationNeighbors = tileMap[destination].neighbors
if(destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
val destinationNeighbors = destination.neighbors
if (destinationNeighbors.contains(currentTile)) // We're right nearby anyway, no need to move
return currentTile
val reachableDestinationNeighbors = destinationNeighbors.filter { distanceToTiles.containsKey(it) && it.unit==null }
if(reachableDestinationNeighbors.isEmpty()) // We can't get closer...
val reachableDestinationNeighbors = destinationNeighbors
.filter { distanceToTiles.containsKey(it) && it.unit == null }
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
return currentTile
destinationTileThisTurn = reachableDestinationNeighbors.minBy { distanceToTiles[it]!! }!!
}
}
else { // If the tile is far away, we need to build a path how to get there, and then take the first step
val path = getShortestPath(currentTile.position, destination, unit)
} else { // If the tile is far away, we need to build a path how to get there, and then take the first step
val path = getShortestPath(destination)
destinationTileThisTurn = path.first()
}
@ -139,7 +139,8 @@ class UnitMovementAlgorithms(val tileMap: TileMap){
return destinationTileThisTurn
}
fun canReach(destination: TileInfo): Boolean {
return getShortestPath(destination).isNotEmpty()
}
}
}

View file

@ -5,7 +5,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitMovementAlgorithms
import com.unciv.logic.map.UnitType
import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter
@ -66,8 +65,8 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
fun tileSelected(selectedTile: TileInfo) {
if(currentlyExecutingAction=="moveTo"){
if(UnitMovementAlgorithms(selectedTile.tileMap)
.getShortestPath(selectedTile.position, selectedTile.position, selectedUnit!!).isEmpty())
if(selectedUnit!!.movementAlgs()
.getShortestPath(selectedUnit!!.getTile().position, selectedTile.position).isEmpty())
return // can't reach there with the selected unit, watcha want me to do?
val reachedTile = selectedUnit!!.headTowards(selectedTile.position)