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.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.UnitType
import com.unciv.models.gamebasics.Building
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.TileImprovement
import com.unciv.ui.utils.getRandom
@ -20,6 +22,7 @@ class Automation {
.filter {
(it.unit == null || it == currentTile)
&& it.improvement == null
&& (it.getCity()==null || it.getCity()!!.civInfo == civInfo) // don't work tiles belonging to another civ
&& it.canBuildImprovement(chooseImprovement(it), civInfo)
}
.maxBy { getPriority(it, civInfo) }
@ -27,6 +30,21 @@ class Automation {
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 {
var priority = 0
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) {
if (civInfo.tech.techsToResearch.isEmpty()) {
val researchableTechs = GameBasics.Technologies.values.filter { civInfo.tech.canBeResearched(it.name) }
@ -74,13 +101,25 @@ class Automation {
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) {
if (city.health < city.getMaxHealth()) trainCombatUnit(city)
}
for (unit in civInfo.getCivUnits()) {
automateUnitMoves(unit)
}
}
private fun trainCombatUnit(city: CityInfo) {
@ -90,7 +129,24 @@ class Automation {
fun automateUnitMoves(unit: MapUnit) {
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
}
@ -178,4 +234,24 @@ class Automation {
// else, go to a random space
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
import com.unciv.logic.map.UnitType
import com.unciv.logic.Automation
import com.unciv.models.gamebasics.Building
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.stats.Stats
@ -16,7 +16,7 @@ class CityConstructions {
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 }
// Library and public school unique (not actualy unique, though...hmm)
@ -62,7 +62,7 @@ class CityConstructions {
fun isBuilding(buildingName: String): Boolean = currentConstruction == buildingName
private fun getConstruction(constructionName: String): IConstruction {
internal fun getConstruction(constructionName: String): IConstruction {
if (GameBasics.Buildings.containsKey(constructionName))
return GameBasics.Buildings[constructionName]!!
else if (GameBasics.Units.containsKey(constructionName))
@ -87,7 +87,7 @@ class CityConstructions {
if (!construction.isBuildable(this)) {
// 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)
chooseNextConstruction()
Automation().chooseNextConstruction(this)
construction = getConstruction(currentConstruction)
} else
currentConstruction = saveCurrentConstruction
@ -99,28 +99,11 @@ class CityConstructions {
inProgressConstructions.remove(currentConstruction)
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 {
if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!!
else return 0
@ -141,7 +124,7 @@ class CityConstructions {
fun purchaseBuilding(buildingName: String) {
cityInfo.civInfo.gold -= getConstruction(buildingName).getGoldCost(cityInfo.civInfo.policies.adoptedPolicies)
getConstruction(buildingName).postBuildEvent(this)
if (currentConstruction == buildingName) chooseNextConstruction()
if (currentConstruction == buildingName) Automation().chooseNextConstruction(this)
cityInfo.cityStats.update()
}
@ -150,11 +133,15 @@ class CityConstructions {
if (cultureBuildingToBuild == null) return
builtBuildings.add(cultureBuildingToBuild)
if (currentConstruction == cultureBuildingToBuild)
chooseNextConstruction()
Automation().chooseNextConstruction(this)
}
companion object {
internal const val Worker = "Worker"
internal const val Settler = "Settler"
}
fun chooseNextConstruction() {
Automation().chooseNextConstruction(this)
}
} // for json parsing, we need to have a default constructor

View file

@ -1,5 +1,7 @@
package com.unciv.logic.city
import com.unciv.logic.Automation
class CityExpansionManager {
@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
// (per game XML files) at 6*(t+0.4813)^1.3
// The second seems to be more based, so I'll go with that
//Speciality of Angkor Wat
fun getCultureToNextTile(): Int {
val numTilesClaimed = cityInfo.tiles.size - 7
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
return Math.round(cultureToNextTile).toInt()
}
@ -31,7 +33,7 @@ class CityExpansionManager {
for (i in 2..3) {
val tiles = cityInfo.civInfo.gameInfo.tileMap.getTilesInDistance(cityInfo.location, i).filter { it.getOwner() == null }
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)
return
}

View file

@ -127,19 +127,6 @@ class CityInfo {
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 {
return 200 // add more later when walls citadl etc.

View file

@ -1,5 +1,6 @@
package com.unciv.logic.city
import com.unciv.logic.Automation
import com.unciv.logic.map.TileInfo
import com.unciv.models.stats.Stats
@ -61,7 +62,7 @@ class PopulationManager {
internal fun autoAssignWorker() {
val toWork: TileInfo? = cityInfo.getTiles()
.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!
cityInfo.workedTiles.add(toWork.position)
}