Separated trade logic from UI display

This commit is contained in:
Yair Morgenstern 2018-08-01 09:00:06 +03:00
parent d8bb0eb9e5
commit d8daf6dc88
5 changed files with 177 additions and 167 deletions

View file

@ -0,0 +1,146 @@
package com.unciv.logic.trade
import com.unciv.UnCivGame
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.DiplomaticStatus
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tile.ResourceType
import com.unciv.ui.utils.tr
import kotlin.math.max
import kotlin.math.sqrt
class TradeLogic(val otherCivilization: CivilizationInfo){
val civInfo = UnCivGame.Current.gameInfo.getPlayerCivilization()
val ourAvailableOffers = getAvailableOffers(civInfo,otherCivilization)
val theirAvailableOffers = getAvailableOffers(otherCivilization,civInfo)
val currentTrade = Trade()
fun getAvailableOffers(civInfo: CivilizationInfo, otherCivilization: CivilizationInfo): TradeOffersList {
val offers = TradeOffersList()
if(civInfo.isAtWarWith(otherCivilization))
offers.add(TradeOffer("Peace Treaty", TradeType.Treaty, 20, 1))
for(entry in civInfo.getCivResources().filterNot { it.key.resourceType == ResourceType.Bonus }) {
val resourceTradeType = if(entry.key.resourceType== ResourceType.Luxury) TradeType.Luxury_Resource
else TradeType.Strategic_Resource
offers.add(TradeOffer(entry.key.name, resourceTradeType, 30, entry.value))
}
for(entry in civInfo.tech.techsResearched
.filterNot { otherCivilization.tech.isResearched(it) }
.filter { otherCivilization.tech.canBeResearched(it) }){
offers.add(TradeOffer(entry, TradeType.Technology, 0, 1))
}
offers.add(TradeOffer("Gold".tr(), TradeType.Gold, 0, civInfo.gold))
offers.add(TradeOffer("Gold per turn".tr(), TradeType.Gold_Per_Turn, 30, civInfo.getStatsForNextTurn().gold.toInt()))
for(city in civInfo.cities.filterNot { it.isCapital() })
offers.add(TradeOffer(city.name, TradeType.City, 0, 1))
return offers
}
fun isTradeAcceptable(): Boolean {
val sumOfTheirOffers = currentTrade.theirOffers.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{
var value = 50*offer.amount // they'll buy at 50 each only, and that's so they can trade it away
if(!theirAvailableOffers.any { it.name==offer.name })
value+=250 // only if they're lacking will they buy the first one at 300
return value
}
}
TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*10
TradeType.Strategic_Resource -> return 50 * offer.amount
TradeType.City -> {
val civ = if(otherCivIsRecieving) civInfo 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
}
// Dunno what this is?
else -> return 1000
}
}
fun evaluteCombatStrength(civInfo: CivilizationInfo): Int {
// Since units become exponentially stronger per combat strength increase, we square em all
fun square(x:Int) = x*x
val unitStrength = civInfo.getCivUnits().map { square(max(it.getBaseUnit().strength, it.getBaseUnit().rangedStrength)) }.sum()
val cityStrength = civInfo.cities.map { square(CityCombatant(it).getCityStrength()) }.sum()
return (sqrt(unitStrength.toDouble()) /*+ sqrt(cityStrength.toDouble())*/).toInt()
}
fun evaluatePeaceCostForThem(): Int {
val ourCombatStrength = evaluteCombatStrength(civInfo)
val theirCombatStrength = 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() {
civInfo.diplomacy[otherCivilization.civName]!!.trades.add(currentTrade)
otherCivilization.diplomacy[civInfo.civName]!!.trades.add(currentTrade.reverse())
// instant transfers
fun transfer(us: CivilizationInfo, them: CivilizationInfo, trade: Trade) {
for (offer in trade.theirOffers) {
if (offer.type == TradeType.Gold) {
us.gold += offer.amount
them.gold -= offer.amount
}
if (offer.type == TradeType.Technology) {
us.tech.techsResearched.add(offer.name)
}
if(offer.type== TradeType.City){
val city = them.cities.first { it.name==offer.name }
city.moveToCiv(us)
city.getCenterTile().getUnits().forEach { it.movementAlgs().teleportToClosestMoveableTile() }
}
if(offer.type== TradeType.Treaty){
if(offer.name=="Peace Treaty"){
us.diplomacy[them.civName]!!.diplomaticStatus= DiplomaticStatus.Peace
for(unit in us.getCivUnits().filter { it.getTile().getOwner()==them })
unit.movementAlgs().teleportToClosestMoveableTile()
}
}
}
}
transfer(civInfo,otherCivilization,currentTrade)
transfer(otherCivilization,civInfo,currentTrade.reverse())
}
}

