summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/BestPriceCalculator.java112
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Production.java106
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Provider.java18
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourcePool.java33
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransaction.java53
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceTransactions.java71
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/ResourceType.java48
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/Resources.java112
-rw-r--r--game-engine/src/main/java/org/luxons/sevenwonders/game/resources/TradingRules.java46
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt5
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt2
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt122
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt95
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Provider.kt8
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransaction.kt15
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt37
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceType.kt28
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt72
-rw-r--r--game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt36
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt25
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt2
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt68
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt43
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt28
-rw-r--r--game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt70
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)
bgstack15