diff options
27 files changed, 544 insertions, 696 deletions
diff --git a/backend/build.gradle b/backend/build.gradle index 6c733559..af5ed8e2 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -65,7 +65,7 @@ dependencies { annotationProcessor 'org.hildan.livedoc:livedoc-javadoc-processor:4.3.2' testCompile 'org.springframework.boot:spring-boot-starter-test' - testCompile 'org.hildan.jackstomp:jackstomp:1.1.0' + testCompile 'org.hildan.jackstomp:jackstomp:2.0.0' checkstyleConfig 'org.hildan.checkstyle:checkstyle-config:2.1.0' } diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt index bebb3233..743e3d1a 100644 --- a/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt +++ b/backend/src/main/kotlin/org/luxons/sevenwonders/config/WebSocketConfig.kt @@ -10,6 +10,8 @@ import org.springframework.web.socket.config.annotation.StompEndpointRegistry import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer import org.springframework.web.socket.server.support.DefaultHandshakeHandler +const val SEVEN_WONDERS_WS_ENDPOINT = "/seven-wonders-websocket" + @Configuration @EnableWebSocketMessageBroker class WebSocketConfig @Autowired constructor(private val topicSubscriptionInterceptor: TopicSubscriptionInterceptor) : @@ -25,7 +27,7 @@ class WebSocketConfig @Autowired constructor(private val topicSubscriptionInterc } override fun registerStompEndpoints(registry: StompEndpointRegistry) { - registry.addEndpoint("/seven-wonders-websocket") + registry.addEndpoint(SEVEN_WONDERS_WS_ENDPOINT) .setHandshakeHandler(handshakeHandler()) .setAllowedOrigins("http://localhost:3000") // to allow frontend server proxy requests in dev mode .withSockJS() diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt index d98e932e..472e5529 100644 --- a/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt +++ b/backend/src/test/kotlin/org/luxons/sevenwonders/test/api/SevenWondersClient.kt @@ -1,25 +1,36 @@ package org.luxons.sevenwonders.test.api +import com.fasterxml.jackson.databind.module.SimpleModule +import org.hildan.jackstomp.JackstompClient +import org.luxons.sevenwonders.config.SEVEN_WONDERS_WS_ENDPOINT +import org.luxons.sevenwonders.game.resources.MutableResources +import org.luxons.sevenwonders.game.resources.Resources +import org.springframework.messaging.converter.MappingJackson2MessageConverter import java.util.concurrent.ExecutionException import java.util.concurrent.TimeoutException -import org.hildan.jackstomp.JackstompClient -import org.hildan.jackstomp.JackstompSession +class SevenWondersClient { + + private val client: JackstompClient -class SevenWondersClient @JvmOverloads constructor(private val client: JackstompClient = JackstompClient()) { + init { + val customMappingsModule = SimpleModule("ConcreteResourcesDeserializationModule") + customMappingsModule.addAbstractTypeMapping(Resources::class.java, MutableResources::class.java) + + val mappingJackson2MessageConverter = MappingJackson2MessageConverter() + mappingJackson2MessageConverter.objectMapper.registerModule(customMappingsModule) + + client = JackstompClient() + client.webSocketClient.messageConverter = mappingJackson2MessageConverter + } @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class) fun connect(serverUrl: String): SevenWondersSession { - val session = client.connect(serverUrl + WEBSOCKET_ENDPOINT) + val session = client.syncConnect(serverUrl + SEVEN_WONDERS_WS_ENDPOINT) return SevenWondersSession(session) } fun stop() { client.stop() } - - companion object { - - private val WEBSOCKET_ENDPOINT = "/seven-wonders-websocket" - } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt index 6a38965f..ad50a1c6 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt @@ -4,11 +4,14 @@ import org.luxons.sevenwonders.game.Player import org.luxons.sevenwonders.game.boards.Board import org.luxons.sevenwonders.game.resources.ResourceTransactions import org.luxons.sevenwonders.game.resources.Resources -import org.luxons.sevenwonders.game.resources.bestPrice +import org.luxons.sevenwonders.game.resources.asResources +import org.luxons.sevenwonders.game.resources.bestSolution +import org.luxons.sevenwonders.game.resources.emptyResources +import org.luxons.sevenwonders.game.resources.execute data class Requirements internal constructor( val gold: Int = 0, - val resources: Resources = Resources() + val resources: Resources = emptyResources() ) { /** * Returns whether the given [board] meets these requirements on its own. @@ -52,8 +55,8 @@ data class Requirements internal constructor( if (producesRequiredResources(board)) { return true } - val bestPrice = bestPrice(resources, player) - return bestPrice != null && bestPrice <= board.gold - gold + val solution = bestSolution(resources, player) + return !solution.possibleTransactions.isEmpty() && solution.price <= board.gold - gold } private fun hasRequiredGold(board: Board): Boolean { @@ -71,7 +74,7 @@ data class Requirements internal constructor( private fun producesRequiredResourcesWithHelp(board: Board, transactions: ResourceTransactions): Boolean { val totalBoughtResources = transactions.asResources() - val remainingResources = this.resources.minus(totalBoughtResources) + val remainingResources = resources - totalBoughtResources return board.production.contains(remainingResources) } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt index 06b46bb2..b766dd31 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt @@ -15,10 +15,10 @@ import java.lang.reflect.Type internal class ProductionSerializer : JsonSerializer<Production>, JsonDeserializer<Production> { override fun serialize(production: Production, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val fixedResources = production.fixedResources + val fixedResources = production.getFixedResources() val choices = production.getAlternativeResources() return when { - fixedResources.isEmpty -> serializeAsChoice(choices, context) + fixedResources.isEmpty() -> serializeAsChoice(choices, context) choices.isEmpty() -> serializeAsResources(fixedResources, context) else -> throw IllegalArgumentException("Cannot serialize a production with mixed fixed resources and choices") } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt index c6c6a962..fcf66d79 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt @@ -10,22 +10,19 @@ import com.google.gson.JsonSerializationContext import com.google.gson.JsonSerializer import org.luxons.sevenwonders.game.resources.ResourceType import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.toResources import java.lang.reflect.Type internal class ResourcesSerializer : JsonSerializer<Resources>, JsonDeserializer<Resources> { override fun serialize(resources: Resources, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val s = resources.asList().map { it.symbol }.joinToString("") + val s = resources.toList().map { it.symbol }.joinToString("") return if (s.isEmpty()) JsonNull.INSTANCE else JsonPrimitive(s) } @Throws(JsonParseException::class) override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Resources { val s = json.asString - val resources = Resources() - for (c in s.toCharArray()) { - resources.add(ResourceType.fromSymbol(c), 1) - } - return resources + return s.toCharArray().map { ResourceType.fromSymbol(it) }.toResources() } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt index ce50e3e0..ed982abb 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt @@ -14,7 +14,7 @@ abstract class Move internal constructor( val type: MoveType = move.type // TODO restore visibility to public - internal val transactions: ResourceTransactions = ResourceTransactions(move.transactions) + internal val transactions: ResourceTransactions = move.transactions internal abstract fun place(discardedCards: MutableList<Card>, settings: Settings) diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt index 3b86fd97..4159cea3 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt @@ -4,20 +4,10 @@ import org.luxons.sevenwonders.game.Player import java.util.ArrayList import java.util.EnumSet -internal fun bestPrice(resources: Resources, player: Player): Int? { - return bestSolution(resources, player)?.price -} - -internal fun bestTransaction(resources: Resources, player: Player): ResourceTransactions? { - return bestSolution(resources, player)?.transactions -} +internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = + BestPriceCalculator(resources, player).computeBestSolution() -internal fun bestSolution(resources: Resources, player: Player): TransactionPlan? { - val calculator = BestPriceCalculator(resources, player) - return calculator.computeBestSolution() -} - -internal data class TransactionPlan(val price: Int, val transactions: ResourceTransactions) +internal data class TransactionPlan(val price: Int, val possibleTransactions: Set<ResourceTransactions>) private class ResourcePool( val provider: Provider?, @@ -30,16 +20,16 @@ private class ResourcePool( private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { private val pools: List<ResourcePool> - private val resourcesLeftToPay: Resources - private val boughtResources: ResourceTransactions = ResourceTransactions() + private val resourcesLeftToPay: MutableResources + private val boughtResources: MutableMap<Provider, MutableResources> = HashMap() private var pricePaid: Int = 0 - var bestSolution: ResourceTransactions? = null - var bestPrice: Int = Integer.MAX_VALUE + private var bestSolutions: MutableSet<ResourceTransactions> = mutableSetOf() + private var bestPrice: Int = Integer.MAX_VALUE init { val board = player.board - this.resourcesLeftToPay = resourcesToPay.minus(board.production.fixedResources) + this.resourcesLeftToPay = resourcesToPay.minus(board.production.getFixedResources()).toMutableResources() this.pools = createResourcePools(player) } @@ -56,24 +46,25 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { for (provider in providers) { val providerBoard = player.getBoard(provider.boardPosition) - val pool = ResourcePool(provider, rules, providerBoard.publicProduction.asChoices().map { it.toMutableSet() }.toSet()) + val choices = providerBoard.publicProduction.asChoices().map { it.toMutableSet() }.toSet() + val pool = ResourcePool(provider, rules, choices) pools.add(pool) } return pools } - fun computeBestSolution(): TransactionPlan? { + fun computeBestSolution(): TransactionPlan { computePossibilities() - return if (bestSolution == null) null else TransactionPlan(bestPrice, bestSolution!!) + return TransactionPlan(bestPrice, bestSolutions) } private fun computePossibilities() { - if (resourcesLeftToPay.isEmpty) { + if (resourcesLeftToPay.isEmpty()) { updateBestSolutionIfNeeded() return } for (type in ResourceType.values()) { - if (resourcesLeftToPay.getQuantity(type) > 0) { + if (resourcesLeftToPay[type] > 0) { for (pool in pools) { if (pool.provider == null) { computeSelfPossibilities(type, pool) @@ -94,14 +85,22 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { private fun computeNeighbourPossibilities(pool: ResourcePool, type: ResourceType, provider: Provider) { val cost = pool.getCost(type) resourcesLeftToPay.remove(type, 1) - boughtResources.add(provider, Resources(type)) - pricePaid += cost + buyOne(provider, type, cost) computePossibilitiesWhenUsing(type, pool) - pricePaid -= cost - boughtResources.remove(provider, Resources(type)) + unbuyOne(provider, type, cost) resourcesLeftToPay.add(type, 1) } + fun buyOne(provider: Provider, type: ResourceType, cost: Int) { + boughtResources.getOrPut(provider) { MutableResources() }.add(type, 1) + pricePaid += cost + } + + fun unbuyOne(provider: Provider, type: ResourceType, cost: Int) { + pricePaid -= cost + boughtResources.get(provider)!!.remove(type, 1) + } + private fun computePossibilitiesWhenUsing(type: ResourceType, pool: ResourcePool) { for (choice in pool.choices) { if (choice.contains(type)) { @@ -114,9 +113,13 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { } private fun updateBestSolutionIfNeeded() { + if (pricePaid > bestPrice) return + if (pricePaid < bestPrice) { bestPrice = pricePaid - bestSolution = ResourceTransactions(boughtResources.asList()) + bestSolutions.clear() } + // avoid mutating the resources from the transactions + bestSolutions.add(boughtResources.mapValues { MutableResources(HashMap(it.value.quantities)) }.toTransactions()) } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt index 2dd6d60f..0b069677 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt @@ -3,35 +3,30 @@ package org.luxons.sevenwonders.game.resources import java.util.Arrays import java.util.EnumSet -class Production internal constructor() { - - val fixedResources = Resources() +data class Production internal constructor( + private val fixedResources: MutableResources = mutableResourcesOf(), private val alternativeResources: MutableSet<Set<ResourceType>> = mutableSetOf() +) { + fun getFixedResources(): Resources = fixedResources - fun getAlternativeResources(): Set<Set<ResourceType>> { - return alternativeResources - } + fun getAlternativeResources(): Set<Set<ResourceType>> = alternativeResources - fun addFixedResource(type: ResourceType, quantity: Int) { - fixedResources.add(type, quantity) - } + fun addFixedResource(type: ResourceType, quantity: Int) = fixedResources.add(type, quantity) fun addChoice(vararg options: ResourceType) { val optionSet = EnumSet.copyOf(Arrays.asList(*options)) alternativeResources.add(optionSet) } - fun addAll(resources: Resources) { - fixedResources.addAll(resources) - } + fun addAll(resources: Resources) = fixedResources.add(resources) fun addAll(production: Production) { - fixedResources.addAll(production.fixedResources) + fixedResources.add(production.fixedResources) alternativeResources.addAll(production.getAlternativeResources()) } internal fun asChoices(): Set<Set<ResourceType>> { - val fixedAsChoices = fixedResources.asList().map{ EnumSet.of(it) }.toSet() + val fixedAsChoices = fixedResources.toList().map { EnumSet.of(it) }.toSet() return fixedAsChoices + alternativeResources } @@ -42,20 +37,22 @@ class Production internal constructor() { return containedInAlternatives(resources - fixedResources) } - private fun containedInAlternatives(resources: Resources): Boolean { - return containedInAlternatives(resources, alternativeResources) - } + private fun containedInAlternatives(resources: Resources): Boolean = + containedInAlternatives(resources.toMutableResources(), alternativeResources) - private fun containedInAlternatives(resources: Resources, alternatives: MutableSet<Set<ResourceType>>): Boolean { - if (resources.isEmpty) { + private fun containedInAlternatives( + resources: MutableResources, + alternatives: MutableSet<Set<ResourceType>> + ): Boolean { + if (resources.isEmpty()) { return true } for (type in ResourceType.values()) { - if (resources.getQuantity(type) <= 0) { + if (resources[type] <= 0) { continue } - val candidate = findFirstAlternativeContaining(alternatives, type) - ?: return false // no alternative produces the resource of this entry + // return if no alternative produces the resource of this entry + val candidate = alternatives.firstOrNull { a -> a.contains(type) } ?: return false resources.remove(type, 1) alternatives.remove(candidate) val remainingAreContainedToo = containedInAlternatives(resources, alternatives) @@ -67,29 +64,4 @@ class Production internal constructor() { } return false } - - private fun findFirstAlternativeContaining( - alternatives: Set<Set<ResourceType>>, - type: ResourceType - ): Set<ResourceType>? { - return alternatives.stream().filter { a -> a.contains(type) }.findAny().orElse(null) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Production - - if (fixedResources != other.fixedResources) return false - if (alternativeResources != other.alternativeResources) return false - - return true - } - - override fun hashCode(): Int { - var result = fixedResources.hashCode() - result = 31 * result + alternativeResources.hashCode() - return result - } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransaction.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransaction.kt deleted file mode 100644 index 30f4d014..00000000 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransaction.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.Player - -data class ResourceTransaction(val provider: Provider, val resources: Resources) { - - internal fun execute(player: Player) { - val board = player.board - val price = board.tradingRules.computeCost(this) - board.removeGold(price) - val providerPosition = provider.boardPosition - val providerBoard = player.getBoard(providerPosition) - providerBoard.addGold(price) - } -} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt index 52d10064..c7ab3636 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt @@ -2,36 +2,25 @@ package org.luxons.sevenwonders.game.resources import org.luxons.sevenwonders.game.Player -internal data class ResourceTransactions(private val resourcesByProvider: MutableMap<Provider, Resources> = mutableMapOf()) { +typealias ResourceTransactions = Collection<ResourceTransaction> - constructor(transactions: Collection<ResourceTransaction>) : this() { - transactions.forEach { t -> add(t.provider, t.resources) } - } +fun noTransactions(): ResourceTransactions = emptyList() - fun add(provider: Provider, resources: Resources) { - resourcesByProvider.merge(provider, resources) { old, new -> old + new } - } +fun Map<Provider, Resources>.toTransactions() = + filter { (_, res) -> !res.isEmpty() }.map { (p, res) -> ResourceTransaction(p, res) } - fun remove(provider: Provider, resources: Resources) { - resourcesByProvider.compute(provider) { _, prevResources -> - if (prevResources == null) { - throw IllegalStateException("Cannot remove resources from resource transactions") - } - prevResources.minus(resources) - } - } +fun ResourceTransactions.asResources(): Resources = map { it.resources }.merge() - fun execute(player: Player) { - asList().forEach { it.execute(player) } - } +internal fun ResourceTransactions.execute(player: Player) = forEach { it.execute(player) } - fun asList(): List<ResourceTransaction> { - return resourcesByProvider - .filter { (_, resources) -> !resources.isEmpty } - .map { (provider, resources) -> ResourceTransaction(provider, resources) } - } +data class ResourceTransaction(val provider: Provider, val resources: Resources) { - fun asResources(): Resources { - return resourcesByProvider.values.fold(Resources(), Resources::plus) + internal fun execute(player: Player) { + val board = player.board + val price = board.tradingRules.computeCost(this) + board.removeGold(price) + val providerPosition = provider.boardPosition + val providerBoard = player.getBoard(providerPosition) + providerBoard.addGold(price) } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt index 96ad0b20..15673bd2 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt @@ -2,71 +2,84 @@ package org.luxons.sevenwonders.game.resources import java.util.NoSuchElementException -class Resources(quantities: Map<ResourceType, Int> = emptyMap()) { +fun emptyResources(): Resources = MutableResources() - private val quantities: MutableMap<ResourceType, Int> = quantities.toMutableMap() +fun resourcesOf(singleResource: ResourceType): Resources = MutableResources(mutableMapOf(singleResource to 1)) - constructor(singleResource: ResourceType): this(mapOf(singleResource to 1)) +fun resourcesOf(vararg resources: ResourceType): Resources = + resources.fold(MutableResources()) { rs, r -> rs.add(r, 1); rs } - val isEmpty: Boolean - get() = size() == 0 +fun resourcesOf(resources: Iterable<ResourceType>): Resources = + resources.fold(MutableResources()) { rs, r -> rs.add(r, 1); rs } - fun add(type: ResourceType, quantity: Int) { - quantities.merge(type, quantity) { x, y -> x + y } - } +fun resourcesOf(vararg resources: Pair<ResourceType, Int>): Resources = + resources.fold(MutableResources()) { rs, (type, qty) -> rs.add(type, qty); rs } - fun remove(type: ResourceType, quantity: Int) { - if (getQuantity(type) < quantity) { - throw NoSuchElementException("Can't remove $quantity resources of type $type") - } - quantities.computeIfPresent(type) { _, oldQty -> oldQty - quantity } - } +internal fun mutableResourcesOf() = MutableResources() - fun addAll(resources: Resources) { - resources.quantities.forEach { type, quantity -> this.add(type, quantity) } - } +internal fun mutableResourcesOf(vararg resources: Pair<ResourceType, Int>) = + resources.fold(MutableResources()) { rs, (type, qty) -> rs.add(type, qty); rs } + +fun Iterable<ResourceType>.toResources(): Resources = resourcesOf(this) + +fun Iterable<Resources>.merge(): Resources = fold(MutableResources()) { r1, r2 -> r1.add(r2); r1} + +internal fun Resources.toMutableResources(): MutableResources { + val resources = MutableResources() + resources.add(this) + return resources +} - fun getQuantity(type: ResourceType): Int = quantities[type] ?: 0 +interface Resources { - fun asList(): List<ResourceType> = quantities.flatMap { e -> List(e.value) { e.key } } + val quantities: Map<ResourceType, Int> - fun containsAll(resources: Resources): Boolean = resources.quantities.all { it.value <= this.getQuantity(it.key) } + val size: Int + get() = quantities.map { it.value }.sum() + + fun isEmpty(): Boolean = size == 0 + + operator fun get(key: ResourceType): Int = quantities.getOrDefault(key, 0) + + fun containsAll(resources: Resources): Boolean = resources.quantities.all { it.value <= this[it.key] } operator fun plus(resources: Resources): Resources { - val new = Resources(this.quantities) - new.addAll(resources) + val new = MutableResources() + new.add(this) + new.add(resources) return new } - /** - * Creates a new [Resources] object containing these resources minus the given resources. - * - * @param resources - * the resources to subtract from these resources - * - * @return a new [Resources] object containing these resources minus the given resources. - */ operator fun minus(resources: Resources): Resources { - val diff = Resources() + val diff = MutableResources() quantities.forEach { type, count -> - val remainder = count - resources.getQuantity(type) + val remainder = count - resources[type] diff.quantities[type] = Math.max(0, remainder) } return diff } - fun size(): Int = quantities.values.sum() + fun toList(): List<ResourceType> = quantities.flatMap { (type, quantity) -> List(quantity) { type } } +} - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false +data class MutableResources( + override val quantities: MutableMap<ResourceType, Int> = mutableMapOf() +) : Resources { - other as Resources + fun add(type: ResourceType, quantity: Int) { + quantities.merge(type, quantity) { x, y -> x + y } + } - if (quantities != other.quantities) return false + fun add(resources: Resources) = resources.quantities.forEach { type, quantity -> this.add(type, quantity) } - return true + fun remove(type: ResourceType, quantity: Int) { + if (this[type] < quantity) { + throw NoSuchElementException("Can't remove $quantity resources of type $type") + } + quantities.computeIfPresent(type) { _, oldQty -> oldQty - quantity } + // to ensure equals() work properly + if (quantities[type] == 0) { + quantities.remove(type) + } } - - override fun hashCode(): Int = quantities.hashCode() } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt index dc0dc489..3b3c81ea 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt @@ -15,22 +15,10 @@ class TradingRules internal constructor(private val defaultCost: Int) { costs.computeIfAbsent(type) { mutableMapOf() }[provider] = cost } - internal fun computeCost(transactions: ResourceTransactions): Int { - return transactions.asList().map { this.computeCost(it) }.sum() - } + internal fun computeCost(transactions: ResourceTransactions): Int = transactions.map { computeCost(it) }.sum() - internal fun computeCost(transaction: ResourceTransaction): Int { - val resources = transaction.resources - val provider = transaction.provider - return computeCost(resources, provider) - } + internal fun computeCost(transact: ResourceTransaction) = computeCost(transact.resources, transact.provider) - private fun computeCost(resources: Resources, provider: Provider): Int { - var total = 0 - for (type in ResourceType.values()) { - val count = resources.getQuantity(type) - total += getCost(type, provider) * count - } - return total - } + private fun computeCost(resources: Resources, provider: Provider): Int = + resources.quantities.map { (type, qty) -> getCost(type, provider) * qty }.sum() } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt index 5f76b42e..fb232d9e 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt @@ -11,7 +11,9 @@ import org.luxons.sevenwonders.game.data.GameDefinitionLoader import org.luxons.sevenwonders.game.data.LAST_AGE import org.luxons.sevenwonders.game.moves.MoveType import org.luxons.sevenwonders.game.resources.ResourceTransaction -import org.luxons.sevenwonders.game.resources.bestTransaction +import org.luxons.sevenwonders.game.resources.ResourceTransactions +import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.bestSolution import org.luxons.sevenwonders.game.test.testCustomizableSettings import java.util.HashMap @@ -78,16 +80,19 @@ class GameTest { return PlayerMove(MoveType.DISCARD, firstCardInHand.card.name) } - private fun findResourcesToBuyFor(handCard: HandCard, turnInfo: PlayerTurnInfo): List<ResourceTransaction> { + private fun findResourcesToBuyFor(handCard: HandCard, turnInfo: PlayerTurnInfo): Collection<ResourceTransaction> { if (handCard.isFree) { return emptyList() } val requiredResources = handCard.card.requirements.resources val table = turnInfo.table val playerIndex = turnInfo.playerIndex - val transactions = bestTransaction(requiredResources, PlayerContext(playerIndex, table, listOf())) // we're supposed to have a best transaction plan because the card is playable - return transactions!!.asList() + return bestTransaction(requiredResources, PlayerContext(playerIndex, table, listOf())) + } + + private fun bestTransaction(resources: Resources, player: Player): ResourceTransactions { + return bestSolution(resources, player).possibleTransactions.first() } private fun createPickGuildMove(turnInfo: PlayerTurnInfo): PlayerMove { diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt index 68799145..0f2a3e0c 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt @@ -18,9 +18,9 @@ import org.luxons.sevenwonders.game.effects.RawPointsIncrease import org.luxons.sevenwonders.game.effects.SpecialAbility import org.luxons.sevenwonders.game.effects.SpecialAbilityActivation import org.luxons.sevenwonders.game.resources.ResourceType +import org.luxons.sevenwonders.game.resources.resourcesOf import org.luxons.sevenwonders.game.score.ScoreCategory import org.luxons.sevenwonders.game.test.addCards -import org.luxons.sevenwonders.game.test.createResources import org.luxons.sevenwonders.game.test.getDifferentColorFrom import org.luxons.sevenwonders.game.test.playCardWithEffect import org.luxons.sevenwonders.game.test.singleBoardPlayer @@ -47,7 +47,7 @@ class BoardTest { @Theory fun initialProduction_containsInitialResource(type: ResourceType) { val board = Board(testWonder(type), 0, Settings(5)) - val resources = createResources(type) + val resources = resourcesOf(type) assertTrue(board.production.contains(resources)) assertTrue(board.publicProduction.contains(resources)) } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt index e9f95b55..b8be570f 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt @@ -8,8 +8,8 @@ import org.luxons.sevenwonders.game.api.Table import org.luxons.sevenwonders.game.boards.Board import org.luxons.sevenwonders.game.effects.ProductionIncrease import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.resources.ResourceTransactions import org.luxons.sevenwonders.game.resources.ResourceType +import org.luxons.sevenwonders.game.resources.noTransactions import org.luxons.sevenwonders.game.test.testCard import org.luxons.sevenwonders.game.wonders.Wonder @@ -36,7 +36,7 @@ class CardTest { table.getBoard(0).gold = 3 table.getBoard(1).gold = 3 table.getBoard(2).gold = 3 - treeFarmCard.applyTo(SimplePlayer(0, table), ResourceTransactions()) + treeFarmCard.applyTo(SimplePlayer(0, table), noTransactions()) assertEquals(2, table.getBoard(0).gold.toLong()) assertEquals(3, table.getBoard(1).gold.toLong()) assertEquals(3, table.getBoard(2).gold.toLong()) diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt index 95a5e174..a26034f2 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt @@ -10,9 +10,9 @@ import org.junit.runner.RunWith import org.luxons.sevenwonders.game.SimplePlayer import org.luxons.sevenwonders.game.api.Table import org.luxons.sevenwonders.game.resources.Provider -import org.luxons.sevenwonders.game.resources.ResourceTransactions import org.luxons.sevenwonders.game.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.emptyResources +import org.luxons.sevenwonders.game.resources.noTransactions import org.luxons.sevenwonders.game.test.createRequirements import org.luxons.sevenwonders.game.test.createTransactions import org.luxons.sevenwonders.game.test.singleBoardPlayer @@ -25,14 +25,14 @@ class RequirementsTest { @Test fun getResources_emptyAfterInit() { val (_, resources) = Requirements() - assertTrue(resources.isEmpty) + assertTrue(resources.isEmpty()) } @Test fun setResources_success() { - val resources = Resources() - val (_, resources1) = Requirements(0, resources) - assertSame(resources, resources1) + val resources = emptyResources() + val requirements = Requirements(0, resources) + assertSame(resources, requirements.resources) } @Theory @@ -43,7 +43,7 @@ class RequirementsTest { val player = singleBoardPlayer(board) assertEquals(boardGold >= requiredGold, requirements.areMetWithoutNeighboursBy(board)) - assertEquals(boardGold >= requiredGold, requirements.areMetWithHelpBy(board, ResourceTransactions())) + assertEquals(boardGold >= requiredGold, requirements.areMetWithHelpBy(board, noTransactions())) assertEquals(boardGold >= requiredGold, requirements.areMetBy(player)) } @@ -55,10 +55,7 @@ class RequirementsTest { val player = singleBoardPlayer(board) assertEquals(initialResource == requiredResource, requirements.areMetWithoutNeighboursBy(board)) - assertEquals( - initialResource == requiredResource, - requirements.areMetWithHelpBy(board, ResourceTransactions()) - ) + assertEquals(initialResource == requiredResource, requirements.areMetWithHelpBy(board, noTransactions())) if (initialResource == requiredResource) { assertTrue(requirements.areMetBy(player)) @@ -67,7 +64,8 @@ class RequirementsTest { @Theory fun resourceRequirement_ownProduction( - initialResource: ResourceType, producedResource: ResourceType, + initialResource: ResourceType, + producedResource: ResourceType, requiredResource: ResourceType ) { assumeTrue(initialResource != requiredResource) @@ -79,10 +77,7 @@ class RequirementsTest { val player = singleBoardPlayer(board) assertEquals(producedResource == requiredResource, requirements.areMetWithoutNeighboursBy(board)) - assertEquals( - producedResource == requiredResource, - requirements.areMetWithHelpBy(board, ResourceTransactions()) - ) + assertEquals(producedResource == requiredResource, requirements.areMetWithHelpBy(board, noTransactions())) if (producedResource == requiredResource) { assertTrue(requirements.areMetBy(player)) @@ -91,7 +86,8 @@ class RequirementsTest { @Theory fun resourceRequirement_boughtResource( - initialResource: ResourceType, boughtResource: ResourceType, + initialResource: ResourceType, + boughtResource: ResourceType, requiredResource: ResourceType ) { assumeTrue(initialResource != requiredResource) @@ -108,10 +104,7 @@ class RequirementsTest { assertFalse(requirements.areMetWithoutNeighboursBy(board)) assertEquals(boughtResource == requiredResource, requirements.areMetWithHelpBy(board, resources)) - - if (boughtResource == requiredResource) { - assertTrue(requirements.areMetBy(player)) - } + assertEquals(boughtResource == requiredResource, requirements.areMetBy(player)) } @Theory @@ -125,10 +118,7 @@ class RequirementsTest { val table = Table(Arrays.asList(board, neighbourBoard)) val player = SimplePlayer(0, table) - val transactions = createTransactions( - Provider.RIGHT_PLAYER, - requiredResource - ) + val transactions = createTransactions(Provider.RIGHT_PLAYER, requiredResource) assertFalse(requirements.areMetWithoutNeighboursBy(board)) assertTrue(requirements.areMetWithHelpBy(board, transactions)) diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt index 5b0a47ab..1b87e593 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt @@ -8,6 +8,7 @@ import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test import org.luxons.sevenwonders.game.effects.ProductionIncrease +import org.luxons.sevenwonders.game.resources.MutableResources import org.luxons.sevenwonders.game.resources.Production import org.luxons.sevenwonders.game.resources.ResourceType import org.luxons.sevenwonders.game.resources.Resources @@ -22,6 +23,7 @@ class ProductionIncreaseSerializerTest { }.type gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) .registerTypeAdapter(Production::class.java, ProductionSerializer()) diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt index f74e21c5..66d4013a 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt @@ -7,6 +7,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test +import org.luxons.sevenwonders.game.resources.MutableResources import org.luxons.sevenwonders.game.resources.Production import org.luxons.sevenwonders.game.resources.ResourceType import org.luxons.sevenwonders.game.resources.Resources @@ -17,10 +18,9 @@ class ProductionSerializerTest { @Before fun setUp() { - val resourceTypeList = object : TypeToken<List<ResourceType>>() { - - }.type + val resourceTypeList = object : TypeToken<List<ResourceType>>() {}.type gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) .registerTypeAdapter(Production::class.java, ProductionSerializer()) diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt index 500c2666..426e0f0e 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt @@ -6,8 +6,11 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test -import org.luxons.sevenwonders.game.resources.ResourceType +import org.luxons.sevenwonders.game.resources.MutableResources +import org.luxons.sevenwonders.game.resources.ResourceType.* import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.emptyResources +import org.luxons.sevenwonders.game.resources.resourcesOf class ResourcesSerializerTest { @@ -15,7 +18,10 @@ class ResourcesSerializerTest { @Before fun setUp() { - gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()).create() + gson = GsonBuilder() + .registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) + .create() } @Test @@ -25,40 +31,31 @@ class ResourcesSerializerTest { @Test fun serialize_emptyResourcesToNull() { - val resources = Resources() + val resources = emptyResources() assertEquals("null", gson!!.toJson(resources)) } @Test fun serialize_singleType() { - val resources = Resources() - resources.add(ResourceType.WOOD, 1) + val resources = resourcesOf(WOOD) assertEquals("\"W\"", gson!!.toJson(resources)) } @Test fun serialize_multipleTimesSameType() { - val resources = Resources() - resources.add(ResourceType.WOOD, 3) + val resources = resourcesOf(WOOD to 3) assertEquals("\"WWW\"", gson!!.toJson(resources)) } @Test fun serialize_mixedTypes() { - val resources = Resources() - resources.add(ResourceType.WOOD, 1) - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 1) + val resources = resourcesOf(WOOD, STONE, CLAY) assertEquals("\"WSC\"", gson!!.toJson(resources)) } @Test fun serialize_mixedTypes_unordered() { - val resources = Resources() - resources.add(ResourceType.CLAY, 1) - resources.add(ResourceType.WOOD, 2) - resources.add(ResourceType.CLAY, 1) - resources.add(ResourceType.STONE, 1) + val resources = resourcesOf(CLAY to 1, WOOD to 2, CLAY to 1, STONE to 1) assertEquals("\"CCWWS\"", gson!!.toJson(resources)) } @@ -69,39 +66,31 @@ class ResourcesSerializerTest { @Test fun deserialize_emptyList() { - val resources = Resources() + val resources = emptyResources() assertEquals(resources, gson!!.fromJson("\"\"", Resources::class.java)) } @Test fun deserialize_singleType() { - val resources = Resources() - resources.add(ResourceType.WOOD, 1) + val resources = resourcesOf(WOOD) assertEquals(resources, gson!!.fromJson("\"W\"", Resources::class.java)) } @Test fun deserialize_multipleTimesSameType() { - val resources = Resources() - resources.add(ResourceType.WOOD, 3) + val resources = resourcesOf(WOOD to 3) assertEquals(resources, gson!!.fromJson("\"WWW\"", Resources::class.java)) } @Test fun deserialize_mixedTypes() { - val resources = Resources() - resources.add(ResourceType.WOOD, 1) - resources.add(ResourceType.CLAY, 1) - resources.add(ResourceType.STONE, 1) + val resources = resourcesOf(WOOD, CLAY, STONE) assertEquals(resources, gson!!.fromJson("\"WCS\"", Resources::class.java)) } @Test fun deserialize_mixedTypes_unordered() { - val resources = Resources() - resources.add(ResourceType.WOOD, 1) - resources.add(ResourceType.CLAY, 2) - resources.add(ResourceType.STONE, 3) + val resources = resourcesOf(WOOD to 1, CLAY to 2, STONE to 3) assertEquals(resources, gson!!.fromJson("\"SCWCSS\"", Resources::class.java)) } } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt index 9d5be34f..7973131c 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt @@ -7,7 +7,7 @@ import org.junit.experimental.theories.Theory import org.junit.runner.RunWith import org.luxons.sevenwonders.game.SimplePlayer import org.luxons.sevenwonders.game.resources.ResourceType -import org.luxons.sevenwonders.game.test.createResources +import org.luxons.sevenwonders.game.resources.resourcesOf import org.luxons.sevenwonders.game.test.fixedProduction import org.luxons.sevenwonders.game.test.testBoard import org.luxons.sevenwonders.game.test.testTable @@ -26,11 +26,11 @@ class ProductionIncreaseTest { effect.applyTo(board) - val resources = createResources(initialType, addedType) + val resources = resourcesOf(initialType, addedType) assertTrue(board.production.contains(resources)) assertFalse(board.publicProduction.contains(resources)) - val moreResources = createResources(initialType, addedType, extraType) + val moreResources = resourcesOf(initialType, addedType, extraType) assertFalse(board.production.contains(moreResources)) assertFalse(board.publicProduction.contains(moreResources)) } @@ -46,11 +46,11 @@ class ProductionIncreaseTest { effect.applyTo(board) - val resources = createResources(initialType, addedType) + val resources = resourcesOf(initialType, addedType) assertTrue(board.production.contains(resources)) assertTrue(board.publicProduction.contains(resources)) - val moreResources = createResources(initialType, addedType, extraType) + val moreResources = resourcesOf(initialType, addedType, extraType) assertFalse(board.production.contains(moreResources)) assertFalse(board.publicProduction.contains(moreResources)) } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt index ca279d97..67537644 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt @@ -7,7 +7,6 @@ import org.luxons.sevenwonders.game.api.Table import org.luxons.sevenwonders.game.resources.Provider.LEFT_PLAYER import org.luxons.sevenwonders.game.resources.Provider.RIGHT_PLAYER import org.luxons.sevenwonders.game.resources.ResourceType.* -import org.luxons.sevenwonders.game.test.createResources import org.luxons.sevenwonders.game.test.createTransaction import org.luxons.sevenwonders.game.test.createTransactions import org.luxons.sevenwonders.game.test.testBoard @@ -16,13 +15,16 @@ import java.util.Arrays class BestPriceCalculatorTest { + private fun solutions(price: Int, vararg resourceTransactions: ResourceTransactions) = + TransactionPlan(price, setOf(*resourceTransactions)) + @Test fun bestPrice_0forEmptyResources() { val table = testTable(3) val player0 = SimplePlayer(0, table) - val emptyResources = Resources() - val emptyTransactions = ResourceTransactions() - assertEquals(TransactionPlan(0, emptyTransactions), bestSolution(emptyResources, player0)) + val emptyResources = emptyResources() + val emptyTransactions = noTransactions() + assertEquals(solutions(0, emptyTransactions), bestSolution(emptyResources, player0)) } @Test @@ -31,12 +33,12 @@ class BestPriceCalculatorTest { val main = testBoard(STONE) val right = testBoard(WOOD) val table = Table(Arrays.asList(main, right, left)) - + val player0 = SimplePlayer(0, table) val player1 = SimplePlayer(1, table) val player2 = SimplePlayer(2, table) - val resources = createResources(STONE, STONE) + val resources = resourcesOf(STONE, STONE) val stoneLeftSingle = createTransaction(LEFT_PLAYER, STONE) val stoneRightSingle = createTransaction(RIGHT_PLAYER, STONE) @@ -45,9 +47,9 @@ class BestPriceCalculatorTest { val stoneRight = createTransactions(stoneRightSingle) val stoneLeftAndRight = createTransactions(stoneLeftSingle, stoneRightSingle) - assertEquals(TransactionPlan(2, stoneLeft), bestSolution(resources, player0)) - assertEquals(TransactionPlan(4, stoneLeftAndRight), bestSolution(resources, player1)) - assertEquals(TransactionPlan(2, stoneRight), bestSolution(resources, player2)) + assertEquals(solutions(2, stoneLeft), bestSolution(resources, player0)) + assertEquals(solutions(4, stoneLeftAndRight), bestSolution(resources, player1)) + assertEquals(solutions(2, stoneRight), bestSolution(resources, player2)) } @Test @@ -65,15 +67,15 @@ class BestPriceCalculatorTest { val player2 = SimplePlayer(2, table) val player3 = SimplePlayer(3, table) - val resources = createResources(WOOD) + val resources = resourcesOf(WOOD) val woodLeft = createTransactions(LEFT_PLAYER, WOOD) val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - assertEquals(TransactionPlan(1, woodRight), bestSolution(resources, player0)) - assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, player1)) - assertEquals(TransactionPlan(2, woodLeft), bestSolution(resources, player2)) - assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, player3)) + assertEquals(solutions(1, woodRight), 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 @@ -93,12 +95,12 @@ class BestPriceCalculatorTest { val player1 = SimplePlayer(1, table) val player2 = SimplePlayer(2, table) - val resources = createResources(WOOD) + val resources = resourcesOf(WOOD) val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - assertEquals(TransactionPlan(1, woodRight), bestSolution(resources, player0)) - assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, player1)) - assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, player2)) + assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player2)) } @Test @@ -121,12 +123,12 @@ class BestPriceCalculatorTest { val player1 = SimplePlayer(1, table) val player2 = SimplePlayer(2, table) - val resources = createResources(ORE, CLAY) + val resources = resourcesOf(ORE, CLAY) val oreAndClayLeft = createTransactions(LEFT_PLAYER, ORE, CLAY) val clayRight = createTransactions(RIGHT_PLAYER, CLAY) - assertEquals(TransactionPlan(1, clayRight), bestSolution(resources, player0)) - assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, player1)) - assertEquals(TransactionPlan(4, oreAndClayLeft), bestSolution(resources, player2)) + assertEquals(solutions(1, clayRight), bestSolution(resources, player0)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) + assertEquals(solutions(4, oreAndClayLeft), bestSolution(resources, player2)) } } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt index 92623a2b..79be288f 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt @@ -3,38 +3,27 @@ package org.luxons.sevenwonders.game.resources import org.junit.Assert.* import org.junit.Before import org.junit.Test +import org.luxons.sevenwonders.game.resources.ResourceType.* import java.util.EnumSet import java.util.HashSet class ProductionTest { - private var emptyResources: Resources = Resources() - private var resources1Wood: Resources = Resources() - private var resources1Stone: Resources = Resources() - private var resources1Stone1Wood: Resources = Resources() - private var resources2Stones: Resources = Resources() - private var resources2Stones3Clay: Resources = Resources() + private lateinit var emptyResources: Resources + private lateinit var resources1Wood: Resources + private lateinit var resources1Stone: Resources + private lateinit var resources1Stone1Wood: Resources + private lateinit var resources2Stones: Resources + private lateinit var resources2Stones3Clay: Resources @Before fun init() { - emptyResources = Resources() - - resources1Wood = Resources() - resources1Wood.add(ResourceType.WOOD, 1) - - resources1Stone = Resources() - resources1Stone.add(ResourceType.STONE, 1) - - resources1Stone1Wood = Resources() - resources1Stone1Wood.add(ResourceType.STONE, 1) - resources1Stone1Wood.add(ResourceType.WOOD, 1) - - resources2Stones = Resources() - resources2Stones.add(ResourceType.STONE, 2) - - resources2Stones3Clay = Resources() - resources2Stones3Clay.add(ResourceType.STONE, 2) - resources2Stones3Clay.add(ResourceType.CLAY, 3) + emptyResources = emptyResources() + resources1Wood = resourcesOf(WOOD) + resources1Stone = resourcesOf(STONE) + resources1Stone1Wood = resourcesOf(STONE to 1, WOOD to 1) + resources2Stones = resourcesOf(STONE to 2) + resources2Stones3Clay = resourcesOf(STONE to 2, CLAY to 3) } @Test @@ -52,67 +41,67 @@ class ProductionTest { @Test fun contains_singleFixedResource_notEnough() { val production = Production() - production.addFixedResource(ResourceType.STONE, 1) + production.addFixedResource(STONE, 1) assertFalse(production.contains(resources2Stones)) } @Test fun contains_singleFixedResource_justEnough() { val production = Production() - production.addFixedResource(ResourceType.STONE, 2) + production.addFixedResource(STONE, 2) assertTrue(production.contains(resources2Stones)) } @Test fun contains_singleFixedResource_moreThanEnough() { val production = Production() - production.addFixedResource(ResourceType.STONE, 3) + production.addFixedResource(STONE, 3) assertTrue(production.contains(resources2Stones)) } @Test fun contains_singleFixedResource_moreThanEnough_amongOthers() { val production = Production() - production.addFixedResource(ResourceType.STONE, 3) - production.addFixedResource(ResourceType.CLAY, 2) + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 2) assertTrue(production.contains(resources2Stones)) } @Test fun contains_multipleFixedResources_notEnoughOfOne() { val production = Production() - production.addFixedResource(ResourceType.STONE, 3) - production.addFixedResource(ResourceType.CLAY, 1) + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 1) assertFalse(production.contains(resources2Stones3Clay)) } @Test fun contains_multipleFixedResources_notEnoughOfBoth() { val production = Production() - production.addFixedResource(ResourceType.STONE, 1) - production.addFixedResource(ResourceType.CLAY, 1) + production.addFixedResource(STONE, 1) + production.addFixedResource(CLAY, 1) assertFalse(production.contains(resources2Stones3Clay)) } @Test fun contains_multipleFixedResources_moreThanEnough() { val production = Production() - production.addFixedResource(ResourceType.STONE, 3) - production.addFixedResource(ResourceType.CLAY, 5) + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 5) assertTrue(production.contains(resources2Stones3Clay)) } @Test fun contains_singleChoice_containsEmpty() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.CLAY) + production.addChoice(STONE, CLAY) assertTrue(production.contains(emptyResources)) } @Test fun contains_singleChoice_enough() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.WOOD) + production.addChoice(STONE, WOOD) assertTrue(production.contains(resources1Wood)) assertTrue(production.contains(resources1Stone)) } @@ -120,42 +109,42 @@ class ProductionTest { @Test fun contains_multipleChoices_notBoth() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.CLAY) - production.addChoice(ResourceType.STONE, ResourceType.CLAY) - production.addChoice(ResourceType.STONE, ResourceType.CLAY) + production.addChoice(STONE, CLAY) + production.addChoice(STONE, CLAY) + production.addChoice(STONE, CLAY) assertFalse(production.contains(resources2Stones3Clay)) } @Test fun contains_multipleChoices_enough() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.ORE) - production.addChoice(ResourceType.STONE, ResourceType.WOOD) + production.addChoice(STONE, ORE) + production.addChoice(STONE, WOOD) assertTrue(production.contains(resources1Stone1Wood)) } @Test fun contains_multipleChoices_enoughReverseOrder() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.WOOD) - production.addChoice(ResourceType.STONE, ResourceType.ORE) + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) assertTrue(production.contains(resources1Stone1Wood)) } @Test fun contains_multipleChoices_moreThanEnough() { val production = Production() - production.addChoice(ResourceType.LOOM, ResourceType.GLASS, ResourceType.PAPYRUS) - production.addChoice(ResourceType.STONE, ResourceType.ORE) - production.addChoice(ResourceType.STONE, ResourceType.WOOD) + production.addChoice(LOOM, GLASS, PAPYRUS) + production.addChoice(STONE, ORE) + production.addChoice(STONE, WOOD) assertTrue(production.contains(resources1Stone1Wood)) } @Test fun contains_mixedFixedAndChoice_enough() { val production = Production() - production.addFixedResource(ResourceType.WOOD, 1) - production.addChoice(ResourceType.STONE, ResourceType.WOOD) + production.addFixedResource(WOOD, 1) + production.addChoice(STONE, WOOD) assertTrue(production.contains(resources1Stone1Wood)) } @@ -194,8 +183,8 @@ class ProductionTest { @Test fun addAll_production_multipleChoices() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.WOOD) - production.addChoice(ResourceType.STONE, ResourceType.ORE) + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) val production2 = Production() production2.addAll(production) @@ -205,8 +194,8 @@ class ProductionTest { @Test fun addAll_production_mixedFixedResourcesAndChoices() { val production = Production() - production.addFixedResource(ResourceType.WOOD, 1) - production.addChoice(ResourceType.STONE, ResourceType.WOOD) + production.addFixedResource(WOOD, 1) + production.addChoice(STONE, WOOD) val production2 = Production() production2.addAll(production) @@ -223,22 +212,22 @@ class ProductionTest { @Test fun asChoices_onlyChoices() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.WOOD) - production.addChoice(ResourceType.STONE, ResourceType.ORE) - production.addChoice(ResourceType.CLAY, ResourceType.LOOM, ResourceType.GLASS) + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) + production.addChoice(CLAY, LOOM, GLASS) assertEquals(production.getAlternativeResources(), production.asChoices()) } @Test fun asChoices_onlyFixed() { val production = Production() - production.addFixedResource(ResourceType.WOOD, 1) - production.addFixedResource(ResourceType.CLAY, 2) + production.addFixedResource(WOOD, 1) + production.addFixedResource(CLAY, 2) val expected = HashSet<Set<ResourceType>>() - expected.add(EnumSet.of(ResourceType.WOOD)) - expected.add(EnumSet.of(ResourceType.CLAY)) - expected.add(EnumSet.of(ResourceType.CLAY)) + expected.add(EnumSet.of(WOOD)) + expected.add(EnumSet.of(CLAY)) + expected.add(EnumSet.of(CLAY)) assertEquals(expected, production.asChoices()) } @@ -246,17 +235,17 @@ class ProductionTest { @Test fun asChoices_mixed() { val production = Production() - production.addChoice(ResourceType.STONE, ResourceType.ORE) - production.addChoice(ResourceType.CLAY, ResourceType.LOOM, ResourceType.GLASS) - production.addFixedResource(ResourceType.WOOD, 1) - production.addFixedResource(ResourceType.CLAY, 2) + production.addChoice(STONE, ORE) + production.addChoice(CLAY, LOOM, GLASS) + production.addFixedResource(WOOD, 1) + production.addFixedResource(CLAY, 2) val expected = HashSet<Set<ResourceType>>() - expected.add(EnumSet.of(ResourceType.STONE, ResourceType.ORE)) - expected.add(EnumSet.of(ResourceType.CLAY, ResourceType.LOOM, ResourceType.GLASS)) - expected.add(EnumSet.of(ResourceType.WOOD)) - expected.add(EnumSet.of(ResourceType.CLAY)) - expected.add(EnumSet.of(ResourceType.CLAY)) + expected.add(EnumSet.of(STONE, ORE)) + expected.add(EnumSet.of(CLAY, LOOM, GLASS)) + expected.add(EnumSet.of(WOOD)) + expected.add(EnumSet.of(CLAY)) + expected.add(EnumSet.of(CLAY)) assertEquals(expected, production.asChoices()) } @@ -272,11 +261,11 @@ class ProductionTest { val production1 = Production() val production2 = Production() assertTrue(production1 == production2) - production1.addFixedResource(ResourceType.GLASS, 1) - production2.addFixedResource(ResourceType.GLASS, 1) + production1.addFixedResource(GLASS, 1) + production2.addFixedResource(GLASS, 1) assertTrue(production1 == production2) - production1.addChoice(ResourceType.ORE, ResourceType.WOOD) - production2.addChoice(ResourceType.ORE, ResourceType.WOOD) + production1.addChoice(ORE, WOOD) + production2.addChoice(ORE, WOOD) assertTrue(production1 == production2) } @@ -285,11 +274,11 @@ class ProductionTest { val production1 = Production() val production2 = Production() assertEquals(production1.hashCode().toLong(), production2.hashCode().toLong()) - production1.addFixedResource(ResourceType.GLASS, 1) - production2.addFixedResource(ResourceType.GLASS, 1) + production1.addFixedResource(GLASS, 1) + production2.addFixedResource(GLASS, 1) assertEquals(production1.hashCode().toLong(), production2.hashCode().toLong()) - production1.addChoice(ResourceType.ORE, ResourceType.WOOD) - production2.addChoice(ResourceType.ORE, ResourceType.WOOD) + production1.addChoice(ORE, WOOD) + production2.addChoice(ORE, WOOD) assertEquals(production1.hashCode().toLong(), production2.hashCode().toLong()) } } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt index 26ab16fa..172df278 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt @@ -2,31 +2,25 @@ package org.luxons.sevenwonders.game.resources import org.junit.Assert.assertEquals import org.junit.Test +import org.luxons.sevenwonders.game.resources.ResourceType.CLAY +import org.luxons.sevenwonders.game.resources.ResourceType.WOOD import org.luxons.sevenwonders.game.test.createTransaction +import org.luxons.sevenwonders.game.test.of class ResourceTransactionsTest { @Test fun toTransactions() { - val transactionList = listOf( - createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD), - createTransaction(Provider.LEFT_PLAYER, ResourceType.CLAY), - createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD) + val transactionMap = mapOf( + Provider.LEFT_PLAYER to (1 of WOOD) + (1 of CLAY), + Provider.RIGHT_PLAYER to (1 of WOOD) ) - val transactions = ResourceTransactions(transactionList) - val expectedNormalized = setOf( - createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD, ResourceType.CLAY), - createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD) + createTransaction(Provider.LEFT_PLAYER, WOOD, CLAY), + createTransaction(Provider.RIGHT_PLAYER, WOOD) ) - assertEquals(expectedNormalized, transactions.asList().toSet()) - } - - @Test(expected = IllegalStateException::class) - fun remove_failsIfNoResourceForThatProvider() { - val transactions = ResourceTransactions() - transactions.remove(Provider.LEFT_PLAYER, Resources(ResourceType.WOOD)) + assertEquals(expectedNormalized, transactionMap.toTransactions().toSet()) } } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt index a6ffb6c6..9fa0bc55 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt @@ -4,6 +4,7 @@ import org.junit.Assert.* import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException +import org.luxons.sevenwonders.game.resources.ResourceType.* import java.util.NoSuchElementException class ResourcesTest { @@ -14,503 +15,417 @@ class ResourcesTest { @Test fun init_shouldBeEmpty() { - val resources = Resources() - for (resourceType in ResourceType.values()) { - assertEquals(0, resources.getQuantity(resourceType).toLong()) + val resources = emptyResources() + for (resourceType in values()) { + assertEquals(0, resources[resourceType]) } - assertEquals(0, resources.size().toLong()) - assertTrue(resources.isEmpty) + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) } @Test fun add_zero() { - val resources = Resources() - resources.add(ResourceType.CLAY, 0) - assertEquals(0, resources.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, resources.size().toLong()) - assertTrue(resources.isEmpty) + val resources = mutableResourcesOf() + resources.add(CLAY, 0) + assertEquals(0, resources[CLAY]) + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) } @Test fun add_simple() { - val resources = Resources() - resources.add(ResourceType.WOOD, 3) - assertEquals(3, resources.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(3, resources.size().toLong()) - assertFalse(resources.isEmpty) + val resources = mutableResourcesOf() + resources.add(WOOD, 3) + assertEquals(3, resources[WOOD]) + assertEquals(3, resources.size) + assertFalse(resources.isEmpty()) } @Test fun add_multipleCallsStacked() { - val resources = Resources() - resources.add(ResourceType.ORE, 3) - resources.add(ResourceType.ORE, 2) - assertEquals(5, resources.getQuantity(ResourceType.ORE).toLong()) - assertEquals(5, resources.size().toLong()) - assertFalse(resources.isEmpty) + val resources = mutableResourcesOf() + resources.add(ORE, 3) + resources.add(ORE, 2) + assertEquals(5, resources[ORE]) + assertEquals(5, resources.size) + assertFalse(resources.isEmpty()) } @Test fun add_interlaced() { - val resources = Resources() - resources.add(ResourceType.GLASS, 3) - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.WOOD, 4) - resources.add(ResourceType.GLASS, 2) - assertEquals(5, resources.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(10, resources.size().toLong()) - assertFalse(resources.isEmpty) + val resources = mutableResourcesOf() + resources.add(GLASS, 3) + resources.add(STONE, 1) + resources.add(WOOD, 4) + resources.add(GLASS, 2) + assertEquals(5, resources[GLASS]) + assertEquals(10, resources.size) + assertFalse(resources.isEmpty()) } @Test fun plus_zero() { - val resources = Resources(mapOf(ResourceType.CLAY to 2)) - val resourcesPlusZero = resources + Resources() - val zeroPlusResources = Resources() + resources + val resources = resourcesOf(CLAY to 2) + val resourcesPlusZero = resources + emptyResources() + val zeroPlusResources = emptyResources() + resources - assertEquals(2, resourcesPlusZero.getQuantity(ResourceType.CLAY)) - assertEquals(2, resourcesPlusZero.size()) - assertEquals(2, zeroPlusResources.getQuantity(ResourceType.CLAY)) - assertEquals(2, zeroPlusResources.size()) + assertEquals(2, resourcesPlusZero[CLAY]) + assertEquals(2, resourcesPlusZero.size) + assertEquals(2, zeroPlusResources[CLAY]) + assertEquals(2, zeroPlusResources.size) } @Test fun plus_sameResource() { - val resources1 = Resources(mapOf(ResourceType.WOOD to 1)) - val resources2 = Resources(mapOf(ResourceType.WOOD to 3)) + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(WOOD to 3) val sum = resources1 + resources2 - assertEquals(4, sum.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(4, sum.size()) + assertEquals(1, resources1.size) + assertEquals(3, resources2.size) + assertEquals(4, sum[WOOD]) + assertEquals(4, sum.size) } @Test - fun plus_differentResources() { - val resources1 = Resources(mapOf(ResourceType.WOOD to 1)) - val resources2 = Resources(mapOf(ResourceType.ORE to 3)) + fun plus_differentemptyResources() { + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(ORE to 3) val sum = resources1 + resources2 - assertEquals(1, sum.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(3, sum.getQuantity(ResourceType.ORE).toLong()) - assertEquals(4, sum.size()) + assertEquals(1, resources1.size) + assertEquals(3, resources2.size) + assertEquals(1, sum[WOOD]) + assertEquals(3, sum[ORE]) + assertEquals(4, sum.size) } @Test - fun plus_overlappingResources() { - val resources1 = Resources(mapOf(ResourceType.WOOD to 1)) - val resources2 = Resources(mapOf(ResourceType.WOOD to 2, ResourceType.ORE to 4)) + fun plus_overlappingemptyResources() { + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(WOOD to 2, ORE to 4) val sum = resources1 + resources2 - assertEquals(3, sum.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(4, sum.getQuantity(ResourceType.ORE).toLong()) - assertEquals(7, sum.size()) + assertEquals(1, resources1.size) + assertEquals(6, resources2.size) + assertEquals(3, sum[WOOD]) + assertEquals(4, sum[ORE]) + assertEquals(7, sum.size) } @Test fun remove_some() { - val resources = Resources() - resources.add(ResourceType.WOOD, 3) - resources.remove(ResourceType.WOOD, 2) - assertEquals(1, resources.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(1, resources.size().toLong()) - assertFalse(resources.isEmpty) + val resources = mutableResourcesOf(WOOD to 3) + resources.remove(WOOD, 2) + assertEquals(1, resources[WOOD]) + assertEquals(1, resources.size) + assertFalse(resources.isEmpty()) } @Test fun remove_all() { - val resources = Resources() - resources.add(ResourceType.WOOD, 3) - resources.remove(ResourceType.WOOD, 3) - assertEquals(0, resources.getQuantity(ResourceType.WOOD).toLong()) - assertEquals(0, resources.size().toLong()) - assertTrue(resources.isEmpty) + val resources = mutableResourcesOf(WOOD to 3) + resources.remove(WOOD, 3) + assertEquals(0, resources[WOOD]) + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) } @Test fun remove_tooMany() { - val resources = Resources() - resources.add(ResourceType.WOOD, 2) + val resources = mutableResourcesOf(WOOD to 2) thrown.expect(NoSuchElementException::class.java) - resources.remove(ResourceType.WOOD, 3) + resources.remove(WOOD, 3) } @Test fun addAll_empty() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - val emptyResources = Resources() + val emptyResources = emptyResources() - resources.addAll(emptyResources) - assertEquals(1, resources.getQuantity(ResourceType.STONE).toLong()) - assertEquals(3, resources.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.LOOM).toLong()) - assertEquals(4, resources.size().toLong()) - assertFalse(resources.isEmpty) + resources.add(emptyResources) + assertEquals(1, resources[STONE]) + assertEquals(3, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(4, resources.size) + assertFalse(resources.isEmpty()) } @Test fun addAll_zeros() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - val emptyResources = Resources() - emptyResources.add(ResourceType.STONE, 0) - emptyResources.add(ResourceType.CLAY, 0) + val emptyResources = resourcesOf(STONE to 0, CLAY to 0) - resources.addAll(emptyResources) - assertEquals(1, resources.getQuantity(ResourceType.STONE).toLong()) - assertEquals(3, resources.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.LOOM).toLong()) - assertEquals(4, resources.size().toLong()) - assertFalse(resources.isEmpty) + resources.add(emptyResources) + assertEquals(1, resources[STONE]) + assertEquals(3, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(4, resources.size) + assertFalse(resources.isEmpty()) } @Test fun addAll_same() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 2, CLAY to 6) - val resources2 = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 6) - - resources.addAll(resources2) - assertEquals(3, resources.getQuantity(ResourceType.STONE).toLong()) - assertEquals(9, resources.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.LOOM).toLong()) - assertEquals(12, resources.size().toLong()) - assertFalse(resources.isEmpty) + resources.add(resources2) + assertEquals(3, resources[STONE]) + assertEquals(9, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(12, resources.size) + assertFalse(resources.isEmpty()) } @Test fun addAll_overlap() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) - - val resources2 = Resources() - resources.add(ResourceType.CLAY, 6) - resources.add(ResourceType.ORE, 4) + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(CLAY to 6, ORE to 4) - resources.addAll(resources2) - assertEquals(1, resources.getQuantity(ResourceType.STONE).toLong()) - assertEquals(9, resources.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(4, resources.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, resources.getQuantity(ResourceType.LOOM).toLong()) - assertEquals(14, resources.size().toLong()) - assertFalse(resources.isEmpty) + resources.add(resources2) + assertEquals(1, resources[STONE]) + assertEquals(9, resources[CLAY]) + assertEquals(4, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(14, resources.size) + assertFalse(resources.isEmpty()) } @Test fun contains_emptyContainsEmpty() { - val emptyResources = Resources() - val emptyResources2 = Resources() + val emptyResources = emptyResources() + val emptyResources2 = emptyResources() assertTrue(emptyResources.containsAll(emptyResources2)) } @Test fun contains_singleTypeContainsEmpty() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - - val emptyResources = Resources() + val resources = resourcesOf(STONE to 1) + val emptyResources = emptyResources() assertTrue(resources.containsAll(emptyResources)) } @Test fun contains_multipleTypesContainsEmpty() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) - - val emptyResources = Resources() + val resources = resourcesOf(STONE to 1, CLAY to 3) + val emptyResources = emptyResources() assertTrue(resources.containsAll(emptyResources)) } @Test fun contains_self() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 1, CLAY to 3) assertTrue(resources.containsAll(resources)) } @Test fun contains_allOfEachType() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) - - val resources2 = Resources() - resources2.add(ResourceType.STONE, 1) - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) assertTrue(resources.containsAll(resources2)) } @Test fun contains_someOfEachType() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.STONE, 1) - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) assertTrue(resources.containsAll(resources2)) } @Test fun contains_someOfSomeTypes() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 3) assertTrue(resources.containsAll(resources2)) } @Test fun contains_allOfSomeTypes() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.CLAY, 4) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 4) assertTrue(resources.containsAll(resources2)) } @Test fun minus_empty() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) - - val emptyResources = Resources() + val resources = resourcesOf(STONE to 1, CLAY to 3) + val emptyResources = emptyResources() val diff = resources.minus(emptyResources) - assertEquals(1, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(3, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(1, diff[STONE]) + assertEquals(3, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_self() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 1, CLAY to 3) val diff = resources.minus(resources) - assertEquals(0, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(0, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_allOfEachType() { - val resources = Resources() - resources.add(ResourceType.STONE, 1) - resources.add(ResourceType.CLAY, 3) - - val resources2 = Resources() - resources2.add(ResourceType.STONE, 1) - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) val diff = resources.minus(resources2) - assertEquals(0, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(0, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_someOfEachType() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.STONE, 1) - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) val diff = resources.minus(resources2) - assertEquals(1, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(1, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(1, diff[STONE]) + assertEquals(1, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_someOfSomeTypes() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.CLAY, 3) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 3) val diff = resources.minus(resources2) - assertEquals(2, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(1, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(2, diff[STONE]) + assertEquals(1, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_allOfSomeTypes() { - val resources = Resources() - resources.add(ResourceType.STONE, 2) - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.CLAY, 4) + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 4) val diff = resources.minus(resources2) - assertEquals(2, diff.getQuantity(ResourceType.STONE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.CLAY).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.ORE).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.GLASS).toLong()) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(2, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) } @Test fun minus_tooMuchOfExistingType() { - val resources = Resources() - resources.add(ResourceType.CLAY, 4) - - val resources2 = Resources() - resources2.add(ResourceType.CLAY, 5) + val resources = resourcesOf(CLAY to 4) + val resources2 = resourcesOf(CLAY to 5) val diff = resources.minus(resources2) - assertEquals(0, diff.getQuantity(ResourceType.CLAY).toLong()) + assertEquals(0, diff[CLAY]) } @Test fun minus_someOfAnAbsentType() { - val resources = Resources() - - val resources2 = Resources() - resources2.add(ResourceType.LOOM, 5) + val resources = emptyResources() + val resources2 = resourcesOf(LOOM to 5) val diff = resources.minus(resources2) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(0, diff[LOOM]) } @Test fun minus_someOfATypeWithZero() { - val resources = Resources() - resources.add(ResourceType.LOOM, 0) - - val resources2 = Resources() - resources2.add(ResourceType.LOOM, 5) + val resources = resourcesOf(LOOM to 0) + val resources2 = resourcesOf(LOOM to 5) val diff = resources.minus(resources2) - assertEquals(0, diff.getQuantity(ResourceType.LOOM).toLong()) + assertEquals(0, diff[LOOM]) } @Test fun isEmpty_noElement() { - val resources = Resources() - assertTrue(resources.isEmpty) + val resources = emptyResources() + assertTrue(resources.isEmpty()) } @Test fun isEmpty_singleZeroElement() { - val resources = Resources() - resources.add(ResourceType.LOOM, 0) - assertTrue(resources.isEmpty) + val resources = resourcesOf(LOOM to 0) + assertTrue(resources.isEmpty()) } @Test fun isEmpty_multipleZeroElements() { - val resources = Resources() - resources.add(ResourceType.WOOD, 0) - resources.add(ResourceType.ORE, 0) - resources.add(ResourceType.LOOM, 0) - assertTrue(resources.isEmpty) + val resources = resourcesOf(WOOD to 0, ORE to 0, LOOM to 0) + assertTrue(resources.isEmpty()) } @Test fun isEmpty_singleElementMoreThanZero() { - val resources = Resources() - resources.add(ResourceType.LOOM, 3) - assertFalse(resources.isEmpty) + val resources = resourcesOf(LOOM to 3) + assertFalse(resources.isEmpty()) } @Test fun isEmpty_mixedZeroAndNonZeroElements() { - val resources = Resources() - resources.add(ResourceType.WOOD, 0) - resources.add(ResourceType.LOOM, 3) - assertFalse(resources.isEmpty) + val resources = resourcesOf(WOOD to 0, LOOM to 3) + assertFalse(resources.isEmpty()) } @Test fun isEmpty_mixedZeroAndNonZeroElements_reverseOrder() { - val resources = Resources() - resources.add(ResourceType.ORE, 3) - resources.add(ResourceType.PAPYRUS, 0) - assertFalse(resources.isEmpty) - } - - @Test - fun equals_falseWhenNull() { - val resources = Resources() - resources.add(ResourceType.GLASS, 1) - - assertFalse(resources == null) + val resources = resourcesOf(ORE to 3, PAPYRUS to 0) + assertFalse(resources.isEmpty()) } @Test fun equals_trueWhenSame() { - val resources = Resources() + val resources = emptyResources() assertEquals(resources, resources) } @Test fun equals_trueWhenSameContent() { - val resources1 = Resources() - val resources2 = Resources() + val resources1 = mutableResourcesOf() + val resources2 = mutableResourcesOf() assertTrue(resources1 == resources2) - resources1.add(ResourceType.GLASS, 1) - resources2.add(ResourceType.GLASS, 1) + resources1.add(GLASS, 1) + resources2.add(GLASS, 1) assertTrue(resources1 == resources2) } @Test fun hashCode_sameWhenSameContent() { - val resources1 = Resources() - val resources2 = Resources() - assertEquals(resources1.hashCode().toLong(), resources2.hashCode().toLong()) - resources1.add(ResourceType.GLASS, 1) - resources2.add(ResourceType.GLASS, 1) - assertEquals(resources1.hashCode().toLong(), resources2.hashCode().toLong()) + val resources1 = mutableResourcesOf() + val resources2 = mutableResourcesOf() + assertEquals(resources1.hashCode(), resources2.hashCode()) + resources1.add(GLASS, 1) + resources2.add(GLASS, 1) + assertEquals(resources1.hashCode(), resources2.hashCode()) } } diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt index 859c997a..ac3f72de 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt @@ -1,15 +1,14 @@ package org.luxons.sevenwonders.game.resources -import java.util.ArrayList - +import org.junit.Assert.assertEquals +import org.junit.Assume.assumeTrue import org.junit.experimental.theories.DataPoints import org.junit.experimental.theories.Theories import org.junit.experimental.theories.Theory import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.test.* - -import org.junit.Assert.assertEquals -import org.junit.Assume.assumeTrue +import org.luxons.sevenwonders.game.test.createTransaction +import org.luxons.sevenwonders.game.test.createTransactions +import java.util.ArrayList @RunWith(Theories::class) class TradingRulesTest { @@ -32,7 +31,7 @@ class TradingRulesTest { @Theory fun computeCost_zeroForNoResources(defaultCost: Int) { val rules = TradingRules(defaultCost) - assertEquals(0, rules.computeCost(ResourceTransactions()).toLong()) + assertEquals(0, rules.computeCost(noTransactions()).toLong()) } @Theory @@ -59,9 +58,11 @@ class TradingRulesTest { @Theory fun computeCost_defaultCostWhenOverrideOnOtherProviderOrType( - defaultCost: Int, overriddenCost: Int, + defaultCost: Int, + overriddenCost: Int, overriddenProvider: Provider, - overriddenType: ResourceType, provider: Provider, + overriddenType: ResourceType, + provider: Provider, type: ResourceType ) { assumeTrue(overriddenProvider != provider || overriddenType != type) @@ -73,8 +74,10 @@ class TradingRulesTest { @Theory fun computeCost_oneDefaultAndOneOverriddenType( - defaultCost: Int, overriddenCost: Int, - overriddenType: ResourceType, provider: Provider, + defaultCost: Int, + overriddenCost: Int, + overriddenType: ResourceType, + provider: Provider, type: ResourceType ) { assumeTrue(overriddenType != type) @@ -86,8 +89,10 @@ class TradingRulesTest { @Theory fun computeCost_oneDefaultAndOneOverriddenProvider( - defaultCost: Int, overriddenCost: Int, - overriddenProvider: Provider, provider: Provider, + defaultCost: Int, + overriddenCost: Int, + overriddenProvider: Provider, + provider: Provider, type: ResourceType ) { assumeTrue(overriddenProvider != provider) @@ -98,10 +103,7 @@ class TradingRulesTest { boughtResources.add(createTransaction(provider, type)) boughtResources.add(createTransaction(overriddenProvider, type)) - assertEquals( - (defaultCost + overriddenCost).toLong(), - rules.computeCost(ResourceTransactions(boughtResources)).toLong() - ) + assertEquals(defaultCost + overriddenCost, rules.computeCost(boughtResources)) } companion object { diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt index bf718d32..1a360a77 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt @@ -24,6 +24,8 @@ import org.luxons.sevenwonders.game.resources.ResourceTransaction import org.luxons.sevenwonders.game.resources.ResourceTransactions import org.luxons.sevenwonders.game.resources.ResourceType import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.noTransactions +import org.luxons.sevenwonders.game.resources.resourcesOf import org.luxons.sevenwonders.game.wonders.Wonder import org.luxons.sevenwonders.game.wonders.WonderStage import java.util.Arrays @@ -90,34 +92,29 @@ private fun createWonderStage(vararg effects: Effect): WonderStage { fun fixedProduction(vararg producedTypes: ResourceType): Production { val production = Production() - val fixedProducedResources = production.fixedResources - fixedProducedResources.addAll(createResources(*producedTypes)) + production.addAll(resourcesOf(*producedTypes)) return production } -fun createResources(vararg types: ResourceType): Resources { - val resources = Resources() - for (producedType in types) { - resources.add(producedType, 1) - } - return resources +infix fun Int.of(type: ResourceType): Resources { + return resourcesOf(type to this) } internal fun createTransactions(provider: Provider, vararg resources: ResourceType): ResourceTransactions { val transaction = createTransaction(provider, *resources) - return ResourceTransactions(listOf(transaction)) + return listOf(transaction) } internal fun createTransactions(vararg transactions: ResourceTransaction): ResourceTransactions { - return ResourceTransactions(Arrays.asList(*transactions)) + return transactions.toList() } fun createTransaction(provider: Provider, vararg resources: ResourceType): ResourceTransaction { - return ResourceTransaction(provider, createResources(*resources)) + return ResourceTransaction(provider, resourcesOf(*resources)) } fun createRequirements(vararg types: ResourceType): Requirements { - val resources = createResources(*types) + val resources = resourcesOf(*types) return Requirements(resources = resources) } @@ -209,7 +206,7 @@ fun createScience(compasses: Int, wheels: Int, tablets: Int, jokers: Int): Scien internal fun playCardWithEffect(player: Player, color: Color, effect: Effect) { val card = testCard(color, effect) player.board.addCard(card) - card.applyTo(player, ResourceTransactions()) + card.applyTo(player, noTransactions()) } internal fun createMove(context: PlayerContext, card: Card, type: MoveType, vararg transactions: ResourceTransaction): Move { |