View file

@ -0,0 +1,12 @@
package com.unciv.logic.trade
data class TradeOffer(var name:String, var type: TradeType, var duration:Int, var amount:Int) {
constructor() : this("", TradeType.Gold,0,0) // so that the json deserializer can work
fun equals(offer: TradeOffer): Boolean {
return offer.name==name
&& offer.type==type
&& offer.amount==amount
}
}

View file

@ -1,6 +1,5 @@
package com.unciv.logic.trade package com.unciv.logic.trade
import com.unciv.ui.TradeOffer
import java.util.* import java.util.*
class TradeOffersList: ArrayList<TradeOffer>(){ class TradeOffersList: ArrayList<TradeOffer>(){

View file

@ -1,8 +1,10 @@
package com.unciv.logic.trade package com.unciv.ui
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.logic.trade.TradeOffersList
import com.unciv.logic.trade.TradeType
import com.unciv.ui.utils.CameraStageBaseScreen import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.addClickListener import com.unciv.ui.utils.addClickListener
import com.unciv.ui.utils.disable import com.unciv.ui.utils.disable
@ -10,7 +12,7 @@ import com.unciv.ui.utils.tr
import kotlin.math.min import kotlin.math.min
class OffersList(val offers: TradeOffersList, val correspondingOffers: TradeOffersList, class OffersList(val offers: TradeOffersList, val correspondingOffers: TradeOffersList,
val otherCivOffers: TradeOffersList, val otherCivCorrespondingOffers:TradeOffersList, val otherCivOffers: TradeOffersList, val otherCivCorrespondingOffers: TradeOffersList,
val onChange: () -> Unit) : ScrollPane(null) { val onChange: () -> Unit) : ScrollPane(null) {
val table= Table(CameraStageBaseScreen.skin).apply { defaults().pad(5f) } val table= Table(CameraStageBaseScreen.skin).apply { defaults().pad(5f) }
init { init {
@ -33,7 +35,7 @@ class OffersList(val offers: TradeOffersList, val correspondingOffers: TradeOffe
val amountTransferred = min(amountPerClick, offer.amount) val amountTransferred = min(amountPerClick, offer.amount)
offers += offer.copy(amount = -amountTransferred) offers += offer.copy(amount = -amountTransferred)
correspondingOffers += offer.copy(amount = amountTransferred) correspondingOffers += offer.copy(amount = amountTransferred)
if(offer.type==TradeType.Treaty) { // this goes both ways, so it doesn't matter which side you click if(offer.type== TradeType.Treaty) { // this goes both ways, so it doesn't matter which side you click
otherCivOffers += offer.copy(amount = -amountTransferred) otherCivOffers += offer.copy(amount = -amountTransferred)
otherCivCorrespondingOffers += offer.copy(amount = amountTransferred) otherCivCorrespondingOffers += offer.copy(amount = amountTransferred)
} }

View file

@ -4,39 +4,13 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.unciv.UnCivGame import com.unciv.UnCivGame
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.DiplomaticStatus import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.OffersList
import com.unciv.logic.trade.Trade
import com.unciv.logic.trade.TradeOffersList
import com.unciv.logic.trade.TradeType
import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.tile.ResourceType
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import kotlin.math.max
import kotlin.math.sqrt
class TradeScreen(otherCivilization: CivilizationInfo) : CameraStageBaseScreen(){
data class TradeOffer(var name:String, var type: TradeType, var duration:Int, var amount:Int) { val tradeLogic = TradeLogic(otherCivilization)
constructor() : this("", TradeType.Gold,0,0) // so that the json deserializer can work
fun equals(offer:TradeOffer): Boolean {
return offer.name==name
&& offer.type==type
&& offer.amount==amount
}
}
class TradeScreen(val otherCivilization: CivilizationInfo) : CameraStageBaseScreen(){
val civInfo = UnCivGame.Current.gameInfo.getPlayerCivilization()
val ourAvailableOffers = getAvailableOffers(civInfo,otherCivilization)
val theirAvailableOffers = getAvailableOffers(otherCivilization,civInfo)
val currentTrade = Trade()
val table = Table(skin) val table = Table(skin)
val tradeText = Label("What do you have in mind?".tr(),skin) val tradeText = Label("What do you have in mind?".tr(),skin)
val offerButton = TextButton("Offer trade".tr(),skin) val offerButton = TextButton("Offer trade".tr(),skin)
@ -48,18 +22,16 @@ class TradeScreen(val otherCivilization: CivilizationInfo) : CameraStageBaseScre
tradeText.setText("What do you have in mind?".tr()) tradeText.setText("What do you have in mind?".tr())
} }
val ourAvailableOffersTable = OffersList(ourAvailableOffers, currentTrade.ourOffers, val ourAvailableOffersTable = OffersList(tradeLogic.ourAvailableOffers, tradeLogic.currentTrade.ourOffers,
theirAvailableOffers, currentTrade.theirOffers) { onChange() } tradeLogic.theirAvailableOffers, tradeLogic.currentTrade.theirOffers) { onChange() }
val ourOffersTable = OffersList(currentTrade.ourOffers, ourAvailableOffers, val ourOffersTable = OffersList(tradeLogic.currentTrade.ourOffers, tradeLogic.ourAvailableOffers,
currentTrade.theirOffers, theirAvailableOffers) { onChange() } tradeLogic.currentTrade.theirOffers, tradeLogic.theirAvailableOffers) { onChange() }
val theirOffersTable = OffersList(currentTrade.theirOffers, theirAvailableOffers, val theirOffersTable = OffersList(tradeLogic.currentTrade.theirOffers, tradeLogic.theirAvailableOffers,
currentTrade.ourOffers, ourAvailableOffers) { onChange() } tradeLogic.currentTrade.ourOffers, tradeLogic.ourAvailableOffers) { onChange() }
val theirAvailableOffersTable = OffersList(theirAvailableOffers, currentTrade.theirOffers, val theirAvailableOffersTable = OffersList(tradeLogic.theirAvailableOffers, tradeLogic.currentTrade.theirOffers,
ourAvailableOffers, currentTrade.ourOffers) { onChange() } tradeLogic.ourAvailableOffers, tradeLogic.currentTrade.ourOffers) { onChange() }
init { init {
val peaceCost = evaluatePeaceCostForThem()
val closeButton = TextButton("Close".tr(), skin) val closeButton = TextButton("Close".tr(), skin)
closeButton.addClickListener { UnCivGame.Current.setWorldScreen() } closeButton.addClickListener { UnCivGame.Current.setWorldScreen() }
closeButton.y = stage.height - closeButton.height - 5 closeButton.y = stage.height - closeButton.height - 5
@ -72,10 +44,10 @@ class TradeScreen(val otherCivilization: CivilizationInfo) : CameraStageBaseScre
offerButton.addClickListener { offerButton.addClickListener {
if(offerButton.text.toString() == "Offer trade".tr()) { if(offerButton.text.toString() == "Offer trade".tr()) {
if(currentTrade.theirOffers.size==0 && currentTrade.ourOffers.size==0){ if(tradeLogic.currentTrade.theirOffers.size==0 && tradeLogic.currentTrade.ourOffers.size==0){
tradeText.setText("There's nothing on the table.".tr()) tradeText.setText("There's nothing on the table.".tr())
} }
else if (isTradeAcceptable(currentTrade)){ else if (tradeLogic.isTradeAcceptable()){
tradeText.setText("That is acceptable.".tr()) tradeText.setText("That is acceptable.".tr())
offerButton.setText("Accept".tr()) offerButton.setText("Accept".tr())
} }
@ -84,37 +56,7 @@ class TradeScreen(val otherCivilization: CivilizationInfo) : CameraStageBaseScre
} }
} }
else if(offerButton.text.toString() == "Accept".tr()){ else if(offerButton.text.toString() == "Accept".tr()){
civInfo.diplomacy[otherCivilization.civName]!!.trades.add(currentTrade) tradeLogic.acceptTrade()
otherCivilization.diplomacy[civInfo.civName]!!.trades.add(currentTrade.reverse())
// instant transfers
fun transfer(us: CivilizationInfo,them:CivilizationInfo, trade: Trade) {
for (offer in trade.theirOffers) {
if (offer.type == TradeType.Gold) {
us.gold += offer.amount
them.gold -= offer.amount
}
if (offer.type == TradeType.Technology) {
us.tech.techsResearched.add(offer.name)
}
if(offer.type==TradeType.City){
val city = them.cities.first { it.name==offer.name }
city.moveToCiv(us)
city.getCenterTile().getUnits().forEach { it.movementAlgs().teleportToClosestMoveableTile() }
}
if(offer.type==TradeType.Treaty){
if(offer.name=="Peace Treaty"){
us.diplomacy[them.civName]!!.diplomaticStatus=DiplomaticStatus.Peace
for(unit in us.getCivUnits().filter { it.getTile().getOwner()==them })
unit.movementAlgs().teleportToClosestMoveableTile()
}
}
}
}
transfer(civInfo,otherCivilization,currentTrade)
transfer(otherCivilization,civInfo,currentTrade.reverse())
val newTradeScreen = TradeScreen(otherCivilization) val newTradeScreen = TradeScreen(otherCivilization)
newTradeScreen.tradeText.setText("Pleasure doing business with you!".tr()) newTradeScreen.tradeText.setText("Pleasure doing business with you!".tr())
UnCivGame.Current.screen = newTradeScreen UnCivGame.Current.screen = newTradeScreen
@ -151,96 +93,5 @@ class TradeScreen(val otherCivilization: CivilizationInfo) : CameraStageBaseScre
theirOffersTable.update() theirOffersTable.update()
} }
fun getAvailableOffers(civInfo: CivilizationInfo, otherCivilization: CivilizationInfo): TradeOffersList {
val offers = TradeOffersList()
if(civInfo.isAtWarWith(otherCivilization))
offers.add(TradeOffer("Peace Treaty",TradeType.Treaty,20,1))
for(entry in civInfo.getCivResources().filterNot { it.key.resourceType == ResourceType.Bonus }) {
val resourceTradeType = if(entry.key.resourceType==ResourceType.Luxury) TradeType.Luxury_Resource
else TradeType.Strategic_Resource
offers.add(TradeOffer(entry.key.name, resourceTradeType, 30, entry.value))
}
for(entry in civInfo.tech.techsResearched
.filterNot { otherCivilization.tech.isResearched(it) }
.filter { otherCivilization.tech.canBeResearched(it) }){
offers.add(TradeOffer(entry, TradeType.Technology,0,1))
}
offers.add(TradeOffer("Gold".tr(), TradeType.Gold,0,civInfo.gold))
offers.add(TradeOffer("Gold per turn".tr(), TradeType.Gold_Per_Turn,30,civInfo.getStatsForNextTurn().gold.toInt()))
for(city in civInfo.cities.filterNot { it.isCapital() })
offers.add(TradeOffer(city.name,TradeType.City,0,1))
return offers
}
fun isTradeAcceptable(trade: Trade): Boolean {
val sumOfTheirOffers = trade.theirOffers.filter { it.type!=TradeType.Treaty } // since treaties should only be evaluated once for 2 sides
.map { evaluateOffer(it,false) }.sum()
val sumOfOurOffers = trade.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{
var value = 50*offer.amount // they'll buy at 50 each only, and that's so they can trade it away
if(!theirAvailableOffers.any { it.name==offer.name })
value+=250 // only if they're lacking will they buy the first one at 300
return value
}
}
TradeType.Technology -> return sqrt(GameBasics.Technologies[offer.name]!!.cost.toDouble()).toInt()*10
TradeType.Strategic_Resource -> return 50 * offer.amount
TradeType.City -> {
val civ = if(otherCivIsRecieving) civInfo 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
}
// Dunno what this is?
else -> return 1000
}
}
fun evaluteCombatStrength(civInfo: CivilizationInfo): Int {
// Since units become exponentially stronger per combat strength increase, we square em all
fun square(x:Int) = x*x
val unitStrength = civInfo.getCivUnits().map { square(max(it.getBaseUnit().strength,it.getBaseUnit().rangedStrength)) }.sum()
val cityStrength = civInfo.cities.map { square(CityCombatant(it).getCityStrength()) }.sum()
return (sqrt(unitStrength.toDouble()) /*+ sqrt(cityStrength.toDouble())*/).toInt()
}
fun evaluatePeaceCostForThem(): Int {
val ourCombatStrength = evaluteCombatStrength(civInfo)
val theirCombatStrength = 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
}
}
} }