From f7c7163aefd9966d863c00e3debe4de55362058c Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:01:16 -0800 Subject: [PATCH 1/8] Split River eval for AI vs Human. Add initial distance penalty --- .../automation/unit/CityLocationTileRanker.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 6f40cd529613c..1345685a23cf9 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -24,6 +24,7 @@ object CityLocationTileRanker { * Returns a hashmap of tiles to their ranking plus the a the highest value tile and its value */ fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity { + val distanceModifier = 2.7f // percentage penalty per aerial distance val range = if (distanceToSearch != null) distanceToSearch else { val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } @@ -43,7 +44,9 @@ object CityLocationTileRanker { val possibleTileLocationsWithRank = possibleCityLocations .map { - val tileValue = rankTileToSettle(it, unit.civ, nearbyCities, baseTileMap, uniqueCache) + var tileValue = rankTileToSettle(it, unit.civ, nearbyCities, baseTileMap, uniqueCache) + val distanceScore = (unit.currentTile.aerialDistanceTo(it) * distanceModifier).coerceIn(0f, 99f) + tileValue *= (100 - distanceScore) / 100 if (tileValue >= minimumValue) bestTilesToFoundCity.tileRankMap[it] = tileValue @@ -90,7 +93,7 @@ object CityLocationTileRanker { val onCoast = newCityTile.isCoastalTile() val onHill = newCityTile.isHill() val isNextToMountain = newCityTile.isAdjacentTo("Mountain") - // Only count a luxary resource that we don't have yet as unique once + // Only count a luxury resource that we don't have yet as unique once val newUniqueLuxuryResources = HashSet() if (onCoast) tileValue += 3 @@ -99,7 +102,12 @@ object CityLocationTileRanker { // Observatories are good, but current implementation not mod-friendly if (isNextToMountain) tileValue += 5 // This bonus for settling on river is a bit outsized for the importance, but otherwise they have a habit of settling 1 tile away - if (newCityTile.isAdjacentToRiver()) tileValue += 20 + if (newCityTile.isAdjacentToRiver()) { + if (civ.isAI()) + tileValue += 20 + else + tileValue += 10 + } // We want to found the city on an oasis because it can't be improved otherwise if (newCityTile.terrainHasUnique(UniqueType.Unbuildable)) tileValue += 3 // If we build the city on a resource tile, then we can't build any special improvements on it From b5ef2e69c42d37a1ee8733c22ae3850bdbbdb3e2 Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:32:59 -0800 Subject: [PATCH 2/8] Human eval doesn't use unseen resources Wider eval for humans --- .../automation/unit/CityLocationTileRanker.kt | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 1345685a23cf9..866e7bf4967b7 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -1,5 +1,6 @@ package com.unciv.logic.automation.unit +import com.unciv.Constants import com.unciv.logic.automation.Automation import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization @@ -26,9 +27,15 @@ object CityLocationTileRanker { fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity { val distanceModifier = 2.7f // percentage penalty per aerial distance val range = if (distanceToSearch != null) distanceToSearch else { - val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 - else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } - (8 - distanceFromHome).coerceIn(1, 5) // Restrict vision when far from home to avoid death marches + if (unit.civ.isHuman()) { + val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 + else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } + (9 - distanceFromHome).coerceIn(2, 5) + } else { + val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 + else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } + (8 - distanceFromHome).coerceIn(1, 5) // Restrict vision when far from home to avoid death marches + } } val nearbyCities = unit.civ.gameInfo.getCities() .filter { it.getCenterTile().aerialDistanceTo(unit.getTile()) <= 7 + range } @@ -111,8 +118,15 @@ object CityLocationTileRanker { // We want to found the city on an oasis because it can't be improved otherwise if (newCityTile.terrainHasUnique(UniqueType.Unbuildable)) tileValue += 3 // If we build the city on a resource tile, then we can't build any special improvements on it - if (newCityTile.resource != null) tileValue -= 4 - if (newCityTile.resource != null && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 + if (civ.isAI()) { + // let AI cheat and see future resources + if (newCityTile.resource != null) tileValue -= 4 + if (newCityTile.resource != null && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 + } else { + // human recommendations don't use non-viewable resources + if (newCityTile.hasViewableResource(civ)) tileValue -= 4 + if (newCityTile.hasViewableResource(civ) && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 + } // Settling on bonus resources tends to waste a food // Settling on luxuries generally speeds up our game, and settling on strategics as well, as the AI cheats and can see them. @@ -163,10 +177,24 @@ object CityLocationTileRanker { // Don't settle near but not on the coast if (rankTile.isCoastalTile() && !onCoast) locationSpecificTileValue -= 2 // Check if there are any new unique luxury resources - if (rankTile.resource != null && rankTile.tileResource.resourceType == ResourceType.Luxury - && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { - locationSpecificTileValue += 10 - newUniqueLuxuryResources.add(rankTile.resource!!) + if (civ.isHuman()) { + if (rankTile.hasViewableResource(civ) && rankTile.tileResource.resourceType == ResourceType.Luxury + && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains( + rankTile.resource + )) + ) { + locationSpecificTileValue += 10 + newUniqueLuxuryResources.add(rankTile.resource!!) + } + } else { + if (rankTile.resource != null && rankTile.tileResource.resourceType == ResourceType.Luxury + && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains( + rankTile.resource + )) + ) { + locationSpecificTileValue += 10 + newUniqueLuxuryResources.add(rankTile.resource!!) + } } // Check if everything else has been calculated, if so return it From 6142c6280bb301f06d219b06085370e927fc968c Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:41:57 -0800 Subject: [PATCH 3/8] Remove human eval Set recommended spots to larger search --- .../automation/unit/CityLocationTileRanker.kt | 14 ++++---------- .../worldscreen/worldmap/WorldMapTileUpdater.kt | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 866e7bf4967b7..fbae2e0e76aa3 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -26,16 +26,10 @@ object CityLocationTileRanker { */ fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity { val distanceModifier = 2.7f // percentage penalty per aerial distance - val range = if (distanceToSearch != null) distanceToSearch else { - if (unit.civ.isHuman()) { - val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 - else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } - (9 - distanceFromHome).coerceIn(2, 5) - } else { - val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 - else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } - (8 - distanceFromHome).coerceIn(1, 5) // Restrict vision when far from home to avoid death marches - } + val range = if (distanceToSearch != null) distanceToSearch else { + val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 + else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } + (8 - distanceFromHome).coerceIn(1, 5) // Restrict vision when far from home to avoid death marches } val nearbyCities = unit.civ.gameInfo.getCities() .filter { it.getCenterTile().aerialDistanceTo(unit.getTile()) <= 7 + range } diff --git a/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt b/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt index 802ec48a3fffe..f9eec1f56e0a4 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt @@ -230,7 +230,7 @@ object WorldMapTileUpdater { // Highlight best tiles for city founding if (unit.hasUnique(UniqueType.FoundCity) && UncivGame.Current.settings.showSettlersSuggestedCityLocations) { - CityLocationTileRanker.getBestTilesToFoundCity(unit, minimumValue = 50f).tileRankMap.asSequence() + CityLocationTileRanker.getBestTilesToFoundCity(unit, 5, minimumValue = 70f).tileRankMap.asSequence() .filter { it.key.isExplored(unit.civ) }.sortedByDescending { it.value }.take(3).forEach { tileGroups[it.key]!!.layerOverlay.showGoodCityLocationIndicator() } From a9005db5354ddff501c358c4129e14d3368b2ce2 Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:43:28 -0800 Subject: [PATCH 4/8] Cleanup Constants --- .../com/unciv/logic/automation/unit/CityLocationTileRanker.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index fbae2e0e76aa3..9175c0f90c705 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -1,6 +1,5 @@ package com.unciv.logic.automation.unit -import com.unciv.Constants import com.unciv.logic.automation.Automation import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization From 176c10c89ee65d1aaa6a05528ec2aaf691533337 Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Thu, 9 Jan 2025 21:45:00 -0800 Subject: [PATCH 5/8] Line cleanup --- .../logic/automation/unit/CityLocationTileRanker.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 9175c0f90c705..2eb4a0ccaeebf 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -172,19 +172,13 @@ object CityLocationTileRanker { // Check if there are any new unique luxury resources if (civ.isHuman()) { if (rankTile.hasViewableResource(civ) && rankTile.tileResource.resourceType == ResourceType.Luxury - && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains( - rankTile.resource - )) - ) { + && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { locationSpecificTileValue += 10 newUniqueLuxuryResources.add(rankTile.resource!!) } } else { if (rankTile.resource != null && rankTile.tileResource.resourceType == ResourceType.Luxury - && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains( - rankTile.resource - )) - ) { + && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { locationSpecificTileValue += 10 newUniqueLuxuryResources.add(rankTile.resource!!) } From 8859761295e2aa5a710e173469d131ced0bd1590 Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:43:45 -0800 Subject: [PATCH 6/8] Remove hidden resource filter Lower threshold back to 50 --- .../automation/unit/CityLocationTileRanker.kt | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 2eb4a0ccaeebf..8e7f79a1f92fc 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -24,7 +24,7 @@ object CityLocationTileRanker { * Returns a hashmap of tiles to their ranking plus the a the highest value tile and its value */ fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity { - val distanceModifier = 2.7f // percentage penalty per aerial distance + val distanceModifier = 2.7f // percentage penalty per aerial distance from unit (Settler) val range = if (distanceToSearch != null) distanceToSearch else { val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) } @@ -102,24 +102,13 @@ object CityLocationTileRanker { // Observatories are good, but current implementation not mod-friendly if (isNextToMountain) tileValue += 5 // This bonus for settling on river is a bit outsized for the importance, but otherwise they have a habit of settling 1 tile away - if (newCityTile.isAdjacentToRiver()) { - if (civ.isAI()) - tileValue += 20 - else - tileValue += 10 - } + if (newCityTile.isAdjacentToRiver()) tileValue += 20 // We want to found the city on an oasis because it can't be improved otherwise if (newCityTile.terrainHasUnique(UniqueType.Unbuildable)) tileValue += 3 // If we build the city on a resource tile, then we can't build any special improvements on it - if (civ.isAI()) { - // let AI cheat and see future resources - if (newCityTile.resource != null) tileValue -= 4 - if (newCityTile.resource != null && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 - } else { - // human recommendations don't use non-viewable resources - if (newCityTile.hasViewableResource(civ)) tileValue -= 4 - if (newCityTile.hasViewableResource(civ) && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 - } + if (newCityTile.resource != null) tileValue -= 4 + if (newCityTile.resource != null && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 + // Settling on bonus resources tends to waste a food // Settling on luxuries generally speeds up our game, and settling on strategics as well, as the AI cheats and can see them. @@ -170,18 +159,10 @@ object CityLocationTileRanker { // Don't settle near but not on the coast if (rankTile.isCoastalTile() && !onCoast) locationSpecificTileValue -= 2 // Check if there are any new unique luxury resources - if (civ.isHuman()) { - if (rankTile.hasViewableResource(civ) && rankTile.tileResource.resourceType == ResourceType.Luxury - && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { - locationSpecificTileValue += 10 - newUniqueLuxuryResources.add(rankTile.resource!!) - } - } else { - if (rankTile.resource != null && rankTile.tileResource.resourceType == ResourceType.Luxury - && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { - locationSpecificTileValue += 10 - newUniqueLuxuryResources.add(rankTile.resource!!) - } + if (rankTile.resource != null && rankTile.tileResource.resourceType == ResourceType.Luxury + && !(civ.hasResource(rankTile.resource!!) || newUniqueLuxuryResources.contains(rankTile.resource))) { + locationSpecificTileValue += 10 + newUniqueLuxuryResources.add(rankTile.resource!!) } // Check if everything else has been calculated, if so return it From 7b084f9d19f1a35e8a9de116112c13caa055e259 Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:44:32 -0800 Subject: [PATCH 7/8] Threshold --- .../com/unciv/logic/automation/unit/CityLocationTileRanker.kt | 1 - .../ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index 8e7f79a1f92fc..da06825772d75 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -108,7 +108,6 @@ object CityLocationTileRanker { // If we build the city on a resource tile, then we can't build any special improvements on it if (newCityTile.resource != null) tileValue -= 4 if (newCityTile.resource != null && newCityTile.tileResource.resourceType == ResourceType.Bonus) tileValue -= 8 - // Settling on bonus resources tends to waste a food // Settling on luxuries generally speeds up our game, and settling on strategics as well, as the AI cheats and can see them. diff --git a/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt b/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt index f9eec1f56e0a4..56025a06003cf 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/worldmap/WorldMapTileUpdater.kt @@ -230,7 +230,7 @@ object WorldMapTileUpdater { // Highlight best tiles for city founding if (unit.hasUnique(UniqueType.FoundCity) && UncivGame.Current.settings.showSettlersSuggestedCityLocations) { - CityLocationTileRanker.getBestTilesToFoundCity(unit, 5, minimumValue = 70f).tileRankMap.asSequence() + CityLocationTileRanker.getBestTilesToFoundCity(unit, 5, minimumValue = 50f).tileRankMap.asSequence() .filter { it.key.isExplored(unit.civ) }.sortedByDescending { it.value }.take(3).forEach { tileGroups[it.key]!!.layerOverlay.showGoodCityLocationIndicator() } From fa0cd55c2cacda8c657a931f7b5da69672a62bbc Mon Sep 17 00:00:00 2001 From: itanasi <44038014+itanasi@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:51:27 -0800 Subject: [PATCH 8/8] Penalty to 3%/distance --- .../com/unciv/logic/automation/unit/CityLocationTileRanker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt index da06825772d75..1be1dd81f967d 100644 --- a/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt +++ b/core/src/com/unciv/logic/automation/unit/CityLocationTileRanker.kt @@ -24,7 +24,7 @@ object CityLocationTileRanker { * Returns a hashmap of tiles to their ranking plus the a the highest value tile and its value */ fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity { - val distanceModifier = 2.7f // percentage penalty per aerial distance from unit (Settler) + val distanceModifier = 3f // percentage penalty per aerial distance from unit (Settler) val range = if (distanceToSearch != null) distanceToSearch else { val distanceFromHome = if (unit.civ.cities.isEmpty()) 0 else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) }