Resolved #601 - gold luxury not accepted in trades

Organized trade buy/sell logic - should be much easier to understand now!
Split into Evaluation and the current trade deal, evaluation split into buying and selling
This commit is contained in:
Yair Morgenstern 2019-03-28 23:05:50 +02:00
parent 81d8dda111
commit 0b47289c3d
4 changed files with 173 additions and 126 deletions

View file

@ -2,11 +2,12 @@ package com.unciv.logic.automation
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tech.Technology
import com.unciv.models.gamebasics.tr
@ -55,9 +56,9 @@ class NextTurnAutomation{
.filter { it.type == TradeType.Technology }
for (theirOffer in theirTradableTechs) {
val theirValue = tradeLogic.evaluateOffer(theirOffer, false)
val theirValue = TradeEvaluation().evaluateBuyCost(theirOffer, civInfo, otherCiv)
val ourOfferList = ourTradableTechs.filter{
tradeLogic.evaluateOffer(it, false) == theirValue
TradeEvaluation().evaluateBuyCost(it, otherCiv, civInfo) == theirValue
&& !tradeLogic.currentTrade.ourOffers.contains(it) }
if (ourOfferList.isNotEmpty()) {
@ -165,7 +166,7 @@ class NextTurnAutomation{
//pay for peace
val tradeLogic = TradeLogic(civInfo, enemy)
var moneyWeNeedToPay = -tradeLogic.evaluatePeaceCostForThem()
var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo,enemy)
if (moneyWeNeedToPay > tradeLogic.ourAvailableOffers.first { it.type == TradeType.Gold }.amount) {
moneyWeNeedToPay = tradeLogic.ourAvailableOffers.first { it.type == TradeType.Gold }.amount
}

View file

@ -0,0 +1,165 @@
package com.unciv.logic.trade
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.ThreatLevel
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tile.ResourceType
import kotlin.math.min
import kotlin.math.sqrt
class TradeEvaluation{
fun isTradeAcceptable(trade: Trade, evaluator: CivilizationInfo, tradePartner: CivilizationInfo): Boolean {
val sumOfTheirOffers = trade.theirOffers.asSequence()
.filter { it.type!= TradeType.Treaty } // since treaties should only be evaluated once for 2 sides
.map { evaluateBuyCost(it,evaluator,tradePartner) }.sum()
val sumOfOurOffers = trade.ourOffers.map { evaluateSellCost(it, evaluator, tradePartner)}.sum()
return sumOfOurOffers <= sumOfTheirOffers
}
fun evaluateBuyCost(offer: TradeOffer, civInfo: CivilizationInfo, tradePartner: CivilizationInfo): Int {
when (offer.type) {
TradeType.Gold -> return offer.amount
TradeType.Gold_Per_Turn -> return offer.amount * offer.duration
TradeType.Treaty -> {
if (offer.name == "Peace Treaty")
return evaluatePeaceCostForThem(civInfo,tradePartner) // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs
else return 1000
}
TradeType.Luxury_Resource -> {
val weAreMissingThisLux = !civInfo.hasResource(offer.name) // first off - do we want this for ourselves?
val civsWhoWillTradeUsForTheLux = civInfo.diplomacy.values.map { it.civInfo } // secondly - should we buy this in order to resell it?
.filter { it != tradePartner }
.filter { !it.hasResource(offer.name) } //they don't have
val ourResourceNames = civInfo.getCivResources().map { it.key.name }
val civsWithLuxToTrade = civsWhoWillTradeUsForTheLux.filter {
// these are other civs who we could trade this lux away to, in order to get a different lux
it.getCivResources().any {
it.value > 1 && it.key.resourceType == ResourceType.Luxury //they have a lux we don't and will be willing to trade it
&& !ourResourceNames.contains(it.key.name)
}
}
var numberOfCivsWhoWouldTradeUsForTheLux = civsWithLuxToTrade.count()
var numberOfLuxesWeAreWillingToBuy = 0
var cost = 0
if (weAreMissingThisLux) { // for ourselves
numberOfLuxesWeAreWillingToBuy += 1
cost += 250
}
while (numberOfLuxesWeAreWillingToBuy < offer.amount && numberOfCivsWhoWouldTradeUsForTheLux > 0) {
numberOfLuxesWeAreWillingToBuy += 1 // for reselling
cost += 50
numberOfCivsWhoWouldTradeUsForTheLux -= 1
}
return cost
}
TradeType.Strategic_Resource -> {
val resources = civInfo.getCivResourcesByName()
val amountWillingToBuy = resources[offer.name]!! - 2
if (amountWillingToBuy <= 0) return 0 // we already have enough.
val amountToBuyInOffer = min(amountWillingToBuy, offer.amount)
val canUseForBuildings = civInfo.cities
.any { city -> city.cityConstructions.getBuildableBuildings().any { it.requiredResource == offer.name } }
val canUseForUnits = civInfo.cities
.any { city -> city.cityConstructions.getConstructableUnits().any { it.requiredResource == offer.name } }
if (!canUseForBuildings && !canUseForUnits) return 0
return 50 * amountToBuyInOffer
}
TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20
TradeType.Introduction -> return 250
TradeType.WarDeclaration -> {
val nameOfCivToDeclareWarOn = offer.name.split(' ').last()
val civToDeclareWarOn = civInfo.gameInfo.getCivilization(nameOfCivToDeclareWarOn)
val threatToThem = Automation().threatAssessment(civInfo,civToDeclareWarOn)
if(civInfo.diplomacy[nameOfCivToDeclareWarOn]!!.diplomaticStatus== DiplomaticStatus.War){
when (threatToThem) {
ThreatLevel.VeryLow -> return 0
ThreatLevel.Low -> return 0
ThreatLevel.Medium -> return 100
ThreatLevel.High -> return 500
ThreatLevel.VeryHigh -> return 1000
}
}
else return 0 // why should we pay you to go fight someone...?
}
TradeType.City -> {
val city = tradePartner.cities.first { it.name==offer.name }
val stats = city.cityStats.currentCityStats
if(civInfo.happiness + city.cityStats.happinessList.values.sum() < 0)
return 0 // we can't really afford to go into negative happiness because of buying a city
val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food
return sumOfStats.toInt() * 100
}
}
}
fun evaluateSellCost(offer: TradeOffer, civInfo: CivilizationInfo, tradePartner: CivilizationInfo): Int {
when (offer.type) {
TradeType.Gold -> return offer.amount
TradeType.Gold_Per_Turn -> return offer.amount * offer.duration
TradeType.Treaty -> {
if (offer.name == "Peace Treaty")
return evaluatePeaceCostForThem(civInfo,tradePartner) // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs
else return 1000
}
TradeType.Luxury_Resource -> {
if(civInfo.getCivResourcesByName()[offer.name]!!>1)
return 250 // fair price
else return 500 // you want to take away our last lux of this type?!
}
TradeType.Strategic_Resource -> return 50*offer.amount
TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20
TradeType.Introduction -> return 250
TradeType.WarDeclaration -> {
val nameOfCivToDeclareWarOn = offer.name.split(' ').last()
val civToDeclareWarOn = civInfo.gameInfo.getCivilization(nameOfCivToDeclareWarOn)
val threatToUs = Automation().threatAssessment(civInfo, civToDeclareWarOn)
when (threatToUs) {
ThreatLevel.VeryLow -> return 100
ThreatLevel.Low -> return 250
ThreatLevel.Medium -> return 500
ThreatLevel.High -> return 1000
ThreatLevel.VeryHigh -> return 10000 // no way boyo
}
}
TradeType.City -> {
val city = civInfo.cities.first { it.name==offer.name }
val stats = city.cityStats.currentCityStats
val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food
return sumOfStats.toInt() * 100
}
}
}
fun evaluatePeaceCostForThem(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo): Int {
val ourCombatStrength = Automation().evaluteCombatStrength(ourCivilization)
val theirCombatStrength = Automation().evaluteCombatStrength(otherCivilization)
if(ourCombatStrength==theirCombatStrength) return 0
if(ourCombatStrength==0) return -1000
if(theirCombatStrength==0) return 1000 // Chumps got no cities or units
if(ourCombatStrength>theirCombatStrength){
val absoluteAdvantage = ourCombatStrength-theirCombatStrength
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()
return (absoluteAdvantage*percentageAdvantage).toInt() * 10
}
else{
val absoluteAdvantage = theirCombatStrength-ourCombatStrength
val percentageAdvantage = absoluteAdvantage / ourCombatStrength.toFloat()
return -(absoluteAdvantage*percentageAdvantage).toInt() * 10
}
}
}

View file

@ -1,14 +1,9 @@
package com.unciv.logic.trade
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.ThreatLevel
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tile.ResourceType
import com.unciv.models.gamebasics.tr
import kotlin.math.min
import kotlin.math.sqrt
class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: CivilizationInfo){
@ -54,123 +49,7 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
return offers
}
fun isTradeAcceptable(): Boolean {
val sumOfTheirOffers = currentTrade.theirOffers.asSequence()
.filter { it.type!= TradeType.Treaty } // since treaties should only be evaluated once for 2 sides
.map { evaluateOffer(it,false) }.sum()
val sumOfOurOffers = currentTrade.ourOffers.map { evaluateOffer(it,true)}.sum()
return sumOfOurOffers >= sumOfTheirOffers
}
fun evaluateOffer(offer: TradeOffer, otherCivIsRecieving:Boolean): Int {
when(offer.type) {
TradeType.Gold -> return offer.amount
TradeType.Gold_Per_Turn -> return offer.amount*offer.duration
TradeType.Luxury_Resource -> {
if(!otherCivIsRecieving){ // they're giving us
var value = 300*offer.amount
if(!theirAvailableOffers.any { it.name==offer.name }) // We want to take away their last luxury or give them one they don't have
value += 400
return value
}
else{
val civsWhoWillTradeUsForTheLux = ourCivilization.diplomacy.values.map { it.civInfo }
.filter { it!= otherCivilization }
.filter { !it.hasResource(offer.name) } //they don't have
val ourResourceNames = ourCivilization.getCivResources().map { it.key.name }
val civsWithLuxToTrade = civsWhoWillTradeUsForTheLux.filter {
it.getCivResources().any {
it.value > 1 && it.key.resourceType == ResourceType.Luxury //they have a lux we don't and will be willing to trade it
&& !ourResourceNames.contains(it.key.name)
}
}
var value = 50*min(offer.amount,civsWithLuxToTrade.size) // they'll buy at 50 each only, and that's so they can trade it away
if(!theirAvailableOffers.any { it.name==offer.name })
value+=200 // only if they're lacking will they buy the first one at 240 (Civ V standard, see https://www.reddit.com/r/civ/comments/1go7i9/luxury_and_strategic_resource_pricing/ & others)
return value
}
}
TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*20
TradeType.Strategic_Resource -> {
if(otherCivIsRecieving) {
val resources = ourCivilization.getCivResourcesByName()
if (resources[offer.name]!! >= 2) return 0 // we already have enough.
val canUseForBuildings = ourCivilization.cities
.any { city-> city.cityConstructions.getBuildableBuildings().any { it.requiredResource==offer.name } }
val canUseForUnits = ourCivilization.cities
.any { city-> city.cityConstructions.getConstructableUnits().any { it.requiredResource==offer.name } }
if(!canUseForBuildings && !canUseForUnits) return 0
return 50 * offer.amount
}
else return 50 * offer.amount
}
TradeType.City -> {
val civ = if(otherCivIsRecieving) ourCivilization else otherCivilization
val city = civ.cities.first { it.name==offer.name }
val stats = city.cityStats.currentCityStats
val sumOfStats = stats.culture+stats.gold+stats.science+stats.production+stats.happiness+stats.food
return sumOfStats.toInt() * 100
}
TradeType.Treaty -> {
if(offer.name=="Peace Treaty")
return evaluatePeaceCostForThem() // Since it will be evaluated twice, once when they evaluate our offer and once when they evaluate theirs
else return 1000
}
TradeType.Introduction -> return 250
TradeType.WarDeclaration -> {
val nameOfCivToDeclareWarOn = offer.name.split(' ').last()
val civToDeclareWarOn = ourCivilization.gameInfo.getCivilization(nameOfCivToDeclareWarOn)
val threatToThem = Automation().threatAssessment(otherCivilization,civToDeclareWarOn)
if(!otherCivIsRecieving) { // we're getting this from them, that is, they're declaring war
when (threatToThem) {
ThreatLevel.VeryLow -> return 100
ThreatLevel.Low -> return 250
ThreatLevel.Medium -> return 500
ThreatLevel.High -> return 1000
ThreatLevel.VeryHigh -> return 10000 // no way
}
}
else{
if(otherCivilization.diplomacy[nameOfCivToDeclareWarOn]!!.diplomaticStatus== DiplomaticStatus.War){
when (threatToThem) {
ThreatLevel.VeryLow -> return 0
ThreatLevel.Low -> return 0
ThreatLevel.Medium -> return 100
ThreatLevel.High -> return 500
ThreatLevel.VeryHigh -> return 1000
}
}
else return 0 // why should we pay you to go fight someone...?
}
}
// Dunno what this is?
else -> return 1000
}
}
fun evaluatePeaceCostForThem(): Int {
val ourCombatStrength = Automation().evaluteCombatStrength(ourCivilization)
val theirCombatStrength = Automation().evaluteCombatStrength(otherCivilization)
if(ourCombatStrength==theirCombatStrength) return 0
if(ourCombatStrength==0) return -1000
if(theirCombatStrength==0) return 1000 // Chumps got no cities or units
if(ourCombatStrength>theirCombatStrength){
val absoluteAdvantage = ourCombatStrength-theirCombatStrength
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()
return (absoluteAdvantage*percentageAdvantage).toInt() * 10
}
else{
val absoluteAdvantage = theirCombatStrength-ourCombatStrength
val percentageAdvantage = absoluteAdvantage / ourCombatStrength.toFloat()
return -(absoluteAdvantage*percentageAdvantage).toInt() * 10
}
}
fun acceptTrade() {
ourCivilization.diplomacy[otherCivilization.civName]!!.trades.add(currentTrade)
otherCivilization.diplomacy[ourCivilization.civName]!!.trades.add(currentTrade.reverse())
@ -212,3 +91,4 @@ class TradeLogic(val ourCivilization:CivilizationInfo, val otherCivilization: Ci
transferTrade(otherCivilization,ourCivilization,currentTrade.reverse())
}
}

View file

@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.trade.TradeEvaluation
import com.unciv.logic.trade.TradeLogic
import com.unciv.models.gamebasics.tr
import com.unciv.ui.utils.CameraStageBaseScreen
@ -32,7 +33,7 @@ class TradeTable(val otherCivilization: CivilizationInfo, stage: Stage, onTradeC
if(tradeLogic.currentTrade.theirOffers.size==0 && tradeLogic.currentTrade.ourOffers.size==0){
tradeText.setText(otherCivilization.getTranslatedNation().neutralLetsHearIt.random().tr())
}
else if (tradeLogic.isTradeAcceptable()){
else if (TradeEvaluation().isTradeAcceptable(tradeLogic.currentTrade.reverse(),otherCivilization,currentPlayerCiv)){
tradeText.setText(otherCivilization.getTranslatedNation().neutralYes.random().tr())
offerButton.setText("Accept".tr())
}