Moved tile ranking and construction picking to Automation

This commit is contained in:
Yair Morgenstern 2018-04-24 21:44:47 +03:00
parent 5eec4deb84
commit c6136acbec
5 changed files with 98 additions and 45 deletions

View file

@ -3,11 +3,13 @@ package com.unciv.logic
import com.unciv.UnCivGame import com.unciv.UnCivGame
import com.unciv.logic.battle.Battle import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitType import com.unciv.logic.map.UnitType
import com.unciv.models.gamebasics.Building
import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.TileImprovement import com.unciv.models.gamebasics.TileImprovement
import com.unciv.ui.utils.getRandom import com.unciv.ui.utils.getRandom
@ -20,6 +22,7 @@ class Automation {
.filter { .filter {
(it.unit == null || it == currentTile) (it.unit == null || it == currentTile)
&& it.improvement == null && it.improvement == null
&& (it.getCity()==null || it.getCity()!!.civInfo == civInfo) // don't work tiles belonging to another civ
&& it.canBuildImprovement(chooseImprovement(it), civInfo) && it.canBuildImprovement(chooseImprovement(it), civInfo)
} }
.maxBy { getPriority(it, civInfo) } .maxBy { getPriority(it, civInfo) }
@ -27,6 +30,21 @@ class Automation {
else return currentTile else return currentTile
} }
internal fun rankTile(tile: TileInfo, civInfo: CivilizationInfo): Float {
val stats = tile.getTileStats(null, civInfo)
var rank = 0.0f
if (stats.food <= 2) rank += stats.food
else rank += (2 + (stats.food - 2) / 2f) // 1 point for each food up to 2, from there on half a point
rank += (stats.gold / 2)
rank += stats.production
rank += stats.science
rank += stats.culture
if (tile.improvement == null) rank += 0.5f // improvement potential!
if (tile.resource != null) rank += 1.0f
return rank
}
private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int { private fun getPriority(tileInfo: TileInfo, civInfo: CivilizationInfo): Int {
var priority = 0 var priority = 0
if (tileInfo.isWorked()) priority += 3 if (tileInfo.isWorked()) priority += 3
@ -67,6 +85,15 @@ class Automation {
} }
} }
fun rankTileAsCityCenter(tileInfo: TileInfo, civInfo: CivilizationInfo): Float {
val bestTilesFromOuterLayer = tileInfo.tileMap.getTilesAtDistance(tileInfo.position,2)
.sortedByDescending { rankTile(it,civInfo) }.take(2)
val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer)
.sortedByDescending { rankTile(it,civInfo) }
.take(5)
return top5Tiles.map { rankTile(it,civInfo) }.sum()
}
fun automateCivMoves(civInfo: CivilizationInfo) { fun automateCivMoves(civInfo: CivilizationInfo) {
if (civInfo.tech.techsToResearch.isEmpty()) { if (civInfo.tech.techsToResearch.isEmpty()) {
val researchableTechs = GameBasics.Technologies.values.filter { civInfo.tech.canBeResearched(it.name) } val researchableTechs = GameBasics.Technologies.values.filter { civInfo.tech.canBeResearched(it.name) }
@ -74,13 +101,25 @@ class Automation {
civInfo.tech.techsResearched.add(techToResearch!!.name) civInfo.tech.techsResearched.add(techToResearch!!.name)
} }
for (unit in civInfo.getCivUnits()) {
automateUnitMoves(unit)
}
// train settler?
if (civInfo.cities.any()
&& civInfo.happiness > 2*civInfo.cities.size +5
&& civInfo.getCivUnits().none { it.name == "Settler" }
&& civInfo.cities.none { it.cityConstructions.currentConstruction == "Settler" }) {
val bestCity = civInfo.cities.maxBy { it.cityStats.currentCityStats.production }!!
if(bestCity.cityConstructions.builtBuildings.size > 1) // 2 buildings or more, otherwisse focus on self first
bestCity.cityConstructions.currentConstruction = "Settler"
}
for (city in civInfo.cities) { for (city in civInfo.cities) {
if (city.health < city.getMaxHealth()) trainCombatUnit(city) if (city.health < city.getMaxHealth()) trainCombatUnit(city)
} }
for (unit in civInfo.getCivUnits()) {
automateUnitMoves(unit)
}
} }
private fun trainCombatUnit(city: CityInfo) { private fun trainCombatUnit(city: CityInfo) {
@ -90,7 +129,24 @@ class Automation {
fun automateUnitMoves(unit: MapUnit) { fun automateUnitMoves(unit: MapUnit) {
if (unit.name == "Settler") { if (unit.name == "Settler") {
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
val tileMap = unit.civInfo.gameInfo.tileMap
// find best city location within 5 tiles
val bestCityLocation = tileMap.getTilesInDistance(unit.getTile().position, 5)
.filterNot { it.isCityCenter || it.neighbors.any { it.isCityCenter } }
.sortedByDescending { rankTileAsCityCenter(it, unit.civInfo) }
.first()
if(unit.getTile() == bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
else {
unit.headTowards(bestCityLocation.position)
if(unit.currentMovement>0 && unit.getTile()==bestCityLocation)
UnitActions().getUnitActions(unit, UnCivGame.Current.worldScreen!!).first { it.name == "Found city" }.action()
}
return return
} }
@ -178,4 +234,24 @@ class Automation {
// else, go to a random space // else, go to a random space
unit.moveToTile(distanceToTiles.keys.filter { it.unit == null }.toList().getRandom()) unit.moveToTile(distanceToTiles.keys.filter { it.unit == null }.toList().getRandom())
} }
fun chooseNextConstruction(cityConstructions: CityConstructions) {
cityConstructions.run {
val buildableNotWonders = getBuildableBuildings().filterNot { (getConstruction(it) as Building).isWonder }
if (!buildableNotWonders.isEmpty()) {
currentConstruction = buildableNotWonders.first()
} else {
val militaryUnits = cityInfo.civInfo.getCivUnits().filter { it.getBaseUnit().unitType != UnitType.Civilian }.size
if (cityInfo.civInfo.getCivUnits().none { it.name == CityConstructions.Worker } || militaryUnits > cityInfo.civInfo.cities.size * 2) {
currentConstruction = CityConstructions.Worker
} else {
trainCombatUnit(cityInfo)
}
}
if (cityInfo.civInfo == cityInfo.civInfo.gameInfo.getPlayerCivilization())
cityInfo.civInfo.addNotification("Work has started on $currentConstruction", cityInfo.location)
}
}
} }

View file

@ -1,6 +1,6 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.map.UnitType import com.unciv.logic.Automation
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.stats.Stats import com.unciv.models.stats.Stats
@ -16,7 +16,7 @@ class CityConstructions {
var currentConstruction: String = "Monument" // default starting building! var currentConstruction: String = "Monument" // default starting building!
private fun getBuildableBuildings(): List<String> = GameBasics.Buildings.values internal fun getBuildableBuildings(): List<String> = GameBasics.Buildings.values
.filter { it.isBuildable(this) }.map { it.name } .filter { it.isBuildable(this) }.map { it.name }
// Library and public school unique (not actualy unique, though...hmm) // Library and public school unique (not actualy unique, though...hmm)
@ -62,7 +62,7 @@ class CityConstructions {
fun isBuilding(buildingName: String): Boolean = currentConstruction == buildingName fun isBuilding(buildingName: String): Boolean = currentConstruction == buildingName
private fun getConstruction(constructionName: String): IConstruction { internal fun getConstruction(constructionName: String): IConstruction {
if (GameBasics.Buildings.containsKey(constructionName)) if (GameBasics.Buildings.containsKey(constructionName))
return GameBasics.Buildings[constructionName]!! return GameBasics.Buildings[constructionName]!!
else if (GameBasics.Units.containsKey(constructionName)) else if (GameBasics.Units.containsKey(constructionName))
@ -87,7 +87,7 @@ class CityConstructions {
if (!construction.isBuildable(this)) { if (!construction.isBuildable(this)) {
// We can't build this building anymore! (Wonder has been built / resource is gone / etc.) // We can't build this building anymore! (Wonder has been built / resource is gone / etc.)
cityInfo.civInfo.addNotification("Cannot continue work on $saveCurrentConstruction", cityInfo.location) cityInfo.civInfo.addNotification("Cannot continue work on $saveCurrentConstruction", cityInfo.location)
chooseNextConstruction() Automation().chooseNextConstruction(this)
construction = getConstruction(currentConstruction) construction = getConstruction(currentConstruction)
} else } else
currentConstruction = saveCurrentConstruction currentConstruction = saveCurrentConstruction
@ -99,28 +99,11 @@ class CityConstructions {
inProgressConstructions.remove(currentConstruction) inProgressConstructions.remove(currentConstruction)
cityInfo.civInfo.addNotification(currentConstruction + " has been built in " + cityInfo.name, cityInfo.location) cityInfo.civInfo.addNotification(currentConstruction + " has been built in " + cityInfo.name, cityInfo.location)
chooseNextConstruction() Automation().chooseNextConstruction(this)
} }
} }
fun chooseNextConstruction() {
val buildableNotWonders = getBuildableBuildings().filterNot{(getConstruction(it) as Building).isWonder}
if(!buildableNotWonders.isEmpty()){
currentConstruction = buildableNotWonders.first()
}
else {
val militaryUnits = cityInfo.civInfo.getCivUnits().filter { it.getBaseUnit().unitType != UnitType.Civilian }.size
if (cityInfo.civInfo.getCivUnits().none { it.name== Worker } || militaryUnits > cityInfo.civInfo.cities.size*2) {
currentConstruction = Worker
} else {
currentConstruction = "Archer"
}
}
if(cityInfo.civInfo == cityInfo.civInfo.gameInfo.getPlayerCivilization())
cityInfo.civInfo.addNotification("Work has started on $currentConstruction", cityInfo.location)
}
private fun workDone(constructionName: String): Int { private fun workDone(constructionName: String): Int {
if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!! if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!!
else return 0 else return 0
@ -141,7 +124,7 @@ class CityConstructions {
fun purchaseBuilding(buildingName: String) { fun purchaseBuilding(buildingName: String) {
cityInfo.civInfo.gold -= getConstruction(buildingName).getGoldCost(cityInfo.civInfo.policies.adoptedPolicies) cityInfo.civInfo.gold -= getConstruction(buildingName).getGoldCost(cityInfo.civInfo.policies.adoptedPolicies)
getConstruction(buildingName).postBuildEvent(this) getConstruction(buildingName).postBuildEvent(this)
if (currentConstruction == buildingName) chooseNextConstruction() if (currentConstruction == buildingName) Automation().chooseNextConstruction(this)
cityInfo.cityStats.update() cityInfo.cityStats.update()
} }
@ -150,11 +133,15 @@ class CityConstructions {
if (cultureBuildingToBuild == null) return if (cultureBuildingToBuild == null) return
builtBuildings.add(cultureBuildingToBuild) builtBuildings.add(cultureBuildingToBuild)
if (currentConstruction == cultureBuildingToBuild) if (currentConstruction == cultureBuildingToBuild)
chooseNextConstruction() Automation().chooseNextConstruction(this)
} }
companion object { companion object {
internal const val Worker = "Worker" internal const val Worker = "Worker"
internal const val Settler = "Settler" internal const val Settler = "Settler"
} }
fun chooseNextConstruction() {
Automation().chooseNextConstruction(this)
}
} // for json parsing, we need to have a default constructor } // for json parsing, we need to have a default constructor

View file

@ -1,5 +1,7 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.Automation
class CityExpansionManager { class CityExpansionManager {
@Transient @Transient
@ -16,11 +18,11 @@ class CityExpansionManager {
// https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it // https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it
// (per game XML files) at 6*(t+0.4813)^1.3 // (per game XML files) at 6*(t+0.4813)^1.3
// The second seems to be more based, so I'll go with that // The second seems to be more based, so I'll go with that
//Speciality of Angkor Wat
fun getCultureToNextTile(): Int { fun getCultureToNextTile(): Int {
val numTilesClaimed = cityInfo.tiles.size - 7 val numTilesClaimed = cityInfo.tiles.size - 7
var cultureToNextTile = 6 * Math.pow(numTilesClaimed + 1.4813, 1.3) var cultureToNextTile = 6 * Math.pow(numTilesClaimed + 1.4813, 1.3)
if (cityInfo.civInfo.buildingUniques.contains("NewTileCostReduction")) cultureToNextTile *= 0.75 if (cityInfo.civInfo.buildingUniques.contains("NewTileCostReduction")) cultureToNextTile *= 0.75 //Speciality of Angkor Wat
if (cityInfo.civInfo.policies.isAdopted("Tradition")) cultureToNextTile *= 0.75 if (cityInfo.civInfo.policies.isAdopted("Tradition")) cultureToNextTile *= 0.75
return Math.round(cultureToNextTile).toInt() return Math.round(cultureToNextTile).toInt()
} }
@ -31,7 +33,7 @@ class CityExpansionManager {
for (i in 2..3) { for (i in 2..3) {
val tiles = cityInfo.civInfo.gameInfo.tileMap.getTilesInDistance(cityInfo.location, i).filter { it.getOwner() == null } val tiles = cityInfo.civInfo.gameInfo.tileMap.getTilesInDistance(cityInfo.location, i).filter { it.getOwner() == null }
if (tiles.isEmpty()) continue if (tiles.isEmpty()) continue
val chosenTile = tiles.maxBy { cityInfo.rankTile(it) } val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) }
cityInfo.tiles.add(chosenTile!!.position) cityInfo.tiles.add(chosenTile!!.position)
return return
} }

View file

@ -127,19 +127,6 @@ class CityInfo {
health = min(health+maxHealth/10, maxHealth) health = min(health+maxHealth/10, maxHealth)
} }
internal fun rankTile(tile: TileInfo): Float {
val stats = tile.getTileStats(this, civInfo)
var rank = 0.0f
if (stats.food <= 2) rank += stats.food
else rank += (2 + (stats.food - 2) / 2f) // 1 point for each food up to 2, from there on half a point
rank += (stats.gold / 2)
rank += stats.production
rank += stats.science
rank += stats.culture
if (tile.improvement == null) rank += 0.5f // improvement potential!
if (tile.resource != null) rank += 1.0f
return rank
}
internal fun getMaxHealth(): Int { internal fun getMaxHealth(): Int {
return 200 // add more later when walls citadl etc. return 200 // add more later when walls citadl etc.

View file

@ -1,5 +1,6 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.Automation
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
@ -61,7 +62,7 @@ class PopulationManager {
internal fun autoAssignWorker() { internal fun autoAssignWorker() {
val toWork: TileInfo? = cityInfo.getTiles() val toWork: TileInfo? = cityInfo.getTiles()
.filterNot { cityInfo.workedTiles.contains(it.position) || cityInfo.location==it.position} .filterNot { cityInfo.workedTiles.contains(it.position) || cityInfo.location==it.position}
.maxBy { cityInfo.rankTile(it) } .maxBy { Automation().rankTile(it,cityInfo.civInfo) }
if (toWork != null) // This is when we've run out of tiles! if (toWork != null) // This is when we've run out of tiles!
cityInfo.workedTiles.add(toWork.position) cityInfo.workedTiles.add(toWork.position)
} }