Improved AI roadbuilding - faster and can overcome obstacles

This commit is contained in:
Yair Morgenstern
2018-10-30 22:37:30 +02:00
parent 10bfdfc8bc
commit 9fe24e2d3d
5 changed files with 111 additions and 51 deletions

View File

@ -17,7 +17,7 @@ class UnCivGame : Game() {
* This exists so that when debugging we can see the entire map. * This exists so that when debugging we can see the entire map.
* Remember to turn this to false before commit and upload! * Remember to turn this to false before commit and upload!
*/ */
val viewEntireMapForDebug = false val viewEntireMapForDebug = true
lateinit var worldScreen: WorldScreen lateinit var worldScreen: WorldScreen
@ -27,11 +27,11 @@ class UnCivGame : Game() {
GameBasics.run { } // just to initialize the GameBasics GameBasics.run { } // just to initialize the GameBasics
settings = GameSaver().getGeneralSettings() settings = GameSaver().getGeneralSettings()
if (GameSaver().getSave("Autosave").exists()) { if (GameSaver().getSave("Autosave").exists()) {
// try { try {
loadGame("Autosave") loadGame("Autosave")
// } catch (ex: Exception) { // silent fail if we can't read the autosave } catch (ex: Exception) { // silent fail if we can't read the autosave
// startNewGame() startNewGame()
// } }
} }
else startNewGame() // screen=LanguagePickerScreen() disabled because of people's negative reviews =( else startNewGame() // screen=LanguagePickerScreen() disabled because of people's negative reviews =(
} }

View File

