mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 23:39:40 +07:00
Updated the tile choosing algorithm for city expansion (#4510)
* Updated the tile choosing algorithm for city expansion to the one used in the base game * Implemented requested changes
This commit is contained in:
@ -3,6 +3,7 @@ package com.unciv.logic.automation
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.BFS
|
||||
import com.unciv.logic.map.RoadStatus
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.models.ruleset.VictoryType
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
@ -116,6 +117,7 @@ object Automation {
|
||||
}
|
||||
}
|
||||
|
||||
// Ranks a tile for any purpose except the expansion algorithm of cities
|
||||
internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float {
|
||||
if (tile == null) return 0f
|
||||
val tileOwner = tile.getOwner()
|
||||
@ -130,6 +132,68 @@ object Automation {
|
||||
}
|
||||
return rank
|
||||
}
|
||||
|
||||
// Ranks a tile for the expansion algorithm of cities
|
||||
internal fun rankTileForExpansion(tile: TileInfo, cityInfo: CityInfo): Int {
|
||||
// https://github.com/Gedemon/Civ5-DLL/blob/aa29e80751f541ae04858b6d2a2c7dcca454201e/CvGameCoreDLL_Expansion1/CvCity.cpp#L10301
|
||||
// Apparently this is not the full calculation. The exact tiles are also
|
||||
// dependent on which tiles are between the chosen tile and the city center
|
||||
// Exact details are not implemented, but can be found in CvAStar.cpp:2119,
|
||||
// function `InfluenceCost()`.
|
||||
// Implementing these will require an additional variable for each terrainType
|
||||
val distance = tile.aerialDistanceTo(cityInfo.getCenterTile())
|
||||
|
||||
// Higher score means tile is less likely to be picked
|
||||
var score = distance * 100
|
||||
|
||||
// Resources are good: less points
|
||||
if (tile.hasViewableResource(cityInfo.civInfo)) {
|
||||
if (tile.getTileResource().resourceType != ResourceType.Bonus) score -= 105
|
||||
else if (distance <= 3) score -= 104
|
||||
|
||||
} else {
|
||||
// Water tiles without resources aren't great
|
||||
if (tile.isWater) score += 25
|
||||
// Can't work it anyways
|
||||
if (distance > 3) score += 100
|
||||
}
|
||||
|
||||
// Improvements are good: less points
|
||||
if (tile.improvement != null &&
|
||||
tile.getImprovementStats(tile.getTileImprovement()!!, cityInfo.civInfo, cityInfo).toHashMap().values.sum() > 0f
|
||||
) score -= 5
|
||||
|
||||
// The original checks if the tile has a road, but adds a score of 0 if it does.
|
||||
// Therefore, this check is removed here.
|
||||
|
||||
if (tile.naturalWonder != null) score -= 105
|
||||
|
||||
// Straight up take the sum of all yields
|
||||
score -= tile.getTileStats(null, cityInfo.civInfo).toHashMap().values.sum().toInt()
|
||||
|
||||
// Check if we get access to better tiles from this tile
|
||||
var adjacentNaturalWonder = false
|
||||
|
||||
for (adjacentTile in tile.neighbors.filter { it.getOwner() == null }) {
|
||||
val adjacentDistance = cityInfo.getCenterTile().aerialDistanceTo(adjacentTile)
|
||||
if (adjacentTile.hasViewableResource(cityInfo.civInfo) &&
|
||||
(adjacentDistance < 3 ||
|
||||
adjacentTile.getTileResource().resourceType != ResourceType.Bonus
|
||||
)
|
||||
) score -= 1
|
||||
if (adjacentTile.naturalWonder != null) {
|
||||
if (adjacentDistance < 3) adjacentNaturalWonder = true
|
||||
score -= 1
|
||||
}
|
||||
}
|
||||
if (adjacentNaturalWonder) score -= 1
|
||||
|
||||
// Tiles not adjacent to owned land are very hard to acquire
|
||||
if (tile.neighbors.none { it.getCity() != null && it.getCity()!!.id == cityInfo.id })
|
||||
score += 1000
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float {
|
||||
var rank = 0.0f
|
||||
|
@ -72,19 +72,18 @@ class CityExpansionManager {
|
||||
return cost.roundToInt()
|
||||
}
|
||||
|
||||
|
||||
fun chooseNewTileToOwn(): TileInfo? {
|
||||
for (i in 2..5) {
|
||||
val tiles = cityInfo.getCenterTile().getTilesInDistance(i)
|
||||
.filter {
|
||||
it.getOwner() == null
|
||||
&& it.neighbors.any { tile -> tile.getCity() == cityInfo }
|
||||
}
|
||||
val chosenTile = tiles.maxByOrNull { Automation.rankTile(it, cityInfo.civInfo) }
|
||||
if (chosenTile != null)
|
||||
return chosenTile
|
||||
val choosableTiles = cityInfo.getCenterTile().getTilesInDistance(5)
|
||||
.filter { it.getOwner() == null }
|
||||
|
||||
// Technically, in the original a random tile with the lowest score was selected
|
||||
// However, doing this requires either caching it, which is way more work,
|
||||
// or selecting all possible tiles and only choosing one when the border expands.
|
||||
// But since the order in which tiles are selected in distance is kinda random anyways,
|
||||
// this is fine.
|
||||
return choosableTiles.minByOrNull {
|
||||
Automation.rankTileForExpansion(it, cityInfo)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
//region state-changing functions
|
||||
|
Reference in New Issue
Block a user