mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 17:59:11 +07:00
Improved AI roadbuilding - faster and can overcome obstacles
This commit is contained in:
@ -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 =(
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
42
core/src/com/unciv/logic/map/BFS.kt
Normal file
42
core/src/com/unciv/logic/map/BFS.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -130,5 +130,4 @@ class TileMap {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user