@ -1,7 +1,7 @@
package com.unciv.logic.automation package com.unciv.logic.automation
import com.unciv.logic.HexMath
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.BFS
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
@ -40,37 +40,48 @@ class WorkerAutomation(val unit: MapUnit) {
if(tile.improvementInProgress!=null) return // we're working! if(tile.improvementInProgress!=null) return // we're working!
} }
fun tryConnectingCities():Boolean{ // returns whether we actually did anything fun tryConnectingCities():Boolean{ // returns whether we actually did anything
val cityThatNeedsConnecting = unit.civInfo.cities.filter { it.population.population>3 && !it.isCapital() val citiesThatNeedConnecting = unit.civInfo.cities.filter { it.population.population>3 && !it.isCapital()
&& !it.cityStats.isConnectedToCapital(RoadStatus.Road) } && !it.cityStats.isConnectedToCapital(RoadStatus.Road) }
.minBy { HexMath().getDistance(it.location, unit.getTile().position) } if(citiesThatNeedConnecting.isEmpty()) return false // do nothing.
if(cityThatNeedsConnecting==null) return false// do nothing.
val closestConnectedCity = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(RoadStatus.Road) } val citiesThatNeedConnectingBfs = citiesThatNeedConnecting
.minBy { HexMath().getDistance(cityThatNeedsConnecting.location,it.location) }!! .map { city -> BFS(city.getCenterTile()){it.isLand() && unit.canPassThrough(it)} }
.toMutableList()
val pathToClosestCity = unit.civInfo.gameInfo.tileMap val connectedCities = unit.civInfo.cities.filter { it.isCapital() || it.cityStats.isConnectedToCapital(RoadStatus.Road) }
.getShortestPathBetweenTwoTiles(cityThatNeedsConnecting.getCenterTile(), .map { it.getCenterTile() }
closestConnectedCity.getCenterTile())
.filter { it.roadStatus==RoadStatus.None}
val unitTile = unit.getTile()
if(unitTile in pathToClosestCity){ while(citiesThatNeedConnectingBfs.any()){
if(unitTile.improvementInProgress==null) for(bfs in citiesThatNeedConnectingBfs.toList()){
unitTile.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!,unit.civInfo) bfs.nextStep()
return true if(bfs.tilesToCheck.isEmpty()){ // can't get to any connected city from here
citiesThatNeedConnectingBfs.remove(bfs)
continue
}
for(city in connectedCities)
if(bfs.tilesToCheck.contains(city)) { // we have a winner!
val pathToCity = bfs.getPathTo(city)
val roadableTiles = pathToCity.filter { it.roadStatus==RoadStatus.None }
val tileToConstructRoadOn :TileInfo
if(unit.currentTile in roadableTiles) tileToConstructRoadOn = unit.currentTile
else{
val reachableTiles = roadableTiles.filter { unit.canMoveTo(it)&& unit.movementAlgs().canReach(it)}
if(!reachableTiles.any()) continue
tileToConstructRoadOn = roadableTiles.minBy { unit.movementAlgs().getShortestPath(it).size }!!
}
unit.movementAlgs().headTowards(tileToConstructRoadOn)
if(unit.currentMovement>0 && unit.currentTile==tileToConstructRoadOn
&& unit.currentTile.improvementInProgress!="Road")
tileToConstructRoadOn.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!,unit.civInfo)
return true
}
}
} }
return false
val closestTileInPathWithNoRoad = pathToClosestCity
.filter { unit.canMoveTo(it)}
.sortedByDescending { HexMath().getDistance(unit.getTile().position, it.position) }
.firstOrNull { unit.movementAlgs().canReach(it) }
if(closestTileInPathWithNoRoad==null) return false
unit.movementAlgs().headTowards(closestTileInPathWithNoRoad)
if(unit.currentMovement>0 && unit.getTile()==closestTileInPathWithNoRoad)
closestTileInPathWithNoRoad.startWorkingOnImprovement(GameBasics.TileImprovements["Road"]!!,unit.civInfo)
return true
} }
/** /**

View File

@ -1,11 +1,10 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.map.BFS
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.Building import com.unciv.models.gamebasics.Building
import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.unit.BaseUnit import com.unciv.models.gamebasics.unit.BaseUnit
import com.unciv.models.gamebasics.unit.UnitType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
@ -224,24 +223,33 @@ class CityStats {
fun isConnectedToCapital(roadType: RoadStatus): Boolean { fun isConnectedToCapital(roadType: RoadStatus): Boolean {
if (cityInfo.civInfo.cities.count() < 2) return false// first city! if (cityInfo.civInfo.cities.count() < 2) return false// first city!
val capitalTile = cityInfo.civInfo.getCapital().getCenterTile()
val tilesReached = HashSet<TileInfo>()
var tilesToCheck: List<TileInfo> = listOf(cityInfo.getCenterTile())
while (tilesToCheck.isNotEmpty()) {
val newTiles = tilesToCheck
.flatMap { it.neighbors }.distinct()
.filter {
!tilesReached.contains(it)
&& !tilesToCheck.contains(it)
&& (roadType !== RoadStatus.Road || it.roadStatus !== RoadStatus.None)
&& (roadType !== RoadStatus.Railroad || it.roadStatus === roadType)
}
if (newTiles.contains(capitalTile)) return true val capitalTile = cityInfo.civInfo.getCapital().getCenterTile()
tilesReached.addAll(tilesToCheck) val BFS =
tilesToCheck = newTiles if(roadType==RoadStatus.Road) BFS(capitalTile){it.roadStatus!=RoadStatus.None}
} else BFS(capitalTile){it.roadStatus == roadType}
return false
val cityTile = cityInfo.getCenterTile()
BFS.stepUntilDestination(cityTile)
return BFS.tilesReached.containsKey(cityTile)
// val tilesReached = HashSet<TileInfo>()
// var tilesToCheck: List<TileInfo> = listOf(cityInfo.getCenterTile())
// while (tilesToCheck.isNotEmpty()) {
// val newTiles = tilesToCheck
// .flatMap { it.neighbors }.distinct()
// .filter {
// !tilesReached.contains(it)
// && !tilesToCheck.contains(it)
// && (roadType !== RoadStatus.Road || it.roadStatus !== RoadStatus.None)
// && (roadType !== RoadStatus.Railroad || it.roadStatus === roadType)
// }
//
// if (newTiles.contains(capitalTile)) return true
// tilesReached.addAll(tilesToCheck)
// tilesToCheck = newTiles
// }
// return false
} }
//endregion //endregion

View File

@ -0,0 +1,42 @@
package com.unciv.logic.map
/**
* Defines intermediate steps of a breadth-first search, for use in either get shortest path or get onnected tiles.
*/
class BFS(val startingPoint: TileInfo, val predicate : (TileInfo) -> Boolean){
var tilesToCheck = ArrayList<TileInfo>()
val tilesReached = HashMap<TileInfo, TileInfo>() // each tile reached points to its parent tile, where we got to it from
init{
tilesToCheck.add(startingPoint)
tilesReached.put(startingPoint,startingPoint)
}
fun stepUntilDestination(destination: TileInfo){
while(!tilesReached.containsKey(destination) && tilesToCheck.isNotEmpty())
nextStep()
}
fun nextStep(){
val newTilesToCheck = ArrayList<TileInfo>()
for(tileInfo in tilesToCheck){
val fitNeighbors = tileInfo.neighbors.asSequence()
.filter(predicate)
.filter{!tilesReached.containsKey(it)}.toList()
fitNeighbors.forEach { tilesReached[it] = tileInfo }
newTilesToCheck.addAll(fitNeighbors)
}
tilesToCheck = newTilesToCheck
}
fun getPathTo(destination: TileInfo): ArrayList<TileInfo> {
val path = ArrayList<TileInfo>()
path.add(destination)
var currentNode = destination
while(currentNode != startingPoint){
currentNode = tilesReached[currentNode]!!
path.add(currentNode)
}
return path
}
}

View File

@ -130,5 +130,4 @@ class TileMap {
return path return path
} }
} }