diff options
Diffstat (limited to 'sw-engine/src')
-rw-r--r-- | sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt | 8 | ||||
-rw-r--r-- | sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt | 17 | ||||
-rw-r--r-- | sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt | 27 | ||||
-rw-r--r-- | sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculator.kt (renamed from sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt) | 61 | ||||
-rw-r--r-- | sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt | 5 | ||||
-rw-r--r-- | sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt | 17 | ||||
-rw-r--r-- | sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt | 4 | ||||
-rw-r--r-- | sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt | 175 | ||||
-rw-r--r-- | sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculatorTest.kt | 201 |
9 files changed, 269 insertions, 246 deletions
diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt index d5468283..b097734a 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt @@ -8,7 +8,7 @@ import org.luxons.sevenwonders.model.cards.CardPlayability import org.luxons.sevenwonders.model.cards.Color import org.luxons.sevenwonders.model.cards.PlayabilityLevel import org.luxons.sevenwonders.model.resources.ResourceTransactions -import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.model.resources.singleOptionNoTransactionNeeded internal data class Card( val name: String, @@ -55,7 +55,7 @@ private object Playability { isPlayable = true, isChainable = true, minPrice = 0, - cheapestTransactions = setOf(noTransactions()), + transactionOptions = singleOptionNoTransactionNeeded(), playabilityLevel = PlayabilityLevel.CHAINABLE, ) @@ -63,7 +63,7 @@ private object Playability { isPlayable = satisfaction.satisfied, isChainable = false, minPrice = satisfaction.minPrice, - cheapestTransactions = satisfaction.cheapestTransactions, + transactionOptions = satisfaction.transactionOptions, playabilityLevel = satisfaction.level, ) @@ -71,7 +71,7 @@ private object Playability { isPlayable = true, isChainable = false, minPrice = 0, - cheapestTransactions = setOf(noTransactions()), + transactionOptions = singleOptionNoTransactionNeeded(), playabilityLevel = PlayabilityLevel.SPECIAL_FREE, ) } diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt index 945f4463..b78a5057 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt @@ -2,12 +2,9 @@ package org.luxons.sevenwonders.engine.cards import org.luxons.sevenwonders.engine.Player import org.luxons.sevenwonders.engine.boards.Board -import org.luxons.sevenwonders.engine.resources.Resources -import org.luxons.sevenwonders.engine.resources.asResources -import org.luxons.sevenwonders.engine.resources.bestSolution -import org.luxons.sevenwonders.engine.resources.emptyResources -import org.luxons.sevenwonders.engine.resources.execute +import org.luxons.sevenwonders.engine.resources.* import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.bestPrice data class Requirements internal constructor( val gold: Int = 0, @@ -37,15 +34,15 @@ data class Requirements internal constructor( } private fun satisfactionWithPotentialHelp(player: Player): RequirementsSatisfaction { - val (minPriceForResources, possibleTransactions) = bestSolution(resources, player) - val minPrice = minPriceForResources + gold - if (possibleTransactions.isEmpty()) { + val options = transactionOptions(resources, player) + val minPrice = options.bestPrice + gold + if (options.isEmpty()) { return RequirementsSatisfaction.unavailableResources() } if (player.board.gold < minPrice) { - return RequirementsSatisfaction.missingGoldForResources(minPrice, possibleTransactions) + return RequirementsSatisfaction.missingGoldForResources(minPrice, options) } - return RequirementsSatisfaction.metWithHelp(minPrice, possibleTransactions) + return RequirementsSatisfaction.metWithHelp(minPrice, options) } /** diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt index f9566981..55ec1edb 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt @@ -1,39 +1,40 @@ package org.luxons.sevenwonders.engine.cards import org.luxons.sevenwonders.model.cards.PlayabilityLevel -import org.luxons.sevenwonders.model.resources.PricedResourceTransactions -import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.model.resources.ResourceTransactionOptions +import org.luxons.sevenwonders.model.resources.noTransactionOptions +import org.luxons.sevenwonders.model.resources.singleOptionNoTransactionNeeded internal data class RequirementsSatisfaction( val satisfied: Boolean, val level: PlayabilityLevel, val minPrice: Int, - val cheapestTransactions: Set<PricedResourceTransactions>, + val transactionOptions: ResourceTransactionOptions, ) { companion object { internal fun noRequirements() = - RequirementsSatisfaction(true, PlayabilityLevel.NO_REQUIREMENTS, 0, setOf(noTransactions())) + RequirementsSatisfaction(true, PlayabilityLevel.NO_REQUIREMENTS, 0, singleOptionNoTransactionNeeded()) internal fun enoughResources() = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_RESOURCES, 0, setOf(noTransactions())) + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_RESOURCES, 0, singleOptionNoTransactionNeeded()) internal fun enoughGold(minPrice: Int) = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD, minPrice, setOf(noTransactions())) + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD, minPrice, singleOptionNoTransactionNeeded()) internal fun enoughResourcesAndGold(minPrice: Int) = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD_AND_RES, minPrice, setOf(noTransactions())) + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD_AND_RES, minPrice, singleOptionNoTransactionNeeded()) - internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set<PricedResourceTransactions>) = - RequirementsSatisfaction(true, PlayabilityLevel.REQUIRES_HELP, minPrice, cheapestTransactions) + internal fun metWithHelp(minPrice: Int, transactionOptions: ResourceTransactionOptions) = + RequirementsSatisfaction(true, PlayabilityLevel.REQUIRES_HELP, minPrice, transactionOptions) internal fun missingRequiredGold(minPrice: Int) = - RequirementsSatisfaction(false, PlayabilityLevel.MISSING_REQUIRED_GOLD, minPrice, emptySet()) + RequirementsSatisfaction(false, PlayabilityLevel.MISSING_REQUIRED_GOLD, minPrice, noTransactionOptions()) - internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set<PricedResourceTransactions>) = - RequirementsSatisfaction(false, PlayabilityLevel.MISSING_GOLD_FOR_RES, minPrice, cheapestTransactions) + internal fun missingGoldForResources(minPrice: Int, transactionOptions: ResourceTransactionOptions) = + RequirementsSatisfaction(false, PlayabilityLevel.MISSING_GOLD_FOR_RES, minPrice, transactionOptions) internal fun unavailableResources() = - RequirementsSatisfaction(false, PlayabilityLevel.UNAVAILABLE_RESOURCES, Int.MAX_VALUE, emptySet()) + RequirementsSatisfaction(false, PlayabilityLevel.UNAVAILABLE_RESOURCES, Int.MAX_VALUE, noTransactionOptions()) } } diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculator.kt index 967bee2c..fbbcaa6e 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculator.kt @@ -1,15 +1,11 @@ package org.luxons.sevenwonders.engine.resources import org.luxons.sevenwonders.engine.Player -import org.luxons.sevenwonders.model.resources.PricedResourceTransactions -import org.luxons.sevenwonders.model.resources.Provider -import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.* import java.util.* -internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = - BestPriceCalculator(resources, player).computeBestSolution() - -data class TransactionPlan(val price: Int, val possibleTransactions: Set<PricedResourceTransactions>) +internal fun transactionOptions(resources: Resources, player: Player): ResourceTransactionOptions = + TransactionOptionsCalculator(resources, player).computeOptions() private class ResourcePool( val provider: Provider?, @@ -21,16 +17,14 @@ private class ResourcePool( fun getCost(type: ResourceType): Int = if (provider == null) 0 else rules.getCost(type, provider) } -private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { +private class TransactionOptionsCalculator(resourcesToPay: Resources, player: Player) { private val pools: List<ResourcePool> private val resourcesLeftToPay: MutableResources private val boughtResources: MutableMap<Provider, MutableResources> = EnumMap(Provider::class.java) - private val pricePaid: MutableMap<Provider, Int> = EnumMap(Provider::class.java) - private var totalPricePaid: Int = 0 + private val pricePaidPerProvider: MutableMap<Provider, Int> = EnumMap(Provider::class.java) - private var bestSolutions: MutableSet<PricedResourceTransactions> = mutableSetOf() - private var bestPrice: Int = Integer.MAX_VALUE + private var optionsSoFar: MutableSet<PricedResourceTransactions> = mutableSetOf() init { val board = player.board @@ -53,14 +47,17 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { return ResourcePool(this, player.board.tradingRules, choices) } - fun computeBestSolution(): TransactionPlan { + fun computeOptions(): ResourceTransactionOptions { computePossibilities() - return TransactionPlan(bestPrice, bestSolutions) + return optionsSoFar.distinctBy { it.costByProvider }.sortedBy { it.totalPrice } } + private val PricedResourceTransactions.costByProvider: Map<Provider, Int> + get() = associate { it.provider to it.totalPrice } + private fun computePossibilities() { if (resourcesLeftToPay.isEmpty()) { - updateBestSolutionIfNeeded() + addCurrentOption() return } for (type in ResourceType.values()) { @@ -93,13 +90,11 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { fun buyOne(provider: Provider, type: ResourceType, cost: Int) { boughtResources.getOrPut(provider) { MutableResources() }.add(type, 1) - pricePaid.merge(provider, cost) { old, new -> old + new } - totalPricePaid += cost + pricePaidPerProvider.merge(provider, cost) { old, new -> old + new } } fun unbuyOne(provider: Provider, type: ResourceType, cost: Int) { - totalPricePaid -= cost - pricePaid.merge(provider, -cost) { old, new -> old + new } + pricePaidPerProvider.merge(provider, -cost) { old, new -> old + new } boughtResources[provider]!!.remove(type, 1) } @@ -114,15 +109,27 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { } } - private fun updateBestSolutionIfNeeded() { - if (totalPricePaid > bestPrice) return - - if (totalPricePaid < bestPrice) { - bestPrice = totalPricePaid - bestSolutions.clear() + private fun addCurrentOption() { + if (optionsSoFar.any { it < pricePaidPerProvider }) { + return } // avoid mutating the resources from the transactions - val transactionSet = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions(pricePaid) - bestSolutions.add(transactionSet) + val transactionsOption = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions(pricePaidPerProvider) + optionsSoFar.add(transactionsOption) + optionsSoFar.removeIf { it > pricePaidPerProvider } } + + private operator fun PricedResourceTransactions.compareTo(prices: Map<Provider, Int>): Int = when { + left == prices.left -> right.compareTo(prices.right) + right == prices.right -> left.compareTo(prices.left) + else -> 0 + } + + private val Map<Provider, Int>.left: Int get() = this[Provider.LEFT_PLAYER] ?: 0 + private val Map<Provider, Int>.right: Int get() = this[Provider.RIGHT_PLAYER] ?: 0 + + private val PricedResourceTransactions.left: Int + get() = firstOrNull { it.provider == Provider.LEFT_PLAYER }?.totalPrice ?: 0 + private val PricedResourceTransactions.right: Int + get() = firstOrNull { it.provider == Provider.RIGHT_PLAYER }?.totalPrice ?: 0 } diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt index 4a99b9e9..03ed2f8f 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt @@ -7,6 +7,7 @@ import org.luxons.sevenwonders.model.cards.CardBack import org.luxons.sevenwonders.model.cards.PlayabilityLevel import org.luxons.sevenwonders.model.resources.ResourceTransactions import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.noTransactionOptions import org.luxons.sevenwonders.model.wonders.WonderBuildability internal class Wonder( @@ -53,14 +54,14 @@ private object Buildability { fun wonderFullyBuilt() = WonderBuildability( isBuildable = false, minPrice = Int.MAX_VALUE, - cheapestTransactions = emptySet(), + transactionsOptions = noTransactionOptions(), playabilityLevel = PlayabilityLevel.WONDER_FULLY_BUILT, ) fun requirementDependent(satisfaction: RequirementsSatisfaction) = WonderBuildability( isBuildable = satisfaction.satisfied, minPrice = satisfaction.minPrice, - cheapestTransactions = satisfaction.cheapestTransactions, + transactionsOptions = satisfaction.transactionOptions, playabilityLevel = satisfaction.level, ) } diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt index 783d3eab..62267c7a 100644 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt @@ -4,23 +4,14 @@ import org.luxons.sevenwonders.engine.data.GameDefinition import org.luxons.sevenwonders.engine.data.LAST_AGE import org.luxons.sevenwonders.engine.test.SEED import org.luxons.sevenwonders.engine.test.testSettings -import org.luxons.sevenwonders.model.Action -import org.luxons.sevenwonders.model.MoveType -import org.luxons.sevenwonders.model.PlayedMove -import org.luxons.sevenwonders.model.PlayerMove -import org.luxons.sevenwonders.model.PlayerTurnInfo +import org.luxons.sevenwonders.model.* import org.luxons.sevenwonders.model.cards.HandCard import org.luxons.sevenwonders.model.cards.TableCard import org.luxons.sevenwonders.model.resources.ResourceTransactions import org.luxons.sevenwonders.model.resources.noTransactions import org.luxons.sevenwonders.model.wonders.deal import kotlin.random.Random -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.test.fail +import kotlin.test.* class GameTest { @@ -91,12 +82,12 @@ class GameTest { private fun createPlayCardMove(turnInfo: PlayerTurnInfo): MoveExpectation { val wonderBuildability = turnInfo.wonderBuildability if (wonderBuildability.isBuildable) { - val transactions = wonderBuildability.cheapestTransactions.first() + val transactions = wonderBuildability.transactionsOptions.first() return planMove(turnInfo, MoveType.UPGRADE_WONDER, turnInfo.hand!!.first(), transactions) } val playableCard = turnInfo.hand!!.firstOrNull { it.playability.isPlayable } return if (playableCard != null) { - planMove(turnInfo, MoveType.PLAY, playableCard, playableCard.playability.cheapestTransactions.first()) + planMove(turnInfo, MoveType.PLAY, playableCard, playableCard.playability.transactionOptions.first()) } else { planMove(turnInfo, MoveType.DISCARD, turnInfo.hand!!.first(), noTransactions()) } diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt index d3fdc94c..36dc8ed1 100644 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt @@ -117,11 +117,11 @@ class RequirementsTest { val satisfaction = requirements.assess(player) if (neighbourHasResource) { - val transactions = setOf( + val transactionOptions = listOf( createPricedTransactions(Provider.LEFT_PLAYER, 2, requiredResource), createPricedTransactions(Provider.RIGHT_PLAYER, 2, requiredResource), ) - assertEquals(RequirementsSatisfaction.metWithHelp(2, transactions), satisfaction) + assertEquals(RequirementsSatisfaction.metWithHelp(2, transactionOptions), satisfaction) } else { assertEquals(RequirementsSatisfaction.unavailableResources(), satisfaction) } diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt deleted file mode 100644 index 1a56caf4..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt +++ /dev/null @@ -1,175 +0,0 @@ -package org.luxons.sevenwonders.engine.resources - -import org.junit.Test -import org.luxons.sevenwonders.engine.SimplePlayer -import org.luxons.sevenwonders.engine.boards.Table -import org.luxons.sevenwonders.engine.test.createPricedTransaction -import org.luxons.sevenwonders.engine.test.createPricedTransactions -import org.luxons.sevenwonders.engine.test.testBoard -import org.luxons.sevenwonders.engine.test.testTable -import org.luxons.sevenwonders.model.resources.PricedResourceTransactions -import org.luxons.sevenwonders.model.resources.Provider.LEFT_PLAYER -import org.luxons.sevenwonders.model.resources.Provider.RIGHT_PLAYER -import org.luxons.sevenwonders.model.resources.ResourceType.* -import org.luxons.sevenwonders.model.resources.noTransactions -import kotlin.test.assertEquals - -class BestPriceCalculatorTest { - - private fun solutions(price: Int, vararg resourceTransactions: PricedResourceTransactions) = - TransactionPlan(price, setOf(*resourceTransactions)) - - @Test - fun bestPrice_0forEmptyResources() { - val table = testTable(3) - val player0 = SimplePlayer(0, table) - val emptyResources = emptyResources() - val emptyTransactions = noTransactions() - assertEquals(solutions(0, emptyTransactions), bestSolution(emptyResources, player0)) - } - - @Test - fun bestPrice_fixedResources_defaultCost() { - val left = testBoard(STONE) - val main = testBoard(STONE) - val right = testBoard(WOOD) - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(STONE, STONE) - - val stoneLeftSingle = createPricedTransaction(LEFT_PLAYER, 2, STONE) - val stoneRightSingle = createPricedTransaction(RIGHT_PLAYER, 2, STONE) - - val stoneLeft = createPricedTransactions(stoneLeftSingle) - val stoneRight = createPricedTransactions(stoneRightSingle) - val stoneLeftAndRight = createPricedTransactions(stoneLeftSingle, stoneRightSingle) - - assertEquals(solutions(2, stoneLeft), bestSolution(resources, player0)) - assertEquals(solutions(4, stoneLeftAndRight), bestSolution(resources, player1)) - assertEquals(solutions(2, stoneRight), bestSolution(resources, player2)) - } - - @Test - fun bestPrice_fixedResources_overridenCost() { - val main = testBoard(STONE) - main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) - - val left = testBoard(WOOD) - val right = testBoard(WOOD) - val opposite = testBoard(GLASS) - val table = Table(listOf(main, right, opposite, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - val player3 = SimplePlayer(3, table) - - val resources = resourcesOf(WOOD) - - val woodLeft = createPricedTransactions(LEFT_PLAYER, 2, WOOD) - val woodRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, WOOD) - val woodRight = createPricedTransactions(RIGHT_PLAYER, 2, WOOD) - - assertEquals(solutions(1, woodRightDiscounted), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(2, woodLeft, woodRight), bestSolution(resources, player2)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player3)) - } - - @Test - fun bestPrice_mixedResources_overridenCost() { - val left = testBoard(WOOD) - - val main = testBoard(STONE) - main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) - - val right = testBoard(ORE) - right.production.addChoice(WOOD, CLAY) - right.publicProduction.addChoice(WOOD, CLAY) - - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(WOOD) - val woodRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, WOOD) - - assertEquals(solutions(1, woodRightDiscounted), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player2)) - } - - @Test - fun bestPrice_mixedResources_overridenCost_sameMultipleTimes() { - val left = testBoard(WOOD) - - val main = testBoard(WOOD) - main.tradingRules.setCost(CLAY, LEFT_PLAYER, 1) - main.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) - - main.publicProduction.addFixedResource(STONE, 1) - main.publicProduction.addChoice(CLAY, ORE) - main.production.addFixedResource(STONE, 1) - main.production.addChoice(CLAY, ORE) - - val right = testBoard(CLAY) - right.publicProduction.addFixedResource(ORE, 1) - right.publicProduction.addFixedResource(CLAY, 1) - right.publicProduction.addFixedResource(WOOD, 2) - right.production.addFixedResource(ORE, 1) - right.production.addFixedResource(CLAY, 1) - right.production.addFixedResource(WOOD, 2) - - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(WOOD, CLAY, CLAY, CLAY) - - val claysRightDiscounted = createPricedTransaction(RIGHT_PLAYER, 2, CLAY, CLAY) - val claysLeft = createPricedTransaction(LEFT_PLAYER, 4, CLAY, CLAY) - val clayLeft = createPricedTransaction(LEFT_PLAYER, 2, CLAY) - val clayRight = createPricedTransaction(RIGHT_PLAYER, 2, CLAY) - - assertEquals(solutions(2, createPricedTransactions(claysRightDiscounted)), bestSolution(resources, player0)) - assertEquals(solutions(2, createPricedTransactions(clayLeft)), bestSolution(resources, player1)) - assertEquals(solutions(6, createPricedTransactions(claysLeft, clayRight)), bestSolution(resources, player2)) - } - - @Test - fun bestPrice_chooseCheapest() { - val left = testBoard(WOOD) - - val main = testBoard(WOOD) - main.production.addChoice(CLAY, ORE) - main.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) - - val right = testBoard(WOOD) - right.production.addFixedResource(ORE, 1) - right.production.addFixedResource(CLAY, 1) - right.publicProduction.addFixedResource(ORE, 1) - right.publicProduction.addFixedResource(CLAY, 1) - - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(ORE, CLAY) - val oreAndClayLeft = createPricedTransactions(LEFT_PLAYER, 4, ORE, CLAY) - val clayRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, CLAY) - - assertEquals(solutions(1, clayRightDiscounted), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(4, oreAndClayLeft), bestSolution(resources, player2)) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculatorTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculatorTest.kt new file mode 100644 index 00000000..da4b608e --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TransactionOptionsCalculatorTest.kt @@ -0,0 +1,201 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Test +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.test.createPricedTransaction +import org.luxons.sevenwonders.engine.test.createPricedTransactions +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testTable +import org.luxons.sevenwonders.model.resources.Provider.LEFT_PLAYER +import org.luxons.sevenwonders.model.resources.Provider.RIGHT_PLAYER +import org.luxons.sevenwonders.model.resources.ResourceType.* +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.model.resources.singleOptionNoTransactionNeeded +import kotlin.test.assertEquals + +class TransactionOptionsCalculatorTest { + + @Test + fun transactionOptions_noResourcesRequired_nothingToBuy() { + val table = testTable(3) + val player0 = SimplePlayer(0, table) + val emptyResources = emptyResources() + assertEquals(singleOptionNoTransactionNeeded(), transactionOptions(emptyResources, player0)) + } + + @Test + fun transactionOptions_initialResources_defaultCost() { + val board2 = testBoard(STONE) + val board0 = testBoard(STONE) + val board1 = testBoard(WOOD) + val table = Table(listOf(board0, board1, board2)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(STONE, STONE) + + val stoneLeftSingle = createPricedTransaction(LEFT_PLAYER, 2, STONE) + val stoneRightSingle = createPricedTransaction(RIGHT_PLAYER, 2, STONE) + + val stoneLeft = createPricedTransactions(stoneLeftSingle) + val stoneRight = createPricedTransactions(stoneRightSingle) + val stoneLeftAndRight = createPricedTransactions(stoneLeftSingle, stoneRightSingle) + + assertEquals(listOf(stoneLeft), transactionOptions(resources, player0)) + assertEquals(listOf(stoneLeftAndRight), transactionOptions(resources, player1)) + assertEquals(listOf(stoneRight), transactionOptions(resources, player2)) + } + + @Test + fun transactionOptions_fixedResources_defaultCost() { + val board2 = testBoard(WOOD) + + val board0 = testBoard(STONE) + board0.publicProduction.addFixedResource(CLAY, 1) + board0.production.addFixedResource(CLAY, 1) + + val board1 = testBoard(ORE) + + val table = Table(listOf(board0, board1, board2)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(STONE, CLAY) + + val stoneAndClayLeft = createPricedTransaction(LEFT_PLAYER, 4, STONE, CLAY) + val stoneAndClayRight = createPricedTransaction(RIGHT_PLAYER, 4, STONE, CLAY) + + assertEquals(singleOptionNoTransactionNeeded(), transactionOptions(resources, player0)) + assertEquals(listOf(createPricedTransactions(stoneAndClayLeft)), transactionOptions(resources, player1)) + assertEquals(listOf(createPricedTransactions(stoneAndClayRight)), transactionOptions(resources, player2)) + } + + @Test + fun transactionOptions_fixedResources_overridenCost() { + val board0 = testBoard(STONE) + board0.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) + + val board1 = testBoard(WOOD) + val board2 = testBoard(GLASS) + val board3 = testBoard(WOOD) + + val table = Table(listOf(board0, board1, board2, board3)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + val player3 = SimplePlayer(3, table) + + val resources = resourcesOf(WOOD) + + val woodLeft = createPricedTransactions(LEFT_PLAYER, 2, WOOD) + val woodRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, WOOD) + val woodRight = createPricedTransactions(RIGHT_PLAYER, 2, WOOD) + + assertEquals(listOf(woodRightDiscounted, woodLeft), transactionOptions(resources, player0)) + assertEquals(listOf(noTransactions()), transactionOptions(resources, player1)) + assertEquals(listOf(woodLeft, woodRight), transactionOptions(resources, player2)) + assertEquals(listOf(noTransactions()), transactionOptions(resources, player3)) + } + + @Test + fun transactionOptions_mixedResources_overridenCost() { + val board0 = testBoard(STONE) + board0.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) + + val board1 = testBoard(ORE) + board1.production.addChoice(WOOD, CLAY) + board1.publicProduction.addChoice(WOOD, CLAY) + + val board2 = testBoard(WOOD) + + val table = Table(listOf(board0, board1, board2)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(WOOD) + + val woodRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, WOOD) + val woodLeft = createPricedTransactions(LEFT_PLAYER, 2, WOOD) + + assertEquals(listOf(woodRightDiscounted, woodLeft), transactionOptions(resources, player0)) + assertEquals(listOf(noTransactions()), transactionOptions(resources, player1)) + assertEquals(listOf(noTransactions()), transactionOptions(resources, player2)) + } + + @Test + fun transactionOptions_mixedResources_overridenCost_sameMultipleTimes() { + val board0 = testBoard(WOOD) + board0.tradingRules.setCost(CLAY, LEFT_PLAYER, 1) + board0.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) + + board0.publicProduction.addFixedResource(STONE, 1) + board0.publicProduction.addChoice(CLAY, ORE) + board0.production.addFixedResource(STONE, 1) + board0.production.addChoice(CLAY, ORE) + + val board1 = testBoard(CLAY) + board1.publicProduction.addFixedResource(ORE, 1) + board1.publicProduction.addFixedResource(CLAY, 1) + board1.publicProduction.addFixedResource(WOOD, 2) + board1.production.addFixedResource(ORE, 1) + board1.production.addFixedResource(CLAY, 1) + board1.production.addFixedResource(WOOD, 2) + + val board2 = testBoard(WOOD) + + val table = Table(listOf(board0, board1, board2)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(WOOD, CLAY, CLAY, CLAY) + + val claysRightDiscounted = createPricedTransaction(RIGHT_PLAYER, 2, CLAY, CLAY) + val claysLeft = createPricedTransaction(LEFT_PLAYER, 4, CLAY, CLAY) + val clayLeft = createPricedTransaction(LEFT_PLAYER, 2, CLAY) + val clayRight = createPricedTransaction(RIGHT_PLAYER, 2, CLAY) + + assertEquals(listOf(createPricedTransactions(claysRightDiscounted)), transactionOptions(resources, player0)) + assertEquals(listOf(createPricedTransactions(clayLeft)), transactionOptions(resources, player1)) + assertEquals(listOf(createPricedTransactions(claysLeft, clayRight)), transactionOptions(resources, player2)) + } + + @Test + fun transactionOptions_cheapestFirst() { + val board0 = testBoard(WOOD) + board0.production.addChoice(CLAY, ORE) + board0.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) + + val board1 = testBoard(WOOD) + board1.production.addFixedResource(ORE, 1) + board1.production.addFixedResource(CLAY, 1) + board1.publicProduction.addFixedResource(ORE, 1) + board1.publicProduction.addFixedResource(CLAY, 1) + + val board2 = testBoard(WOOD) + + val table = Table(listOf(board0, board1, board2)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(ORE, CLAY) + + val clayRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, CLAY) + val oreAndClayLeft = createPricedTransactions(LEFT_PLAYER, 4, ORE, CLAY) + + assertEquals(listOf(clayRightDiscounted), transactionOptions(resources, player0)) + assertEquals(listOf(noTransactions()), transactionOptions(resources, player1)) + assertEquals(listOf(oreAndClayLeft), transactionOptions(resources, player2)) + } +} |