From 719fcf03862174be1454731c59128d278e9523be Mon Sep 17 00:00:00 2001 From: joffrey-bion Date: Fri, 27 Nov 2020 01:25:09 +0100 Subject: Introduce priced transactions This is useful to provide information about the price per provider. --- .../engine/cards/RequirementsSatisfaction.kt | 8 ++-- .../engine/resources/BestPriceCalculator.kt | 26 ++++++------ .../engine/resources/ResourceTransactions.kt | 9 ++-- .../sevenwonders/engine/cards/RequirementsTest.kt | 10 ++--- .../engine/resources/BestPriceCalculatorTest.kt | 49 +++++++++++----------- .../engine/resources/ResourceTransactionsTest.kt | 16 ++++--- .../luxons/sevenwonders/engine/test/TestUtils.kt | 22 ++++++---- 7 files changed, 76 insertions(+), 64 deletions(-) (limited to 'sw-engine/src') 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 3a1ac5ce..f9566981 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,14 +1,14 @@ package org.luxons.sevenwonders.engine.cards import org.luxons.sevenwonders.model.cards.PlayabilityLevel -import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.PricedResourceTransactions import org.luxons.sevenwonders.model.resources.noTransactions internal data class RequirementsSatisfaction( val satisfied: Boolean, val level: PlayabilityLevel, val minPrice: Int, - val cheapestTransactions: Set, + val cheapestTransactions: Set, ) { companion object { @@ -24,13 +24,13 @@ internal data class RequirementsSatisfaction( internal fun enoughResourcesAndGold(minPrice: Int) = RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD_AND_RES, minPrice, setOf(noTransactions())) - internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set) = + internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set) = RequirementsSatisfaction(true, PlayabilityLevel.REQUIRES_HELP, minPrice, cheapestTransactions) internal fun missingRequiredGold(minPrice: Int) = RequirementsSatisfaction(false, PlayabilityLevel.MISSING_REQUIRED_GOLD, minPrice, emptySet()) - internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set) = + internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set) = RequirementsSatisfaction(false, PlayabilityLevel.MISSING_GOLD_FOR_RES, minPrice, cheapestTransactions) internal fun unavailableResources() = 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/BestPriceCalculator.kt index 846e7fd2..967bee2c 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/BestPriceCalculator.kt @@ -1,16 +1,15 @@ 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.ResourceTransactions import org.luxons.sevenwonders.model.resources.ResourceType -import java.util.EnumMap -import java.util.EnumSet +import java.util.* internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = BestPriceCalculator(resources, player).computeBestSolution() -data class TransactionPlan(val price: Int, val possibleTransactions: Set) +data class TransactionPlan(val price: Int, val possibleTransactions: Set) private class ResourcePool( val provider: Provider?, @@ -27,9 +26,10 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { private val pools: List private val resourcesLeftToPay: MutableResources private val boughtResources: MutableMap = EnumMap(Provider::class.java) - private var pricePaid: Int = 0 + private val pricePaid: MutableMap = EnumMap(Provider::class.java) + private var totalPricePaid: Int = 0 - private var bestSolutions: MutableSet = mutableSetOf() + private var bestSolutions: MutableSet = mutableSetOf() private var bestPrice: Int = Integer.MAX_VALUE init { @@ -93,11 +93,13 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { fun buyOne(provider: Provider, type: ResourceType, cost: Int) { boughtResources.getOrPut(provider) { MutableResources() }.add(type, 1) - pricePaid += cost + pricePaid.merge(provider, cost) { old, new -> old + new } + totalPricePaid += cost } fun unbuyOne(provider: Provider, type: ResourceType, cost: Int) { - pricePaid -= cost + totalPricePaid -= cost + pricePaid.merge(provider, -cost) { old, new -> old + new } boughtResources[provider]!!.remove(type, 1) } @@ -113,14 +115,14 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { } private fun updateBestSolutionIfNeeded() { - if (pricePaid > bestPrice) return + if (totalPricePaid > bestPrice) return - if (pricePaid < bestPrice) { - bestPrice = pricePaid + if (totalPricePaid < bestPrice) { + bestPrice = totalPricePaid bestSolutions.clear() } // avoid mutating the resources from the transactions - val transactionSet = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions() + val transactionSet = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions(pricePaid) bestSolutions.add(transactionSet) } } diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt index c3e0fefa..c6309151 100644 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt @@ -2,14 +2,11 @@ package org.luxons.sevenwonders.engine.resources import org.luxons.sevenwonders.engine.Player import org.luxons.sevenwonders.engine.converters.toCountedResourcesList -import org.luxons.sevenwonders.model.resources.CountedResource -import org.luxons.sevenwonders.model.resources.Provider -import org.luxons.sevenwonders.model.resources.ResourceTransaction -import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.* -fun Map.toTransactions(): ResourceTransactions = // +fun Map.toTransactions(price: Map): PricedResourceTransactions = // filterValues { !it.isEmpty() } // - .map { (p, res) -> ResourceTransaction(p, res.toCountedResourcesList()) } // + .map { (p, res) -> PricedResourceTransaction(p, res.toCountedResourcesList(), price[p]!!) } // .toSet() fun ResourceTransactions.asResources(): Resources = flatMap { it.resources }.asResources() 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 d2ef7930..d3fdc94c 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 @@ -9,8 +9,8 @@ import org.junit.runner.RunWith import org.luxons.sevenwonders.engine.SimplePlayer import org.luxons.sevenwonders.engine.boards.Table import org.luxons.sevenwonders.engine.resources.emptyResources +import org.luxons.sevenwonders.engine.test.createPricedTransactions import org.luxons.sevenwonders.engine.test.createRequirements -import org.luxons.sevenwonders.engine.test.createTransactions import org.luxons.sevenwonders.engine.test.singleBoardPlayer import org.luxons.sevenwonders.engine.test.testBoard import org.luxons.sevenwonders.model.resources.Provider @@ -110,7 +110,7 @@ class RequirementsTest { val table = Table(listOf(board, neighbourBoard)) val player = SimplePlayer(0, table) - val resources = createTransactions(Provider.RIGHT_PLAYER, boughtResource) + val resources = createPricedTransactions(Provider.RIGHT_PLAYER, 2, boughtResource) val neighbourHasResource = boughtResource == requiredResource assertEquals(neighbourHasResource, requirements.areMetWithHelpBy(board, resources)) @@ -118,8 +118,8 @@ class RequirementsTest { val satisfaction = requirements.assess(player) if (neighbourHasResource) { val transactions = setOf( - createTransactions(Provider.LEFT_PLAYER, requiredResource), - createTransactions(Provider.RIGHT_PLAYER, requiredResource), + createPricedTransactions(Provider.LEFT_PLAYER, 2, requiredResource), + createPricedTransactions(Provider.RIGHT_PLAYER, 2, requiredResource), ) assertEquals(RequirementsSatisfaction.metWithHelp(2, transactions), satisfaction) } else { @@ -138,7 +138,7 @@ class RequirementsTest { val table = Table(listOf(board, neighbourBoard)) val player = SimplePlayer(0, table) - val transactions = createTransactions(Provider.RIGHT_PLAYER, requiredResource) + val transactions = createPricedTransactions(Provider.RIGHT_PLAYER, 2, requiredResource) assertTrue(requirements.areMetWithHelpBy(board, transactions)) assertTrue(requirements.assess(player).satisfied) 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 index 36e4046f..1a56caf4 100644 --- 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 @@ -3,20 +3,20 @@ 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.createTransaction -import org.luxons.sevenwonders.engine.test.createTransactions +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.ResourceTransactions 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: ResourceTransactions) = + private fun solutions(price: Int, vararg resourceTransactions: PricedResourceTransactions) = TransactionPlan(price, setOf(*resourceTransactions)) @Test @@ -41,12 +41,12 @@ class BestPriceCalculatorTest { val resources = resourcesOf(STONE, STONE) - val stoneLeftSingle = createTransaction(LEFT_PLAYER, STONE) - val stoneRightSingle = createTransaction(RIGHT_PLAYER, STONE) + val stoneLeftSingle = createPricedTransaction(LEFT_PLAYER, 2, STONE) + val stoneRightSingle = createPricedTransaction(RIGHT_PLAYER, 2, STONE) - val stoneLeft = createTransactions(stoneLeftSingle) - val stoneRight = createTransactions(stoneRightSingle) - val stoneLeftAndRight = createTransactions(stoneLeftSingle, stoneRightSingle) + 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)) @@ -70,10 +70,11 @@ class BestPriceCalculatorTest { val resources = resourcesOf(WOOD) - val woodLeft = createTransactions(LEFT_PLAYER, WOOD) - val woodRight = createTransactions(RIGHT_PLAYER, 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, woodRight), bestSolution(resources, player0)) + 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)) @@ -97,9 +98,9 @@ class BestPriceCalculatorTest { val player2 = SimplePlayer(2, table) val resources = resourcesOf(WOOD) - val woodRight = createTransactions(RIGHT_PLAYER, WOOD) + val woodRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, WOOD) - assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) + assertEquals(solutions(1, woodRightDiscounted), bestSolution(resources, player0)) assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) assertEquals(solutions(0, noTransactions()), bestSolution(resources, player2)) } @@ -133,14 +134,14 @@ class BestPriceCalculatorTest { val resources = resourcesOf(WOOD, CLAY, CLAY, CLAY) - val claysRight = createTransaction(RIGHT_PLAYER, CLAY, CLAY) - val claysLeft = createTransaction(LEFT_PLAYER, CLAY, CLAY) - val clayLeft = createTransaction(LEFT_PLAYER, CLAY) - val clayRight = createTransaction(RIGHT_PLAYER, 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, createTransactions(claysRight)), bestSolution(resources, player0)) - assertEquals(solutions(2, createTransactions(clayLeft)), bestSolution(resources, player1)) - assertEquals(solutions(6, createTransactions(claysLeft, clayRight)), bestSolution(resources, player2)) + 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 @@ -164,10 +165,10 @@ class BestPriceCalculatorTest { val player2 = SimplePlayer(2, table) val resources = resourcesOf(ORE, CLAY) - val oreAndClayLeft = createTransactions(LEFT_PLAYER, ORE, CLAY) - val clayRight = createTransactions(RIGHT_PLAYER, CLAY) + val oreAndClayLeft = createPricedTransactions(LEFT_PLAYER, 4, ORE, CLAY) + val clayRightDiscounted = createPricedTransactions(RIGHT_PLAYER, 1, CLAY) - assertEquals(solutions(1, clayRight), bestSolution(resources, player0)) + 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/ResourceTransactionsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt index 7b08be9a..440db729 100644 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt @@ -1,7 +1,8 @@ package org.luxons.sevenwonders.engine.resources import org.junit.Test -import org.luxons.sevenwonders.engine.test.createTransaction +import org.luxons.sevenwonders.engine.test.createPricedTransaction +import org.luxons.sevenwonders.engine.test.createPricedTransactions import org.luxons.sevenwonders.model.resources.Provider import org.luxons.sevenwonders.model.resources.ResourceType import org.luxons.sevenwonders.model.resources.ResourceType.CLAY @@ -16,13 +17,16 @@ class ResourceTransactionsTest { Provider.LEFT_PLAYER to (1 of WOOD) + (1 of CLAY), Provider.RIGHT_PLAYER to (1 of WOOD), ) - - val expectedNormalized = setOf( - createTransaction(Provider.LEFT_PLAYER, WOOD, CLAY), - createTransaction(Provider.RIGHT_PLAYER, WOOD), + val priceMap = mapOf( + Provider.LEFT_PLAYER to 4, + Provider.RIGHT_PLAYER to 2, ) - assertEquals(expectedNormalized, transactionMap.toTransactions().toSet()) + val expectedNormalized = createPricedTransactions( + createPricedTransaction(Provider.LEFT_PLAYER, 4, WOOD, CLAY), + createPricedTransaction(Provider.RIGHT_PLAYER, 2, WOOD), + ) + assertEquals(expectedNormalized, transactionMap.toTransactions(priceMap)) } private infix fun Int.of(type: ResourceType): Resources = resourcesOf(type to this) diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt index a7ed406b..0e8800f3 100644 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt @@ -22,12 +22,7 @@ import org.luxons.sevenwonders.model.Settings import org.luxons.sevenwonders.model.boards.RelativeBoardPosition import org.luxons.sevenwonders.model.cards.CardBack import org.luxons.sevenwonders.model.cards.Color -import org.luxons.sevenwonders.model.resources.CountedResource -import org.luxons.sevenwonders.model.resources.Provider -import org.luxons.sevenwonders.model.resources.ResourceTransaction -import org.luxons.sevenwonders.model.resources.ResourceTransactions -import org.luxons.sevenwonders.model.resources.ResourceType -import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.model.resources.* internal const val SEED: Long = 42 @@ -70,7 +65,20 @@ internal fun createTransactions(provider: Provider, vararg resources: ResourceTy internal fun createTransactions(vararg transactions: ResourceTransaction): ResourceTransactions = transactions.toSet() internal fun createTransaction(provider: Provider, vararg resources: ResourceType): ResourceTransaction = - ResourceTransaction(provider, resources.groupBy { it }.map { (type, reps) -> CountedResource(reps.size, type) }) + createPricedTransaction(provider, 0, *resources) + +internal fun createPricedTransactions(vararg transactions: PricedResourceTransaction): PricedResourceTransactions = + transactions.toSet() + +internal fun createPricedTransactions(provider: Provider, price: Int, vararg resources: ResourceType): PricedResourceTransactions = + createPricedTransactions(createPricedTransaction(provider, price, *resources)) + +internal fun createPricedTransaction(provider: Provider, price: Int, vararg resources: ResourceType) = + PricedResourceTransaction( + provider = provider, + resources = resources.groupBy { it }.map { (type, reps) -> CountedResource(reps.size, type) }, + totalPrice = price, + ) internal fun createRequirements(vararg types: ResourceType): Requirements = Requirements(resources = resourcesOf(*types)) -- cgit