From c916b96d1ef43aceec1061a33363645490d02644 Mon Sep 17 00:00:00 2001 From: Duan Tao Date: Mon, 18 Mar 2019 00:15:36 +0800 Subject: [PATCH 1/4] Redo build order AI. --- .../com/unciv/logic/automation/Automation.kt | 127 +++++++++++++----- 1 file changed, 92 insertions(+), 35 deletions(-) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 5adace7a20..c563214610 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -46,6 +46,11 @@ class Automation { } fun trainCombatUnit(city: CityInfo) { + val name = chooseCombatUnit(city) + city.cityConstructions.currentConstruction = name + } + + fun chooseCombatUnit(city: CityInfo) : String { val combatUnits = city.cityConstructions.getConstructableUnits().filter { !it.unitType.isCivilian() } val chosenUnit: BaseUnit if(!city.civInfo.isAtWar() && city.civInfo.cities.any { it.getCenterTile().militaryUnit==null} @@ -56,15 +61,16 @@ class Automation { val chosenUnitType = combatUnits.map { it.unitType }.distinct().filterNot{it==UnitType.Scout}.getRandom() chosenUnit = combatUnits.filter { it.unitType==chosenUnitType }.maxBy { it.cost }!! } - - city.cityConstructions.currentConstruction = chosenUnit.name + return chosenUnit.name } fun chooseNextConstruction(cityConstructions: CityConstructions) { cityConstructions.run { + if (currentConstruction!="") return val buildableNotWonders = getBuildableBuildings().filterNot { it.isWonder } val buildableWonders = getBuildableBuildings().filter { it.isWonder } + val buildableUnits = getConstructableUnits() val civUnits = cityInfo.civInfo.getCivUnits() val militaryUnits = civUnits.filter { !it.type.isCivilian()}.size @@ -75,43 +81,94 @@ class Automation { val needWorkboat = canBuildWorkboat && cityInfo.getTiles().any { it.isWater() && it.hasViewableResource(cityInfo.civInfo) && it.improvement == null } - val goldBuildings = buildableNotWonders.filter { it.gold>0 } - val wartimeBuildings = buildableNotWonders.filter { it.xpForNewUnits>0 || it.cityStrength>0 }.sortedBy { it.maintenance } - val zeroMaintenanceBuildings = buildableNotWonders.filter { it.maintenance == 0 && it !in wartimeBuildings } - val productionBuildings = buildableNotWonders.filter { it.production>0 } - val happinessBuildings = buildableNotWonders.filter { it.happiness>0 } - val cultureBuildings = buildableNotWonders.filter { it.culture>0 } val isAtWar = cityInfo.civInfo.isAtWar() + val cityProduction = cityInfo.cityStats.currentCityStats.production.toFloat() - when { - buildableNotWonders.isNotEmpty() // if the civ is in the gold red-zone, build markets or somesuch - && cityInfo.civInfo.getStatsForNextTurn().gold <0 - && goldBuildings.isNotEmpty() - -> currentConstruction = goldBuildings.first().name - currentConstruction!="" -> return - buildableNotWonders.any { it.name=="Monument"} -> currentConstruction = "Monument" - buildableNotWonders.any { it.name=="Granary"} -> currentConstruction = "Granary" - militaryUnits trainCombatUnit(cityInfo) - workers==0 -> currentConstruction = CityConstructions.Worker - cityInfo.civInfo.happiness<0 && happinessBuildings.isNotEmpty() -> currentConstruction = happinessBuildings.minBy{ it.cost }!!.name - buildableNotWonders.any { it.name=="Library"} -> currentConstruction = "Library" - buildableNotWonders.any { it.name=="Market"} -> currentConstruction = "Market" - buildableNotWonders.any { it.name=="Forge"} -> currentConstruction = "Forge" - cityInfo.civInfo.happiness>cities && buildableNotWonders.any { it.name=="Aqueduct"} -> currentConstruction = "Aqueduct" - isAtWar && militaryUnits trainCombatUnit(cityInfo) - isAtWar && wartimeBuildings.isNotEmpty() -> currentConstruction = wartimeBuildings.minBy { it.cost }!!.name - //build culture buildings before border expands to half of second ring - cityInfo.tiles.size < 13 && cultureBuildings.isNotEmpty() -> currentConstruction = cultureBuildings.minBy { it.cost }!!.name - productionBuildings.isNotEmpty() -> currentConstruction = productionBuildings.minBy { it.cost }!!.name - zeroMaintenanceBuildings.isNotEmpty() -> currentConstruction = zeroMaintenanceBuildings.minBy { it.cost }!!.name - needWorkboat -> currentConstruction = "Work Boats" - workers currentConstruction = CityConstructions.Worker - militaryUnits trainCombatUnit(cityInfo) - buildableNotWonders.isNotEmpty() -> currentConstruction = buildableNotWonders.minBy { it.maintenance }!!.name - buildableWonders.isNotEmpty() -> currentConstruction = buildableWonders.minBy { it.cost }!!.name - else -> trainCombatUnit(cityInfo) + var buildingValues = HashMap() + //Food buildings : Ganary and lighthouse and hospital + val foodBuilding = buildableNotWonders.filter { it.food>0 + || (it.resourceBonusStats!=null && it.resourceBonusStats!!.food>0) } + .minBy{it.cost} + if (foodBuilding!=null) { + buildingValues[foodBuilding.name] = foodBuilding.cost / cityProduction + if (cityInfo.population.population < buildingValues[foodBuilding.name]!!.toInt()) { + buildingValues[foodBuilding.name] = buildingValues[foodBuilding.name]!! / 2.0f + } } + //Production buildings : Workshop, factory + val productionBuilding = buildableNotWonders.filter { it.production>0 + || (it.resourceBonusStats!=null && it.resourceBonusStats!!.production>0) } + .minBy{it.cost} + if (productionBuilding!=null) { + buildingValues[productionBuilding.name] = productionBuilding.cost / cityProduction / 1.5f + } + + //Gold buildings : Market, bank + val goldBuilding = buildableNotWonders.filter { it.gold>0 + || (it.resourceBonusStats!=null && it.resourceBonusStats!!.gold>0) } + .minBy{it.cost} + if (goldBuilding!=null) { + buildingValues[goldBuilding.name] = goldBuilding.cost / cityProduction / 1.2f + if (cityInfo.civInfo.getStatsForNextTurn().gold<0) { + buildingValues[goldBuilding.name] = buildingValues[goldBuilding.name]!! / 3.0f + } + } + + //Happiness + val happinessBuilding = buildableNotWonders.filter { it.happiness>0 + || (it.resourceBonusStats!=null && it.resourceBonusStats!!.happiness>0) } + .minBy{it.cost} + if (happinessBuilding!=null) { + buildingValues[happinessBuilding.name] = happinessBuilding.cost / cityProduction + if (cityInfo.civInfo.happiness < 0) { + buildingValues[happinessBuilding.name] = buildingValues[happinessBuilding.name]!! / 3.0f + } + } + + //War buildings + val wartimeBuildings = buildableNotWonders.filter { it.xpForNewUnits>0 || it.cityStrength>0 } + .minBy { it.cost } + if (wartimeBuildings!=null) { + buildingValues[wartimeBuildings.name] = wartimeBuildings.cost / cityProduction + } + + //Wonders + val wonder = buildableWonders.minBy { it.cost } + if (wonder!=null) { + buildingValues[wonder.name] = wonder.cost / cityProduction / 4.0f + } + + //other buildings + val other = buildableNotWonders.minBy{it.cost} + if (other!=null) { + buildingValues[other.name] = other.cost / cityProduction * 1.2f + } + + //worker + if (workers<(cities+1)/2) { + buildingValues[CityConstructions.Worker] = + buildableUnits.first{ it.name == CityConstructions.Worker }!!.cost / cityProduction * + (workers/(cities+1)) + } + + //Work boat + if (needWorkboat) { + buildingValues["Work Boats"] = + buildableUnits.first{ it.name == "Work Boats" }!!.cost / cityProduction + } + + //Army + val militaryUnit = chooseCombatUnit(cityInfo) + buildingValues[militaryUnit] = + buildableUnits.first{ it.name == militaryUnit }!!.cost / cityProduction * 2.0f * + (militaryUnits/(cities+1)) + if (isAtWar) { + buildingValues[militaryUnit] = buildingValues[militaryUnit]!! / 3.0f + } + + val name = buildingValues.minBy{it.value}!!.key + currentConstruction = name cityInfo.civInfo.addNotification("Work has started on [$currentConstruction]", cityInfo.location, Color.BROWN) } } From cb9f032e35542e0de21348dc736c2127afcc3688 Mon Sep 17 00:00:00 2001 From: Duan Tao Date: Mon, 18 Mar 2019 11:47:52 +0800 Subject: [PATCH 2/4] Priority tuning part 1. --- .../src/com/unciv/logic/automation/Automation.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index c563214610..5274eea432 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -85,13 +85,13 @@ class Automation { val cityProduction = cityInfo.cityStats.currentCityStats.production.toFloat() var buildingValues = HashMap() - //Food buildings : Ganary and lighthouse and hospital + //Food buildings : Granary and lighthouse and hospital val foodBuilding = buildableNotWonders.filter { it.food>0 || (it.resourceBonusStats!=null && it.resourceBonusStats!!.food>0) } - .minBy{it.cost} + .minBy{ it.cost } if (foodBuilding!=null) { buildingValues[foodBuilding.name] = foodBuilding.cost / cityProduction - if (cityInfo.population.population < buildingValues[foodBuilding.name]!!.toInt()) { + if (cityInfo.population.population < foodBuilding.food + 5) { buildingValues[foodBuilding.name] = buildingValues[foodBuilding.name]!! / 2.0f } } @@ -134,8 +134,8 @@ class Automation { } //Wonders - val wonder = buildableWonders.minBy { it.cost } - if (wonder!=null) { + if (buildableWonders.isNotEmpty()) { + val wonder = buildableWonders.getRandom() buildingValues[wonder.name] = wonder.cost / cityProduction / 4.0f } @@ -148,20 +148,20 @@ class Automation { //worker if (workers<(cities+1)/2) { buildingValues[CityConstructions.Worker] = - buildableUnits.first{ it.name == CityConstructions.Worker }!!.cost / cityProduction * + buildableUnits.first{ it.name == CityConstructions.Worker }.cost / cityProduction * (workers/(cities+1)) } //Work boat if (needWorkboat) { buildingValues["Work Boats"] = - buildableUnits.first{ it.name == "Work Boats" }!!.cost / cityProduction + buildableUnits.first{ it.name == "Work Boats" }.cost / cityProduction * 1.5f } //Army val militaryUnit = chooseCombatUnit(cityInfo) buildingValues[militaryUnit] = - buildableUnits.first{ it.name == militaryUnit }!!.cost / cityProduction * 2.0f * + buildableUnits.first{ it.name == militaryUnit }.cost / cityProduction * 2.0f * (militaryUnits/(cities+1)) if (isAtWar) { buildingValues[militaryUnit] = buildingValues[militaryUnit]!! / 3.0f From af48f80e1adeca53e0a7fb5d1f1323ad9fd8ae1a Mon Sep 17 00:00:00 2001 From: Duan Tao Date: Wed, 20 Mar 2019 10:25:33 +0800 Subject: [PATCH 3/4] Priority tuning part 2. --- core/src/com/unciv/logic/automation/Automation.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 5274eea432..31ed858be8 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -136,7 +136,7 @@ class Automation { //Wonders if (buildableWonders.isNotEmpty()) { val wonder = buildableWonders.getRandom() - buildingValues[wonder.name] = wonder.cost / cityProduction / 4.0f + buildingValues[wonder.name] = wonder.cost / cityProduction / 5.0f } //other buildings @@ -161,8 +161,7 @@ class Automation { //Army val militaryUnit = chooseCombatUnit(cityInfo) buildingValues[militaryUnit] = - buildableUnits.first{ it.name == militaryUnit }.cost / cityProduction * 2.0f * - (militaryUnits/(cities+1)) + buildableUnits.first{ it.name == militaryUnit }.cost / cityProduction * 1.5f * militaryUnits / (cities+1) if (isAtWar) { buildingValues[militaryUnit] = buildingValues[militaryUnit]!! / 3.0f } From 90f84217776f09cff75d5e74bac276c40f3889f7 Mon Sep 17 00:00:00 2001 From: Duan Tao Date: Wed, 20 Mar 2019 22:25:34 +0800 Subject: [PATCH 4/4] Avoid building many wonders at the same time. --- core/src/com/unciv/logic/automation/Automation.kt | 4 +++- core/src/com/unciv/logic/city/CityConstructions.kt | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 31ed858be8..52f6a19104 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -135,8 +135,10 @@ class Automation { //Wonders if (buildableWonders.isNotEmpty()) { + val citiesBuildingWonders = cityInfo.civInfo.cities + .filter { it.cityConstructions.isBuildingWonder() }.size val wonder = buildableWonders.getRandom() - buildingValues[wonder.name] = wonder.cost / cityProduction / 5.0f + buildingValues[wonder.name] = wonder.cost / cityProduction * (citiesBuildingWonders + 1) / 5.0f } //other buildings diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index eec666d1bc..c41bf6b675 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -72,6 +72,14 @@ class CityConstructions { fun isBeingConstructed(constructionName: String): Boolean = currentConstruction == constructionName + fun isBuildingWonder(): Boolean { + val currentConstruction = getCurrentConstruction() + if (currentConstruction is Building) { + return currentConstruction.isWonder + } + return false + } + internal fun getConstruction(constructionName: String): IConstruction { if (GameBasics.Buildings.containsKey(constructionName)) return GameBasics.Buildings[constructionName]!!