diff options
25 files changed, 535 insertions, 720 deletions
diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/BestPriceCalculator.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/BestPriceCalculator.java deleted file mode 100644 index 10ac8343..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/BestPriceCalculator.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -import org.luxons.sevenwonders.game.api.Table; -import org.luxons.sevenwonders.game.boards.Board; - -public class BestPriceCalculator { - - private final Resources resourcesLeftToPay; - - private final List<ResourcePool> pools; - - private final ResourceTransactions boughtResources; - - private int pricePaid; - - private ResourceTransactions bestSolution; - - private int bestPrice; - - private BestPriceCalculator(Resources resourcesToPay, Table table, int playerIndex) { - Board board = table.getBoard(playerIndex); - this.resourcesLeftToPay = resourcesToPay.minus(board.getProduction().getFixedResources()); - this.pools = createResourcePools(table, playerIndex); - this.boughtResources = new ResourceTransactions(); - this.pricePaid = 0; - this.bestSolution = null; - this.bestPrice = Integer.MAX_VALUE; - } - - private static List<ResourcePool> createResourcePools(Table table, int playerIndex) { - Provider[] providers = Provider.values(); - List<ResourcePool> pools = new ArrayList<>(providers.length + 1); - - Board board = table.getBoard(playerIndex); - TradingRules rules = board.getTradingRules(); - // we only take alternative resources here, because fixed resources were already removed for optimization - Set<Set<ResourceType>> ownBoardChoices = board.getProduction().getAlternativeResources(); - pools.add(new ResourcePool(null, rules, ownBoardChoices)); - - for (Provider provider : providers) { - Board providerBoard = table.getBoard(playerIndex, provider.getBoardPosition()); - ResourcePool pool = new ResourcePool(provider, rules, providerBoard.getPublicProduction().asChoices()); - pools.add(pool); - } - return pools; - } - - public static int bestPrice(Resources resources, Table table, int playerIndex) { - BestPriceCalculator bestPriceCalculator = new BestPriceCalculator(resources, table, playerIndex); - bestPriceCalculator.computePossibilities(); - return bestPriceCalculator.bestPrice; - } - - public static ResourceTransactions bestSolution(Resources resources, Table table, int playerIndex) { - BestPriceCalculator calculator = new BestPriceCalculator(resources, table, playerIndex); - calculator.computePossibilities(); - return calculator.bestSolution; - } - - private void computePossibilities() { - if (resourcesLeftToPay.isEmpty()) { - updateBestSolutionIfNeeded(); - return; - } - for (ResourceType type : ResourceType.values()) { - if (resourcesLeftToPay.getQuantity(type) > 0) { - for (ResourcePool pool : pools) { - boolean ownResource = pool.getProvider() == null; - if (ownResource) { - resourcesLeftToPay.remove(type, 1); - computePossibilitiesWhenUsing(type, pool); - resourcesLeftToPay.add(type, 1); - continue; - } - int cost = pool.getCost(type); - - resourcesLeftToPay.remove(type, 1); - boughtResources.add(pool.getProvider(), Resources.of(type)); - pricePaid += cost; - computePossibilitiesWhenUsing(type, pool); - pricePaid -= cost; - boughtResources.remove(pool.getProvider(), Resources.of(type)); - resourcesLeftToPay.add(type, 1); - } - } - } - } - - private void computePossibilitiesWhenUsing(ResourceType type, ResourcePool pool) { - for (Set<ResourceType> choice : pool.getChoices()) { - if (choice.contains(type)) { - Set<ResourceType> temp = EnumSet.copyOf(choice); - choice.clear(); - computePossibilities(); - choice.addAll(temp); - } - } - } - - private void updateBestSolutionIfNeeded() { - if (pricePaid < bestPrice) { - bestPrice = pricePaid; - bestSolution = new ResourceTransactions(boughtResources.toTransactions()); - } - } -} - diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Production.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Production.java deleted file mode 100644 index 7fa83e51..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Production.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.Arrays; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -public class Production { - - private final Resources fixedResources = new Resources(); - - private final Set<Set<ResourceType>> alternativeResources = new HashSet<>(); - - public void addFixedResource(ResourceType type, int quantity) { - fixedResources.add(type, quantity); - } - - public void addChoice(ResourceType... options) { - EnumSet<ResourceType> optionSet = EnumSet.copyOf(Arrays.asList(options)); - alternativeResources.add(optionSet); - } - - public void addAll(Resources resources) { - fixedResources.addAll(resources); - } - - public void addAll(Production production) { - fixedResources.addAll(production.getFixedResources()); - alternativeResources.addAll(production.getAlternativeResources()); - } - - public Resources getFixedResources() { - return fixedResources; - } - - public Set<Set<ResourceType>> getAlternativeResources() { - return alternativeResources; - } - - Set<Set<ResourceType>> asChoices() { - Set<Set<ResourceType>> result = new HashSet<>(fixedResources.size() + alternativeResources.size()); - fixedResources.asList().stream().map(EnumSet::of).forEach(result::add); - result.addAll(alternativeResources); - return result; - } - - public boolean contains(Resources resources) { - if (fixedResources.contains(resources)) { - return true; - } - Resources remaining = resources.minus(fixedResources); - return containedInAlternatives(remaining); - } - - private boolean containedInAlternatives(Resources resources) { - return containedInAlternatives(resources, alternativeResources); - } - - private static boolean containedInAlternatives(Resources resources, Set<Set<ResourceType>> alternatives) { - if (resources.isEmpty()) { - return true; - } - for (ResourceType type : ResourceType.values()) { - if (resources.getQuantity(type) <= 0) { - continue; - } - Set<ResourceType> candidate = findFirstAlternativeContaining(alternatives, type); - if (candidate == null) { - return false; // no alternative produces the resource of this entry - } - resources.remove(type, 1); - alternatives.remove(candidate); - boolean remainingAreContainedToo = containedInAlternatives(resources, alternatives); - resources.add(type, 1); - alternatives.add(candidate); - if (remainingAreContainedToo) { - return true; - } - } - return false; - } - - private static Set<ResourceType> findFirstAlternativeContaining(Set<Set<ResourceType>> alternatives, - ResourceType type) { - return alternatives.stream().filter(a -> a.contains(type)).findAny().orElse(null); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Production that = (Production) o; - return Objects.equals(fixedResources, that.fixedResources) && Objects.equals(alternativeResources, - that.alternativeResources); - } - - @Override - public int hashCode() { - return Objects.hash(fixedResources, alternativeResources); - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Provider.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Provider.java deleted file mode 100644 index 9c4aa3f9..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Provider.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import org.luxons.sevenwonders.game.boards.RelativeBoardPosition; - -public enum Provider { - LEFT_PLAYER(RelativeBoardPosition.LEFT), - RIGHT_PLAYER(RelativeBoardPosition.RIGHT); - - private final RelativeBoardPosition boardPosition; - - Provider(RelativeBoardPosition boardPosition) { - this.boardPosition = boardPosition; - } - - public RelativeBoardPosition getBoardPosition() { - return boardPosition; - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourcePool.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourcePool.java deleted file mode 100644 index 75a02afc..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourcePool.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.Set; - -class ResourcePool { - - private final Set<Set<ResourceType>> choices; - - private final Provider provider; - - private final TradingRules rules; - - ResourcePool(Provider provider, TradingRules rules, Set<Set<ResourceType>> choices) { - this.choices = choices; - this.provider = provider; - this.rules = rules; - } - - Set<Set<ResourceType>> getChoices() { - return choices; - } - - Provider getProvider() { - return provider; - } - - int getCost(ResourceType type) { - if (provider == null) { - return 0; - } - return rules.getCost(type, provider); - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransaction.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransaction.java deleted file mode 100644 index 3cb8eea1..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransaction.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.Objects; - -import org.luxons.sevenwonders.game.api.Table; -import org.luxons.sevenwonders.game.boards.Board; -import org.luxons.sevenwonders.game.boards.RelativeBoardPosition; - -public class ResourceTransaction { - - private final Provider provider; - - private final Resources resources; - - public ResourceTransaction(Provider provider, Resources resources) { - this.provider = provider; - this.resources = resources; - } - - public Provider getProvider() { - return provider; - } - - public Resources getResources() { - return resources; - } - - void execute(Table table, int playerIndex) { - Board board = table.getBoard(playerIndex); - int price = board.getTradingRules().computeCost(this); - board.removeGold(price); - RelativeBoardPosition providerPosition = provider.getBoardPosition(); - Board providerBoard = table.getBoard(playerIndex, providerPosition); - providerBoard.addGold(price); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ResourceTransaction that = (ResourceTransaction) o; - return provider == that.provider && Objects.equals(resources, that.resources); - } - - @Override - public int hashCode() { - return Objects.hash(provider, resources); - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransactions.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransactions.java deleted file mode 100644 index a8fdc6c7..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransactions.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.luxons.sevenwonders.game.api.Table; - -public class ResourceTransactions { - - private final Map<Provider, Resources> resourcesByProvider; - - public ResourceTransactions() { - this.resourcesByProvider = new HashMap<>(); - } - - public ResourceTransactions(Collection<ResourceTransaction> transactions) { - this(); - transactions.forEach(t -> add(t.getProvider(), t.getResources())); - } - - public void add(Provider provider, Resources resources) { - resourcesByProvider.putIfAbsent(provider, new Resources()); - resourcesByProvider.merge(provider, resources, Resources::addAll); - } - - public void remove(Provider provider, Resources resources) { - resourcesByProvider.compute(provider, (p, prevResources) -> { - if (prevResources == null) { - throw new IllegalStateException("Cannot remove resources from resource transactions"); - } - return prevResources.minus(resources); - }); - } - - public void execute(Table table, int playerIndex) { - toTransactions().forEach(t -> t.execute(table, playerIndex)); - } - - public Set<ResourceTransaction> toTransactions() { - return resourcesByProvider.entrySet() - .stream() - .filter(e -> !e.getValue().isEmpty()) - .map(e -> new ResourceTransaction(e.getKey(), e.getValue())) - .collect(Collectors.toSet()); - } - - public Resources asResources() { - return resourcesByProvider.values().stream().reduce(new Resources(), Resources::addAll); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ResourceTransactions that = (ResourceTransactions) o; - return Objects.equals(resourcesByProvider, that.resourcesByProvider); - } - - @Override - public int hashCode() { - return Objects.hash(resourcesByProvider); - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceType.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceType.java deleted file mode 100644 index 644437df..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceType.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.HashMap; -import java.util.Map; - -public enum ResourceType { - WOOD('W'), - STONE('S'), - ORE('O'), - CLAY('C'), - GLASS('G'), - PAPYRUS('P'), - LOOM('L'); - - private static final Map<Character, ResourceType> typesPerSymbol = new HashMap<>(7); - - static { - for (ResourceType type : values()) { - typesPerSymbol.put(type.symbol, type); - } - } - - private final Character symbol; - - ResourceType(Character symbol) { - this.symbol = symbol; - } - - public static ResourceType fromSymbol(String symbol) { - if (symbol.length() != 1) { - throw new IllegalArgumentException("The given symbol must be a valid single-char resource type, got " - + symbol); - } - return fromSymbol(symbol.charAt(0)); - } - - public static ResourceType fromSymbol(Character symbol) { - ResourceType type = typesPerSymbol.get(symbol); - if (type == null) { - throw new IllegalArgumentException(String.format("Unknown resource type symbol '%s'", symbol)); - } - return type; - } - - public Character getSymbol() { - return symbol; - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Resources.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Resources.java deleted file mode 100644 index d354f121..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Resources.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class Resources { - - private final Map<ResourceType, Integer> quantities = new EnumMap<>(ResourceType.class); - - public static Resources of(ResourceType... types) { - Resources resources = new Resources(); - for (ResourceType type : types) { - resources.add(type, 1); - } - return resources; - } - - public void add(ResourceType type, int quantity) { - quantities.merge(type, quantity, (x, y) -> x + y); - } - - public void remove(ResourceType type, int quantity) { - if (getQuantity(type) < quantity) { - throw new NoSuchElementException(String.format("Can't remove %d resources of type %s", quantity, type)); - } - quantities.computeIfPresent(type, (t, oldQty) -> oldQty - quantity); - } - - public Resources addAll(Resources resources) { - resources.quantities.forEach(this::add); - return this; - } - - public int getQuantity(ResourceType type) { - return quantities.getOrDefault(type, 0); - } - - public List<ResourceType> asList() { - return quantities.entrySet() - .stream() - .flatMap(e -> Stream.generate(e::getKey).limit(e.getValue())) - .collect(Collectors.toList()); - } - - public boolean contains(Resources resources) { - return resources.quantities.entrySet().stream().allMatch(this::hasAtLeast); - } - - private boolean hasAtLeast(Entry<ResourceType, Integer> quantity) { - return quantity.getValue() <= getQuantity(quantity.getKey()); - } - - public Resources plus(Resources resources) { - Resources merged = new Resources(); - merged.addAll(this); - merged.addAll(resources); - return merged; - } - - /** - * Creates new {@link Resources} object containing these resources minus the given resources. - * - * @param resources - * the resources to subtract from these resources - * - * @return a new {@link Resources} object containing these resources minus the given resources. - */ - public Resources minus(Resources resources) { - Resources diff = new Resources(); - quantities.forEach((type, count) -> { - int remainder = count - resources.getQuantity(type); - diff.quantities.put(type, Math.max(0, remainder)); - }); - return diff; - } - - public boolean isEmpty() { - return size() == 0; - } - - public int size() { - return quantities.values().stream().reduce(0, Integer::sum); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Resources resources = (Resources) o; - return Objects.equals(quantities, resources.quantities); - } - - @Override - public int hashCode() { - return Objects.hash(quantities); - } - - @Override - public String toString() { - return "Resources{" + "quantities=" + quantities + '}'; - } -} diff --git a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/TradingRules.java b/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/TradingRules.java deleted file mode 100644 index ffbce8ab..00000000 --- a/game-engine/src/main/java/org/luxons/sevenwonders/game/resources/TradingRules.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.luxons.sevenwonders.game.resources; - -import java.util.EnumMap; -import java.util.Map; - -public class TradingRules { - - private final Map<ResourceType, Map<Provider, Integer>> costs = new EnumMap<>(ResourceType.class); - - private final int defaultCost; - - public TradingRules(int defaultCost) { - this.defaultCost = defaultCost; - } - - public Map<ResourceType, Map<Provider, Integer>> getCosts() { - return costs; - } - - int getCost(ResourceType type, Provider provider) { - return costs.computeIfAbsent(type, t -> new EnumMap<>(Provider.class)).getOrDefault(provider, defaultCost); - } - - public void setCost(ResourceType type, Provider provider, int cost) { - costs.computeIfAbsent(type, t -> new EnumMap<>(Provider.class)).put(provider, cost); - } - - public int computeCost(ResourceTransactions transactions) { - return transactions.toTransactions().stream().mapToInt(this::computeCost).sum(); - } - - int computeCost(ResourceTransaction transaction) { - Resources resources = transaction.getResources(); - Provider provider = transaction.getProvider(); - return computeCost(resources, provider); - } - - private int computeCost(Resources resources, Provider provider) { - int total = 0; - for (ResourceType type : ResourceType.values()) { - int count = resources.getQuantity(type); - total += getCost(type, provider) * count; - } - return total; - } -} 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 e04fa7a0..4417620f 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 @@ -2,9 +2,9 @@ package org.luxons.sevenwonders.game.cards import org.luxons.sevenwonders.game.api.Table import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.resources.BestPriceCalculator import org.luxons.sevenwonders.game.resources.ResourceTransactions import org.luxons.sevenwonders.game.resources.Resources +import org.luxons.sevenwonders.game.resources.bestPrice data class Requirements @JvmOverloads constructor( val gold: Int = 0, @@ -55,7 +55,8 @@ data class Requirements @JvmOverloads constructor( if (producesRequiredResources(board)) { return true } - return BestPriceCalculator.bestPrice(resources, table, playerIndex) <= board.gold - gold + val bestPrice = bestPrice(resources, table, playerIndex) + return bestPrice != null && bestPrice <= board.gold - gold } private fun hasRequiredGold(board: Board): Boolean { 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 538fdbb4..781ee3af 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 @@ -16,7 +16,7 @@ class ProductionSerializer : JsonSerializer<Production>, JsonDeserializer<Produc override fun serialize(production: Production, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { val fixedResources = production.fixedResources - val choices = production.alternativeResources + val choices = production.getAlternativeResources() return when { fixedResources.isEmpty -> serializeAsChoice(choices, context) choices.isEmpty() -> serializeAsResources(fixedResources, context) 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 new file mode 100644 index 00000000..6e7d76f6 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt @@ -0,0 +1,122 @@ +package org.luxons.sevenwonders.game.resources + +import org.luxons.sevenwonders.game.api.Table +import java.util.ArrayList +import java.util.EnumSet + +fun bestPrice(resources: Resources, table: Table, playerIndex: Int): Int? { + return bestSolution(resources, table, playerIndex)?.price +} + +fun bestTransaction(resources: Resources, table: Table, playerIndex: Int): ResourceTransactions? { + return bestSolution(resources, table, playerIndex)?.transactions +} + +fun bestSolution(resources: Resources, table: Table, playerIndex: Int): TransactionPlan? { + val calculator = BestPriceCalculator(resources, table, playerIndex) + return calculator.computeBestSolution() +} + +data class TransactionPlan(val price: Int, val transactions: ResourceTransactions) + +private class ResourcePool( + val provider: Provider?, + private val rules: TradingRules, + val choices: Set<MutableSet<ResourceType>> +) { + fun getCost(type: ResourceType): Int = if (provider == null) 0 else rules.getCost(type, provider) +} + +private class BestPriceCalculator(resourcesToPay: Resources, table: Table, playerIndex: Int) { + + private val pools: List<ResourcePool> + private val resourcesLeftToPay: Resources + private val boughtResources: ResourceTransactions = ResourceTransactions() + private var pricePaid: Int = 0 + + var bestSolution: ResourceTransactions? = null + var bestPrice: Int = Integer.MAX_VALUE + + init { + val board = table.getBoard(playerIndex) + this.resourcesLeftToPay = resourcesToPay.minus(board.production.fixedResources) + this.pools = createResourcePools(table, playerIndex) + } + + private fun createResourcePools(table: Table, playerIndex: Int): List<ResourcePool> { + val providers = Provider.values() + + val board = table.getBoard(playerIndex) + val rules = board.tradingRules + + val pools = ArrayList<ResourcePool>(providers.size + 1) + // we only take alternative resources here, because fixed resources were already removed for optimization + val ownBoardChoices = board.production.getAlternativeResources() + pools.add(ResourcePool(null, rules, ownBoardChoices.map { it.toMutableSet() }.toSet())) + + for (provider in providers) { + val providerBoard = table.getBoard(playerIndex, provider.boardPosition) + val pool = ResourcePool(provider, rules, providerBoard.publicProduction.asChoices().map { it.toMutableSet() }.toSet()) + pools.add(pool) + } + return pools + } + + fun computeBestSolution(): TransactionPlan? { + computePossibilities() + return if (bestSolution == null) null else TransactionPlan(bestPrice, bestSolution!!) + } + + private fun computePossibilities() { + if (resourcesLeftToPay.isEmpty) { + updateBestSolutionIfNeeded() + return + } + for (type in ResourceType.values()) { + if (resourcesLeftToPay.getQuantity(type) > 0) { + for (pool in pools) { + if (pool.provider == null) { + computeSelfPossibilities(type, pool) + } else { + computeNeighbourPossibilities(pool, type, pool.provider) + } + } + } + } + } + + private fun computeSelfPossibilities(type: ResourceType, pool: ResourcePool) { + resourcesLeftToPay.remove(type, 1) + computePossibilitiesWhenUsing(type, pool) + resourcesLeftToPay.add(type, 1) + } + + 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 + computePossibilitiesWhenUsing(type, pool) + pricePaid -= cost + boughtResources.remove(provider, Resources(type)) + resourcesLeftToPay.add(type, 1) + } + + private fun computePossibilitiesWhenUsing(type: ResourceType, pool: ResourcePool) { + for (choice in pool.choices) { + if (choice.contains(type)) { + val temp = EnumSet.copyOf(choice) + choice.clear() + computePossibilities() + choice.addAll(temp) + } + } + } + + private fun updateBestSolutionIfNeeded() { + if (pricePaid < bestPrice) { + bestPrice = pricePaid + bestSolution = ResourceTransactions(boughtResources.asList()) + } + } +} 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 new file mode 100644 index 00000000..1a3240c2 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt @@ -0,0 +1,95 @@ +package org.luxons.sevenwonders.game.resources + +import java.util.Arrays +import java.util.EnumSet + +class Production { + + val fixedResources = Resources() + private val alternativeResources: MutableSet<Set<ResourceType>> = mutableSetOf() + + fun getAlternativeResources(): Set<Set<ResourceType>> { + return alternativeResources + } + + 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(production: Production) { + fixedResources.addAll(production.fixedResources) + alternativeResources.addAll(production.getAlternativeResources()) + } + + internal fun asChoices(): Set<Set<ResourceType>> { + val fixedAsChoices = fixedResources.asList().map{ EnumSet.of(it) }.toSet() + return fixedAsChoices + alternativeResources + } + + operator fun contains(resources: Resources): Boolean { + if (fixedResources.containsAll(resources)) { + return true + } + return containedInAlternatives(resources - fixedResources) + } + + private fun containedInAlternatives(resources: Resources): Boolean { + return containedInAlternatives(resources, alternativeResources) + } + + private fun containedInAlternatives(resources: Resources, alternatives: MutableSet<Set<ResourceType>>): Boolean { + if (resources.isEmpty) { + return true + } + for (type in ResourceType.values()) { + if (resources.getQuantity(type) <= 0) { + continue + } + val candidate = findFirstAlternativeContaining(alternatives, type) + ?: return false // no alternative produces the resource of this entry + resources.remove(type, 1) + alternatives.remove(candidate) + val remainingAreContainedToo = containedInAlternatives(resources, alternatives) + resources.add(type, 1) + alternatives.add(candidate) + if (remainingAreContainedToo) { + return true + } + } + 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/Provider.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Provider.kt new file mode 100644 index 00000000..5d0f3159 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Provider.kt @@ -0,0 +1,8 @@ +package org.luxons.sevenwonders.game.resources + +import org.luxons.sevenwonders.game.boards.RelativeBoardPosition + +enum class Provider(val boardPosition: RelativeBoardPosition) { + LEFT_PLAYER(RelativeBoardPosition.LEFT), + RIGHT_PLAYER(RelativeBoardPosition.RIGHT) +} 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 new file mode 100644 index 00000000..c500f0d0 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransaction.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.game.resources + +import org.luxons.sevenwonders.game.api.Table + +data class ResourceTransaction(val provider: Provider, val resources: Resources) { + + internal fun execute(table: Table, playerIndex: Int) { + val board = table.getBoard(playerIndex) + val price = board.tradingRules.computeCost(this) + board.removeGold(price) + val providerPosition = provider.boardPosition + val providerBoard = table.getBoard(playerIndex, 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 new file mode 100644 index 00000000..24556d19 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt @@ -0,0 +1,37 @@ +package org.luxons.sevenwonders.game.resources + +import org.luxons.sevenwonders.game.api.Table + +data class ResourceTransactions(private val resourcesByProvider: MutableMap<Provider, Resources> = mutableMapOf()) { + + constructor(transactions: Collection<ResourceTransaction>) : this() { + transactions.forEach { t -> add(t.provider, t.resources) } + } + + fun add(provider: Provider, resources: Resources) { + resourcesByProvider.merge(provider, resources) { old, new -> old + new } + } + + fun remove(provider: Provider, resources: Resources) { + resourcesByProvider.compute(provider) { p, prevResources -> + if (prevResources == null) { + throw IllegalStateException("Cannot remove resources from resource transactions") + } + prevResources.minus(resources) + } + } + + fun execute(table: Table, playerIndex: Int) { + asList().forEach { t -> t.execute(table, playerIndex) } + } + + fun asList(): List<ResourceTransaction> { + return resourcesByProvider + .filter { (_, resources) -> !resources.isEmpty } + .map { (provider, resources) -> ResourceTransaction(provider, resources) } + } + + fun asResources(): Resources { + return resourcesByProvider.values.fold(Resources(), Resources::plus) + } +} diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceType.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceType.kt new file mode 100644 index 00000000..67b176df --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceType.kt @@ -0,0 +1,28 @@ +package org.luxons.sevenwonders.game.resources + +enum class ResourceType(val symbol: Char?) { + WOOD('W'), + STONE('S'), + ORE('O'), + CLAY('C'), + GLASS('G'), + PAPYRUS('P'), + LOOM('L'); + + companion object { + + private val typesPerSymbol = values().map { it.symbol to it }.toMap() + + fun fromSymbol(symbol: String): ResourceType { + if (symbol.length != 1) { + throw IllegalArgumentException("The given symbol must be a valid single-char resource type, got $symbol") + } + return fromSymbol(symbol[0]) + } + + fun fromSymbol(symbol: Char?): ResourceType { + return typesPerSymbol[symbol] + ?: throw IllegalArgumentException(String.format("Unknown resource type symbol '%s'", symbol)) + } + } +} 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 new file mode 100644 index 00000000..96ad0b20 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt @@ -0,0 +1,72 @@ +package org.luxons.sevenwonders.game.resources + +import java.util.NoSuchElementException + +class Resources(quantities: Map<ResourceType, Int> = emptyMap()) { + + private val quantities: MutableMap<ResourceType, Int> = quantities.toMutableMap() + + constructor(singleResource: ResourceType): this(mapOf(singleResource to 1)) + + val isEmpty: Boolean + get() = size() == 0 + + fun add(type: ResourceType, quantity: Int) { + quantities.merge(type, quantity) { x, y -> x + y } + } + + 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 } + } + + fun addAll(resources: Resources) { + resources.quantities.forEach { type, quantity -> this.add(type, quantity) } + } + + fun getQuantity(type: ResourceType): Int = quantities[type] ?: 0 + + fun asList(): List<ResourceType> = quantities.flatMap { e -> List(e.value) { e.key } } + + fun containsAll(resources: Resources): Boolean = resources.quantities.all { it.value <= this.getQuantity(it.key) } + + operator fun plus(resources: Resources): Resources { + val new = Resources(this.quantities) + new.addAll(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() + quantities.forEach { type, count -> + val remainder = count - resources.getQuantity(type) + diff.quantities[type] = Math.max(0, remainder) + } + return diff + } + + fun size(): Int = quantities.values.sum() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Resources + + if (quantities != other.quantities) return false + + return true + } + + 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 new file mode 100644 index 00000000..b6dc09e6 --- /dev/null +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt @@ -0,0 +1,36 @@ +package org.luxons.sevenwonders.game.resources + +class TradingRules(private val defaultCost: Int) { + + private val costs: MutableMap<ResourceType, MutableMap<Provider, Int>> = mutableMapOf() + + fun getCosts(): Map<ResourceType, Map<Provider, Int>> { + return costs + } + + internal fun getCost(type: ResourceType, provider: Provider): Int = + costs.computeIfAbsent(type) { mutableMapOf() }.getOrDefault(provider, defaultCost) + + fun setCost(type: ResourceType, provider: Provider, cost: Int) { + costs.computeIfAbsent(type) { mutableMapOf() }[provider] = cost + } + + fun computeCost(transactions: ResourceTransactions): Int { + return transactions.asList().map { this.computeCost(it) }.sum() + } + + internal fun computeCost(transaction: ResourceTransaction): Int { + val resources = transaction.resources + val provider = transaction.provider + return computeCost(resources, 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 + } +} 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 028e7a0e..6ec279b6 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 @@ -8,9 +8,10 @@ import org.luxons.sevenwonders.game.api.PlayerMove import org.luxons.sevenwonders.game.api.PlayerTurnInfo import org.luxons.sevenwonders.game.api.Table 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.BestPriceCalculator import org.luxons.sevenwonders.game.resources.ResourceTransaction +import org.luxons.sevenwonders.game.resources.bestTransaction import org.luxons.sevenwonders.game.test.testCustomizableSettings import java.util.HashMap @@ -21,10 +22,9 @@ class GameTest { val nbPlayers = 5 val game = createGame(nbPlayers) - for (age in 1..3) { + for (age in 1..LAST_AGE) { playAge(nbPlayers, game, age) } - game.computeScore() } @@ -59,11 +59,11 @@ class GameTest { } private fun getFirstAvailableMove(turnInfo: PlayerTurnInfo): PlayerMove? { - when (turnInfo.action) { - Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> return createPlayCardMove(turnInfo) - Action.PICK_NEIGHBOR_GUILD -> return createPickGuildMove(turnInfo) - Action.WAIT -> return null - else -> return null + return when (turnInfo.action) { + Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(turnInfo) + Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(turnInfo) + Action.WAIT -> null + else -> null } } @@ -78,15 +78,16 @@ class GameTest { return PlayerMove(MoveType.DISCARD, firstCardInHand.card.name) } - private fun findResourcesToBuyFor(handCard: HandCard, turnInfo: PlayerTurnInfo): Set<ResourceTransaction> { + private fun findResourcesToBuyFor(handCard: HandCard, turnInfo: PlayerTurnInfo): List<ResourceTransaction> { if (handCard.isFree) { - return emptySet() + return emptyList() } val requiredResources = handCard.card.requirements.resources val table = turnInfo.table val playerIndex = turnInfo.playerIndex - val transactions = BestPriceCalculator.bestSolution(requiredResources, table, playerIndex) - return transactions.toTransactions() + val transactions = bestTransaction(requiredResources, table, playerIndex) + // we're supposed to have a best transaction plan because the card is playable + return transactions!!.asList() } private fun createPickGuildMove(turnInfo: PlayerTurnInfo): PlayerMove { 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 ffb4114c..500c2666 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 @@ -59,7 +59,7 @@ class ResourcesSerializerTest { resources.add(ResourceType.WOOD, 2) resources.add(ResourceType.CLAY, 1) resources.add(ResourceType.STONE, 1) - assertEquals("\"WWSCC\"", gson!!.toJson(resources)) + assertEquals("\"CCWWS\"", gson!!.toJson(resources)) } @Test 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 f4cf5294..005959bc 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 @@ -1,29 +1,26 @@ package org.luxons.sevenwonders.game.resources -import java.util.Arrays - +import org.junit.Assert.assertEquals import org.junit.Test import org.luxons.sevenwonders.game.api.Table -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.test.* - -import org.junit.Assert.assertEquals 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.CLAY -import org.luxons.sevenwonders.game.resources.ResourceType.GLASS -import org.luxons.sevenwonders.game.resources.ResourceType.ORE -import org.luxons.sevenwonders.game.resources.ResourceType.STONE -import org.luxons.sevenwonders.game.resources.ResourceType.WOOD +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 +import org.luxons.sevenwonders.game.test.testTable +import java.util.Arrays class BestPriceCalculatorTest { @Test fun bestPrice_0forEmptyResources() { val table = testTable(3) - val resources = Resources() - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 0).toLong()) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 0)) + val emptyResources = Resources() + val emptyTransactions = ResourceTransactions() + assertEquals(TransactionPlan(0, emptyTransactions), bestSolution(emptyResources, table, 0)) } @Test @@ -34,9 +31,6 @@ class BestPriceCalculatorTest { val table = Table(Arrays.asList(main, right, left)) val resources = createResources(STONE, STONE) - assertEquals(2, BestPriceCalculator.bestPrice(resources, table, 0).toLong()) - assertEquals(4, BestPriceCalculator.bestPrice(resources, table, 1).toLong()) - assertEquals(2, BestPriceCalculator.bestPrice(resources, table, 2).toLong()) val stoneLeftSingle = createTransaction(LEFT_PLAYER, STONE) val stoneRightSingle = createTransaction(RIGHT_PLAYER, STONE) @@ -45,9 +39,9 @@ class BestPriceCalculatorTest { val stoneRight = createTransactions(stoneRightSingle) val stoneLeftAndRight = createTransactions(stoneLeftSingle, stoneRightSingle) - assertEquals(stoneLeft, BestPriceCalculator.bestSolution(resources, table, 0)) - assertEquals(stoneLeftAndRight, BestPriceCalculator.bestSolution(resources, table, 1)) - assertEquals(stoneRight, BestPriceCalculator.bestSolution(resources, table, 2)) + assertEquals(TransactionPlan(2, stoneLeft), bestSolution(resources, table, 0)) + assertEquals(TransactionPlan(4, stoneLeftAndRight), bestSolution(resources, table, 1)) + assertEquals(TransactionPlan(2, stoneRight), bestSolution(resources, table, 2)) } @Test @@ -61,17 +55,14 @@ class BestPriceCalculatorTest { val table = Table(Arrays.asList(main, right, opposite, left)) val resources = createResources(WOOD) - assertEquals(1, BestPriceCalculator.bestPrice(resources, table, 0).toLong()) - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 1).toLong()) - assertEquals(2, BestPriceCalculator.bestPrice(resources, table, 2).toLong()) - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 3).toLong()) val woodLeft = createTransactions(LEFT_PLAYER, WOOD) val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - assertEquals(woodRight, BestPriceCalculator.bestSolution(resources, table, 0)) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 1)) - assertEquals(woodLeft, BestPriceCalculator.bestSolution(resources, table, 2)) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 3)) + + assertEquals(TransactionPlan(1, woodRight), bestSolution(resources, table, 0)) + assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, table, 1)) + assertEquals(TransactionPlan(2, woodLeft), bestSolution(resources, table, 2)) + assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, table, 3)) } @Test @@ -88,15 +79,11 @@ class BestPriceCalculatorTest { val table = Table(Arrays.asList(main, right, left)) val resources = createResources(WOOD) - assertEquals(1, BestPriceCalculator.bestPrice(resources, table, 0).toLong()) - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 1).toLong()) - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 2).toLong()) - val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - assertEquals(woodRight, BestPriceCalculator.bestSolution(resources, table, 0)) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 1)) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 2)) + assertEquals(TransactionPlan(1, woodRight), bestSolution(resources, table, 0)) + assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, table, 1)) + assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, table, 2)) } @Test @@ -116,14 +103,11 @@ class BestPriceCalculatorTest { val table = Table(Arrays.asList(main, right, left)) val resources = createResources(ORE, CLAY) - assertEquals(1, BestPriceCalculator.bestPrice(resources, table, 0).toLong()) - assertEquals(0, BestPriceCalculator.bestPrice(resources, table, 1).toLong()) - assertEquals(4, BestPriceCalculator.bestPrice(resources, table, 2).toLong()) - val oreAndClayLeft = createTransactions(LEFT_PLAYER, ORE, CLAY) val clayRight = createTransactions(RIGHT_PLAYER, CLAY) - assertEquals(clayRight, BestPriceCalculator.bestSolution(resources, table, 0)) - assertEquals(ResourceTransactions(), BestPriceCalculator.bestSolution(resources, table, 1)) - assertEquals(oreAndClayLeft, BestPriceCalculator.bestSolution(resources, table, 2)) + + assertEquals(TransactionPlan(1, clayRight), bestSolution(resources, table, 0)) + assertEquals(TransactionPlan(0, ResourceTransactions()), bestSolution(resources, table, 1)) + assertEquals(TransactionPlan(4, oreAndClayLeft), bestSolution(resources, table, 2)) } } 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 27f85c1a..ab0b22cf 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 @@ -12,38 +12,33 @@ import org.junit.Assert.assertTrue class ProductionTest { - private var emptyResources: Resources? = null - - private var resources1Wood: Resources? = null - - private var resources1Stone: Resources? = null - - private var resources1Stone1Wood: Resources? = null - - private var resources2Stones: Resources? = null - - private var resources2Stones3Clay: Resources? = null + 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() @Before fun init() { emptyResources = Resources() resources1Wood = Resources() - resources1Wood!!.add(ResourceType.WOOD, 1) + resources1Wood.add(ResourceType.WOOD, 1) resources1Stone = Resources() - resources1Stone!!.add(ResourceType.STONE, 1) + resources1Stone.add(ResourceType.STONE, 1) resources1Stone1Wood = Resources() - resources1Stone1Wood!!.add(ResourceType.STONE, 1) - resources1Stone1Wood!!.add(ResourceType.WOOD, 1) + resources1Stone1Wood.add(ResourceType.STONE, 1) + resources1Stone1Wood.add(ResourceType.WOOD, 1) resources2Stones = Resources() - resources2Stones!!.add(ResourceType.STONE, 2) + resources2Stones.add(ResourceType.STONE, 2) resources2Stones3Clay = Resources() - resources2Stones3Clay!!.add(ResourceType.STONE, 2) - resources2Stones3Clay!!.add(ResourceType.CLAY, 3) + resources2Stones3Clay.add(ResourceType.STONE, 2) + resources2Stones3Clay.add(ResourceType.CLAY, 3) } @Test @@ -235,7 +230,7 @@ class ProductionTest { production.addChoice(ResourceType.STONE, ResourceType.WOOD) production.addChoice(ResourceType.STONE, ResourceType.ORE) production.addChoice(ResourceType.CLAY, ResourceType.LOOM, ResourceType.GLASS) - assertEquals(production.alternativeResources, production.asChoices()) + assertEquals(production.getAlternativeResources(), production.asChoices()) } @Test @@ -280,16 +275,6 @@ class ProductionTest { } @Test - fun equals_falseWhenDifferentClass() { - val production = Production() - production.addFixedResource(ResourceType.GLASS, 1) - val resources = Resources() - resources.add(ResourceType.GLASS, 1) - - assertFalse(production == resources) - } - - @Test fun equals_trueWhenSame() { val production = Production() assertEquals(production, production) 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 11a03cd4..26ab16fa 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 @@ -1,36 +1,32 @@ package org.luxons.sevenwonders.game.resources -import java.util.ArrayList -import java.util.HashSet - -import org.junit.Test -import org.luxons.sevenwonders.game.test.* - import org.junit.Assert.assertEquals +import org.junit.Test +import org.luxons.sevenwonders.game.test.createTransaction class ResourceTransactionsTest { @Test fun toTransactions() { - val transactionList = ArrayList<ResourceTransaction>() - transactionList.add(createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD)) - transactionList.add(createTransaction(Provider.LEFT_PLAYER, ResourceType.CLAY)) - transactionList.add(createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD)) + val transactionList = listOf( + createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD), + createTransaction(Provider.LEFT_PLAYER, ResourceType.CLAY), + createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD) + ) val transactions = ResourceTransactions(transactionList) - val expectedNormalized = HashSet<ResourceTransaction>() - expectedNormalized.add( - createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD, ResourceType.CLAY) + val expectedNormalized = setOf( + createTransaction(Provider.LEFT_PLAYER, ResourceType.WOOD, ResourceType.CLAY), + createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD) ) - expectedNormalized.add(createTransaction(Provider.RIGHT_PLAYER, ResourceType.WOOD)) - assertEquals(expectedNormalized, HashSet(transactions.toTransactions())) + assertEquals(expectedNormalized, transactions.asList().toSet()) } @Test(expected = IllegalStateException::class) fun remove_failsIfNoResourceForThatProvider() { val transactions = ResourceTransactions() - transactions.remove(Provider.LEFT_PLAYER, Resources.of(ResourceType.WOOD)) + transactions.remove(Provider.LEFT_PLAYER, Resources(ResourceType.WOOD)) } } 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 72419fe3..a6ffb6c6 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 @@ -63,6 +63,50 @@ class ResourcesTest { } @Test + fun plus_zero() { + val resources = Resources(mapOf(ResourceType.CLAY to 2)) + val resourcesPlusZero = resources + Resources() + val zeroPlusResources = Resources() + resources + + assertEquals(2, resourcesPlusZero.getQuantity(ResourceType.CLAY)) + assertEquals(2, resourcesPlusZero.size()) + assertEquals(2, zeroPlusResources.getQuantity(ResourceType.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 sum = resources1 + resources2 + + assertEquals(4, sum.getQuantity(ResourceType.WOOD).toLong()) + assertEquals(4, sum.size()) + } + + @Test + fun plus_differentResources() { + val resources1 = Resources(mapOf(ResourceType.WOOD to 1)) + val resources2 = Resources(mapOf(ResourceType.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()) + } + + @Test + fun plus_overlappingResources() { + val resources1 = Resources(mapOf(ResourceType.WOOD to 1)) + val resources2 = Resources(mapOf(ResourceType.WOOD to 2, ResourceType.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()) + } + + @Test fun remove_some() { val resources = Resources() resources.add(ResourceType.WOOD, 3) @@ -173,7 +217,7 @@ class ResourcesTest { fun contains_emptyContainsEmpty() { val emptyResources = Resources() val emptyResources2 = Resources() - assertTrue(emptyResources.contains(emptyResources2)) + assertTrue(emptyResources.containsAll(emptyResources2)) } @Test @@ -183,7 +227,7 @@ class ResourcesTest { val emptyResources = Resources() - assertTrue(resources.contains(emptyResources)) + assertTrue(resources.containsAll(emptyResources)) } @Test @@ -194,7 +238,7 @@ class ResourcesTest { val emptyResources = Resources() - assertTrue(resources.contains(emptyResources)) + assertTrue(resources.containsAll(emptyResources)) } @Test @@ -203,7 +247,7 @@ class ResourcesTest { resources.add(ResourceType.STONE, 1) resources.add(ResourceType.CLAY, 3) - assertTrue(resources.contains(resources)) + assertTrue(resources.containsAll(resources)) } @Test @@ -216,7 +260,7 @@ class ResourcesTest { resources2.add(ResourceType.STONE, 1) resources2.add(ResourceType.CLAY, 3) - assertTrue(resources.contains(resources2)) + assertTrue(resources.containsAll(resources2)) } @Test @@ -229,7 +273,7 @@ class ResourcesTest { resources2.add(ResourceType.STONE, 1) resources2.add(ResourceType.CLAY, 3) - assertTrue(resources.contains(resources2)) + assertTrue(resources.containsAll(resources2)) } @Test @@ -241,7 +285,7 @@ class ResourcesTest { val resources2 = Resources() resources2.add(ResourceType.CLAY, 3) - assertTrue(resources.contains(resources2)) + assertTrue(resources.containsAll(resources2)) } @Test @@ -253,7 +297,7 @@ class ResourcesTest { val resources2 = Resources() resources2.add(ResourceType.CLAY, 4) - assertTrue(resources.contains(resources2)) + assertTrue(resources.containsAll(resources2)) } @Test @@ -445,16 +489,6 @@ class ResourcesTest { } @Test - fun equals_falseWhenDifferentClass() { - val resources = Resources() - resources.add(ResourceType.GLASS, 1) - val production = Production() - production.addFixedResource(ResourceType.GLASS, 1) - - assertFalse(resources == production) - } - - @Test fun equals_trueWhenSame() { val resources = Resources() assertEquals(resources, resources) |