From 157edc9b8027f63998758d3c91cbaa1053550f0f Mon Sep 17 00:00:00 2001 From: Joffrey BION Date: Wed, 22 May 2019 02:40:48 +0200 Subject: Reorganize packages again --- .../kotlin/org/luxons/sevenwonders/engine/Game.kt | 187 +++ .../org/luxons/sevenwonders/engine/Player.kt | 28 + .../org/luxons/sevenwonders/engine/Settings.kt | 29 + .../org/luxons/sevenwonders/engine/boards/Board.kt | 98 ++ .../luxons/sevenwonders/engine/boards/Military.kt | 33 + .../luxons/sevenwonders/engine/boards/Science.kt | 56 + .../org/luxons/sevenwonders/engine/boards/Table.kt | 60 + .../org/luxons/sevenwonders/engine/cards/Cards.kt | 71 + .../org/luxons/sevenwonders/engine/cards/Decks.kt | 38 + .../org/luxons/sevenwonders/engine/cards/Hands.kt | 39 + .../sevenwonders/engine/cards/Requirements.kt | 83 ++ .../engine/cards/RequirementsSatisfaction.kt | 47 + .../sevenwonders/engine/converters/Boards.kt | 92 ++ .../luxons/sevenwonders/engine/converters/Cards.kt | 31 + .../luxons/sevenwonders/engine/converters/Table.kt | 21 + .../sevenwonders/engine/data/GameDefinition.kt | 92 ++ .../engine/data/definitions/CardDefinition.kt | 25 + .../engine/data/definitions/DecksDefinition.kt | 37 + .../engine/data/definitions/EffectsDefinition.kt | 34 + .../engine/data/definitions/WonderDefinition.kt | 29 + .../data/serializers/NumericEffectSerializer.kt | 38 + .../serializers/ProductionIncreaseSerializer.kt | 47 + .../data/serializers/ProductionSerializer.kt | 58 + .../data/serializers/ResourceTypeSerializer.kt | 26 + .../data/serializers/ResourceTypesSerializer.kt | 30 + .../engine/data/serializers/ResourcesSerializer.kt | 26 + .../data/serializers/ScienceProgressSerializer.kt | 55 + .../engine/effects/BonusPerBoardElement.kt | 36 + .../luxons/sevenwonders/engine/effects/Discount.kt | 19 + .../luxons/sevenwonders/engine/effects/Effect.kt | 15 + .../sevenwonders/engine/effects/EndGameEffect.kt | 9 + .../sevenwonders/engine/effects/GoldIncrease.kt | 8 + .../engine/effects/InstantOwnBoardEffect.kt | 14 + .../engine/effects/MilitaryReinforcements.kt | 8 + .../engine/effects/ProductionIncrease.kt | 14 + .../engine/effects/RawPointsIncrease.kt | 8 + .../sevenwonders/engine/effects/ScienceProgress.kt | 9 + .../sevenwonders/engine/effects/SpecialAbility.kt | 39 + .../engine/effects/SpecialAbilityActivation.kt | 10 + .../sevenwonders/engine/moves/BuildWonderMove.kt | 23 + .../sevenwonders/engine/moves/CardFromHandMove.kt | 15 + .../sevenwonders/engine/moves/CopyGuildMove.kt | 37 + .../sevenwonders/engine/moves/DiscardMove.kt | 18 + .../org/luxons/sevenwonders/engine/moves/Move.kt | 34 + .../sevenwonders/engine/moves/PlayCardMove.kt | 20 + .../sevenwonders/engine/moves/PlayFreeCardMove.kt | 25 + .../engine/resources/BestPriceCalculator.kt | 125 ++ .../sevenwonders/engine/resources/Production.kt | 67 + .../engine/resources/ResourceTransactions.kt | 32 + .../sevenwonders/engine/resources/Resources.kt | 89 ++ .../sevenwonders/engine/resources/TradingRules.kt | 29 + .../org/luxons/sevenwonders/engine/score/Score.kt | 23 + .../luxons/sevenwonders/engine/wonders/Wonder.kt | 65 + .../sevenwonders/engine/wonders/WonderStage.kt | 31 + .../kotlin/org/luxons/sevenwonders/game/Game.kt | 187 --- .../kotlin/org/luxons/sevenwonders/game/Player.kt | 28 - .../org/luxons/sevenwonders/game/Settings.kt | 29 - .../org/luxons/sevenwonders/game/api/Boards.kt | 91 -- .../org/luxons/sevenwonders/game/api/Cards.kt | 31 - .../org/luxons/sevenwonders/game/api/Table.kt | 19 - .../org/luxons/sevenwonders/game/boards/Board.kt | 98 -- .../luxons/sevenwonders/game/boards/Military.kt | 33 - .../org/luxons/sevenwonders/game/boards/Science.kt | 56 - .../org/luxons/sevenwonders/game/boards/Table.kt | 60 - .../org/luxons/sevenwonders/game/cards/Cards.kt | 71 - .../org/luxons/sevenwonders/game/cards/Decks.kt | 38 - .../org/luxons/sevenwonders/game/cards/Hands.kt | 39 - .../luxons/sevenwonders/game/cards/Requirements.kt | 83 -- .../game/cards/RequirementsSatisfaction.kt | 39 - .../sevenwonders/game/data/GameDefinition.kt | 92 -- .../game/data/definitions/CardDefinition.kt | 25 - .../game/data/definitions/DecksDefinition.kt | 37 - .../game/data/definitions/EffectsDefinition.kt | 34 - .../game/data/definitions/WonderDefinition.kt | 29 - .../data/serializers/NumericEffectSerializer.kt | 38 - .../serializers/ProductionIncreaseSerializer.kt | 47 - .../game/data/serializers/ProductionSerializer.kt | 58 - .../data/serializers/ResourceTypeSerializer.kt | 26 - .../data/serializers/ResourceTypesSerializer.kt | 30 - .../game/data/serializers/ResourcesSerializer.kt | 26 - .../data/serializers/ScienceProgressSerializer.kt | 55 - .../game/effects/BonusPerBoardElement.kt | 36 - .../luxons/sevenwonders/game/effects/Discount.kt | 19 - .../org/luxons/sevenwonders/game/effects/Effect.kt | 15 - .../sevenwonders/game/effects/EndGameEffect.kt | 9 - .../sevenwonders/game/effects/GoldIncrease.kt | 8 - .../game/effects/InstantOwnBoardEffect.kt | 14 - .../game/effects/MilitaryReinforcements.kt | 8 - .../game/effects/ProductionIncrease.kt | 14 - .../sevenwonders/game/effects/RawPointsIncrease.kt | 8 - .../sevenwonders/game/effects/ScienceProgress.kt | 9 - .../sevenwonders/game/effects/SpecialAbility.kt | 39 - .../game/effects/SpecialAbilityActivation.kt | 10 - .../sevenwonders/game/moves/BuildWonderMove.kt | 23 - .../sevenwonders/game/moves/CardFromHandMove.kt | 15 - .../sevenwonders/game/moves/CopyGuildMove.kt | 37 - .../luxons/sevenwonders/game/moves/DiscardMove.kt | 18 - .../org/luxons/sevenwonders/game/moves/Move.kt | 34 - .../luxons/sevenwonders/game/moves/PlayCardMove.kt | 20 - .../sevenwonders/game/moves/PlayFreeCardMove.kt | 25 - .../game/resources/BestPriceCalculator.kt | 125 -- .../sevenwonders/game/resources/Production.kt | 67 - .../game/resources/ResourceTransactions.kt | 32 - .../sevenwonders/game/resources/Resources.kt | 89 -- .../sevenwonders/game/resources/TradingRules.kt | 29 - .../org/luxons/sevenwonders/game/score/Score.kt | 23 - .../org/luxons/sevenwonders/game/wonders/Wonder.kt | 64 - .../sevenwonders/game/wonders/WonderStage.kt | 31 - .../org/luxons/sevenwonders/engine/data/cards.json | 1462 ++++++++++++++++++++ .../sevenwonders/engine/data/global_rules.json | 4 + .../luxons/sevenwonders/engine/data/wonders.json | 473 +++++++ .../org/luxons/sevenwonders/game/data/cards.json | 1462 -------------------- .../sevenwonders/game/data/global_rules.json | 4 - .../org/luxons/sevenwonders/game/data/wonders.json | 473 ------- .../org/luxons/sevenwonders/engine/GameTest.kt | 126 ++ .../luxons/sevenwonders/engine/boards/BoardTest.kt | 211 +++ .../sevenwonders/engine/boards/MilitaryTest.kt | 57 + .../engine/boards/RelativeBoardPositionTest.kt | 46 + .../sevenwonders/engine/boards/ScienceTest.kt | 114 ++ .../sevenwonders/engine/cards/CardBackTest.kt | 15 + .../luxons/sevenwonders/engine/cards/CardTest.kt | 43 + .../luxons/sevenwonders/engine/cards/DecksTest.kt | 104 ++ .../engine/cards/HandRotationDirectionTest.kt | 15 + .../luxons/sevenwonders/engine/cards/HandsTest.kt | 128 ++ .../sevenwonders/engine/cards/RequirementsTest.kt | 168 +++ .../sevenwonders/engine/data/GameDefinitionTest.kt | 20 + .../serializers/NumericEffectSerializerTest.kt | 147 ++ .../ProductionIncreaseSerializerTest.kt | 192 +++ .../data/serializers/ProductionSerializerTest.kt | 207 +++ .../data/serializers/ResourceTypeSerializerTest.kt | 56 + .../serializers/ResourceTypesSerializerTest.kt | 80 ++ .../data/serializers/ResourcesSerializerTest.kt | 99 ++ .../serializers/ScienceProgressSerializerTest.kt | 157 +++ .../engine/effects/BonusPerBoardElementTest.kt | 142 ++ .../sevenwonders/engine/effects/DiscountTest.kt | 69 + .../engine/effects/GoldIncreaseTest.kt | 43 + .../engine/effects/MilitaryReinforcementsTest.kt | 44 + .../engine/effects/ProductionIncreaseTest.kt | 73 + .../engine/effects/RawPointsIncreaseTest.kt | 29 + .../engine/effects/ScienceProgressTest.kt | 49 + .../engine/effects/SpecialAbilityActivationTest.kt | 93 ++ .../engine/moves/BuildWonderMoveTest.kt | 83 ++ .../engine/resources/BestPriceCalculatorTest.kt | 139 ++ .../engine/resources/ProductionTest.kt | 293 ++++ .../engine/resources/ResourceTransactionsTest.kt | 29 + .../sevenwonders/engine/resources/ResourcesTest.kt | 436 ++++++ .../engine/resources/TradingRulesTest.kt | 129 ++ .../luxons/sevenwonders/engine/test/TestUtils.kt | 142 ++ .../sevenwonders/engine/wonders/WonderTest.kt | 34 + .../org/luxons/sevenwonders/game/GameTest.kt | 120 -- .../luxons/sevenwonders/game/api/BoardsKtTest.kt | 84 -- .../org/luxons/sevenwonders/game/api/TableTest.kt | 71 - .../luxons/sevenwonders/game/boards/BoardTest.kt | 211 --- .../sevenwonders/game/boards/MilitaryTest.kt | 57 - .../game/boards/RelativeBoardPositionTest.kt | 46 - .../luxons/sevenwonders/game/boards/ScienceTest.kt | 114 -- .../luxons/sevenwonders/game/cards/CardBackTest.kt | 15 - .../org/luxons/sevenwonders/game/cards/CardTest.kt | 43 - .../luxons/sevenwonders/game/cards/DecksTest.kt | 104 -- .../game/cards/HandRotationDirectionTest.kt | 15 - .../luxons/sevenwonders/game/cards/HandsTest.kt | 128 -- .../sevenwonders/game/cards/RequirementsTest.kt | 168 --- .../sevenwonders/game/data/GameDefinitionTest.kt | 20 - .../serializers/NumericEffectSerializerTest.kt | 147 -- .../ProductionIncreaseSerializerTest.kt | 192 --- .../data/serializers/ProductionSerializerTest.kt | 207 --- .../data/serializers/ResourceTypeSerializerTest.kt | 56 - .../serializers/ResourceTypesSerializerTest.kt | 80 -- .../data/serializers/ResourcesSerializerTest.kt | 99 -- .../serializers/ScienceProgressSerializerTest.kt | 157 --- .../game/effects/BonusPerBoardElementTest.kt | 142 -- .../sevenwonders/game/effects/DiscountTest.kt | 69 - .../sevenwonders/game/effects/GoldIncreaseTest.kt | 43 - .../game/effects/MilitaryReinforcementsTest.kt | 44 - .../game/effects/ProductionIncreaseTest.kt | 73 - .../game/effects/RawPointsIncreaseTest.kt | 29 - .../game/effects/ScienceProgressTest.kt | 49 - .../game/effects/SpecialAbilityActivationTest.kt | 93 -- .../sevenwonders/game/moves/BuildWonderMoveTest.kt | 83 -- .../game/resources/BestPriceCalculatorTest.kt | 139 -- .../sevenwonders/game/resources/ProductionTest.kt | 293 ---- .../game/resources/ResourceTransactionsTest.kt | 29 - .../sevenwonders/game/resources/ResourcesTest.kt | 436 ------ .../game/resources/TradingRulesTest.kt | 129 -- .../org/luxons/sevenwonders/game/test/TestUtils.kt | 143 -- .../luxons/sevenwonders/game/wonders/WonderTest.kt | 34 - .../org/luxons/sevenwonders/model/BoardsKtTest.kt | 86 ++ .../org/luxons/sevenwonders/model/TableTest.kt | 71 + 188 files changed, 8140 insertions(+), 8121 deletions(-) create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Player.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Settings.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Military.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Science.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Table.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Decks.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Boards.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Table.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/GameDefinition.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/CardDefinition.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/DecksDefinition.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/EffectsDefinition.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/WonderDefinition.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializer.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElement.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Discount.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Effect.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/EndGameEffect.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncrease.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/InstantOwnBoardEffect.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcements.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncrease.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncrease.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgress.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbility.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivation.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CardFromHandMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CopyGuildMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/DiscardMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayCardMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeCardMove.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Production.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Resources.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TradingRules.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/score/Score.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt create mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/WonderStage.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Player.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Settings.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Military.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Science.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Table.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderDefinition.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializer.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElement.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Discount.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Effect.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/EndGameEffect.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/GoldIncrease.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/InstantOwnBoardEffect.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcements.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncrease.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncrease.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgress.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbility.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivation.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CardFromHandMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CopyGuildMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/DiscardMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayCardMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayFreeCardMove.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/score/Score.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt delete mode 100644 sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt create mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/cards.json create mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/global_rules.json create mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/wonders.json delete mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/cards.json delete mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/global_rules.json delete mode 100644 sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/wonders.json create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/BoardTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/MilitaryTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/RelativeBoardPositionTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/ScienceTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardBackTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/DecksTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandRotationDirectionTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandsTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/GameDefinitionTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializerTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElementTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/DiscountTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncreaseTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcementsTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncreaseTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncreaseTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgressTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivationTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMoveTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ProductionTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourcesTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TradingRulesTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/wonders/WonderTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt delete mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/BoardsKtTest.kt create mode 100644 sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/TableTest.kt (limited to 'sw-engine') diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt new file mode 100644 index 00000000..bbd96d33 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Game.kt @@ -0,0 +1,187 @@ +package org.luxons.sevenwonders.engine + +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.cards.Decks +import org.luxons.sevenwonders.engine.cards.Hands +import org.luxons.sevenwonders.engine.converters.toApiTable +import org.luxons.sevenwonders.engine.converters.toPlayedMove +import org.luxons.sevenwonders.engine.converters.toTableCard +import org.luxons.sevenwonders.engine.data.LAST_AGE +import org.luxons.sevenwonders.engine.effects.SpecialAbility +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.engine.moves.resolve +import org.luxons.sevenwonders.engine.score.ScoreBoard +import org.luxons.sevenwonders.model.Action +import org.luxons.sevenwonders.model.ApiTable +import org.luxons.sevenwonders.model.PlayerMove +import org.luxons.sevenwonders.model.PlayerTurnInfo +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.HandCard + +class Game internal constructor( + val id: Long, + private val settings: Settings, + boards: List, + private val decks: Decks +) { + private val table: Table = Table(boards) + private val players: List = boards.map { SimplePlayer(it.playerIndex, table) } + private val discardedCards: MutableList = mutableListOf() + private val preparedMoves: MutableMap = mutableMapOf() + private var currentTurnInfo: List = emptyList() + private var hands: Hands = Hands(emptyList()) + + init { + startNewAge() + } + + private fun startNewAge() { + table.increaseCurrentAge() + hands = decks.deal(table.currentAge, players.size) + startNewTurn() + } + + private fun startNewTurn() { + currentTurnInfo = players.map { createPlayerTurnInfo(it) } + } + + private fun createPlayerTurnInfo(player: Player): PlayerTurnInfo { + val hand = hands.createHand(player) + val action = determineAction(hand, player.board) + val neighbourGuildCards = table.getNeighbourGuildCards(player.index).map { it.toTableCard(null) } + + return PlayerTurnInfo( + playerIndex = player.index, + table = table.toApiTable(), + action = action, + hand = hand, + preparedMove = preparedMoves[player.index]?.toPlayedMove(), + neighbourGuildCards = neighbourGuildCards + ) + } + + /** + * Returns information for each player about the current turn. + */ + fun getCurrentTurnInfo(): Collection = currentTurnInfo + + private fun determineAction(hand: List, board: Board): Action = when { + endOfGameReached() && board.hasSpecial(SpecialAbility.COPY_GUILD) -> determineCopyGuildAction(board) + hand.size == 1 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_LAST + hand.size == 2 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_2 + hand.isEmpty() -> Action.WAIT + else -> Action.PLAY + } + + private fun determineCopyGuildAction(board: Board): Action { + val neighbourGuildCards = table.getNeighbourGuildCards(board.playerIndex) + return if (neighbourGuildCards.isEmpty()) Action.WAIT else Action.PICK_NEIGHBOR_GUILD + } + + /** + * Prepares the given [move] for the player at the given [playerIndex]. + * + * @return the back of the card that is prepared on the table + */ + fun prepareMove(playerIndex: Int, move: PlayerMove): CardBack { + val card = decks.getCard(table.currentAge, move.cardName) + val context = PlayerContext(playerIndex, table, hands[playerIndex]) + val resolvedMove = move.type.resolve(move, card, context) + preparedMoves[playerIndex] = resolvedMove + return card.back + } + + /** + * Returns true if all players that had to do something have [prepared their move][prepareMove]. This means we are + * ready to [play the current turn][playTurn]. + */ + fun allPlayersPreparedTheirMove(): Boolean { + val nbExpectedMoves = currentTurnInfo.count { it.action !== Action.WAIT } + return preparedMoves.size == nbExpectedMoves + } + + /** + * Plays all the [prepared moves][prepareMove] for the current turn. An exception will be thrown if some players + * had not prepared their moves (unless these players had nothing to do). To avoid this, please check if everyone + * is ready using [allPlayersPreparedTheirMove]. + */ + fun playTurn(): ApiTable { + makeMoves() + if (endOfAgeReached()) { + executeEndOfAgeEvents() + if (!endOfGameReached()) { + startNewAge() + } + } else { + rotateHandsIfRelevant() + startNewTurn() + } + return table.toApiTable() + } + + private fun makeMoves() { + val moves = getMovesToPerform() + + // all cards from this turn need to be placed before executing any effect + // because effects depending on played cards need to take the ones from the current turn into account too + placePreparedCards(moves) + + // same goes for the discarded cards during the last turn, which should be available for special actions + if (hands.maxOneCardRemains()) { + discardLastCardsOfHands() + } + + activatePlayedCards(moves) + + table.lastPlayedMoves = moves + preparedMoves.clear() + } + + private fun getMovesToPerform(): List = + currentTurnInfo.filter { it.action !== Action.WAIT }.map { getMoveToPerformFor(it.playerIndex) } + + private fun getMoveToPerformFor(playerIndex: Int) = + preparedMoves[playerIndex] ?: throw MissingPreparedMoveException(playerIndex) + + private fun endOfAgeReached(): Boolean = hands.isEmpty + + private fun executeEndOfAgeEvents() = table.resolveMilitaryConflicts() + + private fun endOfGameReached(): Boolean = endOfAgeReached() && table.currentAge == LAST_AGE + + private fun rotateHandsIfRelevant() { + // we don't rotate hands if some player can play his last card (with the special ability) + if (!hands.maxOneCardRemains()) { + hands = hands.rotate(table.handRotationDirection) + } + } + + private fun placePreparedCards(playedMoves: List) { + playedMoves.forEach { move -> + move.place(discardedCards, settings) + hands = hands.remove(move.playerContext.index, move.card) + } + } + + private fun discardLastCardsOfHands() = + table.boards.filterNot { it.hasSpecial(SpecialAbility.PLAY_LAST_CARD) }.forEach { discardHand(it.playerIndex) } + + private fun discardHand(playerIndex: Int) { + val hand = hands[playerIndex] + discardedCards.addAll(hand) + hands = hands.discardHand(playerIndex) + } + + private fun activatePlayedCards(playedMoves: List) = + playedMoves.forEach { it.activate(discardedCards, settings) } + + /** + * Computes the score for all players. + */ + fun computeScore(): ScoreBoard = ScoreBoard(table.boards.map { it.computeScore(players[it.playerIndex]) }) + + private class MissingPreparedMoveException internal constructor(playerIndex: Int) : + IllegalStateException("Player $playerIndex has not prepared his move") +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Player.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Player.kt new file mode 100644 index 00000000..e28fa7bf --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Player.kt @@ -0,0 +1,28 @@ +package org.luxons.sevenwonders.engine + +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition + +internal interface Player { + val index: Int + val board: Board + fun getBoard(relativePosition: RelativeBoardPosition): Board +} + +internal data class SimplePlayer( + override val index: Int, + private val table: Table +) : Player { + override val board = table.getBoard(index) + override fun getBoard(relativePosition: RelativeBoardPosition) = table.getBoard(index, relativePosition) +} + +internal data class PlayerContext( + override val index: Int, + private val table: Table, + val hand: List +) : Player by SimplePlayer(index, table) { + val currentAge = table.currentAge +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Settings.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Settings.kt new file mode 100644 index 00000000..7461762a --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/Settings.kt @@ -0,0 +1,29 @@ +package org.luxons.sevenwonders.engine + +import org.luxons.sevenwonders.model.CustomizableSettings +import org.luxons.sevenwonders.model.WonderSide +import org.luxons.sevenwonders.model.WonderSidePickMethod +import kotlin.random.Random + +internal class Settings( + val nbPlayers: Int, + customSettings: CustomizableSettings = CustomizableSettings() +) { + val random: Random = customSettings.randomSeedForTests?.let { Random(it) } ?: Random + val timeLimitInSeconds: Int = customSettings.timeLimitInSeconds + val initialGold: Int = customSettings.initialGold + val discardedCardGold: Int = customSettings.discardedCardGold + val defaultTradingCost: Int = customSettings.defaultTradingCost + val pointsPer3Gold: Int = customSettings.pointsPer3Gold + val lostPointsPerDefeat: Int = customSettings.lostPointsPerDefeat + val wonPointsPerVictoryPerAge: Map = customSettings.wonPointsPerVictoryPerAge + + private val wonderSidePickMethod: WonderSidePickMethod = customSettings.wonderSidePickMethod + private var lastPickedSide: WonderSide? = null + + fun pickWonderSide(): WonderSide { + val newSide = wonderSidePickMethod.pickSide(random, lastPickedSide) + lastPickedSide = newSide + return newSide + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt new file mode 100644 index 00000000..a98da0bf --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Board.kt @@ -0,0 +1,98 @@ +package org.luxons.sevenwonders.engine.boards + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.effects.SpecialAbility +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.engine.resources.TradingRules +import org.luxons.sevenwonders.engine.resources.mutableResourcesOf +import org.luxons.sevenwonders.engine.score.PlayerScore +import org.luxons.sevenwonders.engine.score.ScoreCategory +import org.luxons.sevenwonders.engine.wonders.Wonder +import org.luxons.sevenwonders.model.Age +import org.luxons.sevenwonders.model.cards.Color + +internal class Board(val wonder: Wonder, val playerIndex: Int, settings: Settings) { + + val production = Production(mutableResourcesOf(wonder.initialResource)) + val publicProduction = Production(mutableResourcesOf(wonder.initialResource)) + val science = Science() + val military: Military = Military(settings.lostPointsPerDefeat, settings.wonPointsPerVictoryPerAge) + val tradingRules: TradingRules = TradingRules(settings.defaultTradingCost) + + private val pointsPer3Gold: Int = settings.pointsPer3Gold + + private val playedCards: MutableList = mutableListOf() + private val specialAbilities: MutableSet = hashSetOf() + private val consumedFreeCards: MutableMap = mutableMapOf() + + var gold: Int = settings.initialGold + private set + + var copiedGuild: Card? = null + internal set(copiedGuild) { + if (copiedGuild!!.color !== Color.PURPLE) { + throw IllegalArgumentException("The given card '$copiedGuild' is not a Guild card") + } + field = copiedGuild + } + + fun getPlayedCards(): List = playedCards + + fun addCard(card: Card) { + playedCards.add(card) + } + + fun getNbCardsOfColor(colorFilter: List): Int = playedCards.count { colorFilter.contains(it.color) } + + fun isPlayed(cardName: String): Boolean = playedCards.count { it.name == cardName } > 0 + + fun addGold(amount: Int) { + this.gold += amount + } + + fun removeGold(amount: Int) { + if (gold < amount) { + throw InsufficientFundsException(gold, amount) + } + this.gold -= amount + } + + fun addSpecial(specialAbility: SpecialAbility) { + specialAbilities.add(specialAbility) + } + + fun hasSpecial(specialAbility: SpecialAbility): Boolean = specialAbilities.contains(specialAbility) + + fun canPlayFreeCard(age: Age): Boolean = + hasSpecial(SpecialAbility.ONE_FREE_PER_AGE) && !consumedFreeCards.getOrDefault(age, false) + + fun consumeFreeCard(age: Age) { + consumedFreeCards[age] = true + } + + fun computeScore(player: Player): PlayerScore = PlayerScore( + boardGold = gold, + pointsByCategory = mapOf( + ScoreCategory.CIVIL to computePointsForCards(player, Color.BLUE), + ScoreCategory.MILITARY to military.totalPoints, + ScoreCategory.SCIENCE to science.computePoints(), + ScoreCategory.TRADE to computePointsForCards(player, Color.YELLOW), + ScoreCategory.GUILD to computePointsForCards(player, Color.PURPLE), + ScoreCategory.WONDER to wonder.computePoints(player), + ScoreCategory.GOLD to computeGoldPoints() + ) + ) + + private fun computePointsForCards(player: Player, color: Color): Int = + playedCards.filter { it.color === color } + .flatMap { it.effects } + .map { it.computePoints(player) } + .sum() + + private fun computeGoldPoints(): Int = gold / 3 * pointsPer3Gold + + internal class InsufficientFundsException(current: Int, required: Int) : + IllegalStateException("Current balance is $current gold, but $required are required") +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Military.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Military.kt new file mode 100644 index 00000000..a4cb4ee8 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Military.kt @@ -0,0 +1,33 @@ +package org.luxons.sevenwonders.engine.boards + +import org.luxons.sevenwonders.model.Age + +internal class Military( + private val lostPointsPerDefeat: Int, + private val wonPointsPerVictoryPerAge: Map +) { + var nbShields = 0 + private set + + var totalPoints = 0 + private set + + var nbDefeatTokens = 0 + private set + + internal fun addShields(nbShields: Int) { + this.nbShields += nbShields + } + + internal fun victory(age: Age) { + val wonPoints = wonPointsPerVictoryPerAge[age] ?: throw UnknownAgeException(age) + totalPoints += wonPoints + } + + internal fun defeat() { + totalPoints -= lostPointsPerDefeat + nbDefeatTokens++ + } + + internal class UnknownAgeException(unknownAge: Age) : IllegalArgumentException(unknownAge.toString()) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Science.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Science.kt new file mode 100644 index 00000000..436d8995 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Science.kt @@ -0,0 +1,56 @@ +package org.luxons.sevenwonders.engine.boards + +enum class ScienceType { + COMPASS, + WHEEL, + TABLET +} + +internal class Science { + + private val quantities: MutableMap = mutableMapOf() + + var jokers: Int = 0 + private set + + fun size(): Int = quantities.values.sum() + jokers + + fun add(type: ScienceType, quantity: Int) { + quantities.merge(type, quantity) { x, y -> x + y } + } + + fun addJoker(quantity: Int) { + jokers += quantity + } + + fun addAll(science: Science) { + science.quantities.forEach { type, quantity -> this.add(type, quantity) } + jokers += science.jokers + } + + fun getQuantity(type: ScienceType): Int = quantities.getOrDefault(type, 0) + + fun computePoints(): Int { + val values = ScienceType.values().map(::getQuantity).toMutableList() + return computePoints(values, jokers) + } + + private fun computePoints(values: MutableList, jokers: Int): Int { + if (jokers == 0) { + return computePointsNoJoker(values) + } + var maxPoints = 0 + for (i in values.indices) { + values[i]++ + maxPoints = Math.max(maxPoints, computePoints(values, jokers - 1)) + values[i]-- + } + return maxPoints + } + + private fun computePointsNoJoker(values: List): Int { + val independentSquaresSum = values.map { i -> i * i }.sum() + val nbGroupsOfAll = values.min() ?: 0 + return independentSquaresSum + nbGroupsOfAll * 7 + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Table.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Table.kt new file mode 100644 index 00000000..b8da6ae7 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/boards/Table.kt @@ -0,0 +1,60 @@ +package org.luxons.sevenwonders.engine.boards + +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.model.Age +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.model.boards.neighboursPositions +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.model.cards.HandRotationDirection + +/** + * The table contains what is visible by all the players in the game: the boards and their played cards, and the + * players' information. + */ +internal class Table(val boards: List) { + + val nbPlayers: Int = boards.size + + var currentAge: Age = 0 + private set + + val handRotationDirection: HandRotationDirection + get() = HandRotationDirection.forAge(currentAge) + + var lastPlayedMoves: List = emptyList() + internal set + + fun getBoard(playerIndex: Int): Board = boards[playerIndex] + + fun getBoard(playerIndex: Int, position: RelativeBoardPosition): Board = + boards[position.getIndexFrom(playerIndex, nbPlayers)] + + fun increaseCurrentAge() { + this.currentAge++ + } + + fun resolveMilitaryConflicts() { + repeat(nbPlayers) { + val board1 = getBoard(it) + val board2 = getBoard(it, RelativeBoardPosition.RIGHT) + resolveConflict(board1, board2) + } + } + + private fun resolveConflict(board1: Board, board2: Board) { + val shields1 = board1.military.nbShields + val shields2 = board2.military.nbShields + if (shields1 < shields2) { + board1.military.defeat() + board2.military.victory(currentAge) + } else if (shields1 > shields2) { + board1.military.victory(currentAge) + board2.military.defeat() + } + } + + fun getNeighbourGuildCards(playerIndex: Int): List = neighboursPositions() + .flatMap { getBoard(playerIndex, it).getPlayedCards() } + .filter { it.color == Color.PURPLE } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt new file mode 100644 index 00000000..cfa46d27 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Cards.kt @@ -0,0 +1,71 @@ +package org.luxons.sevenwonders.engine.cards + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.effects.Effect +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.CardPlayability +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.model.cards.PlayabilityLevel +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.noTransactions + +internal data class Card( + val name: String, + val color: Color, + val requirements: Requirements, + val effects: List, + val chainParent: String?, + val chainChildren: List, + val image: String, + val back: CardBack +) { + fun computePlayabilityBy(player: Player): CardPlayability = when { + isAlreadyOnBoard(player.board) -> Playability.incompatibleWithBoard() // cannot play twice the same card + isParentOnBoard(player.board) -> Playability.chainable() + else -> Playability.requirementDependent(requirements.assess(player)) + } + + fun isPlayableOnBoardWith(board: Board, transactions: ResourceTransactions) = + isChainableOn(board) || requirements.areMetWithHelpBy(board, transactions) + + private fun isChainableOn(board: Board): Boolean = !isAlreadyOnBoard(board) && isParentOnBoard(board) + + private fun isAlreadyOnBoard(board: Board): Boolean = board.isPlayed(name) + + private fun isParentOnBoard(board: Board): Boolean = chainParent != null && board.isPlayed(chainParent) + + fun applyTo(player: Player, transactions: ResourceTransactions) { + if (!isChainableOn(player.board)) { + requirements.pay(player, transactions) + } + effects.forEach { it.applyTo(player) } + } +} + +private object Playability { + + internal fun incompatibleWithBoard(): CardPlayability = + CardPlayability( + isPlayable = false, + playabilityLevel = PlayabilityLevel.INCOMPATIBLE_WITH_BOARD + ) + + internal fun chainable(): CardPlayability = + CardPlayability( + isPlayable = true, + isChainable = true, + minPrice = 0, + cheapestTransactions = setOf(noTransactions()), + playabilityLevel = PlayabilityLevel.CHAINABLE + ) + + internal fun requirementDependent(satisfaction: RequirementsSatisfaction): CardPlayability = + CardPlayability( + isPlayable = satisfaction.satisfied, + isChainable = false, + minPrice = satisfaction.minPrice, + cheapestTransactions = satisfaction.cheapestTransactions, + playabilityLevel = satisfaction.level + ) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Decks.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Decks.kt new file mode 100644 index 00000000..01026722 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Decks.kt @@ -0,0 +1,38 @@ +package org.luxons.sevenwonders.engine.cards + +internal fun List.deal(nbPlayers: Int): Hands { + val hands: Map> = this.withIndex() + .groupBy { (index, _) -> index % nbPlayers } + .mapValues { it.value.map { (_, cards) -> cards } } + + val allHands = List(nbPlayers) { i -> hands[i] ?: emptyList() } + return Hands(allHands) +} + +internal class Decks(private val cardsPerAge: Map>) { + + @Throws(Decks.CardNotFoundException::class) + fun getCard(age: Int, cardName: String): Card = + getDeck(age).firstOrNull { c -> c.name == cardName } ?: throw CardNotFoundException(cardName) + + fun deal(age: Int, nbPlayers: Int): Hands { + val deck = getDeck(age) + validateNbCards(deck, nbPlayers) + return deck.deal(nbPlayers) + } + + private fun getDeck(age: Int): List { + return cardsPerAge[age] ?: throw IllegalArgumentException("No deck found for age $age") + } + + private fun validateNbCards(deck: List, nbPlayers: Int) { + if (nbPlayers == 0) { + throw IllegalArgumentException("Cannot deal cards between 0 players") + } + if (deck.size % nbPlayers != 0) { + throw IllegalArgumentException("Cannot deal ${deck.size} cards evenly between $nbPlayers players") + } + } + + inner class CardNotFoundException(message: String) : RuntimeException(message) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt new file mode 100644 index 00000000..9f73cd67 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Hands.kt @@ -0,0 +1,39 @@ +package org.luxons.sevenwonders.engine.cards + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.converters.toHandCard +import org.luxons.sevenwonders.model.cards.HandCard +import org.luxons.sevenwonders.model.cards.HandRotationDirection + +internal class Hands(private val hands: List>) { + + val isEmpty: Boolean = this.hands.all(List::isEmpty) + + operator fun get(playerIndex: Int): List { + return hands[playerIndex] + } + + fun discardHand(playerIndex: Int): Hands { + val mutatedHands = hands.toMutableList() + mutatedHands[playerIndex] = emptyList() + return Hands(mutatedHands) + } + + fun remove(playerIndex: Int, card: Card): Hands { + val mutatedHands = hands.toMutableList() + mutatedHands[playerIndex] = hands[playerIndex] - card + return Hands(mutatedHands) + } + + fun createHand(player: Player): List = hands[player.index].map { c -> c.toHandCard(player) } + + fun rotate(direction: HandRotationDirection): Hands { + val newHands = when (direction) { + HandRotationDirection.RIGHT -> hands.takeLast(1) + hands.dropLast(1) + HandRotationDirection.LEFT -> hands.drop(1) + hands.take(1) + } + return Hands(newHands) + } + + fun maxOneCardRemains(): Boolean = hands.map { it.size }.max() ?: 0 <= 1 +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt new file mode 100644 index 00000000..b9ffe247 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/Requirements.kt @@ -0,0 +1,83 @@ +package org.luxons.sevenwonders.engine.cards + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.engine.resources.asResources +import org.luxons.sevenwonders.engine.resources.bestSolution +import org.luxons.sevenwonders.engine.resources.emptyResources +import org.luxons.sevenwonders.engine.resources.execute +import org.luxons.sevenwonders.model.resources.ResourceTransactions + +data class Requirements internal constructor( + val gold: Int = 0, + val resources: Resources = emptyResources() +) { + /** + * Returns information about the extent to which the given [player] meets these requirements, either on its own or + * by buying resources to neighbours. + */ + internal fun assess(player: Player): RequirementsSatisfaction { + if (player.board.gold < gold) { + return RequirementsSatisfaction.missingRequiredGold(gold) + } + if (resources.isEmpty()) { + if (gold > 0) { + return RequirementsSatisfaction.enoughGold(gold) + } + return RequirementsSatisfaction.noRequirements() + } + if (producesRequiredResources(player.board)) { + if (gold > 0) { + return RequirementsSatisfaction.enoughResourcesAndGold(gold) + } + return RequirementsSatisfaction.enoughResources() + } + return satisfactionWithPotentialHelp(player) + } + + private fun satisfactionWithPotentialHelp(player: Player): RequirementsSatisfaction { + val (minPriceForResources, possibleTransactions) = bestSolution(resources, player) + val minPrice = minPriceForResources + gold + if (possibleTransactions.isEmpty()) { + return RequirementsSatisfaction.unavailableResources() + } + if (player.board.gold < minPrice) { + return RequirementsSatisfaction.missingGoldForResources(minPrice, possibleTransactions) + } + return RequirementsSatisfaction.metWithHelp(minPrice, possibleTransactions) + } + + /** + * Returns whether the given board meets these requirements, if the specified resources are bought from neighbours. + * + * @param board the board to check + * @param boughtResources the resources the player intends to buy + * + * @return true if the given board meets these requirements + */ + internal fun areMetWithHelpBy(board: Board, boughtResources: ResourceTransactions): Boolean { + if (!hasRequiredGold(board, boughtResources)) { + return false + } + return producesRequiredResources(board) || producesRequiredResourcesWithHelp(board, boughtResources) + } + + private fun hasRequiredGold(board: Board, resourceTransactions: ResourceTransactions): Boolean { + val resourcesPrice = board.tradingRules.computeCost(resourceTransactions) + return board.gold >= gold + resourcesPrice + } + + private fun producesRequiredResources(board: Board): Boolean = board.production.contains(resources) + + private fun producesRequiredResourcesWithHelp(board: Board, transactions: ResourceTransactions): Boolean { + val totalBoughtResources = transactions.asResources() + val remainingResources = resources - totalBoughtResources + return board.production.contains(remainingResources) + } + + internal fun pay(player: Player, transactions: ResourceTransactions) { + player.board.removeGold(gold) + transactions.execute(player) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt new file mode 100644 index 00000000..1c5ca8dc --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsSatisfaction.kt @@ -0,0 +1,47 @@ +package org.luxons.sevenwonders.engine.cards + +import org.luxons.sevenwonders.model.cards.PlayabilityLevel +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.noTransactions + +internal data class RequirementsSatisfaction( + val satisfied: Boolean, + val level: PlayabilityLevel, + val minPrice: Int, + val cheapestTransactions: Set +) { + companion object { + + internal fun noRequirements() = + RequirementsSatisfaction(true, PlayabilityLevel.NO_REQUIREMENTS, 0, setOf( + noTransactions() + )) + + internal fun enoughResources() = + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_RESOURCES, 0, setOf( + noTransactions() + )) + + internal fun enoughGold(minPrice: Int) = + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD, minPrice, setOf( + noTransactions() + )) + + internal fun enoughResourcesAndGold(minPrice: Int) = + RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD_AND_RES, minPrice, setOf( + noTransactions() + )) + + internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set) = + RequirementsSatisfaction(true, PlayabilityLevel.REQUIRES_HELP, minPrice, cheapestTransactions) + + internal fun missingRequiredGold(minPrice: Int) = + RequirementsSatisfaction(false, PlayabilityLevel.MISSING_REQUIRED_GOLD, minPrice, emptySet()) + + internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set) = + RequirementsSatisfaction(false, PlayabilityLevel.MISSING_GOLD_FOR_RES, minPrice, cheapestTransactions) + + internal fun unavailableResources() = + RequirementsSatisfaction(false, PlayabilityLevel.UNAVAILABLE_RESOURCES, Int.MAX_VALUE, emptySet()) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Boards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Boards.kt new file mode 100644 index 00000000..ad03c228 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Boards.kt @@ -0,0 +1,92 @@ +package org.luxons.sevenwonders.engine.converters + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Military +import org.luxons.sevenwonders.engine.boards.Science +import org.luxons.sevenwonders.engine.boards.ScienceType +import org.luxons.sevenwonders.engine.cards.Requirements +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.model.MoveType +import org.luxons.sevenwonders.model.boards.ApiBoard +import org.luxons.sevenwonders.model.boards.ApiMilitary +import org.luxons.sevenwonders.model.boards.ApiProduction +import org.luxons.sevenwonders.model.boards.ApiRequirements +import org.luxons.sevenwonders.model.boards.ApiScience +import org.luxons.sevenwonders.model.cards.TableCard +import org.luxons.sevenwonders.model.resources.CountedResource +import org.luxons.sevenwonders.model.wonders.ApiWonder +import org.luxons.sevenwonders.model.wonders.ApiWonderStage +import org.luxons.sevenwonders.engine.boards.Board as InternalBoard +import org.luxons.sevenwonders.engine.wonders.Wonder as InternalWonder +import org.luxons.sevenwonders.engine.wonders.WonderStage as InternalWonderStage + +internal fun InternalBoard.toApiBoard(player: Player, lastMove: Move?): ApiBoard = + ApiBoard( + playerIndex = playerIndex, + wonder = wonder.toApiWonder(player, lastMove), + production = production.toApiProduction(), + publicProduction = publicProduction.toApiProduction(), + science = science.toApiScience(), + military = military.toApiMilitary(), + playedCards = getPlayedCards().map { it.toTableCard(lastMove) }.toColumns(), + gold = gold + ) + +internal fun List.toColumns(): List> { + val cardsByColor = this.groupBy { it.color } + val (resourceCardsCols, otherCols) = cardsByColor.values.partition { it[0].color.isResource } + val resourceCardsCol = resourceCardsCols.flatten() + val otherColsSorted = otherCols.sortedBy { it[0].color } + if (resourceCardsCol.isEmpty()) { + return otherColsSorted // we want only non-empty columns + } + return listOf(resourceCardsCol) + otherColsSorted +} + +internal fun InternalWonder.toApiWonder(player: Player, lastMove: Move?): ApiWonder = + ApiWonder( + name = name, + initialResource = initialResource, + stages = stages.map { it.toApiWonderStage(lastBuiltStage == it, lastMove) }, + image = image, + nbBuiltStages = nbBuiltStages, + buildability = computeBuildabilityBy(player) + ) + +internal fun InternalWonderStage.toApiWonderStage( + isLastBuiltStage: Boolean, + lastMove: Move? +): ApiWonderStage = ApiWonderStage( + cardBack = cardBack, + isBuilt = isBuilt, + requirements = requirements.toApiRequirements(), + builtDuringLastMove = lastMove?.type == MoveType.UPGRADE_WONDER && isLastBuiltStage +) + +internal fun Production.toApiProduction(): ApiProduction = + ApiProduction( + fixedResources = getFixedResources().toCountedResourcesList(), alternativeResources = getAlternativeResources() + ) + +internal fun Requirements.toApiRequirements(): ApiRequirements = + ApiRequirements( + gold = gold, resources = resources.toCountedResourcesList() + ) + +internal fun Resources.toCountedResourcesList(): List = + quantities.filterValues { it > 0 } + .map { (type, count) -> CountedResource(count, type) } + .sortedBy { it.type } + +internal fun Military.toApiMilitary(): ApiMilitary = + ApiMilitary(nbShields, totalPoints, nbDefeatTokens) + +internal fun Science.toApiScience(): ApiScience = + ApiScience( + jokers = jokers, + nbWheels = getQuantity(ScienceType.WHEEL), + nbCompasses = getQuantity(ScienceType.COMPASS), + nbTablets = getQuantity(ScienceType.TABLET) + ) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt new file mode 100644 index 00000000..3d3471bf --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Cards.kt @@ -0,0 +1,31 @@ +package org.luxons.sevenwonders.engine.converters + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.model.cards.HandCard +import org.luxons.sevenwonders.model.cards.TableCard + +internal fun Card.toTableCard(lastMove: Move? = null): TableCard = + TableCard( + name = name, + color = color, + requirements = requirements.toApiRequirements(), + chainParent = chainParent, + chainChildren = chainChildren, + image = image, + back = back, + playedDuringLastMove = lastMove != null && this.name == lastMove.card.name + ) + +internal fun Card.toHandCard(player: Player): HandCard = + HandCard( + name = name, + color = color, + requirements = requirements.toApiRequirements(), + chainParent = chainParent, + chainChildren = chainChildren, + image = image, + back = back, + playability = computePlayabilityBy(player) + ) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Table.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Table.kt new file mode 100644 index 00000000..d2f5410b --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/converters/Table.kt @@ -0,0 +1,21 @@ +package org.luxons.sevenwonders.engine.converters + +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.model.ApiTable +import org.luxons.sevenwonders.model.PlayedMove + +internal fun Table.toApiTable(): ApiTable = ApiTable( + boards = boards.mapIndexed { i, b -> b.toApiBoard(SimplePlayer(i, this), lastPlayedMoves.getOrNull(i)) }, + currentAge = currentAge, + handRotationDirection = handRotationDirection, + lastPlayedMoves = lastPlayedMoves.map { it.toPlayedMove() } +) + +internal fun Move.toPlayedMove(): PlayedMove = PlayedMove( + playerIndex = playerContext.index, + type = type, + card = card.toTableCard(this), + transactions = transactions +) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/GameDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/GameDefinition.kt new file mode 100644 index 00000000..962b88e1 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/GameDefinition.kt @@ -0,0 +1,92 @@ +package org.luxons.sevenwonders.engine.data + +import com.github.salomonbrys.kotson.typeToken +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.luxons.sevenwonders.engine.Game +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.data.definitions.DecksDefinition +import org.luxons.sevenwonders.engine.data.definitions.WonderDefinition +import org.luxons.sevenwonders.engine.data.serializers.NumericEffectSerializer +import org.luxons.sevenwonders.engine.data.serializers.ProductionIncreaseSerializer +import org.luxons.sevenwonders.engine.data.serializers.ProductionSerializer +import org.luxons.sevenwonders.engine.data.serializers.ResourceTypeSerializer +import org.luxons.sevenwonders.engine.data.serializers.ResourceTypesSerializer +import org.luxons.sevenwonders.engine.data.serializers.ResourcesSerializer +import org.luxons.sevenwonders.engine.data.serializers.ScienceProgressSerializer +import org.luxons.sevenwonders.engine.effects.GoldIncrease +import org.luxons.sevenwonders.engine.effects.MilitaryReinforcements +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.effects.RawPointsIncrease +import org.luxons.sevenwonders.engine.effects.ScienceProgress +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.model.Age +import org.luxons.sevenwonders.model.CustomizableSettings +import org.luxons.sevenwonders.model.resources.ResourceType + +internal const val LAST_AGE: Age = 3 + +internal data class GlobalRules( + val minPlayers: Int, + val maxPlayers: Int +) + +class GameDefinition internal constructor( + rules: GlobalRules, + private val wonders: List, + private val decksDefinition: DecksDefinition +) { + val minPlayers: Int = rules.minPlayers + val maxPlayers: Int = rules.maxPlayers + + fun initGame(id: Long, customSettings: CustomizableSettings, nbPlayers: Int): Game { + val settings = Settings(nbPlayers, customSettings) + val boards = assignBoards(settings, nbPlayers) + val decks = decksDefinition.prepareDecks(settings.nbPlayers, settings.random) + return Game(id, settings, boards, decks) + } + + private fun assignBoards(settings: Settings, nbPlayers: Int): List { + return wonders.shuffled(settings.random) + .take(nbPlayers) + .mapIndexed { i, wDef -> Board(wDef.create(settings.pickWonderSide()), i, settings) } + } + + companion object { + + fun load(): GameDefinition { + val gson: Gson = createGson() + val rules = loadJson("global_rules.json", GlobalRules::class.java, gson) + val wonders = loadJson("wonders.json", Array::class.java, gson) + val decksDefinition = loadJson("cards.json", DecksDefinition::class.java, gson) + return GameDefinition(rules, wonders.toList(), decksDefinition) + } + } +} + +private fun loadJson(filename: String, clazz: Class, gson: Gson): T { + val packageAsPath = GameDefinition::class.java.`package`.name.replace('.', '/') + val resourcePath = "/$packageAsPath/$filename" + val resource = GameDefinition::class.java.getResource(resourcePath) + val json = resource.readText() + return gson.fromJson(json, clazz) +} + +private fun createGson(): Gson { + return GsonBuilder().disableHtmlEscaping() + .registerTypeAdapter(ResourcesSerializer()) + .registerTypeAdapter(ResourceTypeSerializer()) + .registerTypeAdapter>(ResourceTypesSerializer()) + .registerTypeAdapter(ProductionSerializer()) + .registerTypeAdapter(ProductionIncreaseSerializer()) + .registerTypeAdapter(NumericEffectSerializer()) + .registerTypeAdapter(NumericEffectSerializer()) + .registerTypeAdapter(NumericEffectSerializer()) + .registerTypeAdapter(ScienceProgressSerializer()) + .create() +} + +private inline fun GsonBuilder.registerTypeAdapter(typeAdapter: Any): GsonBuilder = + this.registerTypeAdapter(typeToken(), typeAdapter) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/CardDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/CardDefinition.kt new file mode 100644 index 00000000..1785ad49 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/CardDefinition.kt @@ -0,0 +1,25 @@ +package org.luxons.sevenwonders.engine.data.definitions + +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.cards.Requirements +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.Color + +internal class CardDefinition( + private val name: String, + private val color: Color, + private val requirements: Requirements?, + private val effect: EffectsDefinition, + private val chainParent: String?, + private val chainChildren: List?, + private val image: String, + private val countPerNbPlayer: Map +) { + fun create(back: CardBack, nbPlayers: Int): List = List(countPerNbPlayer[nbPlayers] ?: 0) { create(back) } + + fun create(back: CardBack): Card { + val reqs = requirements ?: Requirements() + val children = chainChildren ?: emptyList() + return Card(name, color, reqs, effect.create(), chainParent, children, image, back) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/DecksDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/DecksDefinition.kt new file mode 100644 index 00000000..c4f8cdc7 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/DecksDefinition.kt @@ -0,0 +1,37 @@ +package org.luxons.sevenwonders.engine.data.definitions + +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.cards.Decks +import org.luxons.sevenwonders.model.cards.CardBack +import kotlin.random.Random + +internal class DeckDefinition( + val cards: List, + val backImage: String +) { + fun create(nbPlayers: Int): List = cards.flatMap { it.create( + CardBack( + backImage + ), nbPlayers) } +} + +internal class DecksDefinition( + private val age1: DeckDefinition, + private val age2: DeckDefinition, + private val age3: DeckDefinition, + private val guildCards: List +) { + fun prepareDecks(nbPlayers: Int, random: Random) = Decks( + mapOf( + 1 to age1.create(nbPlayers).shuffled(random), + 2 to age2.create(nbPlayers).shuffled(random), + 3 to (age3.create(nbPlayers) + pickGuildCards(nbPlayers, random)).shuffled(random) + ) + ) + + private fun pickGuildCards(nbPlayers: Int, random: Random): List { + val back = CardBack(age3.backImage) + val guild = guildCards.map { it.create(back) }.shuffled(random) + return guild.subList(0, nbPlayers + 2) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/EffectsDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/EffectsDefinition.kt new file mode 100644 index 00000000..0322d657 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/EffectsDefinition.kt @@ -0,0 +1,34 @@ +package org.luxons.sevenwonders.engine.data.definitions + +import org.luxons.sevenwonders.engine.effects.BonusPerBoardElement +import org.luxons.sevenwonders.engine.effects.Discount +import org.luxons.sevenwonders.engine.effects.Effect +import org.luxons.sevenwonders.engine.effects.GoldIncrease +import org.luxons.sevenwonders.engine.effects.MilitaryReinforcements +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.effects.RawPointsIncrease +import org.luxons.sevenwonders.engine.effects.ScienceProgress +import org.luxons.sevenwonders.engine.effects.SpecialAbility +import org.luxons.sevenwonders.engine.effects.SpecialAbilityActivation + +internal class EffectsDefinition( + private val gold: GoldIncrease? = null, + private val military: MilitaryReinforcements? = null, + private val science: ScienceProgress? = null, + private val discount: Discount? = null, + private val perBoardElement: BonusPerBoardElement? = null, + private val production: ProductionIncrease? = null, + private val points: RawPointsIncrease? = null, + private val action: SpecialAbility? = null +) { + fun create(): List = mutableListOf().apply { + gold?.let { add(it) } + military?.let { add(it) } + science?.let { add(it) } + discount?.let { add(it) } + perBoardElement?.let { add(it) } + production?.let { add(it) } + points?.let { add(it) } + action?.let { add(SpecialAbilityActivation(it)) } + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/WonderDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/WonderDefinition.kt new file mode 100644 index 00000000..4c5c8e09 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/definitions/WonderDefinition.kt @@ -0,0 +1,29 @@ +package org.luxons.sevenwonders.engine.data.definitions + +import org.luxons.sevenwonders.engine.cards.Requirements +import org.luxons.sevenwonders.engine.wonders.Wonder +import org.luxons.sevenwonders.engine.wonders.WonderStage +import org.luxons.sevenwonders.model.WonderSide +import org.luxons.sevenwonders.model.resources.ResourceType + +internal class WonderDefinition( + private val name: String, + private val sides: Map +) { + fun create(wonderSide: WonderSide): Wonder = sides[wonderSide]!!.createWonder(name) +} + +internal class WonderSideDefinition( + private val initialResource: ResourceType, + private val stages: List, + private val image: String +) { + fun createWonder(name: String): Wonder = Wonder(name, initialResource, stages.map { it.create() }, image) +} + +internal class WonderStageDefinition( + private val requirements: Requirements, + private val effects: EffectsDefinition +) { + fun create(): WonderStage = WonderStage(requirements, effects.create()) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializer.kt new file mode 100644 index 00000000..a149e8a0 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializer.kt @@ -0,0 +1,38 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.engine.effects.Effect +import org.luxons.sevenwonders.engine.effects.GoldIncrease +import org.luxons.sevenwonders.engine.effects.MilitaryReinforcements +import org.luxons.sevenwonders.engine.effects.RawPointsIncrease +import java.lang.reflect.Type + +internal class NumericEffectSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize(effect: Effect, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + val value: Int = when (effect) { + is MilitaryReinforcements -> effect.count + is GoldIncrease -> effect.amount + is RawPointsIncrease -> effect.points + else -> throw IllegalArgumentException("Unknown numeric effect " + effect.javaClass.name) + } + return JsonPrimitive(value) + } + + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Effect { + val value = json.asInt + return when (typeOfT) { + MilitaryReinforcements::class.java -> MilitaryReinforcements(value) + GoldIncrease::class.java -> GoldIncrease(value) + RawPointsIncrease::class.java -> RawPointsIncrease(value) + else -> throw IllegalArgumentException("Unknown numeric effet " + typeOfT.typeName) + } + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializer.kt new file mode 100644 index 00000000..5115649f --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializer.kt @@ -0,0 +1,47 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.resources.Production +import java.lang.reflect.Type + +internal class ProductionIncreaseSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize( + productionIncrease: ProductionIncrease, + typeOfSrc: Type, + context: JsonSerializationContext + ): JsonElement { + val production = productionIncrease.production + val json = context.serialize(production) + return if (!json.isJsonNull && !productionIncrease.isSellable) { JsonPrimitive("(${json.asString})") } else json + } + + @Throws(JsonParseException::class) + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): ProductionIncrease { + var prodJson = json + + var resourcesStr = prodJson.asString + val isSellable = !resourcesStr.startsWith("(") + if (!isSellable) { + resourcesStr = unwrapBrackets(resourcesStr) + prodJson = JsonPrimitive(resourcesStr) + } + val production = context.deserialize(prodJson, Production::class.java) + return ProductionIncrease(production, isSellable) + } + + private fun unwrapBrackets(str: String): String { + return str.substring(1, str.length - 1) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializer.kt new file mode 100644 index 00000000..3f9d3797 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializer.kt @@ -0,0 +1,58 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonParseException +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.model.resources.ResourceType +import java.lang.reflect.Type + +internal class ProductionSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize(production: Production, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + val fixedResources = production.getFixedResources() + val choices = production.getAlternativeResources() + return when { + fixedResources.isEmpty() -> serializeAsChoice(choices, context) + choices.isEmpty() -> serializeAsResources(fixedResources, context) + else -> throw IllegalArgumentException("Cannot serialize a production with mixed fixed resources and choices") + } + } + + private fun serializeAsChoice(choices: Set>, context: JsonSerializationContext): JsonElement { + if (choices.isEmpty()) { + return JsonNull.INSTANCE + } + if (choices.size > 1) { + throw IllegalArgumentException("Cannot serialize a production with more than one choice") + } + val str = choices.flatMap { it }.map { it.symbol }.joinToString("/") + return context.serialize(str) + } + + private fun serializeAsResources(fixedResources: Resources, context: JsonSerializationContext): JsonElement { + return context.serialize(fixedResources) + } + + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Production { + val resourcesStr = json.asString + val production = Production() + if (resourcesStr.contains("/")) { + production.addChoice(*createChoice(resourcesStr)) + } else { + val fixedResources = context.deserialize(json, Resources::class.java) + production.addAll(fixedResources) + } + return production + } + + private fun createChoice(choiceStr: String): Array { + return choiceStr.split("/").map { ResourceType.fromSymbol(it) }.toTypedArray() + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializer.kt new file mode 100644 index 00000000..b308fd24 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializer.kt @@ -0,0 +1,26 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.model.resources.ResourceType +import java.lang.reflect.Type + +internal class ResourceTypeSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize(type: ResourceType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement = + JsonPrimitive(type.symbol) + + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ResourceType { + val str = json.asString + if (str.isEmpty()) { + throw IllegalArgumentException("Empty string is not a valid resource level") + } + return ResourceType.fromSymbol(str[0]) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializer.kt new file mode 100644 index 00000000..2383685c --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializer.kt @@ -0,0 +1,30 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.model.resources.ResourceType +import java.lang.reflect.Type + +internal class ResourceTypesSerializer : JsonSerializer>, JsonDeserializer> { + + override fun serialize( + resources: List, + typeOfSrc: Type, + context: JsonSerializationContext + ): JsonElement { + val s = resources.map { it.symbol }.joinToString("") + return JsonPrimitive(s) + } + + @Throws(JsonParseException::class) + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext + ): List = json.asString.map { ResourceType.fromSymbol(it) } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializer.kt new file mode 100644 index 00000000..4226411d --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializer.kt @@ -0,0 +1,26 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.engine.resources.toResources +import org.luxons.sevenwonders.model.resources.ResourceType +import java.lang.reflect.Type + +internal class ResourcesSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize(resources: Resources, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { + val s = resources.toList().map { it.symbol }.joinToString("") + return if (s.isEmpty()) JsonNull.INSTANCE else JsonPrimitive(s) + } + + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Resources = + json.asString.map { ResourceType.fromSymbol(it) to 1 }.toResources() +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializer.kt new file mode 100644 index 00000000..bad74e54 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializer.kt @@ -0,0 +1,55 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import org.luxons.sevenwonders.engine.boards.Science +import org.luxons.sevenwonders.engine.boards.ScienceType +import org.luxons.sevenwonders.engine.effects.ScienceProgress +import java.lang.reflect.Type + +internal class ScienceProgressSerializer : JsonSerializer, JsonDeserializer { + + override fun serialize( + scienceProgress: ScienceProgress, + typeOfSrc: Type, + context: JsonSerializationContext + ): JsonElement { + val science = scienceProgress.science + + if (science.size() > 1) { + throw UnsupportedOperationException("Cannot serialize science containing more than one element") + } + + for (type in ScienceType.values()) { + val quantity = science.getQuantity(type) + if (quantity == 1) { + return context.serialize(type) + } + } + + return if (science.jokers == 1) JsonPrimitive("any") else JsonNull.INSTANCE + } + + @Throws(JsonParseException::class) + override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ScienceProgress { + val s = json.asString + val science = Science() + if ("any" == s) { + science.addJoker(1) + } else { + science.add(deserializeScienceType(json, context), 1) + } + return ScienceProgress(science) + } + + private fun deserializeScienceType(json: JsonElement, context: JsonDeserializationContext): ScienceType { + return context.deserialize(json, ScienceType::class.java) + ?: throw IllegalArgumentException("Invalid science level " + json.asString) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElement.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElement.kt new file mode 100644 index 00000000..96d177c9 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElement.kt @@ -0,0 +1,36 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.model.cards.Color + +enum class BoardElementType { + CARD, + BUILT_WONDER_STAGES, + DEFEAT_TOKEN +} + +internal data class BonusPerBoardElement( + val boards: List, + val type: BoardElementType, + val gold: Int = 0, + val points: Int = 0, + val colors: List? = null // only relevant if type=CARD +) : Effect { + + override fun applyTo(player: Player) = player.board.addGold(gold * nbMatchingElementsFor(player)) + + override fun computePoints(player: Player): Int = points * nbMatchingElementsFor(player) + + private fun nbMatchingElementsFor(player: Player): Int = boards + .map(player::getBoard) + .map(::nbMatchingElementsIn) + .sum() + + private fun nbMatchingElementsIn(board: Board): Int = when (type) { + BoardElementType.CARD -> board.getNbCardsOfColor(colors!!) + BoardElementType.BUILT_WONDER_STAGES -> board.wonder.nbBuiltStages + BoardElementType.DEFEAT_TOKEN -> board.military.nbDefeatTokens + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Discount.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Discount.kt new file mode 100644 index 00000000..28ffb49d --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Discount.kt @@ -0,0 +1,19 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceType + +internal data class Discount( + val resourceTypes: List = emptyList(), + val providers: List = emptyList(), + val discountedPrice: Int = 1 +) : InstantOwnBoardEffect() { + + public override fun applyTo(board: Board) { + val rules = board.tradingRules + for (type in resourceTypes) { + providers.forEach { rules.setCost(type, it, discountedPrice) } + } + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Effect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Effect.kt new file mode 100644 index 00000000..dcc86f8d --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/Effect.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player + +/** + * Represents an effect than can be applied to a player's board when playing a card or building his wonder. The effect + * may affect (or depend on) the adjacent boards. It can have an instantaneous effect on the board, or be postponed to + * the end of game where point calculations take place. + */ +internal interface Effect { + + fun applyTo(player: Player) + + fun computePoints(player: Player): Int +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/EndGameEffect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/EndGameEffect.kt new file mode 100644 index 00000000..635812d1 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/EndGameEffect.kt @@ -0,0 +1,9 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player + +internal abstract class EndGameEffect : Effect { + + // EndGameEffects don't do anything when applied to the board, they simply give more points in the end + override fun applyTo(player: Player) = Unit +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncrease.kt new file mode 100644 index 00000000..b3ffc7f8 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncrease.kt @@ -0,0 +1,8 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.boards.Board + +internal data class GoldIncrease(val amount: Int) : InstantOwnBoardEffect() { + + public override fun applyTo(board: Board) = board.addGold(amount) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/InstantOwnBoardEffect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/InstantOwnBoardEffect.kt new file mode 100644 index 00000000..fd2fe080 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/InstantOwnBoardEffect.kt @@ -0,0 +1,14 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board + +internal abstract class InstantOwnBoardEffect : Effect { + + override fun applyTo(player: Player) = applyTo(player.board) + + protected abstract fun applyTo(board: Board) + + // InstantEffects are only important when applied to the board, they don't give extra points in the end + override fun computePoints(player: Player): Int = 0 +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcements.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcements.kt new file mode 100644 index 00000000..0694c037 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcements.kt @@ -0,0 +1,8 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.boards.Board + +internal data class MilitaryReinforcements(val count: Int) : InstantOwnBoardEffect() { + + public override fun applyTo(board: Board) = board.military.addShields(count) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncrease.kt new file mode 100644 index 00000000..6458ad20 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncrease.kt @@ -0,0 +1,14 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.resources.Production + +internal data class ProductionIncrease(val production: Production, val isSellable: Boolean) : InstantOwnBoardEffect() { + + public override fun applyTo(board: Board) { + board.production.addAll(production) + if (isSellable) { + board.publicProduction.addAll(production) + } + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncrease.kt new file mode 100644 index 00000000..b4a97a84 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncrease.kt @@ -0,0 +1,8 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player + +internal data class RawPointsIncrease(val points: Int) : EndGameEffect() { + + override fun computePoints(player: Player): Int = points +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgress.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgress.kt new file mode 100644 index 00000000..4ea8626f --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgress.kt @@ -0,0 +1,9 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.boards.Science + +internal class ScienceProgress(val science: Science) : InstantOwnBoardEffect() { + + public override fun applyTo(board: Board) = board.science.addAll(science) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbility.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbility.kt new file mode 100644 index 00000000..857c7e63 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbility.kt @@ -0,0 +1,39 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board + +enum class SpecialAbility { + + /** + * The player can play the last card of each age instead of discarding it. This card can be played by paying its + * cost, discarded to gain 3 coins or used in the construction of his or her Wonder. + */ + PLAY_LAST_CARD, + + /** + * Once per age, a player can construct a building from his or her hand for free. + */ + ONE_FREE_PER_AGE, + + /** + * The player can look at all cards discarded since the beginning of the game, pick one and build it for free. + */ + PLAY_DISCARDED, + + /** + * The player can, at the end of the game, "copy" a Guild of his or her choice (purple card), built by one of his or + * her two neighboring cities. + */ + COPY_GUILD { + override fun computePoints(player: Player): Int { + val copiedGuild = player.board.copiedGuild + ?: throw IllegalStateException("The copied Guild has not been chosen, cannot compute points") + return copiedGuild.effects.stream().mapToInt { it.computePoints(player) }.sum() + } + }; + + internal fun apply(board: Board) = board.addSpecial(this) + + internal open fun computePoints(player: Player): Int = 0 +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivation.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivation.kt new file mode 100644 index 00000000..6043c551 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivation.kt @@ -0,0 +1,10 @@ +package org.luxons.sevenwonders.engine.effects + +import org.luxons.sevenwonders.engine.Player + +internal data class SpecialAbilityActivation(val specialAbility: SpecialAbility) : Effect { + + override fun applyTo(player: Player) = specialAbility.apply(player.board) + + override fun computePoints(player: Player): Int = specialAbility.computePoints(player) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMove.kt new file mode 100644 index 00000000..942d9db1 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMove.kt @@ -0,0 +1,23 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.PlayerMove + +internal class BuildWonderMove(move: PlayerMove, card: Card, player: PlayerContext) : + CardFromHandMove(move, card, player) { + + private val wonder = player.board.wonder + + init { + if (!wonder.isNextStageBuildable(playerContext.board, transactions)) { + throw InvalidMoveException(this, "all levels are already built, or the given resources are insufficient") + } + } + + override fun place(discardedCards: MutableList, settings: Settings) = wonder.placeCard(card.back) + + override fun activate(discardedCards: List, settings: Settings) = + wonder.activateLastBuiltStage(playerContext, transactions) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CardFromHandMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CardFromHandMove.kt new file mode 100644 index 00000000..42db8040 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CardFromHandMove.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.PlayerMove + +internal abstract class CardFromHandMove(move: PlayerMove, card: Card, player: PlayerContext) : + Move(move, card, player) { + + init { + if (!player.hand.contains(card)) { + throw InvalidMoveException(this, "card '${card.name}' not in hand") + } + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CopyGuildMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CopyGuildMove.kt new file mode 100644 index 00000000..9cb57481 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/CopyGuildMove.kt @@ -0,0 +1,37 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.effects.SpecialAbility +import org.luxons.sevenwonders.model.PlayerMove +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.model.cards.Color + +internal class CopyGuildMove(move: PlayerMove, card: Card, player: PlayerContext) : Move(move, card, player) { + + init { + val board = player.board + if (!board.hasSpecial(SpecialAbility.COPY_GUILD)) { + throw InvalidMoveException(this, "no ability to copy guild cards") + } + if (card.color !== Color.PURPLE) { + throw InvalidMoveException(this, "card '${card.name}' is not a guild card") + } + val leftNeighbourHasIt = neighbourHasTheCard(RelativeBoardPosition.LEFT) + val rightNeighbourHasIt = neighbourHasTheCard(RelativeBoardPosition.RIGHT) + if (!leftNeighbourHasIt && !rightNeighbourHasIt) { + throw InvalidMoveException(this, "neighbours don't have card '${card.name}'") + } + } + + private fun neighbourHasTheCard(position: RelativeBoardPosition): Boolean = + playerContext.getBoard(position).getPlayedCards().contains(card) + + // nothing special to do here + override fun place(discardedCards: MutableList, settings: Settings) = Unit + + override fun activate(discardedCards: List, settings: Settings) { + playerContext.board.copiedGuild = card + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/DiscardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/DiscardMove.kt new file mode 100644 index 00000000..a8f2aebf --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/DiscardMove.kt @@ -0,0 +1,18 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.PlayerMove + +internal class DiscardMove(move: PlayerMove, card: Card, player: PlayerContext) : + CardFromHandMove(move, card, player) { + + override fun place(discardedCards: MutableList, settings: Settings) { + discardedCards.add(card) + } + + override fun activate(discardedCards: List, settings: Settings) { + playerContext.board.addGold(settings.discardedCardGold) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt new file mode 100644 index 00000000..6ae92b9a --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/Move.kt @@ -0,0 +1,34 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.MoveType +import org.luxons.sevenwonders.model.PlayerMove +import org.luxons.sevenwonders.model.resources.ResourceTransactions + +internal abstract class Move( + val move: PlayerMove, + val card: Card, + val playerContext: PlayerContext +) { + val type: MoveType = move.type + + val transactions: ResourceTransactions = move.transactions + + abstract fun place(discardedCards: MutableList, settings: Settings) + + abstract fun activate(discardedCards: List, settings: Settings) +} + +class InvalidMoveException internal constructor(move: Move, message: String) : IllegalArgumentException( + "Player ${move.playerContext.index} cannot perform move ${move.type}: $message" +) + +internal fun MoveType.resolve(move: PlayerMove, card: Card, context: PlayerContext): Move = when (this) { + MoveType.PLAY -> PlayCardMove(move, card, context) + MoveType.PLAY_FREE -> PlayFreeCardMove(move, card, context) + MoveType.UPGRADE_WONDER -> BuildWonderMove(move, card, context) + MoveType.DISCARD -> DiscardMove(move, card, context) + MoveType.COPY_GUILD -> CopyGuildMove(move, card, context) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayCardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayCardMove.kt new file mode 100644 index 00000000..17b1a48a --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayCardMove.kt @@ -0,0 +1,20 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.PlayerMove + +internal class PlayCardMove(move: PlayerMove, card: Card, player: PlayerContext) : + CardFromHandMove(move, card, player) { + + init { + if (!card.isPlayableOnBoardWith(player.board, transactions)) { + throw InvalidMoveException(this, "requirements not met to play the card ${card.name}") + } + } + + override fun place(discardedCards: MutableList, settings: Settings) = playerContext.board.addCard(card) + + override fun activate(discardedCards: List, settings: Settings) = card.applyTo(playerContext, transactions) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeCardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeCardMove.kt new file mode 100644 index 00000000..310500c7 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/moves/PlayFreeCardMove.kt @@ -0,0 +1,25 @@ +package org.luxons.sevenwonders.engine.moves + +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.PlayerMove + +internal class PlayFreeCardMove(move: PlayerMove, card: Card, playerContext: PlayerContext) : + CardFromHandMove(move, card, playerContext) { + + init { + val board = playerContext.board + if (!board.canPlayFreeCard(playerContext.currentAge)) { + throw InvalidMoveException(this, "no free card available for the current age ${playerContext.currentAge}") + } + } + + override fun place(discardedCards: MutableList, settings: Settings) = playerContext.board.addCard(card) + + override fun activate(discardedCards: List, settings: Settings) { + // only apply effects, without paying the cost + card.effects.forEach { it.applyTo(playerContext) } + playerContext.board.consumeFreeCard(playerContext.currentAge) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt new file mode 100644 index 00000000..4f2ccd07 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculator.kt @@ -0,0 +1,125 @@ +package org.luxons.sevenwonders.engine.resources + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.ResourceType +import java.util.EnumSet + +internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = + BestPriceCalculator(resources, player).computeBestSolution() + +data class TransactionPlan(val price: Int, val possibleTransactions: Set) + +private class ResourcePool( + val provider: Provider?, + private val rules: TradingRules, + choices: Set> +) { + val choices: Set> = choices.map { it.toMutableSet() }.toSet() + + fun getCost(type: ResourceType): Int = if (provider == null) 0 else rules.getCost(type, provider) +} + +private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { + + private val pools: List + private val resourcesLeftToPay: MutableResources + private val boughtResources: MutableMap = HashMap() + private var pricePaid: Int = 0 + + private var bestSolutions: MutableSet = mutableSetOf() + private var bestPrice: Int = Integer.MAX_VALUE + + init { + val board = player.board + this.resourcesLeftToPay = resourcesToPay.minus(board.production.getFixedResources()).toMutableResources() + this.pools = createResourcePools(player) + } + + private fun createResourcePools(player: Player): List { + // we only take alternative resources here, because fixed resources were already removed for optimization + val ownBoardChoices = player.board.production.getAlternativeResources() + val ownPool = ResourcePool(null, player.board.tradingRules, ownBoardChoices) + val providerPools = Provider.values().map { it.toResourcePoolFor(player) } + + return providerPools + ownPool + } + + private fun Provider.toResourcePoolFor(player: Player): ResourcePool { + val providerBoard = player.getBoard(boardPosition) + val choices = providerBoard.publicProduction.asChoices() + return ResourcePool(this, player.board.tradingRules, choices) + } + + fun computeBestSolution(): TransactionPlan { + computePossibilities() + return TransactionPlan(bestPrice, bestSolutions) + } + + private fun computePossibilities() { + if (resourcesLeftToPay.isEmpty()) { + updateBestSolutionIfNeeded() + return + } + for (type in ResourceType.values()) { + if (resourcesLeftToPay[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) + buyOne(provider, type, cost) + computePossibilitiesWhenUsing(type, pool) + unbuyOne(provider, type, cost) + resourcesLeftToPay.add(type, 1) + } + + fun buyOne(provider: Provider, type: ResourceType, cost: Int) { + boughtResources.getOrPut(provider) { MutableResources() }.add(type, 1) + pricePaid += cost + } + + fun unbuyOne(provider: Provider, type: ResourceType, cost: Int) { + pricePaid -= cost + boughtResources.get(provider)!!.remove(type, 1) + } + + private fun computePossibilitiesWhenUsing(type: ResourceType, pool: ResourcePool) { + for (choice in pool.choices) { + if (choice.contains(type)) { + val temp = EnumSet.copyOf(choice) + choice.clear() + computePossibilities() + choice.addAll(temp) + } + } + } + + private fun updateBestSolutionIfNeeded() { + if (pricePaid > bestPrice) return + + if (pricePaid < bestPrice) { + bestPrice = pricePaid + bestSolutions.clear() + } + // avoid mutating the resources from the transactions + val transactionSet = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions() + bestSolutions.add(transactionSet) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Production.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Production.kt new file mode 100644 index 00000000..88203144 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Production.kt @@ -0,0 +1,67 @@ +package org.luxons.sevenwonders.engine.resources + +import org.luxons.sevenwonders.model.resources.ResourceType +import java.util.EnumSet + +data class Production internal constructor( + private val fixedResources: MutableResources = mutableResourcesOf(), + private val alternativeResources: MutableSet> = mutableSetOf() +) { + fun getFixedResources(): Resources = fixedResources + + fun getAlternativeResources(): Set> = alternativeResources + + fun addFixedResource(type: ResourceType, quantity: Int) = fixedResources.add(type, quantity) + + fun addChoice(vararg options: ResourceType) { + val optionSet = EnumSet.copyOf(options.toList()) + alternativeResources.add(optionSet) + } + + fun addAll(resources: Resources) = fixedResources.add(resources) + + fun addAll(production: Production) { + fixedResources.add(production.fixedResources) + alternativeResources.addAll(production.getAlternativeResources()) + } + + internal fun asChoices(): Set> { + val fixedAsChoices = fixedResources.toList().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 = + containedInAlternatives(resources.toMutableResources(), alternativeResources) + + private fun containedInAlternatives( + resources: MutableResources, + alternatives: MutableSet> + ): Boolean { + if (resources.isEmpty()) { + return true + } + for (type in ResourceType.values()) { + if (resources[type] <= 0) { + continue + } + // return if no alternative produces the resource of this entry + val candidate = alternatives.firstOrNull { a -> a.contains(type) } ?: return false + resources.remove(type, 1) + alternatives.remove(candidate) + val remainingAreContainedToo = containedInAlternatives(resources, alternatives) + resources.add(type, 1) + alternatives.add(candidate) + if (remainingAreContainedToo) { + return true + } + } + return false + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt new file mode 100644 index 00000000..be9b9e76 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactions.kt @@ -0,0 +1,32 @@ +package org.luxons.sevenwonders.engine.resources + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.converters.toCountedResourcesList +import org.luxons.sevenwonders.model.resources.CountedResource +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceTransaction +import org.luxons.sevenwonders.model.resources.ResourceTransactions + +fun Map.toTransactions(): ResourceTransactions = + filterValues { !it.isEmpty() } + .map { (p, res) -> ResourceTransaction(p, res.toCountedResourcesList()) } + .toSet() + +fun ResourceTransactions.asResources(): Resources = flatMap { it.resources }.asResources() + +fun ResourceTransaction.asResources(): Resources = resources.asResources() + +fun List.asResources(): Resources = map { it.asResources() }.merge() + +fun CountedResource.asResources(): Resources = resourcesOf(type to count) + +internal fun ResourceTransactions.execute(player: Player) = forEach { it.execute(player) } + +internal fun ResourceTransaction.execute(player: Player) { + val board = player.board + val price = board.tradingRules.computeCost(this) + board.removeGold(price) + val providerPosition = provider.boardPosition + val providerBoard = player.getBoard(providerPosition) + providerBoard.addGold(price) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Resources.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Resources.kt new file mode 100644 index 00000000..0edf9588 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/Resources.kt @@ -0,0 +1,89 @@ +package org.luxons.sevenwonders.engine.resources + +import org.luxons.sevenwonders.model.resources.ResourceType + +fun emptyResources(): Resources = MutableResources() + +fun resourcesOf(singleResource: ResourceType): Resources = mapOf(singleResource to 1).toMutableResources() + +fun resourcesOf(vararg resources: ResourceType): Resources = mutableResourcesOf(*resources) + +fun resourcesOf(vararg resources: Pair): Resources = mutableResourcesOf(*resources) + +fun Iterable>.toResources(): Resources = toMutableResources() + +/** + * Creates [Resources] from a copy of the given map. Future modifications to the input map won't affect the return + * resources. + */ +fun Map.toResources(): Resources = toMutableResources() + +fun Iterable.merge(): Resources = fold(MutableResources()) { r1, r2 -> r1.add(r2); r1 } + +internal fun mutableResourcesOf() = MutableResources() + +internal fun mutableResourcesOf(vararg resources: ResourceType): MutableResources = + resources.map { it to 1 }.toMutableResources() + +internal fun mutableResourcesOf(vararg resources: Pair) = resources.asIterable().toMutableResources() + +internal fun Iterable>.toMutableResources(): MutableResources = + fold(MutableResources()) { mr, (type, qty) -> mr.add(type, qty); mr } + +internal fun Map.toMutableResources(): MutableResources = MutableResources(toMutableMap()) + +internal fun Resources.toMutableResources(): MutableResources = quantities.toMutableResources() + +interface Resources { + + val quantities: Map + + val size: Int + get() = quantities.map { it.value }.sum() + + fun isEmpty(): Boolean = size == 0 + + operator fun get(key: ResourceType): Int = quantities.getOrDefault(key, 0) + + fun containsAll(resources: Resources): Boolean = resources.quantities.all { it.value <= this[it.key] } + + operator fun plus(resources: Resources): Resources = + ResourceType.values().map { it to this[it] + resources[it] }.toResources() + + /** + * Returns new resources containing these resources minus the given [resources]. If the given resources contain + * more than these resources contain for a resource type, then the resulting resources will contain none of that + * type. + */ + operator fun minus(resources: Resources): Resources = + quantities.mapValues { (type, q) -> Math.max(0, q - resources[type]) }.toResources() + + fun toList(): List = quantities.flatMap { (type, quantity) -> List(quantity) { type } } + + fun copy(): Resources = quantities.toResources() +} + +class MutableResources( + override val quantities: MutableMap = mutableMapOf() +) : Resources { + + fun add(type: ResourceType, quantity: Int) { + quantities.merge(type, quantity) { x, y -> x + y } + } + + fun add(resources: Resources) = resources.quantities.forEach { type, quantity -> this.add(type, quantity) } + + fun remove(type: ResourceType, quantity: Int) { + if (this[type] < quantity) { + throw NoSuchElementException("Can't remove $quantity resources of type $type") + } + quantities.computeIfPresent(type) { _, oldQty -> oldQty - quantity } + } + + override fun equals(other: Any?): Boolean = + other is Resources && quantities.filterValues { it > 0 } == other.quantities.filterValues { it > 0 } + + override fun hashCode(): Int = quantities.filterValues { it > 0 }.hashCode() + + override fun toString(): String = "$quantities" +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TradingRules.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TradingRules.kt new file mode 100644 index 00000000..e0686d9c --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/resources/TradingRules.kt @@ -0,0 +1,29 @@ +package org.luxons.sevenwonders.engine.resources + +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceTransaction +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.ResourceType + +class TradingRules internal constructor(private val defaultCost: Int) { + + private val costs: MutableMap> = mutableMapOf() + + fun getCosts(): Map> { + return costs + } + + internal fun getCost(type: ResourceType, provider: Provider): Int = + costs.computeIfAbsent(type) { mutableMapOf() }.getOrDefault(provider, defaultCost) + + internal fun setCost(type: ResourceType, provider: Provider, cost: Int) { + costs.computeIfAbsent(type) { mutableMapOf() }[provider] = cost + } + + internal fun computeCost(transactions: ResourceTransactions): Int = transactions.map { computeCost(it) }.sum() + + internal fun computeCost(transact: ResourceTransaction) = computeCost(transact.asResources(), transact.provider) + + private fun computeCost(resources: Resources, provider: Provider): Int = + resources.quantities.map { (type, qty) -> getCost(type, provider) * qty }.sum() +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/score/Score.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/score/Score.kt new file mode 100644 index 00000000..4da27a59 --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/score/Score.kt @@ -0,0 +1,23 @@ +package org.luxons.sevenwonders.engine.score + +class ScoreBoard(scores: Collection) { + + val scores: Collection = scores.sortedDescending() +} + +data class PlayerScore(val boardGold: Int, val pointsByCategory: Map) : Comparable { + + val totalPoints = pointsByCategory.map { it.value }.sum() + + override fun compareTo(other: PlayerScore) = compareValuesBy(this, other, { it.totalPoints }, { it.boardGold }) +} + +enum class ScoreCategory { + CIVIL, + SCIENCE, + MILITARY, + TRADE, + GUILD, + WONDER, + GOLD +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt new file mode 100644 index 00000000..2fd369ee --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/Wonder.kt @@ -0,0 +1,65 @@ +package org.luxons.sevenwonders.engine.wonders + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.cards.RequirementsSatisfaction +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.PlayabilityLevel +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.wonders.WonderBuildability + +internal class Wonder( + val name: String, + val initialResource: ResourceType, + val stages: List, + val image: String +) { + val nbBuiltStages: Int + get() = stages.count { it.isBuilt } + + private val nextStage: WonderStage + get() { + if (nbBuiltStages == stages.size) { + throw IllegalStateException("This wonder has already reached its maximum level") + } + return stages[nbBuiltStages] + } + + val lastBuiltStage: WonderStage? + get() = stages.getOrNull(nbBuiltStages - 1) + + fun computeBuildabilityBy(player: Player): WonderBuildability { + if (nbBuiltStages == stages.size) { + return Buildability.alreadyBuilt() + } + return Buildability.requirementDependent(nextStage.requirements.assess(player)) + } + + fun isNextStageBuildable(board: Board, boughtResources: ResourceTransactions): Boolean = + nbBuiltStages < stages.size && nextStage.isBuildable(board, boughtResources) + + fun placeCard(cardBack: CardBack) = nextStage.placeCard(cardBack) + + fun activateLastBuiltStage(player: Player, boughtResources: ResourceTransactions) = + lastBuiltStage!!.activate(player, boughtResources) + + fun computePoints(player: Player): Int = + stages.filter { it.isBuilt }.flatMap { it.effects }.sumBy { it.computePoints(player) } +} + +private object Buildability { + + fun alreadyBuilt() = WonderBuildability( + isBuildable = false, + playabilityLevel = PlayabilityLevel.INCOMPATIBLE_WITH_BOARD + ) + + internal fun requirementDependent(satisfaction: RequirementsSatisfaction) = + WonderBuildability( + isBuildable = satisfaction.satisfied, + minPrice = satisfaction.minPrice, + cheapestTransactions = satisfaction.cheapestTransactions, + playabilityLevel = satisfaction.level + ) +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/WonderStage.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/WonderStage.kt new file mode 100644 index 00000000..974c6a7a --- /dev/null +++ b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/engine/wonders/WonderStage.kt @@ -0,0 +1,31 @@ +package org.luxons.sevenwonders.engine.wonders + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.cards.Requirements +import org.luxons.sevenwonders.engine.effects.Effect +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.resources.ResourceTransactions + +internal class WonderStage( + val requirements: Requirements, + val effects: List +) { + var cardBack: CardBack? = null + private set + + val isBuilt: Boolean + get() = cardBack != null + + fun isBuildable(board: Board, boughtResources: ResourceTransactions): Boolean = + requirements.areMetWithHelpBy(board, boughtResources) + + fun placeCard(cardBack: CardBack) { + this.cardBack = cardBack + } + + fun activate(player: Player, boughtResources: ResourceTransactions) { + effects.forEach { it.applyTo(player) } + requirements.pay(player, boughtResources) + } +} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt deleted file mode 100644 index b8866e9f..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt +++ /dev/null @@ -1,187 +0,0 @@ -package org.luxons.sevenwonders.game - -import org.luxons.sevenwonders.game.api.Action -import org.luxons.sevenwonders.game.api.cards.HandCard -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.api.PlayerTurnInfo -import org.luxons.sevenwonders.game.api.toApiTable -import org.luxons.sevenwonders.game.api.toPlayedMove -import org.luxons.sevenwonders.game.api.toTableCard -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.cards.Decks -import org.luxons.sevenwonders.game.cards.Hands -import org.luxons.sevenwonders.game.data.LAST_AGE -import org.luxons.sevenwonders.game.effects.SpecialAbility -import org.luxons.sevenwonders.game.moves.Move -import org.luxons.sevenwonders.game.moves.resolve -import org.luxons.sevenwonders.game.score.ScoreBoard -import org.luxons.sevenwonders.game.api.ApiTable as ApiTable - -class Game internal constructor( - val id: Long, - private val settings: Settings, - boards: List, - private val decks: Decks -) { - private val table: Table = Table(boards) - private val players: List = boards.map { SimplePlayer(it.playerIndex, table) } - private val discardedCards: MutableList = mutableListOf() - private val preparedMoves: MutableMap = mutableMapOf() - private var currentTurnInfo: List = emptyList() - private var hands: Hands = Hands(emptyList()) - - init { - startNewAge() - } - - private fun startNewAge() { - table.increaseCurrentAge() - hands = decks.deal(table.currentAge, players.size) - startNewTurn() - } - - private fun startNewTurn() { - currentTurnInfo = players.map { createPlayerTurnInfo(it) } - } - - private fun createPlayerTurnInfo(player: Player): PlayerTurnInfo { - val hand = hands.createHand(player) - val action = determineAction(hand, player.board) - val neighbourGuildCards = table.getNeighbourGuildCards(player.index).map { it.toTableCard(null) } - - return PlayerTurnInfo( - playerIndex = player.index, - table = table.toApiTable(), - action = action, - hand = hand, - preparedMove = preparedMoves[player.index]?.toPlayedMove(), - neighbourGuildCards = neighbourGuildCards - ) - } - - /** - * Returns information for each player about the current turn. - */ - fun getCurrentTurnInfo(): Collection = currentTurnInfo - - private fun determineAction(hand: List, board: Board): Action = when { - endOfGameReached() && board.hasSpecial(SpecialAbility.COPY_GUILD) -> determineCopyGuildAction(board) - hand.size == 1 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_LAST - hand.size == 2 && board.hasSpecial(SpecialAbility.PLAY_LAST_CARD) -> Action.PLAY_2 - hand.isEmpty() -> Action.WAIT - else -> Action.PLAY - } - - private fun determineCopyGuildAction(board: Board): Action { - val neighbourGuildCards = table.getNeighbourGuildCards(board.playerIndex) - return if (neighbourGuildCards.isEmpty()) Action.WAIT else Action.PICK_NEIGHBOR_GUILD - } - - /** - * Prepares the given [move] for the player at the given [playerIndex]. - * - * @return the back of the card that is prepared on the table - */ - fun prepareMove(playerIndex: Int, move: PlayerMove): CardBack { - val card = decks.getCard(table.currentAge, move.cardName) - val context = PlayerContext(playerIndex, table, hands[playerIndex]) - val resolvedMove = move.type.resolve(move, card, context) - preparedMoves[playerIndex] = resolvedMove - return card.back - } - - /** - * Returns true if all players that had to do something have [prepared their move][prepareMove]. This means we are - * ready to [play the current turn][playTurn]. - */ - fun allPlayersPreparedTheirMove(): Boolean { - val nbExpectedMoves = currentTurnInfo.count { it.action !== Action.WAIT } - return preparedMoves.size == nbExpectedMoves - } - - /** - * Plays all the [prepared moves][prepareMove] for the current turn. An exception will be thrown if some players - * had not prepared their moves (unless these players had nothing to do). To avoid this, please check if everyone - * is ready using [allPlayersPreparedTheirMove]. - */ - fun playTurn(): ApiTable { - makeMoves() - if (endOfAgeReached()) { - executeEndOfAgeEvents() - if (!endOfGameReached()) { - startNewAge() - } - } else { - rotateHandsIfRelevant() - startNewTurn() - } - return table.toApiTable() - } - - private fun makeMoves() { - val moves = getMovesToPerform() - - // all cards from this turn need to be placed before executing any effect - // because effects depending on played cards need to take the ones from the current turn into account too - placePreparedCards(moves) - - // same goes for the discarded cards during the last turn, which should be available for special actions - if (hands.maxOneCardRemains()) { - discardLastCardsOfHands() - } - - activatePlayedCards(moves) - - table.lastPlayedMoves = moves - preparedMoves.clear() - } - - private fun getMovesToPerform(): List = - currentTurnInfo.filter { it.action !== Action.WAIT }.map { getMoveToPerformFor(it.playerIndex) } - - private fun getMoveToPerformFor(playerIndex: Int) = - preparedMoves[playerIndex] ?: throw MissingPreparedMoveException(playerIndex) - - private fun endOfAgeReached(): Boolean = hands.isEmpty - - private fun executeEndOfAgeEvents() = table.resolveMilitaryConflicts() - - private fun endOfGameReached(): Boolean = endOfAgeReached() && table.currentAge == LAST_AGE - - private fun rotateHandsIfRelevant() { - // we don't rotate hands if some player can play his last card (with the special ability) - if (!hands.maxOneCardRemains()) { - hands = hands.rotate(table.handRotationDirection) - } - } - - private fun placePreparedCards(playedMoves: List) { - playedMoves.forEach { move -> - move.place(discardedCards, settings) - hands = hands.remove(move.playerContext.index, move.card) - } - } - - private fun discardLastCardsOfHands() = - table.boards.filterNot { it.hasSpecial(SpecialAbility.PLAY_LAST_CARD) }.forEach { discardHand(it.playerIndex) } - - private fun discardHand(playerIndex: Int) { - val hand = hands[playerIndex] - discardedCards.addAll(hand) - hands = hands.discardHand(playerIndex) - } - - private fun activatePlayedCards(playedMoves: List) = - playedMoves.forEach { it.activate(discardedCards, settings) } - - /** - * Computes the score for all players. - */ - fun computeScore(): ScoreBoard = ScoreBoard(table.boards.map { it.computeScore(players[it.playerIndex]) }) - - private class MissingPreparedMoveException internal constructor(playerIndex: Int) : - IllegalStateException("Player $playerIndex has not prepared his move") -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Player.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Player.kt deleted file mode 100644 index 877ea530..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Player.kt +++ /dev/null @@ -1,28 +0,0 @@ -package org.luxons.sevenwonders.game - -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.cards.Card - -internal interface Player { - val index: Int - val board: Board - fun getBoard(relativePosition: RelativeBoardPosition): Board -} - -internal data class SimplePlayer( - override val index: Int, - private val table: Table -) : Player { - override val board = table.getBoard(index) - override fun getBoard(relativePosition: RelativeBoardPosition) = table.getBoard(index, relativePosition) -} - -internal data class PlayerContext( - override val index: Int, - private val table: Table, - val hand: List -) : Player by SimplePlayer(index, table) { - val currentAge = table.currentAge -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Settings.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Settings.kt deleted file mode 100644 index ad6cb105..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/Settings.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.luxons.sevenwonders.game - -import org.luxons.sevenwonders.game.api.CustomizableSettings -import org.luxons.sevenwonders.game.api.WonderSide -import org.luxons.sevenwonders.game.api.WonderSidePickMethod -import kotlin.random.Random - -internal class Settings( - val nbPlayers: Int, - customSettings: CustomizableSettings = CustomizableSettings() -) { - val random: Random = customSettings.randomSeedForTests?.let { Random(it) } ?: Random - val timeLimitInSeconds: Int = customSettings.timeLimitInSeconds - val initialGold: Int = customSettings.initialGold - val discardedCardGold: Int = customSettings.discardedCardGold - val defaultTradingCost: Int = customSettings.defaultTradingCost - val pointsPer3Gold: Int = customSettings.pointsPer3Gold - val lostPointsPerDefeat: Int = customSettings.lostPointsPerDefeat - val wonPointsPerVictoryPerAge: Map = customSettings.wonPointsPerVictoryPerAge - - private val wonderSidePickMethod: WonderSidePickMethod = customSettings.wonderSidePickMethod - private var lastPickedSide: WonderSide? = null - - fun pickWonderSide(): WonderSide { - val newSide = wonderSidePickMethod.pickSide(random, lastPickedSide) - lastPickedSide = newSide - return newSide - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt deleted file mode 100644 index a9e3232d..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Boards.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.luxons.sevenwonders.game.api - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.api.boards.ApiBoard -import org.luxons.sevenwonders.game.api.boards.ApiMilitary -import org.luxons.sevenwonders.game.api.boards.ApiProduction -import org.luxons.sevenwonders.game.api.boards.ApiRequirements -import org.luxons.sevenwonders.game.api.boards.ApiScience -import org.luxons.sevenwonders.game.boards.Military -import org.luxons.sevenwonders.game.boards.Science -import org.luxons.sevenwonders.game.boards.ScienceType -import org.luxons.sevenwonders.game.cards.Requirements -import org.luxons.sevenwonders.game.api.cards.TableCard -import org.luxons.sevenwonders.game.moves.Move -import org.luxons.sevenwonders.game.api.resources.CountedResource -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.resources.Resources -import org.luxons.sevenwonders.game.api.wonders.ApiWonder -import org.luxons.sevenwonders.game.api.wonders.ApiWonderStage -import org.luxons.sevenwonders.game.boards.Board as InternalBoard -import org.luxons.sevenwonders.game.wonders.Wonder as InternalWonder -import org.luxons.sevenwonders.game.wonders.WonderStage as InternalWonderStage - -internal fun InternalBoard.toApiBoard(player: Player, lastMove: Move?): ApiBoard = - ApiBoard( - playerIndex = playerIndex, - wonder = wonder.toApiWonder(player, lastMove), - production = production.toApiProduction(), - publicProduction = publicProduction.toApiProduction(), - science = science.toApiScience(), - military = military.toApiMilitary(), - playedCards = getPlayedCards().map { it.toTableCard(lastMove) }.toColumns(), - gold = gold - ) - -internal fun List.toColumns(): List> { - val cardsByColor = this.groupBy { it.color } - val (resourceCardsCols, otherCols) = cardsByColor.values.partition { it[0].color.isResource } - val resourceCardsCol = resourceCardsCols.flatten() - val otherColsSorted = otherCols.sortedBy { it[0].color } - if (resourceCardsCol.isEmpty()) { - return otherColsSorted // we want only non-empty columns - } - return listOf(resourceCardsCol) + otherColsSorted -} - -internal fun InternalWonder.toApiWonder(player: Player, lastMove: Move?): ApiWonder = - ApiWonder( - name = name, - initialResource = initialResource, - stages = stages.map { it.toApiWonderStage(lastBuiltStage == it, lastMove) }, - image = image, - nbBuiltStages = nbBuiltStages, - buildability = computeBuildabilityBy(player) - ) - -internal fun InternalWonderStage.toApiWonderStage( - isLastBuiltStage: Boolean, - lastMove: Move? -): ApiWonderStage = ApiWonderStage( - cardBack = cardBack, - isBuilt = isBuilt, - requirements = requirements.toApiRequirements(), - builtDuringLastMove = lastMove?.type == MoveType.UPGRADE_WONDER && isLastBuiltStage -) - -internal fun Production.toApiProduction(): ApiProduction = - ApiProduction( - fixedResources = getFixedResources().toCountedResourcesList(), alternativeResources = getAlternativeResources() - ) - -internal fun Requirements.toApiRequirements(): ApiRequirements = - ApiRequirements( - gold = gold, resources = resources.toCountedResourcesList() - ) - -internal fun Resources.toCountedResourcesList(): List = - quantities.filterValues { it > 0 } - .map { (type, count) -> CountedResource(count, type) } - .sortedBy { it.type } - -internal fun Military.toApiMilitary(): ApiMilitary = - ApiMilitary(nbShields, totalPoints, nbDefeatTokens) - -internal fun Science.toApiScience(): ApiScience = - ApiScience( - jokers = jokers, - nbWheels = getQuantity(ScienceType.WHEEL), - nbCompasses = getQuantity(ScienceType.COMPASS), - nbTablets = getQuantity(ScienceType.TABLET) - ) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt deleted file mode 100644 index 9580e3be..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Cards.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.luxons.sevenwonders.game.api - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.HandCard -import org.luxons.sevenwonders.game.api.cards.TableCard -import org.luxons.sevenwonders.game.moves.Move - -internal fun Card.toTableCard(lastMove: Move? = null): TableCard = - TableCard( - name = name, - color = color, - requirements = requirements.toApiRequirements(), - chainParent = chainParent, - chainChildren = chainChildren, - image = image, - back = back, - playedDuringLastMove = lastMove != null && this.name == lastMove.card.name - ) - -internal fun Card.toHandCard(player: Player): HandCard = - HandCard( - name = name, - color = color, - requirements = requirements.toApiRequirements(), - chainParent = chainParent, - chainChildren = chainChildren, - image = image, - back = back, - playability = computePlayabilityBy(player) - ) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt deleted file mode 100644 index a5f95456..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/api/Table.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.luxons.sevenwonders.game.api - -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.moves.Move -import org.luxons.sevenwonders.game.boards.Table as InternalTable - -internal fun InternalTable.toApiTable(): ApiTable = ApiTable( - boards = boards.mapIndexed { i, b -> b.toApiBoard(SimplePlayer(i, this), lastPlayedMoves.getOrNull(i)) }, - currentAge = currentAge, - handRotationDirection = handRotationDirection, - lastPlayedMoves = lastPlayedMoves.map { it.toPlayedMove() } -) - -internal fun Move.toPlayedMove(): PlayedMove = PlayedMove( - playerIndex = playerContext.index, - type = type, - card = card.toTableCard(this), - transactions = transactions -) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt deleted file mode 100644 index 38bae9ea..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.Age -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.effects.SpecialAbility -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.resources.TradingRules -import org.luxons.sevenwonders.game.resources.mutableResourcesOf -import org.luxons.sevenwonders.game.score.PlayerScore -import org.luxons.sevenwonders.game.score.ScoreCategory -import org.luxons.sevenwonders.game.wonders.Wonder - -internal class Board(val wonder: Wonder, val playerIndex: Int, settings: Settings) { - - val production = Production(mutableResourcesOf(wonder.initialResource)) - val publicProduction = Production(mutableResourcesOf(wonder.initialResource)) - val science = Science() - val military: Military = Military(settings.lostPointsPerDefeat, settings.wonPointsPerVictoryPerAge) - val tradingRules: TradingRules = TradingRules(settings.defaultTradingCost) - - private val pointsPer3Gold: Int = settings.pointsPer3Gold - - private val playedCards: MutableList = mutableListOf() - private val specialAbilities: MutableSet = hashSetOf() - private val consumedFreeCards: MutableMap = mutableMapOf() - - var gold: Int = settings.initialGold - private set - - var copiedGuild: Card? = null - internal set(copiedGuild) { - if (copiedGuild!!.color !== Color.PURPLE) { - throw IllegalArgumentException("The given card '$copiedGuild' is not a Guild card") - } - field = copiedGuild - } - - fun getPlayedCards(): List = playedCards - - fun addCard(card: Card) { - playedCards.add(card) - } - - fun getNbCardsOfColor(colorFilter: List): Int = playedCards.count { colorFilter.contains(it.color) } - - fun isPlayed(cardName: String): Boolean = playedCards.count { it.name == cardName } > 0 - - fun addGold(amount: Int) { - this.gold += amount - } - - fun removeGold(amount: Int) { - if (gold < amount) { - throw InsufficientFundsException(gold, amount) - } - this.gold -= amount - } - - fun addSpecial(specialAbility: SpecialAbility) { - specialAbilities.add(specialAbility) - } - - fun hasSpecial(specialAbility: SpecialAbility): Boolean = specialAbilities.contains(specialAbility) - - fun canPlayFreeCard(age: Age): Boolean = - hasSpecial(SpecialAbility.ONE_FREE_PER_AGE) && !consumedFreeCards.getOrDefault(age, false) - - fun consumeFreeCard(age: Age) { - consumedFreeCards[age] = true - } - - fun computeScore(player: Player): PlayerScore = PlayerScore( - boardGold = gold, - pointsByCategory = mapOf( - ScoreCategory.CIVIL to computePointsForCards(player, Color.BLUE), - ScoreCategory.MILITARY to military.totalPoints, - ScoreCategory.SCIENCE to science.computePoints(), - ScoreCategory.TRADE to computePointsForCards(player, Color.YELLOW), - ScoreCategory.GUILD to computePointsForCards(player, Color.PURPLE), - ScoreCategory.WONDER to wonder.computePoints(player), - ScoreCategory.GOLD to computeGoldPoints() - ) - ) - - private fun computePointsForCards(player: Player, color: Color): Int = - playedCards.filter { it.color === color } - .flatMap { it.effects } - .map { it.computePoints(player) } - .sum() - - private fun computeGoldPoints(): Int = gold / 3 * pointsPer3Gold - - internal class InsufficientFundsException(current: Int, required: Int) : - IllegalStateException("Current balance is $current gold, but $required are required") -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Military.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Military.kt deleted file mode 100644 index 98404d94..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Military.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.luxons.sevenwonders.game.api.Age - -internal class Military( - private val lostPointsPerDefeat: Int, - private val wonPointsPerVictoryPerAge: Map -) { - var nbShields = 0 - private set - - var totalPoints = 0 - private set - - var nbDefeatTokens = 0 - private set - - internal fun addShields(nbShields: Int) { - this.nbShields += nbShields - } - - internal fun victory(age: Age) { - val wonPoints = wonPointsPerVictoryPerAge[age] ?: throw UnknownAgeException(age) - totalPoints += wonPoints - } - - internal fun defeat() { - totalPoints -= lostPointsPerDefeat - nbDefeatTokens++ - } - - internal class UnknownAgeException(unknownAge: Age) : IllegalArgumentException(unknownAge.toString()) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Science.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Science.kt deleted file mode 100644 index a152c7db..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Science.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -enum class ScienceType { - COMPASS, - WHEEL, - TABLET -} - -internal class Science { - - private val quantities: MutableMap = mutableMapOf() - - var jokers: Int = 0 - private set - - fun size(): Int = quantities.values.sum() + jokers - - fun add(type: ScienceType, quantity: Int) { - quantities.merge(type, quantity) { x, y -> x + y } - } - - fun addJoker(quantity: Int) { - jokers += quantity - } - - fun addAll(science: Science) { - science.quantities.forEach { type, quantity -> this.add(type, quantity) } - jokers += science.jokers - } - - fun getQuantity(type: ScienceType): Int = quantities.getOrDefault(type, 0) - - fun computePoints(): Int { - val values = ScienceType.values().map(::getQuantity).toMutableList() - return computePoints(values, jokers) - } - - private fun computePoints(values: MutableList, jokers: Int): Int { - if (jokers == 0) { - return computePointsNoJoker(values) - } - var maxPoints = 0 - for (i in values.indices) { - values[i]++ - maxPoints = Math.max(maxPoints, computePoints(values, jokers - 1)) - values[i]-- - } - return maxPoints - } - - private fun computePointsNoJoker(values: List): Int { - val independentSquaresSum = values.map { i -> i * i }.sum() - val nbGroupsOfAll = values.min() ?: 0 - return independentSquaresSum + nbGroupsOfAll * 7 - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Table.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Table.kt deleted file mode 100644 index 04b3d104..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Table.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.luxons.sevenwonders.game.api.Age -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.api.boards.neighboursPositions -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.api.cards.HandRotationDirection -import org.luxons.sevenwonders.game.moves.Move - -/** - * The table contains what is visible by all the players in the game: the boards and their played cards, and the - * players' information. - */ -internal class Table(val boards: List) { - - val nbPlayers: Int = boards.size - - var currentAge: Age = 0 - private set - - val handRotationDirection: HandRotationDirection - get() = HandRotationDirection.forAge(currentAge) - - var lastPlayedMoves: List = emptyList() - internal set - - fun getBoard(playerIndex: Int): Board = boards[playerIndex] - - fun getBoard(playerIndex: Int, position: RelativeBoardPosition): Board = - boards[position.getIndexFrom(playerIndex, nbPlayers)] - - fun increaseCurrentAge() { - this.currentAge++ - } - - fun resolveMilitaryConflicts() { - repeat(nbPlayers) { - val board1 = getBoard(it) - val board2 = getBoard(it, RelativeBoardPosition.RIGHT) - resolveConflict(board1, board2) - } - } - - private fun resolveConflict(board1: Board, board2: Board) { - val shields1 = board1.military.nbShields - val shields2 = board2.military.nbShields - if (shields1 < shields2) { - board1.military.defeat() - board2.military.victory(currentAge) - } else if (shields1 > shields2) { - board1.military.victory(currentAge) - board2.military.defeat() - } - } - - fun getNeighbourGuildCards(playerIndex: Int): List = neighboursPositions() - .flatMap { getBoard(playerIndex, it).getPlayedCards() } - .filter { it.color == Color.PURPLE } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt deleted file mode 100644 index b179a2e4..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Cards.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.api.cards.CardPlayability -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.api.cards.PlayabilityLevel -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.effects.Effect -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.noTransactions - -internal data class Card( - val name: String, - val color: Color, - val requirements: Requirements, - val effects: List, - val chainParent: String?, - val chainChildren: List, - val image: String, - val back: CardBack -) { - fun computePlayabilityBy(player: Player): CardPlayability = when { - isAlreadyOnBoard(player.board) -> Playability.incompatibleWithBoard() // cannot play twice the same card - isParentOnBoard(player.board) -> Playability.chainable() - else -> Playability.requirementDependent(requirements.assess(player)) - } - - fun isPlayableOnBoardWith(board: Board, transactions: ResourceTransactions) = - isChainableOn(board) || requirements.areMetWithHelpBy(board, transactions) - - private fun isChainableOn(board: Board): Boolean = !isAlreadyOnBoard(board) && isParentOnBoard(board) - - private fun isAlreadyOnBoard(board: Board): Boolean = board.isPlayed(name) - - private fun isParentOnBoard(board: Board): Boolean = chainParent != null && board.isPlayed(chainParent) - - fun applyTo(player: Player, transactions: ResourceTransactions) { - if (!isChainableOn(player.board)) { - requirements.pay(player, transactions) - } - effects.forEach { it.applyTo(player) } - } -} - -private object Playability { - - internal fun incompatibleWithBoard(): CardPlayability = - CardPlayability( - isPlayable = false, - playabilityLevel = PlayabilityLevel.INCOMPATIBLE_WITH_BOARD - ) - - internal fun chainable(): CardPlayability = - CardPlayability( - isPlayable = true, - isChainable = true, - minPrice = 0, - cheapestTransactions = setOf(noTransactions()), - playabilityLevel = PlayabilityLevel.CHAINABLE - ) - - internal fun requirementDependent(satisfaction: RequirementsSatisfaction): CardPlayability = - CardPlayability( - isPlayable = satisfaction.satisfied, - isChainable = false, - minPrice = satisfaction.minPrice, - cheapestTransactions = satisfaction.cheapestTransactions, - playabilityLevel = satisfaction.level - ) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt deleted file mode 100644 index f3c5d471..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Decks.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -internal fun List.deal(nbPlayers: Int): Hands { - val hands: Map> = this.withIndex() - .groupBy { (index, _) -> index % nbPlayers } - .mapValues { it.value.map { (_, cards) -> cards } } - - val allHands = List(nbPlayers) { i -> hands[i] ?: emptyList() } - return Hands(allHands) -} - -internal class Decks(private val cardsPerAge: Map>) { - - @Throws(Decks.CardNotFoundException::class) - fun getCard(age: Int, cardName: String): Card = - getDeck(age).firstOrNull { c -> c.name == cardName } ?: throw CardNotFoundException(cardName) - - fun deal(age: Int, nbPlayers: Int): Hands { - val deck = getDeck(age) - validateNbCards(deck, nbPlayers) - return deck.deal(nbPlayers) - } - - private fun getDeck(age: Int): List { - return cardsPerAge[age] ?: throw IllegalArgumentException("No deck found for age $age") - } - - private fun validateNbCards(deck: List, nbPlayers: Int) { - if (nbPlayers == 0) { - throw IllegalArgumentException("Cannot deal cards between 0 players") - } - if (deck.size % nbPlayers != 0) { - throw IllegalArgumentException("Cannot deal ${deck.size} cards evenly between $nbPlayers players") - } - } - - inner class CardNotFoundException(message: String) : RuntimeException(message) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt deleted file mode 100644 index 1dc5e5df..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Hands.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.api.cards.HandCard -import org.luxons.sevenwonders.game.api.cards.HandRotationDirection -import org.luxons.sevenwonders.game.api.toHandCard - -internal class Hands(private val hands: List>) { - - val isEmpty: Boolean = this.hands.all(List::isEmpty) - - operator fun get(playerIndex: Int): List { - return hands[playerIndex] - } - - fun discardHand(playerIndex: Int): Hands { - val mutatedHands = hands.toMutableList() - mutatedHands[playerIndex] = emptyList() - return Hands(mutatedHands) - } - - fun remove(playerIndex: Int, card: Card): Hands { - val mutatedHands = hands.toMutableList() - mutatedHands[playerIndex] = hands[playerIndex] - card - return Hands(mutatedHands) - } - - fun createHand(player: Player): List = hands[player.index].map { c -> c.toHandCard(player) } - - fun rotate(direction: HandRotationDirection): Hands { - val newHands = when (direction) { - HandRotationDirection.RIGHT -> hands.takeLast(1) + hands.dropLast(1) - HandRotationDirection.LEFT -> hands.drop(1) + hands.take(1) - } - return Hands(newHands) - } - - fun maxOneCardRemains(): Boolean = hands.map { it.size }.max() ?: 0 <= 1 -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt deleted file mode 100644 index 3cdf8141..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/Requirements.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.resources.Resources -import org.luxons.sevenwonders.game.resources.asResources -import org.luxons.sevenwonders.game.resources.bestSolution -import org.luxons.sevenwonders.game.resources.emptyResources -import org.luxons.sevenwonders.game.resources.execute - -data class Requirements internal constructor( - val gold: Int = 0, - val resources: Resources = emptyResources() -) { - /** - * Returns information about the extent to which the given [player] meets these requirements, either on its own or - * by buying resources to neighbours. - */ - internal fun assess(player: Player): RequirementsSatisfaction { - if (player.board.gold < gold) { - return RequirementsSatisfaction.missingRequiredGold(gold) - } - if (resources.isEmpty()) { - if (gold > 0) { - return RequirementsSatisfaction.enoughGold(gold) - } - return RequirementsSatisfaction.noRequirements() - } - if (producesRequiredResources(player.board)) { - if (gold > 0) { - return RequirementsSatisfaction.enoughResourcesAndGold(gold) - } - return RequirementsSatisfaction.enoughResources() - } - return satisfactionWithPotentialHelp(player) - } - - private fun satisfactionWithPotentialHelp(player: Player): RequirementsSatisfaction { - val (minPriceForResources, possibleTransactions) = bestSolution(resources, player) - val minPrice = minPriceForResources + gold - if (possibleTransactions.isEmpty()) { - return RequirementsSatisfaction.unavailableResources() - } - if (player.board.gold < minPrice) { - return RequirementsSatisfaction.missingGoldForResources(minPrice, possibleTransactions) - } - return RequirementsSatisfaction.metWithHelp(minPrice, possibleTransactions) - } - - /** - * Returns whether the given board meets these requirements, if the specified resources are bought from neighbours. - * - * @param board the board to check - * @param boughtResources the resources the player intends to buy - * - * @return true if the given board meets these requirements - */ - internal fun areMetWithHelpBy(board: Board, boughtResources: ResourceTransactions): Boolean { - if (!hasRequiredGold(board, boughtResources)) { - return false - } - return producesRequiredResources(board) || producesRequiredResourcesWithHelp(board, boughtResources) - } - - private fun hasRequiredGold(board: Board, resourceTransactions: ResourceTransactions): Boolean { - val resourcesPrice = board.tradingRules.computeCost(resourceTransactions) - return board.gold >= gold + resourcesPrice - } - - private fun producesRequiredResources(board: Board): Boolean = board.production.contains(resources) - - private fun producesRequiredResourcesWithHelp(board: Board, transactions: ResourceTransactions): Boolean { - val totalBoughtResources = transactions.asResources() - val remainingResources = resources - totalBoughtResources - return board.production.contains(remainingResources) - } - - internal fun pay(player: Player, transactions: ResourceTransactions) { - player.board.removeGold(gold) - transactions.execute(player) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt deleted file mode 100644 index 3df2811f..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/cards/RequirementsSatisfaction.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.luxons.sevenwonders.game.api.cards.PlayabilityLevel -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.noTransactions - -internal data class RequirementsSatisfaction( - val satisfied: Boolean, - val level: PlayabilityLevel, - val minPrice: Int, - val cheapestTransactions: Set -) { - companion object { - - internal fun noRequirements() = - RequirementsSatisfaction(true, PlayabilityLevel.NO_REQUIREMENTS, 0, setOf(noTransactions())) - - internal fun enoughResources() = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_RESOURCES, 0, setOf(noTransactions())) - - internal fun enoughGold(minPrice: Int) = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD, minPrice, setOf(noTransactions())) - - internal fun enoughResourcesAndGold(minPrice: Int) = - RequirementsSatisfaction(true, PlayabilityLevel.ENOUGH_GOLD_AND_RES, minPrice, setOf(noTransactions())) - - internal fun metWithHelp(minPrice: Int, cheapestTransactions: Set) = - RequirementsSatisfaction(true, PlayabilityLevel.REQUIRES_HELP, minPrice, cheapestTransactions) - - internal fun missingRequiredGold(minPrice: Int) = - RequirementsSatisfaction(false, PlayabilityLevel.MISSING_REQUIRED_GOLD, minPrice, emptySet()) - - internal fun missingGoldForResources(minPrice: Int, cheapestTransactions: Set) = - RequirementsSatisfaction(false, PlayabilityLevel.MISSING_GOLD_FOR_RES, minPrice, cheapestTransactions) - - internal fun unavailableResources() = - RequirementsSatisfaction(false, PlayabilityLevel.UNAVAILABLE_RESOURCES, Int.MAX_VALUE, emptySet()) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt deleted file mode 100644 index f682b970..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/GameDefinition.kt +++ /dev/null @@ -1,92 +0,0 @@ -package org.luxons.sevenwonders.game.data - -import com.github.salomonbrys.kotson.typeToken -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.luxons.sevenwonders.game.Game -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.Age -import org.luxons.sevenwonders.game.api.CustomizableSettings -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.data.definitions.DecksDefinition -import org.luxons.sevenwonders.game.data.definitions.WonderDefinition -import org.luxons.sevenwonders.game.data.serializers.NumericEffectSerializer -import org.luxons.sevenwonders.game.data.serializers.ProductionIncreaseSerializer -import org.luxons.sevenwonders.game.data.serializers.ProductionSerializer -import org.luxons.sevenwonders.game.data.serializers.ResourceTypeSerializer -import org.luxons.sevenwonders.game.data.serializers.ResourceTypesSerializer -import org.luxons.sevenwonders.game.data.serializers.ResourcesSerializer -import org.luxons.sevenwonders.game.data.serializers.ScienceProgressSerializer -import org.luxons.sevenwonders.game.effects.GoldIncrease -import org.luxons.sevenwonders.game.effects.MilitaryReinforcements -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.effects.RawPointsIncrease -import org.luxons.sevenwonders.game.effects.ScienceProgress -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources - -internal const val LAST_AGE: Age = 3 - -internal data class GlobalRules( - val minPlayers: Int, - val maxPlayers: Int -) - -class GameDefinition internal constructor( - rules: GlobalRules, - private val wonders: List, - private val decksDefinition: DecksDefinition -) { - val minPlayers: Int = rules.minPlayers - val maxPlayers: Int = rules.maxPlayers - - fun initGame(id: Long, customSettings: CustomizableSettings, nbPlayers: Int): Game { - val settings = Settings(nbPlayers, customSettings) - val boards = assignBoards(settings, nbPlayers) - val decks = decksDefinition.prepareDecks(settings.nbPlayers, settings.random) - return Game(id, settings, boards, decks) - } - - private fun assignBoards(settings: Settings, nbPlayers: Int): List { - return wonders.shuffled(settings.random) - .take(nbPlayers) - .mapIndexed { i, wDef -> Board(wDef.create(settings.pickWonderSide()), i, settings) } - } - - companion object { - - fun load(): GameDefinition { - val gson: Gson = createGson() - val rules = loadJson("global_rules.json", GlobalRules::class.java, gson) - val wonders = loadJson("wonders.json", Array::class.java, gson) - val decksDefinition = loadJson("cards.json", DecksDefinition::class.java, gson) - return GameDefinition(rules, wonders.toList(), decksDefinition) - } - } -} - -private fun loadJson(filename: String, clazz: Class, gson: Gson): T { - val packageAsPath = GameDefinition::class.java.`package`.name.replace('.', '/') - val resourcePath = "/$packageAsPath/$filename" - val resource = GameDefinition::class.java.getResource(resourcePath) - val json = resource.readText() - return gson.fromJson(json, clazz) -} - -private fun createGson(): Gson { - return GsonBuilder().disableHtmlEscaping() - .registerTypeAdapter(ResourcesSerializer()) - .registerTypeAdapter(ResourceTypeSerializer()) - .registerTypeAdapter>(ResourceTypesSerializer()) - .registerTypeAdapter(ProductionSerializer()) - .registerTypeAdapter(ProductionIncreaseSerializer()) - .registerTypeAdapter(NumericEffectSerializer()) - .registerTypeAdapter(NumericEffectSerializer()) - .registerTypeAdapter(NumericEffectSerializer()) - .registerTypeAdapter(ScienceProgressSerializer()) - .create() -} - -private inline fun GsonBuilder.registerTypeAdapter(typeAdapter: Any): GsonBuilder = - this.registerTypeAdapter(typeToken(), typeAdapter) diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt deleted file mode 100644 index 879c2c68..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/CardDefinition.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.luxons.sevenwonders.game.data.definitions - -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.cards.Requirements - -internal class CardDefinition( - private val name: String, - private val color: Color, - private val requirements: Requirements?, - private val effect: EffectsDefinition, - private val chainParent: String?, - private val chainChildren: List?, - private val image: String, - private val countPerNbPlayer: Map -) { - fun create(back: CardBack, nbPlayers: Int): List = List(countPerNbPlayer[nbPlayers] ?: 0) { create(back) } - - fun create(back: CardBack): Card { - val reqs = requirements ?: Requirements() - val children = chainChildren ?: emptyList() - return Card(name, color, reqs, effect.create(), chainParent, children, image, back) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt deleted file mode 100644 index a4011e84..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/DecksDefinition.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.luxons.sevenwonders.game.data.definitions - -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.cards.Decks -import kotlin.random.Random - -internal class DeckDefinition( - val cards: List, - val backImage: String -) { - fun create(nbPlayers: Int): List = cards.flatMap { it.create( - CardBack( - backImage - ), nbPlayers) } -} - -internal class DecksDefinition( - private val age1: DeckDefinition, - private val age2: DeckDefinition, - private val age3: DeckDefinition, - private val guildCards: List -) { - fun prepareDecks(nbPlayers: Int, random: Random) = Decks( - mapOf( - 1 to age1.create(nbPlayers).shuffled(random), - 2 to age2.create(nbPlayers).shuffled(random), - 3 to (age3.create(nbPlayers) + pickGuildCards(nbPlayers, random)).shuffled(random) - ) - ) - - private fun pickGuildCards(nbPlayers: Int, random: Random): List { - val back = CardBack(age3.backImage) - val guild = guildCards.map { it.create(back) }.shuffled(random) - return guild.subList(0, nbPlayers + 2) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt deleted file mode 100644 index 4d0348ea..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.luxons.sevenwonders.game.data.definitions - -import org.luxons.sevenwonders.game.effects.BonusPerBoardElement -import org.luxons.sevenwonders.game.effects.Discount -import org.luxons.sevenwonders.game.effects.Effect -import org.luxons.sevenwonders.game.effects.GoldIncrease -import org.luxons.sevenwonders.game.effects.MilitaryReinforcements -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.effects.RawPointsIncrease -import org.luxons.sevenwonders.game.effects.ScienceProgress -import org.luxons.sevenwonders.game.effects.SpecialAbility -import org.luxons.sevenwonders.game.effects.SpecialAbilityActivation - -internal class EffectsDefinition( - private val gold: GoldIncrease? = null, - private val military: MilitaryReinforcements? = null, - private val science: ScienceProgress? = null, - private val discount: Discount? = null, - private val perBoardElement: BonusPerBoardElement? = null, - private val production: ProductionIncrease? = null, - private val points: RawPointsIncrease? = null, - private val action: SpecialAbility? = null -) { - fun create(): List = mutableListOf().apply { - gold?.let { add(it) } - military?.let { add(it) } - science?.let { add(it) } - discount?.let { add(it) } - perBoardElement?.let { add(it) } - production?.let { add(it) } - points?.let { add(it) } - action?.let { add(SpecialAbilityActivation(it)) } - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderDefinition.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderDefinition.kt deleted file mode 100644 index 6b694acc..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderDefinition.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.luxons.sevenwonders.game.data.definitions - -import org.luxons.sevenwonders.game.api.WonderSide -import org.luxons.sevenwonders.game.cards.Requirements -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.wonders.Wonder -import org.luxons.sevenwonders.game.wonders.WonderStage - -internal class WonderDefinition( - private val name: String, - private val sides: Map -) { - fun create(wonderSide: WonderSide): Wonder = sides[wonderSide]!!.createWonder(name) -} - -internal class WonderSideDefinition( - private val initialResource: ResourceType, - private val stages: List, - private val image: String -) { - fun createWonder(name: String): Wonder = Wonder(name, initialResource, stages.map { it.create() }, image) -} - -internal class WonderStageDefinition( - private val requirements: Requirements, - private val effects: EffectsDefinition -) { - fun create(): WonderStage = WonderStage(requirements, effects.create()) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializer.kt deleted file mode 100644 index 9a9a006e..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializer.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.effects.Effect -import org.luxons.sevenwonders.game.effects.GoldIncrease -import org.luxons.sevenwonders.game.effects.MilitaryReinforcements -import org.luxons.sevenwonders.game.effects.RawPointsIncrease -import java.lang.reflect.Type - -internal class NumericEffectSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize(effect: Effect, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val value: Int = when (effect) { - is MilitaryReinforcements -> effect.count - is GoldIncrease -> effect.amount - is RawPointsIncrease -> effect.points - else -> throw IllegalArgumentException("Unknown numeric effect " + effect.javaClass.name) - } - return JsonPrimitive(value) - } - - @Throws(JsonParseException::class) - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Effect { - val value = json.asInt - return when (typeOfT) { - MilitaryReinforcements::class.java -> MilitaryReinforcements(value) - GoldIncrease::class.java -> GoldIncrease(value) - RawPointsIncrease::class.java -> RawPointsIncrease(value) - else -> throw IllegalArgumentException("Unknown numeric effet " + typeOfT.typeName) - } - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializer.kt deleted file mode 100644 index 0970a968..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializer.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.resources.Production -import java.lang.reflect.Type - -internal class ProductionIncreaseSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize( - productionIncrease: ProductionIncrease, - typeOfSrc: Type, - context: JsonSerializationContext - ): JsonElement { - val production = productionIncrease.production - val json = context.serialize(production) - return if (!json.isJsonNull && !productionIncrease.isSellable) { JsonPrimitive("(${json.asString})") } else json - } - - @Throws(JsonParseException::class) - override fun deserialize( - json: JsonElement, - typeOfT: Type, - context: JsonDeserializationContext - ): ProductionIncrease { - var prodJson = json - - var resourcesStr = prodJson.asString - val isSellable = !resourcesStr.startsWith("(") - if (!isSellable) { - resourcesStr = unwrapBrackets(resourcesStr) - prodJson = JsonPrimitive(resourcesStr) - } - val production = context.deserialize(prodJson, Production::class.java) - return ProductionIncrease(production, isSellable) - } - - private fun unwrapBrackets(str: String): String { - return str.substring(1, str.length - 1) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt deleted file mode 100644 index 9f3c4721..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializer.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonParseException -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources -import java.lang.reflect.Type - -internal class ProductionSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize(production: Production, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val fixedResources = production.getFixedResources() - val choices = production.getAlternativeResources() - return when { - fixedResources.isEmpty() -> serializeAsChoice(choices, context) - choices.isEmpty() -> serializeAsResources(fixedResources, context) - else -> throw IllegalArgumentException("Cannot serialize a production with mixed fixed resources and choices") - } - } - - private fun serializeAsChoice(choices: Set>, context: JsonSerializationContext): JsonElement { - if (choices.isEmpty()) { - return JsonNull.INSTANCE - } - if (choices.size > 1) { - throw IllegalArgumentException("Cannot serialize a production with more than one choice") - } - val str = choices.flatMap { it }.map { it.symbol }.joinToString("/") - return context.serialize(str) - } - - private fun serializeAsResources(fixedResources: Resources, context: JsonSerializationContext): JsonElement { - return context.serialize(fixedResources) - } - - @Throws(JsonParseException::class) - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Production { - val resourcesStr = json.asString - val production = Production() - if (resourcesStr.contains("/")) { - production.addChoice(*createChoice(resourcesStr)) - } else { - val fixedResources = context.deserialize(json, Resources::class.java) - production.addAll(fixedResources) - } - return production - } - - private fun createChoice(choiceStr: String): Array { - return choiceStr.split("/").map { ResourceType.fromSymbol(it) }.toTypedArray() - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializer.kt deleted file mode 100644 index 37dd2cdb..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializer.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import java.lang.reflect.Type - -internal class ResourceTypeSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize(type: ResourceType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement = - JsonPrimitive(type.symbol) - - @Throws(JsonParseException::class) - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ResourceType { - val str = json.asString - if (str.isEmpty()) { - throw IllegalArgumentException("Empty string is not a valid resource level") - } - return ResourceType.fromSymbol(str[0]) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializer.kt deleted file mode 100644 index 8dc43af4..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializer.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import java.lang.reflect.Type - -internal class ResourceTypesSerializer : JsonSerializer>, JsonDeserializer> { - - override fun serialize( - resources: List, - typeOfSrc: Type, - context: JsonSerializationContext - ): JsonElement { - val s = resources.map { it.symbol }.joinToString("") - return JsonPrimitive(s) - } - - @Throws(JsonParseException::class) - override fun deserialize( - json: JsonElement, - typeOfT: Type, - context: JsonDeserializationContext - ): List = json.asString.map { ResourceType.fromSymbol(it) } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt deleted file mode 100644 index 0d9230d8..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializer.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources -import org.luxons.sevenwonders.game.resources.toResources -import java.lang.reflect.Type - -internal class ResourcesSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize(resources: Resources, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { - val s = resources.toList().map { it.symbol }.joinToString("") - return if (s.isEmpty()) JsonNull.INSTANCE else JsonPrimitive(s) - } - - @Throws(JsonParseException::class) - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Resources = - json.asString.map { ResourceType.fromSymbol(it) to 1 }.toResources() -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializer.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializer.kt deleted file mode 100644 index d6dc9ae3..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializer.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.google.gson.JsonDeserializationContext -import com.google.gson.JsonDeserializer -import com.google.gson.JsonElement -import com.google.gson.JsonNull -import com.google.gson.JsonParseException -import com.google.gson.JsonPrimitive -import com.google.gson.JsonSerializationContext -import com.google.gson.JsonSerializer -import org.luxons.sevenwonders.game.boards.Science -import org.luxons.sevenwonders.game.boards.ScienceType -import org.luxons.sevenwonders.game.effects.ScienceProgress -import java.lang.reflect.Type - -internal class ScienceProgressSerializer : JsonSerializer, JsonDeserializer { - - override fun serialize( - scienceProgress: ScienceProgress, - typeOfSrc: Type, - context: JsonSerializationContext - ): JsonElement { - val science = scienceProgress.science - - if (science.size() > 1) { - throw UnsupportedOperationException("Cannot serialize science containing more than one element") - } - - for (type in ScienceType.values()) { - val quantity = science.getQuantity(type) - if (quantity == 1) { - return context.serialize(type) - } - } - - return if (science.jokers == 1) JsonPrimitive("any") else JsonNull.INSTANCE - } - - @Throws(JsonParseException::class) - override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ScienceProgress { - val s = json.asString - val science = Science() - if ("any" == s) { - science.addJoker(1) - } else { - science.add(deserializeScienceType(json, context), 1) - } - return ScienceProgress(science) - } - - private fun deserializeScienceType(json: JsonElement, context: JsonDeserializationContext): ScienceType { - return context.deserialize(json, ScienceType::class.java) - ?: throw IllegalArgumentException("Invalid science level " + json.asString) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElement.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElement.kt deleted file mode 100644 index f16c9f78..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElement.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.api.cards.Color - -enum class BoardElementType { - CARD, - BUILT_WONDER_STAGES, - DEFEAT_TOKEN -} - -internal data class BonusPerBoardElement( - val boards: List, - val type: BoardElementType, - val gold: Int = 0, - val points: Int = 0, - val colors: List? = null // only relevant if type=CARD -) : Effect { - - override fun applyTo(player: Player) = player.board.addGold(gold * nbMatchingElementsFor(player)) - - override fun computePoints(player: Player): Int = points * nbMatchingElementsFor(player) - - private fun nbMatchingElementsFor(player: Player): Int = boards - .map(player::getBoard) - .map(::nbMatchingElementsIn) - .sum() - - private fun nbMatchingElementsIn(board: Board): Int = when (type) { - BoardElementType.CARD -> board.getNbCardsOfColor(colors!!) - BoardElementType.BUILT_WONDER_STAGES -> board.wonder.nbBuiltStages - BoardElementType.DEFEAT_TOKEN -> board.military.nbDefeatTokens - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Discount.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Discount.kt deleted file mode 100644 index 0809d279..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Discount.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceType - -internal data class Discount( - val resourceTypes: List = emptyList(), - val providers: List = emptyList(), - val discountedPrice: Int = 1 -) : InstantOwnBoardEffect() { - - public override fun applyTo(board: Board) { - val rules = board.tradingRules - for (type in resourceTypes) { - providers.forEach { rules.setCost(type, it, discountedPrice) } - } - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Effect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Effect.kt deleted file mode 100644 index 55744669..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/Effect.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player - -/** - * Represents an effect than can be applied to a player's board when playing a card or building his wonder. The effect - * may affect (or depend on) the adjacent boards. It can have an instantaneous effect on the board, or be postponed to - * the end of game where point calculations take place. - */ -internal interface Effect { - - fun applyTo(player: Player) - - fun computePoints(player: Player): Int -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/EndGameEffect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/EndGameEffect.kt deleted file mode 100644 index b4e7a683..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/EndGameEffect.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player - -internal abstract class EndGameEffect : Effect { - - // EndGameEffects don't do anything when applied to the board, they simply give more points in the end - override fun applyTo(player: Player) = Unit -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/GoldIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/GoldIncrease.kt deleted file mode 100644 index f6e37841..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/GoldIncrease.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.boards.Board - -internal data class GoldIncrease(val amount: Int) : InstantOwnBoardEffect() { - - public override fun applyTo(board: Board) = board.addGold(amount) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/InstantOwnBoardEffect.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/InstantOwnBoardEffect.kt deleted file mode 100644 index 0b50656c..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/InstantOwnBoardEffect.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board - -internal abstract class InstantOwnBoardEffect : Effect { - - override fun applyTo(player: Player) = applyTo(player.board) - - protected abstract fun applyTo(board: Board) - - // InstantEffects are only important when applied to the board, they don't give extra points in the end - override fun computePoints(player: Player): Int = 0 -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcements.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcements.kt deleted file mode 100644 index a168943c..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcements.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.boards.Board - -internal data class MilitaryReinforcements(val count: Int) : InstantOwnBoardEffect() { - - public override fun applyTo(board: Board) = board.military.addShields(count) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncrease.kt deleted file mode 100644 index 4bd4a27f..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncrease.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.resources.Production - -internal data class ProductionIncrease(val production: Production, val isSellable: Boolean) : InstantOwnBoardEffect() { - - public override fun applyTo(board: Board) { - board.production.addAll(production) - if (isSellable) { - board.publicProduction.addAll(production) - } - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncrease.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncrease.kt deleted file mode 100644 index a47686da..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncrease.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player - -internal data class RawPointsIncrease(val points: Int) : EndGameEffect() { - - override fun computePoints(player: Player): Int = points -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgress.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgress.kt deleted file mode 100644 index 462330db..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgress.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.boards.Science - -internal class ScienceProgress(val science: Science) : InstantOwnBoardEffect() { - - public override fun applyTo(board: Board) = board.science.addAll(science) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbility.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbility.kt deleted file mode 100644 index d271134f..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbility.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board - -enum class SpecialAbility { - - /** - * The player can play the last card of each age instead of discarding it. This card can be played by paying its - * cost, discarded to gain 3 coins or used in the construction of his or her Wonder. - */ - PLAY_LAST_CARD, - - /** - * Once per age, a player can construct a building from his or her hand for free. - */ - ONE_FREE_PER_AGE, - - /** - * The player can look at all cards discarded since the beginning of the game, pick one and build it for free. - */ - PLAY_DISCARDED, - - /** - * The player can, at the end of the game, "copy" a Guild of his or her choice (purple card), built by one of his or - * her two neighboring cities. - */ - COPY_GUILD { - override fun computePoints(player: Player): Int { - val copiedGuild = player.board.copiedGuild - ?: throw IllegalStateException("The copied Guild has not been chosen, cannot compute points") - return copiedGuild.effects.stream().mapToInt { it.computePoints(player) }.sum() - } - }; - - internal fun apply(board: Board) = board.addSpecial(this) - - internal open fun computePoints(player: Player): Int = 0 -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivation.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivation.kt deleted file mode 100644 index 66521679..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivation.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.luxons.sevenwonders.game.Player - -internal data class SpecialAbilityActivation(val specialAbility: SpecialAbility) : Effect { - - override fun applyTo(player: Player) = specialAbility.apply(player.board) - - override fun computePoints(player: Player): Int = specialAbility.computePoints(player) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMove.kt deleted file mode 100644 index c4508401..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMove.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.cards.Card - -internal class BuildWonderMove(move: PlayerMove, card: Card, player: PlayerContext) : - CardFromHandMove(move, card, player) { - - private val wonder = player.board.wonder - - init { - if (!wonder.isNextStageBuildable(playerContext.board, transactions)) { - throw InvalidMoveException(this, "all levels are already built, or the given resources are insufficient") - } - } - - override fun place(discardedCards: MutableList, settings: Settings) = wonder.placeCard(card.back) - - override fun activate(discardedCards: List, settings: Settings) = - wonder.activateLastBuiltStage(playerContext, transactions) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CardFromHandMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CardFromHandMove.kt deleted file mode 100644 index 17d612af..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CardFromHandMove.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.cards.Card - -internal abstract class CardFromHandMove(move: PlayerMove, card: Card, player: PlayerContext) : - Move(move, card, player) { - - init { - if (!player.hand.contains(card)) { - throw InvalidMoveException(this, "card '${card.name}' not in hand") - } - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CopyGuildMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CopyGuildMove.kt deleted file mode 100644 index b637eb6c..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/CopyGuildMove.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.effects.SpecialAbility - -internal class CopyGuildMove(move: PlayerMove, card: Card, player: PlayerContext) : Move(move, card, player) { - - init { - val board = player.board - if (!board.hasSpecial(SpecialAbility.COPY_GUILD)) { - throw InvalidMoveException(this, "no ability to copy guild cards") - } - if (card.color !== Color.PURPLE) { - throw InvalidMoveException(this, "card '${card.name}' is not a guild card") - } - val leftNeighbourHasIt = neighbourHasTheCard(RelativeBoardPosition.LEFT) - val rightNeighbourHasIt = neighbourHasTheCard(RelativeBoardPosition.RIGHT) - if (!leftNeighbourHasIt && !rightNeighbourHasIt) { - throw InvalidMoveException(this, "neighbours don't have card '${card.name}'") - } - } - - private fun neighbourHasTheCard(position: RelativeBoardPosition): Boolean = - playerContext.getBoard(position).getPlayedCards().contains(card) - - // nothing special to do here - override fun place(discardedCards: MutableList, settings: Settings) = Unit - - override fun activate(discardedCards: List, settings: Settings) { - playerContext.board.copiedGuild = card - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/DiscardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/DiscardMove.kt deleted file mode 100644 index a8cd42a6..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/DiscardMove.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.cards.Card - -internal class DiscardMove(move: PlayerMove, card: Card, player: PlayerContext) : - CardFromHandMove(move, card, player) { - - override fun place(discardedCards: MutableList, settings: Settings) { - discardedCards.add(card) - } - - override fun activate(discardedCards: List, settings: Settings) { - playerContext.board.addGold(settings.discardedCardGold) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt deleted file mode 100644 index 134918d0..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/Move.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.api.MoveType -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions - -internal abstract class Move( - val move: PlayerMove, - val card: Card, - val playerContext: PlayerContext -) { - val type: MoveType = move.type - - val transactions: ResourceTransactions = move.transactions - - abstract fun place(discardedCards: MutableList, settings: Settings) - - abstract fun activate(discardedCards: List, settings: Settings) -} - -class InvalidMoveException internal constructor(move: Move, message: String) : IllegalArgumentException( - "Player ${move.playerContext.index} cannot perform move ${move.type}: $message" -) - -internal fun MoveType.resolve(move: PlayerMove, card: Card, context: PlayerContext): Move = when (this) { - MoveType.PLAY -> PlayCardMove(move, card, context) - MoveType.PLAY_FREE -> PlayFreeCardMove(move, card, context) - MoveType.UPGRADE_WONDER -> BuildWonderMove(move, card, context) - MoveType.DISCARD -> DiscardMove(move, card, context) - MoveType.COPY_GUILD -> CopyGuildMove(move, card, context) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayCardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayCardMove.kt deleted file mode 100644 index 3596b164..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayCardMove.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.cards.Card - -internal class PlayCardMove(move: PlayerMove, card: Card, player: PlayerContext) : - CardFromHandMove(move, card, player) { - - init { - if (!card.isPlayableOnBoardWith(player.board, transactions)) { - throw InvalidMoveException(this, "requirements not met to play the card ${card.name}") - } - } - - override fun place(discardedCards: MutableList, settings: Settings) = playerContext.board.addCard(card) - - override fun activate(discardedCards: List, settings: Settings) = card.applyTo(playerContext, transactions) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayFreeCardMove.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayFreeCardMove.kt deleted file mode 100644 index bac185d9..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/moves/PlayFreeCardMove.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.cards.Card - -internal class PlayFreeCardMove(move: PlayerMove, card: Card, playerContext: PlayerContext) : - CardFromHandMove(move, card, playerContext) { - - init { - val board = playerContext.board - if (!board.canPlayFreeCard(playerContext.currentAge)) { - throw InvalidMoveException(this, "no free card available for the current age ${playerContext.currentAge}") - } - } - - override fun place(discardedCards: MutableList, settings: Settings) = playerContext.board.addCard(card) - - override fun activate(discardedCards: List, settings: Settings) { - // only apply effects, without paying the cost - card.effects.forEach { it.applyTo(playerContext) } - playerContext.board.consumeFreeCard(playerContext.currentAge) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt deleted file mode 100644 index e4d4c6c4..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt +++ /dev/null @@ -1,125 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.ResourceType -import java.util.EnumSet - -internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = - BestPriceCalculator(resources, player).computeBestSolution() - -data class TransactionPlan(val price: Int, val possibleTransactions: Set) - -private class ResourcePool( - val provider: Provider?, - private val rules: TradingRules, - choices: Set> -) { - val choices: Set> = choices.map { it.toMutableSet() }.toSet() - - fun getCost(type: ResourceType): Int = if (provider == null) 0 else rules.getCost(type, provider) -} - -private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { - - private val pools: List - private val resourcesLeftToPay: MutableResources - private val boughtResources: MutableMap = HashMap() - private var pricePaid: Int = 0 - - private var bestSolutions: MutableSet = mutableSetOf() - private var bestPrice: Int = Integer.MAX_VALUE - - init { - val board = player.board - this.resourcesLeftToPay = resourcesToPay.minus(board.production.getFixedResources()).toMutableResources() - this.pools = createResourcePools(player) - } - - private fun createResourcePools(player: Player): List { - // we only take alternative resources here, because fixed resources were already removed for optimization - val ownBoardChoices = player.board.production.getAlternativeResources() - val ownPool = ResourcePool(null, player.board.tradingRules, ownBoardChoices) - val providerPools = Provider.values().map { it.toResourcePoolFor(player) } - - return providerPools + ownPool - } - - private fun Provider.toResourcePoolFor(player: Player): ResourcePool { - val providerBoard = player.getBoard(boardPosition) - val choices = providerBoard.publicProduction.asChoices() - return ResourcePool(this, player.board.tradingRules, choices) - } - - fun computeBestSolution(): TransactionPlan { - computePossibilities() - return TransactionPlan(bestPrice, bestSolutions) - } - - private fun computePossibilities() { - if (resourcesLeftToPay.isEmpty()) { - updateBestSolutionIfNeeded() - return - } - for (type in ResourceType.values()) { - if (resourcesLeftToPay[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) - buyOne(provider, type, cost) - computePossibilitiesWhenUsing(type, pool) - unbuyOne(provider, type, cost) - resourcesLeftToPay.add(type, 1) - } - - fun buyOne(provider: Provider, type: ResourceType, cost: Int) { - boughtResources.getOrPut(provider) { MutableResources() }.add(type, 1) - pricePaid += cost - } - - fun unbuyOne(provider: Provider, type: ResourceType, cost: Int) { - pricePaid -= cost - boughtResources.get(provider)!!.remove(type, 1) - } - - private fun computePossibilitiesWhenUsing(type: ResourceType, pool: ResourcePool) { - for (choice in pool.choices) { - if (choice.contains(type)) { - val temp = EnumSet.copyOf(choice) - choice.clear() - computePossibilities() - choice.addAll(temp) - } - } - } - - private fun updateBestSolutionIfNeeded() { - if (pricePaid > bestPrice) return - - if (pricePaid < bestPrice) { - bestPrice = pricePaid - bestSolutions.clear() - } - // avoid mutating the resources from the transactions - val transactionSet = boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions() - bestSolutions.add(transactionSet) - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt deleted file mode 100644 index f3c71e2a..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.api.resources.ResourceType -import java.util.EnumSet - -data class Production internal constructor( - private val fixedResources: MutableResources = mutableResourcesOf(), - private val alternativeResources: MutableSet> = mutableSetOf() -) { - fun getFixedResources(): Resources = fixedResources - - fun getAlternativeResources(): Set> = alternativeResources - - fun addFixedResource(type: ResourceType, quantity: Int) = fixedResources.add(type, quantity) - - fun addChoice(vararg options: ResourceType) { - val optionSet = EnumSet.copyOf(options.toList()) - alternativeResources.add(optionSet) - } - - fun addAll(resources: Resources) = fixedResources.add(resources) - - fun addAll(production: Production) { - fixedResources.add(production.fixedResources) - alternativeResources.addAll(production.getAlternativeResources()) - } - - internal fun asChoices(): Set> { - val fixedAsChoices = fixedResources.toList().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 = - containedInAlternatives(resources.toMutableResources(), alternativeResources) - - private fun containedInAlternatives( - resources: MutableResources, - alternatives: MutableSet> - ): Boolean { - if (resources.isEmpty()) { - return true - } - for (type in ResourceType.values()) { - if (resources[type] <= 0) { - continue - } - // return if no alternative produces the resource of this entry - val candidate = alternatives.firstOrNull { a -> a.contains(type) } ?: return false - resources.remove(type, 1) - alternatives.remove(candidate) - val remainingAreContainedToo = containedInAlternatives(resources, alternatives) - resources.add(type, 1) - alternatives.add(candidate) - if (remainingAreContainedToo) { - return true - } - } - return false - } -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt deleted file mode 100644 index a7554768..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactions.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.api.resources.CountedResource -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceTransaction -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.toCountedResourcesList - -fun Map.toTransactions(): ResourceTransactions = - filterValues { !it.isEmpty() } - .map { (p, res) -> ResourceTransaction(p, res.toCountedResourcesList()) } - .toSet() - -fun ResourceTransactions.asResources(): Resources = flatMap { it.resources }.asResources() - -fun ResourceTransaction.asResources(): Resources = resources.asResources() - -fun List.asResources(): Resources = map { it.asResources() }.merge() - -fun CountedResource.asResources(): Resources = resourcesOf(type to count) - -internal fun ResourceTransactions.execute(player: Player) = forEach { it.execute(player) } - -internal fun ResourceTransaction.execute(player: Player) { - val board = player.board - val price = board.tradingRules.computeCost(this) - board.removeGold(price) - val providerPosition = provider.boardPosition - val providerBoard = player.getBoard(providerPosition) - providerBoard.addGold(price) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt deleted file mode 100644 index bc893062..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.api.resources.ResourceType - -fun emptyResources(): Resources = MutableResources() - -fun resourcesOf(singleResource: ResourceType): Resources = mapOf(singleResource to 1).toMutableResources() - -fun resourcesOf(vararg resources: ResourceType): Resources = mutableResourcesOf(*resources) - -fun resourcesOf(vararg resources: Pair): Resources = mutableResourcesOf(*resources) - -fun Iterable>.toResources(): Resources = toMutableResources() - -/** - * Creates [Resources] from a copy of the given map. Future modifications to the input map won't affect the return - * resources. - */ -fun Map.toResources(): Resources = toMutableResources() - -fun Iterable.merge(): Resources = fold(MutableResources()) { r1, r2 -> r1.add(r2); r1 } - -internal fun mutableResourcesOf() = MutableResources() - -internal fun mutableResourcesOf(vararg resources: ResourceType): MutableResources = - resources.map { it to 1 }.toMutableResources() - -internal fun mutableResourcesOf(vararg resources: Pair) = resources.asIterable().toMutableResources() - -internal fun Iterable>.toMutableResources(): MutableResources = - fold(MutableResources()) { mr, (type, qty) -> mr.add(type, qty); mr } - -internal fun Map.toMutableResources(): MutableResources = MutableResources(toMutableMap()) - -internal fun Resources.toMutableResources(): MutableResources = quantities.toMutableResources() - -interface Resources { - - val quantities: Map - - val size: Int - get() = quantities.map { it.value }.sum() - - fun isEmpty(): Boolean = size == 0 - - operator fun get(key: ResourceType): Int = quantities.getOrDefault(key, 0) - - fun containsAll(resources: Resources): Boolean = resources.quantities.all { it.value <= this[it.key] } - - operator fun plus(resources: Resources): Resources = - ResourceType.values().map { it to this[it] + resources[it] }.toResources() - - /** - * Returns new resources containing these resources minus the given [resources]. If the given resources contain - * more than these resources contain for a resource type, then the resulting resources will contain none of that - * type. - */ - operator fun minus(resources: Resources): Resources = - quantities.mapValues { (type, q) -> Math.max(0, q - resources[type]) }.toResources() - - fun toList(): List = quantities.flatMap { (type, quantity) -> List(quantity) { type } } - - fun copy(): Resources = quantities.toResources() -} - -class MutableResources( - override val quantities: MutableMap = mutableMapOf() -) : Resources { - - fun add(type: ResourceType, quantity: Int) { - quantities.merge(type, quantity) { x, y -> x + y } - } - - fun add(resources: Resources) = resources.quantities.forEach { type, quantity -> this.add(type, quantity) } - - fun remove(type: ResourceType, quantity: Int) { - if (this[type] < quantity) { - throw NoSuchElementException("Can't remove $quantity resources of type $type") - } - quantities.computeIfPresent(type) { _, oldQty -> oldQty - quantity } - } - - override fun equals(other: Any?): Boolean = - other is Resources && quantities.filterValues { it > 0 } == other.quantities.filterValues { it > 0 } - - override fun hashCode(): Int = quantities.filterValues { it > 0 }.hashCode() - - override fun toString(): String = "$quantities" -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt deleted file mode 100644 index c516f4b1..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/TradingRules.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceTransaction -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.ResourceType - -class TradingRules internal constructor(private val defaultCost: Int) { - - private val costs: MutableMap> = mutableMapOf() - - fun getCosts(): Map> { - return costs - } - - internal fun getCost(type: ResourceType, provider: Provider): Int = - costs.computeIfAbsent(type) { mutableMapOf() }.getOrDefault(provider, defaultCost) - - internal fun setCost(type: ResourceType, provider: Provider, cost: Int) { - costs.computeIfAbsent(type) { mutableMapOf() }[provider] = cost - } - - internal fun computeCost(transactions: ResourceTransactions): Int = transactions.map { computeCost(it) }.sum() - - internal fun computeCost(transact: ResourceTransaction) = computeCost(transact.asResources(), transact.provider) - - private fun computeCost(resources: Resources, provider: Provider): Int = - resources.quantities.map { (type, qty) -> getCost(type, provider) * qty }.sum() -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/score/Score.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/score/Score.kt deleted file mode 100644 index c1d34d5d..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/score/Score.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.luxons.sevenwonders.game.score - -class ScoreBoard(scores: Collection) { - - val scores: Collection = scores.sortedDescending() -} - -data class PlayerScore(val boardGold: Int, val pointsByCategory: Map) : Comparable { - - val totalPoints = pointsByCategory.map { it.value }.sum() - - override fun compareTo(other: PlayerScore) = compareValuesBy(this, other, { it.totalPoints }, { it.boardGold }) -} - -enum class ScoreCategory { - CIVIL, - SCIENCE, - MILITARY, - TRADE, - GUILD, - WONDER, - GOLD -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt deleted file mode 100644 index 49ce9a67..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/Wonder.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.luxons.sevenwonders.game.wonders - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.api.cards.PlayabilityLevel -import org.luxons.sevenwonders.game.cards.RequirementsSatisfaction -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.wonders.WonderBuildability - -internal class Wonder( - val name: String, - val initialResource: ResourceType, - val stages: List, - val image: String -) { - val nbBuiltStages: Int - get() = stages.count { it.isBuilt } - - private val nextStage: WonderStage - get() { - if (nbBuiltStages == stages.size) { - throw IllegalStateException("This wonder has already reached its maximum level") - } - return stages[nbBuiltStages] - } - - val lastBuiltStage: WonderStage? - get() = stages.getOrNull(nbBuiltStages - 1) - - fun computeBuildabilityBy(player: Player): WonderBuildability { - if (nbBuiltStages == stages.size) { - return Buildability.alreadyBuilt() - } - return Buildability.requirementDependent(nextStage.requirements.assess(player)) - } - - fun isNextStageBuildable(board: Board, boughtResources: ResourceTransactions): Boolean = - nbBuiltStages < stages.size && nextStage.isBuildable(board, boughtResources) - - fun placeCard(cardBack: CardBack) = nextStage.placeCard(cardBack) - - fun activateLastBuiltStage(player: Player, boughtResources: ResourceTransactions) = - lastBuiltStage!!.activate(player, boughtResources) - - fun computePoints(player: Player): Int = - stages.filter { it.isBuilt }.flatMap { it.effects }.sumBy { it.computePoints(player) } -} - -private object Buildability { - - fun alreadyBuilt() = WonderBuildability( - isBuildable = false, playabilityLevel = PlayabilityLevel.INCOMPATIBLE_WITH_BOARD - ) - - internal fun requirementDependent(satisfaction: RequirementsSatisfaction) = - WonderBuildability( - isBuildable = satisfaction.satisfied, - minPrice = satisfaction.minPrice, - cheapestTransactions = satisfaction.cheapestTransactions, - playabilityLevel = satisfaction.level - ) -} diff --git a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt b/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt deleted file mode 100644 index cb2bf386..00000000 --- a/sw-engine/src/main/kotlin/org/luxons/sevenwonders/game/wonders/WonderStage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.luxons.sevenwonders.game.wonders - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.cards.Requirements -import org.luxons.sevenwonders.game.effects.Effect -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions - -internal class WonderStage( - val requirements: Requirements, - val effects: List -) { - var cardBack: CardBack? = null - private set - - val isBuilt: Boolean - get() = cardBack != null - - fun isBuildable(board: Board, boughtResources: ResourceTransactions): Boolean = - requirements.areMetWithHelpBy(board, boughtResources) - - fun placeCard(cardBack: CardBack) { - this.cardBack = cardBack - } - - fun activate(player: Player, boughtResources: ResourceTransactions) { - effects.forEach { it.applyTo(player) } - requirements.pay(player, boughtResources) - } -} diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/cards.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/cards.json new file mode 100644 index 00000000..bd2d5893 --- /dev/null +++ b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/cards.json @@ -0,0 +1,1462 @@ +{ + "age1": { + "cards": [ + { + "name": "Clay Pit", + "color": "BROWN", + "effect": { + "production": "O/C" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1 + }, + "image": "claypit.png" + }, { + "name": "Clay Pool", + "color": "BROWN", + "effect": { + "production": "C" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "claypool.png" + }, { + "name": "Excavation", + "color": "BROWN", + "effect": { + "production": "S/C" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 1, + "6": 1, + "7": 1 + }, + "image": "excavation.png" + }, { + "name": "Forest Cave", + "color": "BROWN", + "effect": { + "production": "W/O" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 0, + "4": 0, + "5": 1, + "6": 1, + "7": 1 + }, + "image": "forestcave.png" + }, { + "name": "Lumber Yard", + "color": "BROWN", + "effect": { + "production": "W" + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "lumberyard.png" + }, { + "name": "Mine", + "color": "BROWN", + "effect": { + "production": "S/O" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 0, + "4": 0, + "5": 0, + "6": 1, + "7": 1 + }, + "image": "mine.png" + }, { + "name": "Ore Vein", + "color": "BROWN", + "effect": { + "production": "O" + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "orevein.png" + }, { + "name": "Stone Pit", + "color": "BROWN", + "effect": { + "production": "S" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "stonepit.png" + }, { + "name": "Timber Yard", + "color": "BROWN", + "effect": { + "production": "W/S" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 1 + }, + "image": "timberyard.png" + }, { + "name": "Tree Farm", + "color": "BROWN", + "effect": { + "production": "W/C" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 0, + "4": 0, + "5": 0, + "6": 1, + "7": 1 + }, + "image": "treefarm.png" + }, { + "name": "Glassworks", + "color": "GREY", + "effect": { + "production": "G" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "glassworks.png" + }, { + "name": "Loom", + "color": "GREY", + "effect": { + "production": "L" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "loom.png" + }, { + "name": "Press", + "color": "GREY", + "effect": { + "production": "P" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "press.png" + }, { + "name": "East Trading Post", + "color": "YELLOW", + "effect": { + "discount": { + "resourceTypes": "CSOW", + "providers": [ + "RIGHT_PLAYER" + ], + "discountedPrice": 1 + } + }, + "chainChildren": [ + "Forum" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "easttradingpost.png" + }, { + "name": "Marketplace", + "color": "YELLOW", + "effect": { + "discount": { + "resourceTypes": "LGP", + "providers": [ + "LEFT_PLAYER", "RIGHT_PLAYER" + ], + "discountedPrice": 1 + } + }, + "chainChildren": [ + "Caravansery" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "marketplace.png" + }, { + "name": "Tavern", + "color": "YELLOW", + "effect": { + "gold": 5 + }, + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 2, + "6": 2, + "7": 3 + }, + "image": "tavern.png" + }, { + "name": "West Trading Post", + "color": "YELLOW", + "effect": { + "discount": { + "resourceTypes": "CSOW", + "providers": [ + "LEFT_PLAYER" + ], + "discountedPrice": 1 + } + }, + "chainChildren": [ + "Forum" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "westtradingpost.png" + }, { + "name": "Altar", + "color": "BLUE", + "effect": { + "points": 2 + }, + "chainChildren": [ + "Temple" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "altar.png" + }, { + "name": "Baths", + "color": "BLUE", + "effect": { + "points": 3 + }, + "requirements": { + "resources": "S" + }, + "chainChildren": [ + "Aquaduct" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "baths.png" + }, { + "name": "Pawnshop", + "color": "BLUE", + "effect": { + "points": 3 + }, + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "pawnshop.png" + }, { + "name": "Theater", + "color": "BLUE", + "effect": { + "points": 2 + }, + "chainChildren": [ + "Statue" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "theater.png" + }, { + "name": "Apothecary", + "color": "GREEN", + "effect": { + "science": "COMPASS" + }, + "requirements": { + "resources": "L" + }, + "chainChildren": [ + "Stables", "Dispensary" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "apothecary.png" + }, { + "name": "Scriptorium", + "color": "GREEN", + "effect": { + "science": "TABLET" + }, + "requirements": { + "resources": "P" + }, + "chainChildren": [ + "Courthouse", "Library" + ], + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "scriptorium.png" + }, { + "name": "Workshop", + "color": "GREEN", + "effect": { + "science": "WHEEL" + }, + "requirements": { + "resources": "G" + }, + "chainChildren": [ + "Archery Range", "Laboratory" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "workshop.png" + }, { + "name": "Barracks", + "color": "RED", + "effect": { + "military": 1 + }, + "requirements": { + "resources": "O" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "barracks.png" + }, { + "name": "Guard Tower", + "color": "RED", + "effect": { + "military": 1 + }, + "requirements": { + "resources": "C" + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "guardtower.png" + }, { + "name": "Stockade", + "color": "RED", + "effect": { + "military": 1 + }, + "requirements": { + "resources": "W" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "stockade.png" + } + ], + "backImage": "age1.png" + }, + "age2": { + "cards": [ + { + "name": "Brickyard", + "color": "BROWN", + "effect": { + "production": "CC" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "brickyard.png" + }, { + "name": "Foundry", + "color": "BROWN", + "effect": { + "production": "OO" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "foundry.png" + }, { + "name": "Quarry", + "color": "BROWN", + "effect": { + "production": "SS" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "quarry.png" + }, { + "name": "Sawmill", + "color": "BROWN", + "effect": { + "production": "WW" + }, + "requirements": { + "gold": 1 + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "sawmill.png" + }, { + "name": "Glassworks", + "color": "GREY", + "effect": { + "production": "G" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "glassworks.png" + }, { + "name": "Loom", + "color": "GREY", + "effect": { + "production": "L" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "loom.png" + }, { + "name": "Press", + "color": "GREY", + "effect": { + "production": "P" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "press.png" + }, { + "name": "Bazar", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF", "LEFT", "RIGHT" + ], + "gold": 0, + "points": 2, + "type": "CARD", + "colors": [ + "GREY" + ] + } + }, + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "bazar.png" + }, { + "name": "Caravansery", + "color": "YELLOW", + "effect": { + "production": "(W/S/O/C)" + }, + "requirements": { + "resources": "WW" + }, + "chainParent": "Marketplace", + "chainChildren": [ + "Lighthouse" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 3, + "7": 3 + }, + "image": "caravansery.png" + }, { + "name": "Forum", + "color": "YELLOW", + "effect": { + "production": "(G/P/L)" + }, + "requirements": { + "resources": "CC" + }, + "chainParent": "East Trading Post", + "chainChildren": [ + "Haven" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 3 + }, + "image": "forum.png" + }, { + "name": "Vineyard", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF", "LEFT", "RIGHT" + ], + "points": 1, + "type": "CARD", + "colors": [ + "BROWN" + ] + } + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "vineyard.png" + }, { + "name": "Aqueduct", + "color": "BLUE", + "effect": { + "points": 5 + }, + "requirements": { + "resources": "SSS" + }, + "chainParent": "Baths", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "aqueduct.png" + }, { + "name": "Courthouse", + "color": "BLUE", + "effect": { + "points": 4 + }, + "requirements": { + "resources": "CCL" + }, + "chainParent": "Scriptorium", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "courthouse.png" + }, { + "name": "Statue", + "color": "BLUE", + "effect": { + "points": 4 + }, + "requirements": { + "resources": "WOO" + }, + "chainParent": "Theater", + "chainChildren": [ + "Gardens" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "statue.png" + }, { + "name": "Temple", + "color": "BLUE", + "effect": { + "points": 3 + }, + "requirements": { + "resources": "WCG" + }, + "chainParent": "Altar", + "chainChildren": [ + "Pantheon" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "temple.png" + }, { + "name": "Dispensary", + "color": "GREEN", + "effect": { + "science": "COMPASS" + }, + "requirements": { + "resources": "OOG" + }, + "chainParent": "Apothecary", + "chainChildren": [ + "Arena", "Lodge" + ], + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "dispensary.png" + }, { + "name": "Laboratory", + "color": "GREEN", + "effect": { + "science": "WHEEL" + }, + "requirements": { + "resources": "CCP" + }, + "chainParent": "Workshop", + "chainChildren": [ + "Siege Workshop", "Observatory" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "laboratory.png" + }, { + "name": "Library", + "color": "GREEN", + "effect": { + "science": "TABLET" + }, + "requirements": { + "resources": "SSL" + }, + "chainParent": "Scriptorium", + "chainChildren": [ + "Senate", "University" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "library.png" + }, { + "name": "School", + "color": "GREEN", + "effect": { + "science": "TABLET" + }, + "requirements": { + "resources": "WP" + }, + "chainChildren": [ + "Academy", "Study" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "school.png" + }, { + "name": "Archery Range", + "color": "RED", + "effect": { + "military": 2 + }, + "requirements": { + "resources": "WWO" + }, + "chainParent": "Workshop", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "archeryrange.png" + }, { + "name": "Stables", + "color": "RED", + "effect": { + "military": 2 + }, + "requirements": { + "resources": "WOC" + }, + "chainParent": "Apothecary", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "stables.png" + }, { + "name": "Training Ground", + "color": "RED", + "effect": { + "military": 2 + }, + "requirements": { + "resources": "WOO" + }, + "chainChildren": [ + "Circus" + ], + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 1, + "6": 2, + "7": 3 + }, + "image": "trainingground.png" + }, { + "name": "Walls", + "color": "RED", + "effect": { + "military": 2 + }, + "requirements": { + "resources": "SSS" + }, + "chainChildren": [ + "Fortifications" + ], + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "walls.png" + } + ], + "backImage": "age2.png" + }, + "age3": { + "cards": [ + { + "name": "Arena", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF" + ], + "gold": 3, + "points": 1, + "type": "BUILT_WONDER_STAGES" + } + }, + "requirements": { + "resources": "SSO" + }, + "chainParent": "Dispensary", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 3 + }, + "image": "arena.png" + }, { + "name": "Chamber of Commerce", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF" + ], + "gold": 2, + "points": 2, + "type": "CARD", + "colors": [ + "GREY" + ] + } + }, + "requirements": { + "resources": "CCP" + }, + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "chamberofcommerce.png" + }, { + "name": "Haven", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF" + ], + "gold": 1, + "points": 1, + "type": "CARD", + "colors": [ + "BROWN" + ] + } + }, + "requirements": { + "resources": "WOL" + }, + "chainParent": "Forum", + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "haven.png" + }, { + "name": "Lighthouse", + "color": "YELLOW", + "effect": { + "perBoardElement": { + "boards": [ + "SELF" + ], + "gold": 1, + "points": 1, + "type": "CARD", + "colors": [ + "GREY" + ] + } + }, + "requirements": { + "resources": "SG" + }, + "chainParent": "Caravansery", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "lighthouse.png" + }, { + "name": "Gardens", + "color": "BLUE", + "effect": { + "points": 5 + }, + "requirements": { + "resources": "WCC" + }, + "chainParent": "Statue", + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "gardens.png" + }, { + "name": "Palace", + "color": "BLUE", + "effect": { + "points": 8 + }, + "requirements": { + "resources": "WSOCGPL" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "palace.png" + }, { + "name": "Pantheon", + "color": "BLUE", + "effect": { + "points": 7 + }, + "requirements": { + "resources": "OCCGPL" + }, + "chainParent": "Temple", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "pantheon.png" + }, { + "name": "Senate", + "color": "BLUE", + "effect": { + "points": 6 + }, + "requirements": { + "resources": "WWSO" + }, + "chainParent": "Library", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "senate.png" + }, { + "name": "Town Hall", + "color": "BLUE", + "effect": { + "points": 6 + }, + "requirements": { + "resources": "SSOG" + }, + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 3, + "7": 3 + }, + "image": "townhall.png" + }, { + "name": "Academy", + "color": "GREEN", + "effect": { + "science": "COMPASS" + }, + "requirements": { + "resources": "SSSG" + }, + "chainParent": "School", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "academy.png" + }, { + "name": "Lodge", + "color": "GREEN", + "effect": { + "science": "COMPASS" + }, + "requirements": { + "resources": "CCPL" + }, + "chainParent": "Dispensary", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 2, + "7": 2 + }, + "image": "lodge.png" + }, { + "name": "Observatory", + "color": "GREEN", + "effect": { + "science": "WHEEL" + }, + "requirements": { + "resources": "OOGL" + }, + "chainParent": "Laboratory", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "observatory.png" + }, { + "name": "Study", + "color": "GREEN", + "effect": { + "science": "WHEEL" + }, + "requirements": { + "resources": "WPL" + }, + "chainParent": "School", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "study.png" + }, { + "name": "University", + "color": "GREEN", + "effect": { + "science": "TABLET" + }, + "requirements": { + "resources": "WWGP" + }, + "chainParent": "Library", + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "university.png" + }, { + "name": "Arsenal", + "color": "RED", + "effect": { + "military": 3 + }, + "requirements": { + "resources": "WWOL" + }, + "countPerNbPlayer": { + "3": 1, + "4": 2, + "5": 2, + "6": 2, + "7": 3 + }, + "image": "arsenal.png" + }, { + "name": "Circus", + "color": "RED", + "effect": { + "military": 3 + }, + "requirements": { + "resources": "SSSO" + }, + "chainParent": "Training Ground", + "countPerNbPlayer": { + "3": 0, + "4": 1, + "5": 2, + "6": 3, + "7": 3 + }, + "image": "circus.png" + }, { + "name": "Fortifications", + "color": "RED", + "effect": { + "military": 3 + }, + "requirements": { + "resources": "SOOO" + }, + "chainParent": "Walls", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 1, + "6": 1, + "7": 2 + }, + "image": "fortifications.png" + }, { + "name": "Siege Workshop", + "color": "RED", + "effect": { + "military": 3 + }, + "requirements": { + "resources": "WCCC" + }, + "chainParent": "Laboratory", + "countPerNbPlayer": { + "3": 1, + "4": 1, + "5": 2, + "6": 2, + "7": 2 + }, + "image": "siegeworkshop.png" + } + ], + "backImage": "age3.png" + }, + "guildCards": [ + { + "name": "Builders Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "SELF", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "BUILT_WONDER_STAGES" + } + }, + "requirements": { + "resources": "SSCCG" + }, + "image": "buildersguild.png" + }, { + "name": "Craftsmens Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 2, + "type": "CARD", + "colors": [ + "GREY" + ] + } + }, + "requirements": { + "resources": "SSOO" + }, + "image": "craftsmensguild.png" + }, { + "name": "Magistrates Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "BLUE" + ] + } + }, + "requirements": { + "resources": "WWWSL" + }, + "image": "magistratesguild.png" + }, { + "name": "Philosophers Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "GREEN" + ] + } + }, + "requirements": { + "resources": "CCCPL" + }, + "image": "philosophersguild.png" + }, { + "name": "Scientists Guild", + "color": "PURPLE", + "effect": { + "science": "any" + }, + "requirements": { + "resources": "WWOOP" + }, + "image": "scientistsguild.png" + }, { + "name": "Shipowners Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "SELF" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "BROWN", "GREY", "PURPLE" + ] + } + }, + "requirements": { + "resources": "WWWGP" + }, + "image": "shipownersguild.png" + }, { + "name": "Spies Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "RED" + ] + } + }, + "requirements": { + "resources": "CCCG" + }, + "image": "spiesguild.png" + }, { + "name": "Strategists Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "DEFEAT_TOKEN" + } + }, + "requirements": { + "resources": "SOOL" + }, + "image": "strategistsguild.png" + }, { + "name": "Traders Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "YELLOW" + ] + } + }, + "requirements": { + "resources": "GPL" + }, + "image": "tradersguild.png" + }, { + "name": "Workers Guild", + "color": "PURPLE", + "effect": { + "perBoardElement": { + "boards": [ + "LEFT", "RIGHT" + ], + "gold": 0, + "points": 1, + "type": "CARD", + "colors": [ + "BROWN" + ] + } + }, + "requirements": { + "resources": "WSOOC" + }, + "image": "workersguild.png" + } + ] +} diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/global_rules.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/global_rules.json new file mode 100644 index 00000000..9b486fe6 --- /dev/null +++ b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/global_rules.json @@ -0,0 +1,4 @@ +{ + "minPlayers": 3, + "maxPlayers": 7 +} diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/wonders.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/wonders.json new file mode 100644 index 00000000..fc3ac69d --- /dev/null +++ b/sw-engine/src/main/resources/org/luxons/sevenwonders/engine/data/wonders.json @@ -0,0 +1,473 @@ +[ + { + "name": "alexandria", + "sides": { + "A": { + "initialResource": "G", + "stages": [ + { + "requirements": { + "resources": "SS" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "OO" + }, + "effects": { + "production": "(W/S/O/C)" + } + }, + { + "requirements": { + "resources": "GG" + }, + "effects": { + "points": 7 + } + } + ], + "image": "alexandriaA.png" + }, + "B": { + "initialResource": "G", + "stages": [ + { + "requirements": { + "resources": "CC" + }, + "effects": { + "production": "(W/S/O/C)" + } + }, + { + "requirements": { + "resources": "WW" + }, + "effects": { + "production": "(G/P/L)" + } + }, + { + "requirements": { + "resources": "SSS" + }, + "effects": { + "points": 7 + } + } + ], + "image": "alexandriaB.png" + } + } + }, + { + "name": "babylon", + "sides": { + "A": { + "initialResource": "C", + "stages": [ + { + "requirements": { + "resources": "CC" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "WWW" + }, + "effects": { + "science": "any" + } + }, + { + "requirements": { + "resources": "CCCC" + }, + "effects": { + "points": 7 + } + } + ], + "image": "babylonA.png" + }, + "B": { + "initialResource": "C", + "stages": [ + { + "requirements": { + "resources": "CL" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "WWG" + }, + "effects": { + "action": "PLAY_LAST_CARD" + } + }, + { + "requirements": { + "resources": "CCCP" + }, + "effects": { + "science": "any" + } + } + ], + "image": "babylonB.png" + } + } + }, + { + "name": "ephesos", + "sides": { + "A": { + "initialResource": "P", + "stages": [ + { + "requirements": { + "resources": "SS" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "WW" + }, + "effects": { + "gold": 9 + } + }, + { + "requirements": { + "resources": "PP" + }, + "effects": { + "points": 7 + } + } + ], + "image": "ephesosA.png" + }, + "B": { + "initialResource": "P", + "stages": [ + { + "requirements": { + "resources": "SS" + }, + "effects": { + "gold": 4, + "points": 2 + } + }, + { + "requirements": { + "resources": "WW" + }, + "effects": { + "gold": 4, + "points": 3 + } + }, + { + "requirements": { + "resources": "GPL" + }, + "effects": { + "gold": 4, + "points": 5 + } + } + ], + "image": "ephesosB.png" + } + } + }, + { + "name": "gizah", + "sides": { + "A": { + "initialResource": "S", + "stages": [ + { + "requirements": { + "resources": "SS" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "WWW" + }, + "effects": { + "points": 5 + } + }, + { + "requirements": { + "resources": "SSSS" + }, + "effects": { + "points": 7 + } + } + ], + "image": "gizahA.png" + }, + "B": { + "initialResource": "S", + "stages": [ + { + "requirements": { + "resources": "WW" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "SSS" + }, + "effects": { + "points": 5 + } + }, + { + "requirements": { + "resources": "CCC" + }, + "effects": { + "points": 5 + } + }, + { + "requirements": { + "resources": "SSSSP" + }, + "effects": { + "points": 7 + } + } + ], + "image": "gizahB.png" + } + } + }, + { + "name": "halikarnassus", + "sides": { + "A": { + "initialResource": "L", + "stages": [ + { + "requirements": { + "resources": "CC" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "OOO" + }, + "effects": { + "action": "PLAY_DISCARDED" + } + }, + { + "requirements": { + "resources": "LL" + }, + "effects": { + "points": 7 + } + } + ], + "image": "halikarnassusA.png" + }, + "B": { + "initialResource": "L", + "stages": [ + { + "requirements": { + "resources": "OO" + }, + "effects": { + "points": 2, + "action": "PLAY_DISCARDED" + } + }, + { + "requirements": { + "resources": "CCC" + }, + "effects": { + "points": 1, + "action": "PLAY_DISCARDED" + } + }, + { + "requirements": { + "resources": "GPL" + }, + "effects": { + "action": "PLAY_DISCARDED" + } + } + ], + "image": "halikarnassusB.png" + } + } + }, + { + "name": "olympia", + "sides": { + "A": { + "initialResource": "W", + "stages": [ + { + "requirements": { + "resources": "WW" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "SS" + }, + "effects": { + "action": "ONE_FREE" + } + }, + { + "requirements": { + "resources": "OO" + }, + "effects": { + "points": 7 + } + } + ], + "image": "olympiaA.png" + }, + "B": { + "initialResource": "W", + "stages": [ + { + "requirements": { + "resources": "WW" + }, + "effects": { + "discount": { + "resourceTypes": "WSOC", + "providers": [ + "LEFT_PLAYER", + "RIGHT_PLAYER" + ], + "discountedPrice": 1 + } + } + }, + { + "requirements": { + "resources": "SS" + }, + "effects": { + "points": 5 + } + }, + { + "requirements": { + "resources": "OOL" + }, + "effects": { + "action": "COPY_GUILD" + } + } + ], + "image": "olympiaB.png" + } + } + }, + { + "name": "rhodos", + "sides": { + "A": { + "initialResource": "O", + "stages": [ + { + "requirements": { + "resources": "WW" + }, + "effects": { + "points": 3 + } + }, + { + "requirements": { + "resources": "CCC" + }, + "effects": { + "military": 2 + } + }, + { + "requirements": { + "resources": "OOOO" + }, + "effects": { + "points": 7 + } + } + ], + "image": "rhodosA.png" + }, + "B": { + "initialResource": "O", + "stages": [ + { + "requirements": { + "resources": "SSS" + }, + "effects": { + "gold": 3, + "military": 1, + "points": 3 + } + }, + { + "requirements": { + "resources": "OOOO" + }, + "effects": { + "gold": 4, + "military": 1, + "points": 4 + } + } + ], + "image": "rhodosB.png" + } + } + } +] diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/cards.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/cards.json deleted file mode 100644 index bd2d5893..00000000 --- a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/cards.json +++ /dev/null @@ -1,1462 +0,0 @@ -{ - "age1": { - "cards": [ - { - "name": "Clay Pit", - "color": "BROWN", - "effect": { - "production": "O/C" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 1 - }, - "image": "claypit.png" - }, { - "name": "Clay Pool", - "color": "BROWN", - "effect": { - "production": "C" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "claypool.png" - }, { - "name": "Excavation", - "color": "BROWN", - "effect": { - "production": "S/C" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 1, - "6": 1, - "7": 1 - }, - "image": "excavation.png" - }, { - "name": "Forest Cave", - "color": "BROWN", - "effect": { - "production": "W/O" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 0, - "4": 0, - "5": 1, - "6": 1, - "7": 1 - }, - "image": "forestcave.png" - }, { - "name": "Lumber Yard", - "color": "BROWN", - "effect": { - "production": "W" - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "lumberyard.png" - }, { - "name": "Mine", - "color": "BROWN", - "effect": { - "production": "S/O" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 0, - "4": 0, - "5": 0, - "6": 1, - "7": 1 - }, - "image": "mine.png" - }, { - "name": "Ore Vein", - "color": "BROWN", - "effect": { - "production": "O" - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "orevein.png" - }, { - "name": "Stone Pit", - "color": "BROWN", - "effect": { - "production": "S" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "stonepit.png" - }, { - "name": "Timber Yard", - "color": "BROWN", - "effect": { - "production": "W/S" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 1 - }, - "image": "timberyard.png" - }, { - "name": "Tree Farm", - "color": "BROWN", - "effect": { - "production": "W/C" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 0, - "4": 0, - "5": 0, - "6": 1, - "7": 1 - }, - "image": "treefarm.png" - }, { - "name": "Glassworks", - "color": "GREY", - "effect": { - "production": "G" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "glassworks.png" - }, { - "name": "Loom", - "color": "GREY", - "effect": { - "production": "L" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "loom.png" - }, { - "name": "Press", - "color": "GREY", - "effect": { - "production": "P" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "press.png" - }, { - "name": "East Trading Post", - "color": "YELLOW", - "effect": { - "discount": { - "resourceTypes": "CSOW", - "providers": [ - "RIGHT_PLAYER" - ], - "discountedPrice": 1 - } - }, - "chainChildren": [ - "Forum" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "easttradingpost.png" - }, { - "name": "Marketplace", - "color": "YELLOW", - "effect": { - "discount": { - "resourceTypes": "LGP", - "providers": [ - "LEFT_PLAYER", "RIGHT_PLAYER" - ], - "discountedPrice": 1 - } - }, - "chainChildren": [ - "Caravansery" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "marketplace.png" - }, { - "name": "Tavern", - "color": "YELLOW", - "effect": { - "gold": 5 - }, - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 2, - "6": 2, - "7": 3 - }, - "image": "tavern.png" - }, { - "name": "West Trading Post", - "color": "YELLOW", - "effect": { - "discount": { - "resourceTypes": "CSOW", - "providers": [ - "LEFT_PLAYER" - ], - "discountedPrice": 1 - } - }, - "chainChildren": [ - "Forum" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "westtradingpost.png" - }, { - "name": "Altar", - "color": "BLUE", - "effect": { - "points": 2 - }, - "chainChildren": [ - "Temple" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "altar.png" - }, { - "name": "Baths", - "color": "BLUE", - "effect": { - "points": 3 - }, - "requirements": { - "resources": "S" - }, - "chainChildren": [ - "Aquaduct" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "baths.png" - }, { - "name": "Pawnshop", - "color": "BLUE", - "effect": { - "points": 3 - }, - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "pawnshop.png" - }, { - "name": "Theater", - "color": "BLUE", - "effect": { - "points": 2 - }, - "chainChildren": [ - "Statue" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "theater.png" - }, { - "name": "Apothecary", - "color": "GREEN", - "effect": { - "science": "COMPASS" - }, - "requirements": { - "resources": "L" - }, - "chainChildren": [ - "Stables", "Dispensary" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "apothecary.png" - }, { - "name": "Scriptorium", - "color": "GREEN", - "effect": { - "science": "TABLET" - }, - "requirements": { - "resources": "P" - }, - "chainChildren": [ - "Courthouse", "Library" - ], - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "scriptorium.png" - }, { - "name": "Workshop", - "color": "GREEN", - "effect": { - "science": "WHEEL" - }, - "requirements": { - "resources": "G" - }, - "chainChildren": [ - "Archery Range", "Laboratory" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "workshop.png" - }, { - "name": "Barracks", - "color": "RED", - "effect": { - "military": 1 - }, - "requirements": { - "resources": "O" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "barracks.png" - }, { - "name": "Guard Tower", - "color": "RED", - "effect": { - "military": 1 - }, - "requirements": { - "resources": "C" - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "guardtower.png" - }, { - "name": "Stockade", - "color": "RED", - "effect": { - "military": 1 - }, - "requirements": { - "resources": "W" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "stockade.png" - } - ], - "backImage": "age1.png" - }, - "age2": { - "cards": [ - { - "name": "Brickyard", - "color": "BROWN", - "effect": { - "production": "CC" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "brickyard.png" - }, { - "name": "Foundry", - "color": "BROWN", - "effect": { - "production": "OO" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "foundry.png" - }, { - "name": "Quarry", - "color": "BROWN", - "effect": { - "production": "SS" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "quarry.png" - }, { - "name": "Sawmill", - "color": "BROWN", - "effect": { - "production": "WW" - }, - "requirements": { - "gold": 1 - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "sawmill.png" - }, { - "name": "Glassworks", - "color": "GREY", - "effect": { - "production": "G" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "glassworks.png" - }, { - "name": "Loom", - "color": "GREY", - "effect": { - "production": "L" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "loom.png" - }, { - "name": "Press", - "color": "GREY", - "effect": { - "production": "P" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "press.png" - }, { - "name": "Bazar", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF", "LEFT", "RIGHT" - ], - "gold": 0, - "points": 2, - "type": "CARD", - "colors": [ - "GREY" - ] - } - }, - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "bazar.png" - }, { - "name": "Caravansery", - "color": "YELLOW", - "effect": { - "production": "(W/S/O/C)" - }, - "requirements": { - "resources": "WW" - }, - "chainParent": "Marketplace", - "chainChildren": [ - "Lighthouse" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 3, - "7": 3 - }, - "image": "caravansery.png" - }, { - "name": "Forum", - "color": "YELLOW", - "effect": { - "production": "(G/P/L)" - }, - "requirements": { - "resources": "CC" - }, - "chainParent": "East Trading Post", - "chainChildren": [ - "Haven" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 3 - }, - "image": "forum.png" - }, { - "name": "Vineyard", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF", "LEFT", "RIGHT" - ], - "points": 1, - "type": "CARD", - "colors": [ - "BROWN" - ] - } - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "vineyard.png" - }, { - "name": "Aqueduct", - "color": "BLUE", - "effect": { - "points": 5 - }, - "requirements": { - "resources": "SSS" - }, - "chainParent": "Baths", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "aqueduct.png" - }, { - "name": "Courthouse", - "color": "BLUE", - "effect": { - "points": 4 - }, - "requirements": { - "resources": "CCL" - }, - "chainParent": "Scriptorium", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "courthouse.png" - }, { - "name": "Statue", - "color": "BLUE", - "effect": { - "points": 4 - }, - "requirements": { - "resources": "WOO" - }, - "chainParent": "Theater", - "chainChildren": [ - "Gardens" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "statue.png" - }, { - "name": "Temple", - "color": "BLUE", - "effect": { - "points": 3 - }, - "requirements": { - "resources": "WCG" - }, - "chainParent": "Altar", - "chainChildren": [ - "Pantheon" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "temple.png" - }, { - "name": "Dispensary", - "color": "GREEN", - "effect": { - "science": "COMPASS" - }, - "requirements": { - "resources": "OOG" - }, - "chainParent": "Apothecary", - "chainChildren": [ - "Arena", "Lodge" - ], - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "dispensary.png" - }, { - "name": "Laboratory", - "color": "GREEN", - "effect": { - "science": "WHEEL" - }, - "requirements": { - "resources": "CCP" - }, - "chainParent": "Workshop", - "chainChildren": [ - "Siege Workshop", "Observatory" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "laboratory.png" - }, { - "name": "Library", - "color": "GREEN", - "effect": { - "science": "TABLET" - }, - "requirements": { - "resources": "SSL" - }, - "chainParent": "Scriptorium", - "chainChildren": [ - "Senate", "University" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "library.png" - }, { - "name": "School", - "color": "GREEN", - "effect": { - "science": "TABLET" - }, - "requirements": { - "resources": "WP" - }, - "chainChildren": [ - "Academy", "Study" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "school.png" - }, { - "name": "Archery Range", - "color": "RED", - "effect": { - "military": 2 - }, - "requirements": { - "resources": "WWO" - }, - "chainParent": "Workshop", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "archeryrange.png" - }, { - "name": "Stables", - "color": "RED", - "effect": { - "military": 2 - }, - "requirements": { - "resources": "WOC" - }, - "chainParent": "Apothecary", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "stables.png" - }, { - "name": "Training Ground", - "color": "RED", - "effect": { - "military": 2 - }, - "requirements": { - "resources": "WOO" - }, - "chainChildren": [ - "Circus" - ], - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 1, - "6": 2, - "7": 3 - }, - "image": "trainingground.png" - }, { - "name": "Walls", - "color": "RED", - "effect": { - "military": 2 - }, - "requirements": { - "resources": "SSS" - }, - "chainChildren": [ - "Fortifications" - ], - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "walls.png" - } - ], - "backImage": "age2.png" - }, - "age3": { - "cards": [ - { - "name": "Arena", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF" - ], - "gold": 3, - "points": 1, - "type": "BUILT_WONDER_STAGES" - } - }, - "requirements": { - "resources": "SSO" - }, - "chainParent": "Dispensary", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 3 - }, - "image": "arena.png" - }, { - "name": "Chamber of Commerce", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF" - ], - "gold": 2, - "points": 2, - "type": "CARD", - "colors": [ - "GREY" - ] - } - }, - "requirements": { - "resources": "CCP" - }, - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "chamberofcommerce.png" - }, { - "name": "Haven", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF" - ], - "gold": 1, - "points": 1, - "type": "CARD", - "colors": [ - "BROWN" - ] - } - }, - "requirements": { - "resources": "WOL" - }, - "chainParent": "Forum", - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "haven.png" - }, { - "name": "Lighthouse", - "color": "YELLOW", - "effect": { - "perBoardElement": { - "boards": [ - "SELF" - ], - "gold": 1, - "points": 1, - "type": "CARD", - "colors": [ - "GREY" - ] - } - }, - "requirements": { - "resources": "SG" - }, - "chainParent": "Caravansery", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "lighthouse.png" - }, { - "name": "Gardens", - "color": "BLUE", - "effect": { - "points": 5 - }, - "requirements": { - "resources": "WCC" - }, - "chainParent": "Statue", - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "gardens.png" - }, { - "name": "Palace", - "color": "BLUE", - "effect": { - "points": 8 - }, - "requirements": { - "resources": "WSOCGPL" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "palace.png" - }, { - "name": "Pantheon", - "color": "BLUE", - "effect": { - "points": 7 - }, - "requirements": { - "resources": "OCCGPL" - }, - "chainParent": "Temple", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "pantheon.png" - }, { - "name": "Senate", - "color": "BLUE", - "effect": { - "points": 6 - }, - "requirements": { - "resources": "WWSO" - }, - "chainParent": "Library", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "senate.png" - }, { - "name": "Town Hall", - "color": "BLUE", - "effect": { - "points": 6 - }, - "requirements": { - "resources": "SSOG" - }, - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 3, - "7": 3 - }, - "image": "townhall.png" - }, { - "name": "Academy", - "color": "GREEN", - "effect": { - "science": "COMPASS" - }, - "requirements": { - "resources": "SSSG" - }, - "chainParent": "School", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "academy.png" - }, { - "name": "Lodge", - "color": "GREEN", - "effect": { - "science": "COMPASS" - }, - "requirements": { - "resources": "CCPL" - }, - "chainParent": "Dispensary", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 2, - "7": 2 - }, - "image": "lodge.png" - }, { - "name": "Observatory", - "color": "GREEN", - "effect": { - "science": "WHEEL" - }, - "requirements": { - "resources": "OOGL" - }, - "chainParent": "Laboratory", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "observatory.png" - }, { - "name": "Study", - "color": "GREEN", - "effect": { - "science": "WHEEL" - }, - "requirements": { - "resources": "WPL" - }, - "chainParent": "School", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "study.png" - }, { - "name": "University", - "color": "GREEN", - "effect": { - "science": "TABLET" - }, - "requirements": { - "resources": "WWGP" - }, - "chainParent": "Library", - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "university.png" - }, { - "name": "Arsenal", - "color": "RED", - "effect": { - "military": 3 - }, - "requirements": { - "resources": "WWOL" - }, - "countPerNbPlayer": { - "3": 1, - "4": 2, - "5": 2, - "6": 2, - "7": 3 - }, - "image": "arsenal.png" - }, { - "name": "Circus", - "color": "RED", - "effect": { - "military": 3 - }, - "requirements": { - "resources": "SSSO" - }, - "chainParent": "Training Ground", - "countPerNbPlayer": { - "3": 0, - "4": 1, - "5": 2, - "6": 3, - "7": 3 - }, - "image": "circus.png" - }, { - "name": "Fortifications", - "color": "RED", - "effect": { - "military": 3 - }, - "requirements": { - "resources": "SOOO" - }, - "chainParent": "Walls", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 1, - "6": 1, - "7": 2 - }, - "image": "fortifications.png" - }, { - "name": "Siege Workshop", - "color": "RED", - "effect": { - "military": 3 - }, - "requirements": { - "resources": "WCCC" - }, - "chainParent": "Laboratory", - "countPerNbPlayer": { - "3": 1, - "4": 1, - "5": 2, - "6": 2, - "7": 2 - }, - "image": "siegeworkshop.png" - } - ], - "backImage": "age3.png" - }, - "guildCards": [ - { - "name": "Builders Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "SELF", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "BUILT_WONDER_STAGES" - } - }, - "requirements": { - "resources": "SSCCG" - }, - "image": "buildersguild.png" - }, { - "name": "Craftsmens Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 2, - "type": "CARD", - "colors": [ - "GREY" - ] - } - }, - "requirements": { - "resources": "SSOO" - }, - "image": "craftsmensguild.png" - }, { - "name": "Magistrates Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "BLUE" - ] - } - }, - "requirements": { - "resources": "WWWSL" - }, - "image": "magistratesguild.png" - }, { - "name": "Philosophers Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "GREEN" - ] - } - }, - "requirements": { - "resources": "CCCPL" - }, - "image": "philosophersguild.png" - }, { - "name": "Scientists Guild", - "color": "PURPLE", - "effect": { - "science": "any" - }, - "requirements": { - "resources": "WWOOP" - }, - "image": "scientistsguild.png" - }, { - "name": "Shipowners Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "SELF" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "BROWN", "GREY", "PURPLE" - ] - } - }, - "requirements": { - "resources": "WWWGP" - }, - "image": "shipownersguild.png" - }, { - "name": "Spies Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "RED" - ] - } - }, - "requirements": { - "resources": "CCCG" - }, - "image": "spiesguild.png" - }, { - "name": "Strategists Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "DEFEAT_TOKEN" - } - }, - "requirements": { - "resources": "SOOL" - }, - "image": "strategistsguild.png" - }, { - "name": "Traders Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "YELLOW" - ] - } - }, - "requirements": { - "resources": "GPL" - }, - "image": "tradersguild.png" - }, { - "name": "Workers Guild", - "color": "PURPLE", - "effect": { - "perBoardElement": { - "boards": [ - "LEFT", "RIGHT" - ], - "gold": 0, - "points": 1, - "type": "CARD", - "colors": [ - "BROWN" - ] - } - }, - "requirements": { - "resources": "WSOOC" - }, - "image": "workersguild.png" - } - ] -} diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/global_rules.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/global_rules.json deleted file mode 100644 index 9b486fe6..00000000 --- a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/global_rules.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "minPlayers": 3, - "maxPlayers": 7 -} diff --git a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/wonders.json b/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/wonders.json deleted file mode 100644 index fc3ac69d..00000000 --- a/sw-engine/src/main/resources/org/luxons/sevenwonders/game/data/wonders.json +++ /dev/null @@ -1,473 +0,0 @@ -[ - { - "name": "alexandria", - "sides": { - "A": { - "initialResource": "G", - "stages": [ - { - "requirements": { - "resources": "SS" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "OO" - }, - "effects": { - "production": "(W/S/O/C)" - } - }, - { - "requirements": { - "resources": "GG" - }, - "effects": { - "points": 7 - } - } - ], - "image": "alexandriaA.png" - }, - "B": { - "initialResource": "G", - "stages": [ - { - "requirements": { - "resources": "CC" - }, - "effects": { - "production": "(W/S/O/C)" - } - }, - { - "requirements": { - "resources": "WW" - }, - "effects": { - "production": "(G/P/L)" - } - }, - { - "requirements": { - "resources": "SSS" - }, - "effects": { - "points": 7 - } - } - ], - "image": "alexandriaB.png" - } - } - }, - { - "name": "babylon", - "sides": { - "A": { - "initialResource": "C", - "stages": [ - { - "requirements": { - "resources": "CC" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "WWW" - }, - "effects": { - "science": "any" - } - }, - { - "requirements": { - "resources": "CCCC" - }, - "effects": { - "points": 7 - } - } - ], - "image": "babylonA.png" - }, - "B": { - "initialResource": "C", - "stages": [ - { - "requirements": { - "resources": "CL" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "WWG" - }, - "effects": { - "action": "PLAY_LAST_CARD" - } - }, - { - "requirements": { - "resources": "CCCP" - }, - "effects": { - "science": "any" - } - } - ], - "image": "babylonB.png" - } - } - }, - { - "name": "ephesos", - "sides": { - "A": { - "initialResource": "P", - "stages": [ - { - "requirements": { - "resources": "SS" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "WW" - }, - "effects": { - "gold": 9 - } - }, - { - "requirements": { - "resources": "PP" - }, - "effects": { - "points": 7 - } - } - ], - "image": "ephesosA.png" - }, - "B": { - "initialResource": "P", - "stages": [ - { - "requirements": { - "resources": "SS" - }, - "effects": { - "gold": 4, - "points": 2 - } - }, - { - "requirements": { - "resources": "WW" - }, - "effects": { - "gold": 4, - "points": 3 - } - }, - { - "requirements": { - "resources": "GPL" - }, - "effects": { - "gold": 4, - "points": 5 - } - } - ], - "image": "ephesosB.png" - } - } - }, - { - "name": "gizah", - "sides": { - "A": { - "initialResource": "S", - "stages": [ - { - "requirements": { - "resources": "SS" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "WWW" - }, - "effects": { - "points": 5 - } - }, - { - "requirements": { - "resources": "SSSS" - }, - "effects": { - "points": 7 - } - } - ], - "image": "gizahA.png" - }, - "B": { - "initialResource": "S", - "stages": [ - { - "requirements": { - "resources": "WW" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "SSS" - }, - "effects": { - "points": 5 - } - }, - { - "requirements": { - "resources": "CCC" - }, - "effects": { - "points": 5 - } - }, - { - "requirements": { - "resources": "SSSSP" - }, - "effects": { - "points": 7 - } - } - ], - "image": "gizahB.png" - } - } - }, - { - "name": "halikarnassus", - "sides": { - "A": { - "initialResource": "L", - "stages": [ - { - "requirements": { - "resources": "CC" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "OOO" - }, - "effects": { - "action": "PLAY_DISCARDED" - } - }, - { - "requirements": { - "resources": "LL" - }, - "effects": { - "points": 7 - } - } - ], - "image": "halikarnassusA.png" - }, - "B": { - "initialResource": "L", - "stages": [ - { - "requirements": { - "resources": "OO" - }, - "effects": { - "points": 2, - "action": "PLAY_DISCARDED" - } - }, - { - "requirements": { - "resources": "CCC" - }, - "effects": { - "points": 1, - "action": "PLAY_DISCARDED" - } - }, - { - "requirements": { - "resources": "GPL" - }, - "effects": { - "action": "PLAY_DISCARDED" - } - } - ], - "image": "halikarnassusB.png" - } - } - }, - { - "name": "olympia", - "sides": { - "A": { - "initialResource": "W", - "stages": [ - { - "requirements": { - "resources": "WW" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "SS" - }, - "effects": { - "action": "ONE_FREE" - } - }, - { - "requirements": { - "resources": "OO" - }, - "effects": { - "points": 7 - } - } - ], - "image": "olympiaA.png" - }, - "B": { - "initialResource": "W", - "stages": [ - { - "requirements": { - "resources": "WW" - }, - "effects": { - "discount": { - "resourceTypes": "WSOC", - "providers": [ - "LEFT_PLAYER", - "RIGHT_PLAYER" - ], - "discountedPrice": 1 - } - } - }, - { - "requirements": { - "resources": "SS" - }, - "effects": { - "points": 5 - } - }, - { - "requirements": { - "resources": "OOL" - }, - "effects": { - "action": "COPY_GUILD" - } - } - ], - "image": "olympiaB.png" - } - } - }, - { - "name": "rhodos", - "sides": { - "A": { - "initialResource": "O", - "stages": [ - { - "requirements": { - "resources": "WW" - }, - "effects": { - "points": 3 - } - }, - { - "requirements": { - "resources": "CCC" - }, - "effects": { - "military": 2 - } - }, - { - "requirements": { - "resources": "OOOO" - }, - "effects": { - "points": 7 - } - } - ], - "image": "rhodosA.png" - }, - "B": { - "initialResource": "O", - "stages": [ - { - "requirements": { - "resources": "SSS" - }, - "effects": { - "gold": 3, - "military": 1, - "points": 3 - } - }, - { - "requirements": { - "resources": "OOOO" - }, - "effects": { - "gold": 4, - "military": 1, - "points": 4 - } - } - ], - "image": "rhodosB.png" - } - } - } -] diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt new file mode 100644 index 00000000..935d66e5 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/GameTest.kt @@ -0,0 +1,126 @@ +package org.luxons.sevenwonders.engine + +import org.luxons.sevenwonders.model.Action +import org.luxons.sevenwonders.model.PlayedMove +import org.luxons.sevenwonders.model.PlayerMove +import org.luxons.sevenwonders.model.PlayerTurnInfo +import org.luxons.sevenwonders.model.cards.HandCard +import org.luxons.sevenwonders.model.cards.TableCard +import org.luxons.sevenwonders.engine.data.GameDefinition +import org.luxons.sevenwonders.engine.data.LAST_AGE +import org.luxons.sevenwonders.model.MoveType +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.test.testCustomizableSettings +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class GameTest { + + @Test + fun testFullGame3Players() = playGame(nbPlayers = 3) + + @Test + fun testFullGame5Players() = playGame(nbPlayers = 6) + + @Test + fun testFullGame7Players() = playGame(nbPlayers = 7) + + private fun playGame(nbPlayers: Int) { + val game = createGame(nbPlayers) + + (1..LAST_AGE).forEach { playAge(nbPlayers, game, it) } + + game.computeScore() + } + + private fun playAge(nbPlayers: Int, game: Game, age: Int) { + repeat(6) { + playTurn(nbPlayers, game, age, 7 - it) + } + } + + private fun createGame(nbPlayers: Int): Game = + GameDefinition.load().initGame(0, testCustomizableSettings(), nbPlayers) + + private fun playTurn(nbPlayers: Int, game: Game, ageToCheck: Int, handSize: Int) { + val turnInfos = game.getCurrentTurnInfo() + assertEquals(nbPlayers, turnInfos.size) + turnInfos.forEach { + assertEquals(ageToCheck, it.currentAge) + assertEquals(handSize, it.hand.size) + } + + val moveExpectations = turnInfos.mapNotNull { it.firstAvailableMove() } + + moveExpectations.forEach { game.prepareMove(it.playerIndex, it.moveToSend) } + assertTrue(game.allPlayersPreparedTheirMove()) + + val table = game.playTurn() + + val expectedMoves = moveExpectations.map { it.expectedPlayedMove } + assertEquals(expectedMoves, table.lastPlayedMoves) + } + + private fun PlayerTurnInfo.firstAvailableMove(): MoveExpectation? = when (action) { + Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(this) + Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(this) + Action.WAIT -> null + } + + private fun createPlayCardMove(turnInfo: PlayerTurnInfo): MoveExpectation { + val wonderBuildability = turnInfo.wonderBuildability + if (wonderBuildability.isBuildable) { + val transactions = wonderBuildability.cheapestTransactions.first() + return planMove(turnInfo, MoveType.UPGRADE_WONDER, turnInfo.hand.first(), transactions) + } + val playableCard = turnInfo.hand.firstOrNull { it.playability.isPlayable } + return if (playableCard != null) { + planMove(turnInfo, MoveType.PLAY, playableCard, playableCard.playability.cheapestTransactions.first()) + } else { + planMove(turnInfo, MoveType.DISCARD, turnInfo.hand.first(), + noTransactions() + ) + } + } + + private fun createPickGuildMove(turnInfo: PlayerTurnInfo): MoveExpectation { + val neighbourGuilds = turnInfo.neighbourGuildCards + + // the game should send action WAIT if no guild cards are available around + assertFalse(neighbourGuilds.isEmpty()) + return MoveExpectation( + turnInfo.playerIndex, + PlayerMove( + MoveType.COPY_GUILD, + neighbourGuilds.first().name + ), + PlayedMove( + turnInfo.playerIndex, + MoveType.COPY_GUILD, + neighbourGuilds.first(), + noTransactions() + ) + ) + } + + data class MoveExpectation(val playerIndex: Int, val moveToSend: PlayerMove, val expectedPlayedMove: PlayedMove) + + private fun planMove( + turnInfo: PlayerTurnInfo, + moveType: MoveType, + card: HandCard, + transactions: ResourceTransactions + ): MoveExpectation = MoveExpectation( + turnInfo.playerIndex, + PlayerMove(moveType, card.name, transactions), + PlayedMove(turnInfo.playerIndex, moveType, card.toPlayedCard(), transactions) + ) + + private fun HandCard.toPlayedCard(): TableCard = + TableCard( + name, color, requirements, chainParent, chainChildren, image, back, true + ) +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/BoardTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/BoardTest.kt new file mode 100644 index 00000000..0b514e5f --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/BoardTest.kt @@ -0,0 +1,211 @@ +package org.luxons.sevenwonders.engine.boards + +import junit.framework.TestCase.assertEquals +import org.junit.Assume.assumeTrue +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.FromDataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.boards.Board.InsufficientFundsException +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.engine.effects.RawPointsIncrease +import org.luxons.sevenwonders.engine.effects.SpecialAbility +import org.luxons.sevenwonders.engine.effects.SpecialAbilityActivation +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.resources.resourcesOf +import org.luxons.sevenwonders.engine.score.ScoreCategory +import org.luxons.sevenwonders.engine.test.addCards +import org.luxons.sevenwonders.engine.test.getDifferentColorFrom +import org.luxons.sevenwonders.engine.test.playCardWithEffect +import org.luxons.sevenwonders.engine.test.singleBoardPlayer +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testCard +import org.luxons.sevenwonders.engine.test.testSettings +import org.luxons.sevenwonders.engine.test.testWonder +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertSame +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class BoardTest { + + @Theory + fun initialGold_respectsSettings(@FromDataPoints("gold") goldAmountInSettings: Int) { + val settings = testSettings(initialGold = goldAmountInSettings) + val board = Board(testWonder(), 0, settings) + assertEquals(goldAmountInSettings, board.gold) + } + + @Theory + fun initialProduction_containsInitialResource(type: ResourceType) { + val board = Board(testWonder(type), 0, testSettings()) + val resources = resourcesOf(type) + assertTrue(board.production.contains(resources)) + assertTrue(board.publicProduction.contains(resources)) + } + + @Theory + fun removeGold_successfulWhenNotTooMuch( + @FromDataPoints("gold") initialGold: Int, + @FromDataPoints("gold") goldRemoved: Int + ) { + assumeTrue(goldRemoved >= 0) + assumeTrue(initialGold >= goldRemoved) + + val board = Board(testWonder(), 0, testSettings(initialGold = initialGold)) + board.removeGold(goldRemoved) + assertEquals(initialGold - goldRemoved, board.gold) + } + + @Theory + fun removeGold_failsWhenTooMuch( + @FromDataPoints("gold") initialGold: Int, + @FromDataPoints("gold") goldRemoved: Int + ) { + assumeTrue(goldRemoved >= 0) + assumeTrue(initialGold < goldRemoved) + + assertFailsWith { + val board = Board(testWonder(), 0, testSettings(initialGold = initialGold)) + board.removeGold(goldRemoved) + } + } + + @Theory + fun getNbCardsOfColor_properCount_singleColor( + type: ResourceType, + @FromDataPoints("nbCards") nbCards: Int, + @FromDataPoints("nbCards") nbOtherCards: Int, + color: Color + ) { + val board = testBoard(initialResource = type) + addCards(board, nbCards, nbOtherCards, color) + assertEquals(nbCards, board.getNbCardsOfColor(listOf(color))) + } + + @Theory + fun getNbCardsOfColor_properCount_multiColors( + type: ResourceType, + @FromDataPoints("nbCards") nbCards1: Int, + @FromDataPoints("nbCards") nbCards2: Int, + @FromDataPoints("nbCards") nbOtherCards: Int, + color1: Color, + color2: Color + ) { + val board = testBoard(initialResource = type) + addCards(board, nbCards1, color1) + addCards(board, nbCards2, color2) + addCards(board, nbOtherCards, getDifferentColorFrom(color1, color2)) + assertEquals(nbCards1 + nbCards2, board.getNbCardsOfColor(listOf(color1, color2))) + } + + @Test + fun setCopiedGuild_succeedsOnPurpleCard() { + val board = testBoard() + val card = testCard(color = Color.PURPLE) + + board.copiedGuild = card + assertSame(card, board.copiedGuild) + } + + @Theory + fun setCopiedGuild_failsOnNonPurpleCard(color: Color) { + assumeTrue(color !== Color.PURPLE) + val board = testBoard() + val card = testCard(color = color) + + assertFailsWith { + board.copiedGuild = card + } + } + + @Theory + fun hasSpecial(applied: SpecialAbility, tested: SpecialAbility) { + val board = testBoard() + val special = SpecialAbilityActivation(applied) + + special.applyTo(singleBoardPlayer(board)) + + assertEquals(applied === tested, board.hasSpecial(tested)) + } + + @Test + fun canPlayFreeCard() { + val board = testBoard() + val special = SpecialAbilityActivation(SpecialAbility.ONE_FREE_PER_AGE) + + special.applyTo(singleBoardPlayer(board)) + + assertTrue(board.canPlayFreeCard(0)) + assertTrue(board.canPlayFreeCard(1)) + assertTrue(board.canPlayFreeCard(2)) + + board.consumeFreeCard(0) + + assertFalse(board.canPlayFreeCard(0)) + assertTrue(board.canPlayFreeCard(1)) + assertTrue(board.canPlayFreeCard(2)) + + board.consumeFreeCard(1) + + assertFalse(board.canPlayFreeCard(0)) + assertFalse(board.canPlayFreeCard(1)) + assertTrue(board.canPlayFreeCard(2)) + + board.consumeFreeCard(2) + + assertFalse(board.canPlayFreeCard(0)) + assertFalse(board.canPlayFreeCard(1)) + assertFalse(board.canPlayFreeCard(2)) + } + + @Theory + fun computePoints_gold(@FromDataPoints("gold") gold: Int) { + assumeTrue(gold >= 0) + val board = testBoard(initialGold = gold) + + val score = board.computeScore(singleBoardPlayer(board)) + assertEquals(gold / 3, score.pointsByCategory[ScoreCategory.GOLD]) + assertEquals(gold / 3, score.totalPoints) + } + + @Theory + fun computePoints_(@FromDataPoints("gold") gold: Int) { + assumeTrue(gold >= 0) + val board = testBoard(initialGold = gold) + + val effect = RawPointsIncrease(5) + playCardWithEffect(singleBoardPlayer(board), Color.BLUE, effect) + + val score = board.computeScore(singleBoardPlayer(board)) + assertEquals(gold / 3, score.pointsByCategory[ScoreCategory.GOLD]) + assertEquals(5, score.pointsByCategory[ScoreCategory.CIVIL]) + assertEquals(5 + gold / 3, score.totalPoints) + } + + companion object { + + @JvmStatic + @DataPoints("gold") + fun goldAmounts(): IntArray = intArrayOf(-3, -1, 0, 1, 2, 3) + + @JvmStatic + @DataPoints("nbCards") + fun nbCards(): IntArray = intArrayOf(0, 1, 2) + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + + @JvmStatic + @DataPoints + fun colors(): Array = Color.values() + + @JvmStatic + @DataPoints + fun specialAbilities(): Array = SpecialAbility.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/MilitaryTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/MilitaryTest.kt new file mode 100644 index 00000000..509d8670 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/MilitaryTest.kt @@ -0,0 +1,57 @@ +package org.luxons.sevenwonders.engine.boards + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.FromDataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.boards.Military.UnknownAgeException +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(Theories::class) +class MilitaryTest { + + @Theory + fun victory_addsCorrectPoints( + @FromDataPoints("ages") age: Int, + @FromDataPoints("points") nbPointsPerVictory: Int + ) { + val military = createMilitary(age, nbPointsPerVictory, 0) + val initialPoints = military.totalPoints + + military.victory(age) + assertEquals(initialPoints + nbPointsPerVictory, military.totalPoints) + } + + @Theory + fun victory_failsIfUnknownAge(@FromDataPoints("points") nbPointsPerVictory: Int) { + val military = createMilitary(0, nbPointsPerVictory, 0) + assertFailsWith { + military.victory(1) + } + } + + @Theory + fun defeat_removesCorrectPoints(@FromDataPoints("points") nbPointsLostPerDefeat: Int) { + val military = createMilitary(0, 0, nbPointsLostPerDefeat) + val initialPoints = military.totalPoints + + military.defeat() + assertEquals(initialPoints - nbPointsLostPerDefeat, military.totalPoints) + } + + companion object { + + @JvmStatic + @DataPoints("points") + fun points(): IntArray = intArrayOf(0, 1, 3, 5) + + @JvmStatic + @DataPoints("ages") + fun ages(): IntArray = intArrayOf(1, 2, 3) + + private fun createMilitary(age: Int, nbPointsPerVictory: Int, nbPointsPerDefeat: Int): Military = + Military(nbPointsPerDefeat, mapOf(age to nbPointsPerVictory)) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/RelativeBoardPositionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/RelativeBoardPositionTest.kt new file mode 100644 index 00000000..2c6aa9ad --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/RelativeBoardPositionTest.kt @@ -0,0 +1,46 @@ +package org.luxons.sevenwonders.engine.boards + +import org.junit.Assume.assumeTrue +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class RelativeBoardPositionTest { + + @Theory + fun getIndexFrom_wrapLeft(nbPlayers: Int) { + assumeTrue(nbPlayers >= 2) + val last = nbPlayers - 1 + assertEquals(last, RelativeBoardPosition.LEFT.getIndexFrom(0, nbPlayers)) + assertEquals(0, RelativeBoardPosition.SELF.getIndexFrom(0, nbPlayers)) + assertEquals(1, RelativeBoardPosition.RIGHT.getIndexFrom(0, nbPlayers)) + } + + @Theory + fun getIndexFrom_wrapRight(nbPlayers: Int) { + assumeTrue(nbPlayers >= 2) + val last = nbPlayers - 1 + assertEquals(last - 1, RelativeBoardPosition.LEFT.getIndexFrom(last, nbPlayers)) + assertEquals(last, RelativeBoardPosition.SELF.getIndexFrom(last, nbPlayers)) + assertEquals(0, RelativeBoardPosition.RIGHT.getIndexFrom(last, nbPlayers)) + } + + @Theory + fun getIndexFrom_noWrap(nbPlayers: Int) { + assumeTrue(nbPlayers >= 3) + assertEquals(0, RelativeBoardPosition.LEFT.getIndexFrom(1, nbPlayers)) + assertEquals(1, RelativeBoardPosition.SELF.getIndexFrom(1, nbPlayers)) + assertEquals(2, RelativeBoardPosition.RIGHT.getIndexFrom(1, nbPlayers)) + } + + companion object { + + @JvmStatic + @DataPoints + fun nbPlayers(): IntArray = intArrayOf(1, 2, 3, 5, 7, 9) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/ScienceTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/ScienceTest.kt new file mode 100644 index 00000000..e3f6f47d --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/boards/ScienceTest.kt @@ -0,0 +1,114 @@ +package org.luxons.sevenwonders.engine.boards + +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.test.createScience +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class ScienceTest { + + @Test + fun addAll_empty() { + val initial = createScience(3, 4, 5, 1) + val empty = Science() + initial.addAll(empty) + assertEquals(3, initial.getQuantity(ScienceType.COMPASS)) + assertEquals(4, initial.getQuantity(ScienceType.WHEEL)) + assertEquals(5, initial.getQuantity(ScienceType.TABLET)) + assertEquals(1, initial.jokers) + } + + @Test + fun addAll_noJoker() { + val initial = createScience(3, 4, 5, 1) + val other = createScience(1, 2, 3, 0) + initial.addAll(other) + assertEquals(4, initial.getQuantity(ScienceType.COMPASS)) + assertEquals(6, initial.getQuantity(ScienceType.WHEEL)) + assertEquals(8, initial.getQuantity(ScienceType.TABLET)) + assertEquals(1, initial.jokers) + } + + @Test + fun addAll_withJokers() { + val initial = createScience(3, 4, 5, 1) + val other = createScience(0, 0, 0, 3) + initial.addAll(other) + assertEquals(3, initial.getQuantity(ScienceType.COMPASS)) + assertEquals(4, initial.getQuantity(ScienceType.WHEEL)) + assertEquals(5, initial.getQuantity(ScienceType.TABLET)) + assertEquals(4, initial.jokers) + } + + @Test + fun addAll_mixed() { + val initial = createScience(3, 4, 5, 1) + val other = createScience(1, 2, 3, 4) + initial.addAll(other) + assertEquals(4, initial.getQuantity(ScienceType.COMPASS)) + assertEquals(6, initial.getQuantity(ScienceType.WHEEL)) + assertEquals(8, initial.getQuantity(ScienceType.TABLET)) + assertEquals(5, initial.jokers) + } + + @Theory + fun computePoints_compassesOnly_noJoker(compasses: Int) { + val science = createScience(compasses, 0, 0, 0) + assertEquals(compasses * compasses, science.computePoints()) + } + + @Theory + fun computePoints_wheelsOnly_noJoker(wheels: Int) { + val science = createScience(0, wheels, 0, 0) + assertEquals(wheels * wheels, science.computePoints()) + } + + @Theory + fun computePoints_tabletsOnly_noJoker(tablets: Int) { + val science = createScience(0, 0, tablets, 0) + assertEquals(tablets * tablets, science.computePoints()) + } + + @Theory + fun computePoints_allSameNoJoker(eachSymbol: Int) { + val science = createScience(eachSymbol, eachSymbol, eachSymbol, 0) + assertEquals(3 * eachSymbol * eachSymbol + 7 * eachSymbol, science.computePoints()) + } + + @Theory + fun computePoints_expectation(expectation: IntArray) { + val science = createScience(expectation[0], expectation[1], expectation[2], expectation[3]) + assertEquals(expectation[4], science.computePoints()) + } + + companion object { + + @JvmStatic + @DataPoints + fun quantitiesWithExpectedPoints(): Array = arrayOf( + // compasses, wheels, tablets, jokers, expected points + intArrayOf(0, 0, 0, 1, 1), + intArrayOf(0, 0, 1, 0, 1), + intArrayOf(0, 0, 0, 2, 4), + intArrayOf(0, 0, 1, 1, 4), + intArrayOf(0, 0, 2, 0, 4), + intArrayOf(0, 0, 0, 3, 10), + intArrayOf(0, 0, 1, 2, 10), + intArrayOf(0, 1, 1, 1, 10), + intArrayOf(1, 1, 1, 0, 10), + intArrayOf(0, 0, 0, 4, 16), + intArrayOf(0, 0, 1, 3, 16), + intArrayOf(0, 0, 2, 2, 16), + intArrayOf(0, 0, 3, 1, 16), + intArrayOf(0, 0, 4, 0, 16) + ) + + @JvmStatic + @DataPoints + fun quantitiesDataPoints(): IntArray = intArrayOf(0, 1, 3, 5, 8) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardBackTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardBackTest.kt new file mode 100644 index 00000000..44bdc10d --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardBackTest.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Test +import org.luxons.sevenwonders.model.cards.CardBack +import kotlin.test.assertEquals + +class CardBackTest { + + @Test + fun initializedWithImage() { + val imagePath = "whateverimage.png" + val (image) = CardBack(imagePath) + assertEquals(imagePath, image) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardTest.kt new file mode 100644 index 00000000..8fd673b2 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/CardTest.kt @@ -0,0 +1,43 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Test +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.test.testCard +import org.luxons.sevenwonders.engine.test.testSettings +import org.luxons.sevenwonders.engine.wonders.Wonder +import kotlin.test.assertEquals + +class CardTest { + + @Test + fun playCardCostingMoney() { + val initialGold = 3 + val price = 1 + val settings = testSettings(3, initialGold) + + val boards = listOf( + Board(Wonder("TestWonder", ResourceType.WOOD, emptyList(), ""), 0, settings), + Board(Wonder("TestWonder", ResourceType.STONE, emptyList(), ""), 1, settings), + Board(Wonder("TestWonder", ResourceType.PAPYRUS, emptyList(), ""), 2, settings) + ) + val table = Table(boards) + + val treeFarmRequirements = Requirements(gold = price) + val treeFarmProduction = Production().apply { addChoice(ResourceType.WOOD, ResourceType.CLAY) } + val treeFarmEffect = ProductionIncrease(treeFarmProduction, false) + val treeFarmCard = testCard("Tree Farm", Color.BROWN, treeFarmRequirements, treeFarmEffect) + + treeFarmCard.applyTo(SimplePlayer(0, table), noTransactions()) + + assertEquals(initialGold - price, table.getBoard(0).gold) + assertEquals(initialGold, table.getBoard(1).gold) + assertEquals(initialGold, table.getBoard(2).gold) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/DecksTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/DecksTest.kt new file mode 100644 index 00000000..bd2615cd --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/DecksTest.kt @@ -0,0 +1,104 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Assume.assumeTrue +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.cards.Decks.CardNotFoundException +import org.luxons.sevenwonders.engine.test.sampleCards +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class DecksTest { + + @Test + fun getCard_failsOnEmptyNameWhenDeckIsEmpty() { + val decks = createDecks(0, 0) + assertFailsWith { + decks.getCard(0, "") + } + } + + @Test + fun getCard_failsWhenDeckIsEmpty() { + val decks = createDecks(0, 0) + assertFailsWith { + decks.getCard(0, "Any name") + } + } + + @Test + fun getCard_failsWhenCardIsNotFound() { + val decks = createDecks(3, 20) + assertFailsWith { + decks.getCard(1, "Unknown name") + } + } + + @Test + fun getCard_succeedsWhenCardIsFound() { + val decks = createDecks(3, 20) + val (name) = decks.getCard(1, "Test Card 3") + assertEquals("Test Card 3", name) + } + + @Test + fun deal_failsOnZeroPlayers() { + val decks = createDecks(3, 20) + assertFailsWith { + decks.deal(1, 0) + } + } + + @Test + fun deal_failsOnMissingAge() { + val decks = createDecks(2, 0) + assertFailsWith { + decks.deal(4, 10) + } + } + + @Theory + fun deal_failsWhenTooFewPlayers(nbPlayers: Int, nbCards: Int) { + assumeTrue(nbCards % nbPlayers != 0) + val decks = createDecks(1, nbCards) + assertFailsWith { + decks.deal(1, nbPlayers) + } + } + + @Theory + fun deal_succeedsOnZeroCards(nbPlayers: Int) { + val decks = createDecks(1, 0) + val hands = decks.deal(1, nbPlayers) + repeat(nbPlayers) { i -> + assertNotNull(hands[i]) + assertTrue(hands[i].isEmpty()) + } + } + + @Theory + fun deal_evenDistribution(nbPlayers: Int, nbCardsPerPlayer: Int) { + val nbCardsPerAge = nbPlayers * nbCardsPerPlayer + val decks = createDecks(1, nbCardsPerAge) + val hands = decks.deal(1, nbPlayers) + repeat(nbPlayers) { i -> + assertEquals(nbCardsPerPlayer, hands[i].size) + } + } + + companion object { + + @JvmStatic + @DataPoints + fun dataPoints(): IntArray = intArrayOf(1, 2, 3, 5, 10) + + private fun createDecks(nbAges: Int, nbCardsPerAge: Int): Decks = + Decks((1..nbAges).map { it to sampleCards(nbCardsPerAge) }.toMap()) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandRotationDirectionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandRotationDirectionTest.kt new file mode 100644 index 00000000..aa681c9c --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandRotationDirectionTest.kt @@ -0,0 +1,15 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Test +import org.luxons.sevenwonders.model.cards.HandRotationDirection +import kotlin.test.assertEquals + +class HandRotationDirectionTest { + + @Test + fun testAgesDirections() { + assertEquals(HandRotationDirection.LEFT, HandRotationDirection.forAge(1)) + assertEquals(HandRotationDirection.RIGHT, HandRotationDirection.forAge(2)) + assertEquals(HandRotationDirection.LEFT, HandRotationDirection.forAge(3)) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandsTest.kt new file mode 100644 index 00000000..b8491434 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/HandsTest.kt @@ -0,0 +1,128 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Assume.assumeTrue +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.FromDataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.cards.HandRotationDirection +import org.luxons.sevenwonders.engine.test.sampleCards +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class HandsTest { + + @Test + fun get_failsOnMissingPlayer() { + val hands = createHands(4, 7) + + assertFailsWith { hands[5] } + } + + @Test + fun get_retrievesCorrectCards() { + val hand0 = sampleCards(5, 0) + val hand1 = sampleCards(10, 5) + val hands = Hands(listOf(hand0, hand1)) + assertEquals(hand0, hands[0]) + assertEquals(hand1, hands[1]) + } + + @Theory + fun isEmpty_falseWhenAtLeast1_allSame( + @FromDataPoints("nbPlayers") nbPlayers: Int, + @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int + ) { + assumeTrue(nbCardsPerPlayer >= 1) + val hands = createHands(nbPlayers, nbCardsPerPlayer) + assertFalse(hands.isEmpty) + } + + @Theory + fun isEmpty_trueWhenAllEmpty(@FromDataPoints("nbPlayers") nbPlayers: Int) { + val hands = createHands(nbPlayers, 0) + assertTrue(hands.isEmpty) + } + + @Theory + fun maxOneCardRemains_falseWhenAtLeast2_allSame( + @FromDataPoints("nbPlayers") nbPlayers: Int, + @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int + ) { + assumeTrue(nbCardsPerPlayer >= 2) + val hands = createHands(nbPlayers, nbCardsPerPlayer) + assertFalse(hands.maxOneCardRemains()) + } + + @Theory + fun maxOneCardRemains_trueWhenAtMost1_allSame( + @FromDataPoints("nbPlayers") nbPlayers: Int, + @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int + ) { + assumeTrue(nbCardsPerPlayer <= 1) + val hands = createHands(nbPlayers, nbCardsPerPlayer) + assertTrue(hands.maxOneCardRemains()) + } + + @Theory + fun maxOneCardRemains_trueWhenAtMost1_someZero(@FromDataPoints("nbPlayers") nbPlayers: Int) { + val hands = createHands(nbPlayers, 1) + assertTrue(hands.maxOneCardRemains()) + } + + @Test + fun rotate_movesOfCorrectOffset_right() { + val hands = createHands(3, 7) + val rotated = hands.rotate(HandRotationDirection.RIGHT) + assertEquals(rotated[1], hands[0]) + assertEquals(rotated[2], hands[1]) + assertEquals(rotated[0], hands[2]) + } + + @Test + fun rotate_movesOfCorrectOffset_left() { + val hands = createHands(3, 7) + val rotated = hands.rotate(HandRotationDirection.LEFT) + assertEquals(rotated[2], hands[0]) + assertEquals(rotated[0], hands[1]) + assertEquals(rotated[1], hands[2]) + } + + @Test + fun createHand_containsAllCards() { + val hand0 = sampleCards(5, 0) + val hand1 = sampleCards(10, 5) + val hands = Hands(listOf(hand0, hand1)) + + val table = testTable(2) + val hand = hands.createHand(SimplePlayer(0, table)) + + assertEquals(hand0.map { it.name }, hand.map { it.name }) + } + + companion object { + + @JvmStatic + @DataPoints("nbCardsPerPlayer") + fun nbCardsPerPlayer(): IntArray { + return intArrayOf(0, 1, 2, 3, 4, 5, 6, 7) + } + + @JvmStatic + @DataPoints("nbPlayers") + fun nbPlayers(): IntArray { + return intArrayOf(3, 4, 5, 6, 7) + } + + private fun createHands(nbPlayers: Int, nbCardsPerPlayer: Int): Hands { + return sampleCards(nbCardsPerPlayer * nbPlayers, 0).deal(nbPlayers) + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt new file mode 100644 index 00000000..da0ae124 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/cards/RequirementsTest.kt @@ -0,0 +1,168 @@ +package org.luxons.sevenwonders.engine.cards + +import org.junit.Assume.assumeTrue +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.resources.emptyResources +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.test.createRequirements +import org.luxons.sevenwonders.engine.test.createTransactions +import org.luxons.sevenwonders.engine.test.singleBoardPlayer +import org.luxons.sevenwonders.engine.test.testBoard +import kotlin.test.assertEquals +import kotlin.test.assertSame +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class RequirementsTest { + + @Test + fun getResources_emptyAfterInit() { + val (_, resources) = Requirements() + assertTrue(resources.isEmpty()) + } + + @Test + fun setResources_success() { + val resources = emptyResources() + val requirements = Requirements(0, resources) + assertSame(resources, requirements.resources) + } + + @Theory + fun goldRequirement(boardGold: Int, requiredGold: Int) { + val requirements = Requirements(requiredGold) + + val board = testBoard(ResourceType.CLAY, boardGold) + val player = singleBoardPlayer(board) + + assertEquals(boardGold >= requiredGold, requirements.areMetWithHelpBy(board, + noTransactions() + )) + + val satisfaction = requirements.assess(player) + if (boardGold >= requiredGold) { + if (requiredGold == 0) { + assertEquals(RequirementsSatisfaction.noRequirements(), satisfaction) + } else { + assertEquals(RequirementsSatisfaction.enoughGold(requiredGold), satisfaction) + } + } else { + assertEquals(RequirementsSatisfaction.missingRequiredGold(requiredGold), satisfaction) + } + } + + @Theory + fun resourceRequirement_initialResource(initialResource: ResourceType, requiredResource: ResourceType) { + val requirements = createRequirements(requiredResource) + + val board = testBoard(initialResource, 0) + val player = singleBoardPlayer(board) + + assertEquals(initialResource == requiredResource, requirements.areMetWithHelpBy(board, + noTransactions() + )) + + if (initialResource == requiredResource) { + val satisfaction = requirements.assess(player) + assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction) + } + } + + @Theory + fun resourceRequirement_ownProduction( + initialResource: ResourceType, + producedResource: ResourceType, + requiredResource: ResourceType + ) { + assumeTrue(initialResource != requiredResource) + + val requirements = createRequirements(requiredResource) + + val board = testBoard(initialResource, 0) + board.production.addFixedResource(producedResource, 1) + val player = singleBoardPlayer(board) + + assertEquals(producedResource == requiredResource, requirements.areMetWithHelpBy(board, + noTransactions() + )) + + if (producedResource == requiredResource) { + val satisfaction = requirements.assess(player) + assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction) + } + } + + @Theory + fun resourceRequirement_boughtResource( + initialResource: ResourceType, + boughtResource: ResourceType, + requiredResource: ResourceType + ) { + assumeTrue(initialResource != requiredResource) + + val requirements = createRequirements(requiredResource) + + val board = testBoard(initialResource, 2) + val neighbourBoard = testBoard(initialResource, 0) + neighbourBoard.publicProduction.addFixedResource(boughtResource, 1) + val table = Table(listOf(board, neighbourBoard)) + val player = SimplePlayer(0, table) + + val resources = createTransactions(Provider.RIGHT_PLAYER, boughtResource) + + val neighbourHasResource = boughtResource == requiredResource + assertEquals(neighbourHasResource, requirements.areMetWithHelpBy(board, resources)) + + val satisfaction = requirements.assess(player) + if (neighbourHasResource) { + val transactions = setOf( + createTransactions(Provider.LEFT_PLAYER, requiredResource), + createTransactions(Provider.RIGHT_PLAYER, requiredResource) + ) + assertEquals(RequirementsSatisfaction.metWithHelp(2, transactions), satisfaction) + } else { + assertEquals(RequirementsSatisfaction.unavailableResources(), satisfaction) + } + } + + @Theory + fun pay_boughtResource(initialResource: ResourceType, requiredResource: ResourceType) { + assumeTrue(initialResource != requiredResource) + + val requirements = createRequirements(requiredResource) + + val board = testBoard(initialResource, 2) + val neighbourBoard = testBoard(requiredResource, 0) + val table = Table(listOf(board, neighbourBoard)) + val player = SimplePlayer(0, table) + + val transactions = createTransactions(Provider.RIGHT_PLAYER, requiredResource) + + assertTrue(requirements.areMetWithHelpBy(board, transactions)) + assertTrue(requirements.assess(player).satisfied) + + requirements.pay(player, transactions) + + assertEquals(0, board.gold) + assertEquals(2, neighbourBoard.gold) + } + + companion object { + + @JvmStatic + @DataPoints + fun goldAmounts(): IntArray = intArrayOf(0, 1, 2, 5) + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/GameDefinitionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/GameDefinitionTest.kt new file mode 100644 index 00000000..b9b7bb3c --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/GameDefinitionTest.kt @@ -0,0 +1,20 @@ +package org.luxons.sevenwonders.engine.data + +import org.junit.Test +import org.luxons.sevenwonders.model.CustomizableSettings +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class GameDefinitionTest { + + @Test + fun successfulGameInit() { + val gameDefinition = GameDefinition.load() + assertNotNull(gameDefinition) + assertEquals(3, gameDefinition.minPlayers) + assertEquals(7, gameDefinition.maxPlayers) + + val game = gameDefinition.initGame(0, CustomizableSettings(), 7) + assertNotNull(game) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializerTest.kt new file mode 100644 index 00000000..6a399977 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/NumericEffectSerializerTest.kt @@ -0,0 +1,147 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.junit.Before +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.effects.GoldIncrease +import org.luxons.sevenwonders.engine.effects.MilitaryReinforcements +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.effects.RawPointsIncrease +import org.luxons.sevenwonders.engine.resources.Production +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(Theories::class) +class NumericEffectSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + gson = GsonBuilder().registerTypeAdapter(MilitaryReinforcements::class.java, NumericEffectSerializer()) + .registerTypeAdapter(RawPointsIncrease::class.java, NumericEffectSerializer()) + .registerTypeAdapter(GoldIncrease::class.java, NumericEffectSerializer()) + // ProductionIncrease is not a numeric effect, it is here for negative testing purpose + .registerTypeAdapter(ProductionIncrease::class.java, NumericEffectSerializer()).create() + } + + @Test + fun serialize_militaryReinforcements_null() { + assertEquals("null", gson.toJson(null, MilitaryReinforcements::class.java)) + } + + @Test + fun serialize_rawPointsIncrease_null() { + assertEquals("null", gson.toJson(null, RawPointsIncrease::class.java)) + } + + @Test + fun serialize_goldIncrease_null() { + assertEquals("null", gson.toJson(null, GoldIncrease::class.java)) + } + + @Test + fun serialize_failOnUnknownType() { + assertFailsWith { + gson.toJson(ProductionIncrease(Production(), false)) + } + } + + @Theory + fun serialize_militaryReinforcements(count: Int) { + val reinforcements = MilitaryReinforcements(count) + assertEquals(count.toString(), gson.toJson(reinforcements)) + } + + @Theory + fun serialize_rawPointsIncrease(count: Int) { + val points = RawPointsIncrease(count) + assertEquals(count.toString(), gson.toJson(points)) + } + + @Theory + fun serialize_goldIncrease(count: Int) { + val goldIncrease = GoldIncrease(count) + assertEquals(count.toString(), gson.toJson(goldIncrease)) + } + + @Theory + fun deserialize_militaryReinforcements(count: Int) { + val reinforcements = MilitaryReinforcements(count) + assertEquals(reinforcements, gson.fromJson(count.toString())) + } + + @Theory + fun deserialize_rawPointsIncrease(count: Int) { + val points = RawPointsIncrease(count) + assertEquals(points, gson.fromJson(count.toString())) + } + + @Theory + fun deserialize_goldIncrease(count: Int) { + val goldIncrease = GoldIncrease(count) + assertEquals(goldIncrease, gson.fromJson(count.toString())) + } + + @Test + fun deserialize_militaryReinforcements_failOnEmptyString() { + assertFailsWith { + gson.fromJson("\"\"") + } + } + + @Test + fun deserialize_rawPointsIncrease_failOnEmptyString() { + assertFailsWith { + gson.fromJson("\"\"") + } + } + + @Test + fun deserialize_goldIncrease_failOnEmptyString() { + assertFailsWith { + gson.fromJson("\"\"") + } + } + + @Test + fun deserialize_militaryReinforcements_failOnNonNumericString() { + assertFailsWith { + gson.fromJson("\"abc\"") + } + } + + @Test + fun deserialize_rawPointsIncrease_failOnNonNumericString() { + assertFailsWith { + gson.fromJson("\"abc\"") + } + } + + @Test + fun deserialize_goldIncrease_failOnNonNumericString() { + assertFailsWith { + gson.fromJson("\"abc\"") + } + } + + @Test + fun deserialize_failOnUnknownType() { + assertFailsWith { + gson.fromJson("\"2\"") + } + } + + companion object { + + @JvmStatic + @DataPoints + fun dataPoints(): IntArray = intArrayOf(-2, -1, 0, 1, 2, 5) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializerTest.kt new file mode 100644 index 00000000..709f2a87 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionIncreaseSerializerTest.kt @@ -0,0 +1,192 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.engine.effects.ProductionIncrease +import org.luxons.sevenwonders.engine.resources.MutableResources +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.resources.Resources +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +class ProductionIncreaseSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + val resourceTypeList = object : TypeToken>() {}.type + gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) + .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) + .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) + .registerTypeAdapter(Production::class.java, ProductionSerializer()) + .registerTypeAdapter(ProductionIncrease::class.java, ProductionIncreaseSerializer()) + .create() + } + + private fun create(sellable: Boolean, wood: Int, stone: Int, clay: Int): ProductionIncrease { + val production = Production() + if (wood > 0) { + production.addFixedResource(ResourceType.WOOD, wood) + } + if (stone > 0) { + production.addFixedResource(ResourceType.STONE, stone) + } + if (clay > 0) { + production.addFixedResource(ResourceType.CLAY, clay) + } + return ProductionIncrease(production, sellable) + } + + private fun createChoice(sellable: Boolean, vararg types: ResourceType): ProductionIncrease { + val production = Production() + production.addChoice(*types) + return ProductionIncrease(production, sellable) + } + + @Test + fun serialize_nullAsNull() { + assertEquals("null", gson.toJson(null, ProductionIncrease::class.java)) + } + + @Test + fun serialize_emptyProdIncreaseAsNull() { + val prodIncrease = ProductionIncrease(Production(), false) + assertEquals("null", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_singleType() { + val prodIncrease = create(true, 1, 0, 0) + assertEquals("\"W\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_mixedTypes() { + val prodIncrease = create(true, 1, 1, 1) + assertEquals("\"WSC\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_mixedTypes_notSellable() { + val prodIncrease = create(false, 1, 1, 1) + assertEquals("\"(WSC)\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice2() { + val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) + assertEquals("\"W/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice3() { + val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) + assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice3_notSellable() { + val prodIncrease = createChoice(false, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) + assertEquals("\"(W/O/C)\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice2_unordered() { + val prodIncrease = createChoice(true, ResourceType.CLAY, ResourceType.WOOD) + assertEquals("\"W/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice3_unordered() { + val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) + assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_failIfMultipleChoices() { + val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) + prodIncrease.production.addChoice(ResourceType.ORE, ResourceType.GLASS) + assertFailsWith { + gson.toJson(prodIncrease) + } + } + + @Test + fun serialize_failIfMixedFixedAndChoices() { + val prodIncrease = create(true, 1, 0, 0) + prodIncrease.production.addChoice(ResourceType.WOOD, ResourceType.CLAY) + assertFailsWith { + gson.toJson(prodIncrease) + } + } + + @Test + fun deserialize_nullFromNull() { + assertNull(gson.fromJson("null", ProductionIncrease::class.java)) + } + + @Test + fun deserialize_emptyList() { + val prodIncrease = ProductionIncrease(Production(), true) + assertEquals(prodIncrease, gson.fromJson("\"\"")) + } + + @Test + fun deserialize_failOnGarbageString() { + assertFailsWith(IllegalArgumentException::class) { + gson.fromJson("\"this is garbage\"") + } + } + + @Test + fun deserialize_failOnGarbageStringWithSlashes() { + assertFailsWith(IllegalArgumentException::class) { + gson.fromJson("\"this/is/garbage\"") + } + } + + @Test + fun deserialize_singleType() { + val prodIncrease = create(true, 1, 0, 0) + assertEquals(prodIncrease, gson.fromJson("\"W\"")) + } + + @Test + fun deserialize_multipleTimesSameType_notSellable() { + val prodIncrease = create(false, 3, 0, 0) + assertEquals(prodIncrease, gson.fromJson("\"(WWW)\"")) + } + + @Test + fun deserialize_mixedTypes() { + val prodIncrease = create(true, 1, 1, 1) + assertEquals(prodIncrease, gson.fromJson("\"WCS\"")) + } + + @Test + fun deserialize_choice2() { + val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) + assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) + } + + @Test + fun deserialize_choice3_notSellable() { + val prodIncrease = createChoice(false, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) + assertEquals(prodIncrease, gson.fromJson("\"(W/O/C)\"")) + } + + @Test + fun deserialize_failOnMultipleResourcesInChoice() { + assertFailsWith(IllegalArgumentException::class) { + gson.fromJson("\"W/SS/C\"") + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializerTest.kt new file mode 100644 index 00000000..4af1d900 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ProductionSerializerTest.kt @@ -0,0 +1,207 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.engine.resources.MutableResources +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.resources.Resources +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +class ProductionSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + val resourceTypeList = object : TypeToken>() {}.type + gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) + .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) + .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) + .registerTypeAdapter(Production::class.java, ProductionSerializer()).create() + } + + private fun create(wood: Int, stone: Int, clay: Int): Production { + val production = Production() + if (wood > 0) { + production.addFixedResource(ResourceType.WOOD, wood) + } + if (stone > 0) { + production.addFixedResource(ResourceType.STONE, stone) + } + if (clay > 0) { + production.addFixedResource(ResourceType.CLAY, clay) + } + return production + } + + private fun createChoice(vararg types: ResourceType): Production { + val production = Production() + production.addChoice(*types) + return production + } + + @Test + fun serialize_nullAsNull() { + assertEquals("null", gson.toJson(null, Production::class.java)) + } + + @Test + fun serialize_emptyProdIncreaseAsNull() { + val prodIncrease = Production() + assertEquals("null", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_singleType() { + val prodIncrease = create(1, 0, 0) + assertEquals("\"W\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_multipleTimesSameType() { + val prodIncrease = create(3, 0, 0) + assertEquals("\"WWW\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_mixedTypes() { + val prodIncrease = create(1, 1, 1) + assertEquals("\"WSC\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_mixedTypesMultiple() { + val prodIncrease = create(2, 1, 2) + assertEquals("\"WWSCC\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice2() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY) + assertEquals("\"W/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice3() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) + assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice2_unordered() { + val prodIncrease = createChoice(ResourceType.CLAY, ResourceType.WOOD) + assertEquals("\"W/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_choice3_unordered() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) + assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) + } + + @Test + fun serialize_failIfMultipleChoices() { + val production = createChoice(ResourceType.WOOD, ResourceType.CLAY) + production.addChoice(ResourceType.ORE, ResourceType.GLASS) + assertFailsWith { + gson.toJson(production) + } + } + + @Test + fun serialize_failIfMixedFixedAndChoices() { + val production = create(1, 0, 0) + production.addChoice(ResourceType.WOOD, ResourceType.CLAY) + assertFailsWith { + gson.toJson(production) + } + } + + @Test + fun deserialize_nullFromNull() { + assertNull(gson.fromJson("null", Production::class.java)) + } + + @Test + fun deserialize_emptyList() { + val prodIncrease = Production() + assertEquals(prodIncrease, gson.fromJson("\"\"")) + } + + @Test + fun deserialize_failOnGarbageString() { + assertFailsWith { + gson.fromJson("\"this is garbage\"") + } + } + + @Test + fun deserialize_failOnGarbageStringWithSlashes() { + assertFailsWith { + gson.fromJson("\"this/is/garbage\"") + } + } + + @Test + fun deserialize_singleType() { + val prodIncrease = create(1, 0, 0) + assertEquals(prodIncrease, gson.fromJson("\"W\"")) + } + + @Test + fun deserialize_multipleTimesSameType() { + val prodIncrease = create(3, 0, 0) + assertEquals(prodIncrease, gson.fromJson("\"WWW\"")) + } + + @Test + fun deserialize_mixedTypes() { + val prodIncrease = create(1, 1, 1) + assertEquals(prodIncrease, gson.fromJson("\"WCS\"")) + } + + @Test + fun deserialize_mixedTypes_unordered() { + val prodIncrease = create(1, 3, 2) + assertEquals(prodIncrease, gson.fromJson("\"SCWCSS\"")) + } + + @Test + fun deserialize_choice2() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY) + assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) + } + + @Test + fun deserialize_choice3() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) + assertEquals(prodIncrease, gson.fromJson("\"W/O/C\"")) + } + + @Test + fun deserialize_choice2_unordered() { + val prodIncrease = createChoice(ResourceType.CLAY, ResourceType.WOOD) + assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) + } + + @Test + fun deserialize_choice3_unordered() { + val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) + assertEquals(prodIncrease, gson.fromJson("\"W/O/C\"")) + } + + @Test + fun deserialize_failOnMultipleResourcesInChoice() { + assertFailsWith { + gson.fromJson("\"W/SS/C\"") + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializerTest.kt new file mode 100644 index 00000000..e256efc3 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypeSerializerTest.kt @@ -0,0 +1,56 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.model.resources.ResourceType +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull + +class ResourceTypeSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + gson = GsonBuilder().registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()).create() + } + + @Test + fun serialize_useSymbolForEachType() { + ResourceType.values().forEach { type -> + val expectedJson = "\"" + type.symbol + "\"" + assertEquals(expectedJson, gson.toJson(type)) + } + } + + @Test + fun deserialize_useSymbolForEachType() { + ResourceType.values().forEach { type -> + val typeInJson = "\"" + type.symbol + "\"" + assertEquals(type, gson.fromJson(typeInJson)) + } + } + + @Test + fun deserialize_nullFromNull() { + assertNull(gson.fromJson("null", ResourceType::class.java)) + } + + @Test + fun deserialize_failsOnEmptyString() { + assertFailsWith { + gson.fromJson("\"\"") + } + } + + @Test + fun deserialize_failsOnGarbageString() { + assertFailsWith { + gson.fromJson("\"thisisgarbage\"") + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializerTest.kt new file mode 100644 index 00000000..8c2ce2fc --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourceTypesSerializerTest.kt @@ -0,0 +1,80 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.github.salomonbrys.kotson.typeToken +import com.github.salomonbrys.kotson.typedToJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.model.resources.ResourceType +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ResourceTypesSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + gson = GsonBuilder().registerTypeAdapter(typeToken>(), ResourceTypesSerializer()).create() + } + + @Test + fun serialize_null() { + assertEquals("null", gson.toJson(null, typeToken>())) + } + + @Test + fun serialize_emptyList() { + val types = emptyList() + assertEquals("\"\"", gson.typedToJson(types)) + } + + @Test + fun serialize_singleType() { + val types = listOf(ResourceType.WOOD) + assertEquals("\"W\"", gson.typedToJson(types)) + } + + @Test + fun serialize_multipleTimesSameType() { + val types = List(3) { ResourceType.WOOD } + assertEquals("\"WWW\"", gson.typedToJson(types)) + } + + @Test + fun serialize_mixedTypes() { + val types = listOf(ResourceType.WOOD, ResourceType.CLAY, ResourceType.STONE) + assertEquals("\"WCS\"", gson.typedToJson(types)) + } + + @Test + fun deserialize_null() { + assertNull(gson.fromJson("null", typeToken>())) + } + + @Test + fun deserialize_emptyList() { + val types = emptyList() + assertEquals(types, gson.fromJson("\"\"")) + } + + @Test + fun deserialize_singleType() { + val types = listOf(ResourceType.WOOD) + assertEquals(types, gson.fromJson("\"W\"")) + } + + @Test + fun deserialize_multipleTimesSameType() { + val types = List(3) { ResourceType.WOOD } + assertEquals(types, gson.fromJson("\"WWW\"")) + } + + @Test + fun deserialize_mixedTypes() { + val types = listOf(ResourceType.WOOD, ResourceType.CLAY, ResourceType.STONE) + assertEquals(types, gson.fromJson("\"WCS\"")) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializerTest.kt new file mode 100644 index 00000000..c8074f2d --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ResourcesSerializerTest.kt @@ -0,0 +1,99 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.engine.resources.MutableResources +import org.luxons.sevenwonders.model.resources.ResourceType.CLAY +import org.luxons.sevenwonders.model.resources.ResourceType.STONE +import org.luxons.sevenwonders.model.resources.ResourceType.WOOD +import org.luxons.sevenwonders.engine.resources.Resources +import org.luxons.sevenwonders.engine.resources.emptyResources +import org.luxons.sevenwonders.engine.resources.resourcesOf +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ResourcesSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + gson = GsonBuilder() + .registerTypeAdapter(Resources::class.java, ResourcesSerializer()) + .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) + .create() + } + + @Test + fun serialize_null() { + assertEquals("null", gson.toJson(null, Resources::class.java)) + } + + @Test + fun serialize_emptyResourcesToNull() { + val resources = emptyResources() + assertEquals("null", gson.toJson(resources)) + } + + @Test + fun serialize_singleType() { + val resources = resourcesOf(WOOD) + assertEquals("\"W\"", gson.toJson(resources)) + } + + @Test + fun serialize_multipleTimesSameType() { + val resources = resourcesOf(WOOD to 3) + assertEquals("\"WWW\"", gson.toJson(resources)) + } + + @Test + fun serialize_mixedTypes() { + val resources = resourcesOf(WOOD, STONE, CLAY) + assertEquals("\"WSC\"", gson.toJson(resources)) + } + + @Test + fun serialize_mixedTypes_unordered() { + val resources = resourcesOf(CLAY to 1, WOOD to 2, CLAY to 1, STONE to 1) + assertEquals("\"CCWWS\"", gson.toJson(resources)) + } + + @Test + fun deserialize_null() { + assertNull(gson.fromJson("null", Resources::class.java)) + } + + @Test + fun deserialize_emptyList() { + val resources = emptyResources() + assertEquals(resources, gson.fromJson("\"\"")) + } + + @Test + fun deserialize_singleType() { + val resources = resourcesOf(WOOD) + assertEquals(resources, gson.fromJson("\"W\"")) + } + + @Test + fun deserialize_multipleTimesSameType() { + val resources = resourcesOf(WOOD to 3) + assertEquals(resources, gson.fromJson("\"WWW\"")) + } + + @Test + fun deserialize_mixedTypes() { + val resources = resourcesOf(WOOD, CLAY, STONE) + assertEquals(resources, gson.fromJson("\"WCS\"")) + } + + @Test + fun deserialize_mixedTypes_unordered() { + val resources = resourcesOf(WOOD to 1, CLAY to 2, STONE to 3) + assertEquals(resources, gson.fromJson("\"SCWCSS\"")) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializerTest.kt new file mode 100644 index 00000000..19b1e572 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/data/serializers/ScienceProgressSerializerTest.kt @@ -0,0 +1,157 @@ +package org.luxons.sevenwonders.engine.data.serializers + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.engine.boards.ScienceType +import org.luxons.sevenwonders.engine.effects.ScienceProgress +import org.luxons.sevenwonders.engine.test.createScienceProgress +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull + +private const val TABLET_STR = "\"TABLET\"" +private const val COMPASS_STR = "\"COMPASS\"" +private const val WHEEL_STR = "\"WHEEL\"" +private const val JOKER_STR = "\"any\"" + +class ScienceProgressSerializerTest { + + private lateinit var gson: Gson + + @Before + fun setUp() { + gson = GsonBuilder().registerTypeAdapter(ScienceProgress::class.java, ScienceProgressSerializer()).create() + } + + @Test + fun serialize_emptyToNull() { + val progress = createScienceProgress(0, 0, 0, 0) + val json = gson.toJson(progress) + assertEquals("null", json) + } + + @Test + fun serialize_oneCompass() { + val progress = createScienceProgress(1, 0, 0, 0) + val json = gson.toJson(progress) + assertEquals(COMPASS_STR, json) + } + + @Test + fun serialize_oneWheel() { + val progress = createScienceProgress(0, 1, 0, 0) + val json = gson.toJson(progress) + assertEquals(WHEEL_STR, json) + } + + @Test + fun serialize_oneTablet() { + val progress = createScienceProgress(0, 0, 1, 0) + val json = gson.toJson(progress) + assertEquals(TABLET_STR, json) + } + + @Test + fun serialize_oneJoker() { + val progress = createScienceProgress(0, 0, 0, 1) + val json = gson.toJson(progress) + assertEquals(JOKER_STR, json) + } + + @Test + fun serialize_failOnMultipleCompasses() { + assertFailsWith { + val progress = createScienceProgress(2, 0, 0, 0) + gson.toJson(progress) + } + } + + @Test + fun serialize_failOnMultipleWheels() { + assertFailsWith { + val progress = createScienceProgress(0, 2, 0, 0) + gson.toJson(progress) + } + } + + @Test + fun serialize_failOnMultipleTablets() { + assertFailsWith { + val progress = createScienceProgress(0, 0, 2, 0) + gson.toJson(progress) + } + } + + @Test + fun serialize_failOnMultipleJokers() { + assertFailsWith { + val progress = createScienceProgress(0, 0, 0, 2) + gson.toJson(progress) + } + } + + @Test + fun serialize_failOnMixedElements() { + assertFailsWith { + val progress = createScienceProgress(1, 1, 0, 0) + gson.toJson(progress) + } + } + + @Test + fun deserialize_failOnEmptyString() { + assertFailsWith { + gson.fromJson("\"\"") + } + } + + @Test + fun deserialize_failOnGarbageString() { + assertFailsWith { + gson.fromJson("thisisgarbage") + } + } + + @Test + fun deserialize_compass() { + val progress = gson.fromJson(COMPASS_STR) + assertNotNull(progress.science) + assertEquals(1, progress.science.getQuantity(ScienceType.COMPASS)) + assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) + assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) + assertEquals(0, progress.science.jokers) + } + + @Test + fun deserialize_wheel() { + val progress = gson.fromJson(WHEEL_STR) + assertNotNull(progress.science) + assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) + assertEquals(1, progress.science.getQuantity(ScienceType.WHEEL)) + assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) + assertEquals(0, progress.science.jokers) + } + + @Test + fun deserialize_tablet() { + val progress = gson.fromJson(TABLET_STR) + assertNotNull(progress.science) + assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) + assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) + assertEquals(1, progress.science.getQuantity(ScienceType.TABLET)) + assertEquals(0, progress.science.jokers) + } + + @Test + fun deserialize_joker() { + val progress = gson.fromJson(JOKER_STR) + assertNotNull(progress.science) + assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) + assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) + assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) + assertEquals(1, progress.science.jokers) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElementTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElementTest.kt new file mode 100644 index 00000000..ce914aab --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/BonusPerBoardElementTest.kt @@ -0,0 +1,142 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.Before +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.engine.test.addCards +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class BonusPerBoardElementTest { + + private lateinit var table: Table + private lateinit var player0: Player + + @Before + fun setUp() { + table = testTable(4) + player0 = SimplePlayer(0, table) + } + + @Theory + fun computePoints_countsCards( + boardPosition: RelativeBoardPosition, + nbCards: Int, + nbOtherCards: Int, + points: Int, + gold: Int, + color: Color + ) { + val board = table.getBoard(0, boardPosition) + addCards(board, nbCards, nbOtherCards, color) + + val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.CARD, gold, points, listOf(color)) + + assertEquals(nbCards * points, bonus.computePoints(player0)) + } + + @Theory + fun computePoints_countsDefeatTokens( + boardPosition: RelativeBoardPosition, + nbDefeatTokens: Int, + points: Int, + gold: Int + ) { + val board = table.getBoard(0, boardPosition) + repeat(nbDefeatTokens) { + board.military.defeat() + } + + val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.DEFEAT_TOKEN, gold, points, listOf()) + + assertEquals(nbDefeatTokens * points, bonus.computePoints(player0)) + } + + @Theory + fun computePoints_countsWonderStages(boardPosition: RelativeBoardPosition, nbStages: Int, points: Int, gold: Int) { + val board = table.getBoard(0, boardPosition) + repeat(nbStages) { + board.wonder.placeCard(CardBack("")) + } + + val bonus = + BonusPerBoardElement(listOf(boardPosition), BoardElementType.BUILT_WONDER_STAGES, gold, points, listOf()) + + assertEquals(nbStages * points, bonus.computePoints(player0)) + } + + @Theory + fun apply_countsCards( + boardPosition: RelativeBoardPosition, + nbCards: Int, + nbOtherCards: Int, + points: Int, + gold: Int, + color: Color + ) { + val board = table.getBoard(0, boardPosition) + addCards(board, nbCards, nbOtherCards, color) + + val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.CARD, gold, points, listOf(color)) + + val selfBoard = table.getBoard(0) + val initialGold = selfBoard.gold + bonus.applyTo(player0) + assertEquals(initialGold + nbCards * gold, selfBoard.gold) + } + + @Theory + fun apply_countsDefeatTokens(boardPosition: RelativeBoardPosition, nbDefeatTokens: Int, points: Int, gold: Int) { + val board = table.getBoard(0, boardPosition) + repeat(nbDefeatTokens) { + board.military.defeat() + } + + val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.DEFEAT_TOKEN, gold, points, listOf()) + + val selfBoard = table.getBoard(0) + val initialGold = selfBoard.gold + bonus.applyTo(player0) + assertEquals(initialGold + nbDefeatTokens * gold, selfBoard.gold) + } + + @Theory + fun apply_countsWonderStages(boardPosition: RelativeBoardPosition, nbStages: Int, points: Int, gold: Int) { + val board = table.getBoard(0, boardPosition) + repeat(nbStages) { + board.wonder.placeCard(CardBack("")) + } + + val bonus = + BonusPerBoardElement(listOf(boardPosition), BoardElementType.BUILT_WONDER_STAGES, gold, points, listOf()) + + val selfBoard = table.getBoard(0) + val initialGold = selfBoard.gold + bonus.applyTo(player0) + assertEquals(initialGold + nbStages * gold, selfBoard.gold) + } + + companion object { + + @JvmStatic + @DataPoints + fun values(): IntArray = intArrayOf(0, 1, 2, 3) + + @JvmStatic + @DataPoints + fun colors(): Array = Color.values() + + @JvmStatic + @DataPoints + fun positions(): Array = RelativeBoardPosition.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/DiscountTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/DiscountTest.kt new file mode 100644 index 00000000..d989d24a --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/DiscountTest.kt @@ -0,0 +1,69 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.Assume +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.test.createTransactions +import org.luxons.sevenwonders.engine.test.testBoard +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class DiscountTest { + + @Theory + fun apply_givesDiscountedPrice(discountedPrice: Int, discountedType: ResourceType, provider: Provider) { + val board = testBoard(ResourceType.CLAY, 3) + val discount = Discount(listOf(discountedType), listOf(provider), discountedPrice) + discount.applyTo(board) + + val transactions = createTransactions(provider, discountedType) + assertEquals(discountedPrice, board.tradingRules.computeCost(transactions)) + } + + @Theory + fun apply_doesNotAffectOtherResources( + discountedPrice: Int, + discountedType: ResourceType, + provider: Provider, + otherType: ResourceType, + otherProvider: Provider + ) { + Assume.assumeTrue(otherProvider != provider) + Assume.assumeTrue(otherType != discountedType) + + val board = testBoard(ResourceType.CLAY, 3) + val discount = Discount(listOf(discountedType), listOf(provider), discountedPrice) + discount.applyTo(board) + + // this is the default in the settings used by TestUtilsKt.testBoard() + val normalPrice = 2 + + val fromOtherType = createTransactions(provider, otherType) + assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherType)) + + val fromOtherProvider = createTransactions(otherProvider, discountedType) + assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherProvider)) + + val fromOtherProviderAndType = createTransactions(otherProvider, otherType) + assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherProviderAndType)) + } + + companion object { + + @JvmStatic + @DataPoints + fun discountedPrices(): IntArray = intArrayOf(0, 1, 2) + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + + @JvmStatic + @DataPoints + fun providers(): Array = Provider.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncreaseTest.kt new file mode 100644 index 00000000..67bb3862 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/GoldIncreaseTest.kt @@ -0,0 +1,43 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class GoldIncreaseTest { + + @Theory + fun apply_increaseGoldWithRightAmount(initialAmount: Int, goldIncreaseAmount: Int, type: ResourceType) { + val board = testBoard(type, initialAmount) + val goldIncrease = GoldIncrease(goldIncreaseAmount) + + goldIncrease.applyTo(board) + + assertEquals(initialAmount + goldIncreaseAmount, board.gold) + } + + @Theory + fun computePoints_isAlwaysZero(gold: Int) { + val goldIncrease = GoldIncrease(gold) + val player = SimplePlayer(0, testTable(5)) + assertEquals(0, goldIncrease.computePoints(player)) + } + + companion object { + + @JvmStatic + @DataPoints + fun goldAmounts(): IntArray = intArrayOf(-5, -1, 0, 1, 2, 5, 10) + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcementsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcementsTest.kt new file mode 100644 index 00000000..23b5959e --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/MilitaryReinforcementsTest.kt @@ -0,0 +1,44 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class MilitaryReinforcementsTest { + + @Theory + fun apply_increaseGoldWithRightAmount(initialShields: Int, additionalShields: Int, type: ResourceType) { + val board = testBoard(type) + board.military.addShields(initialShields) + + val reinforcements = MilitaryReinforcements(additionalShields) + reinforcements.applyTo(board) + + assertEquals(initialShields + additionalShields, board.military.nbShields) + } + + @Theory + fun computePoints_isAlwaysZero(shields: Int) { + val reinforcements = MilitaryReinforcements(shields) + val player = SimplePlayer(0, testTable(5)) + assertEquals(0, reinforcements.computePoints(player)) + } + + companion object { + + @JvmStatic + @DataPoints + fun shieldCounts(): IntArray = intArrayOf(0, 1, 2, 3, 5) + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncreaseTest.kt new file mode 100644 index 00000000..235b2fa7 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ProductionIncreaseTest.kt @@ -0,0 +1,73 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.resources.resourcesOf +import org.luxons.sevenwonders.engine.test.fixedProduction +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class ProductionIncreaseTest { + + @Theory + fun apply_boardContainsAddedResourceType( + initialType: ResourceType, + addedType: ResourceType, + extraType: ResourceType + ) { + val board = testBoard(initialType) + val effect = ProductionIncrease(fixedProduction(addedType), false) + + effect.applyTo(board) + + val resources = resourcesOf(initialType, addedType) + assertTrue(board.production.contains(resources)) + assertFalse(board.publicProduction.contains(resources)) + + val moreResources = resourcesOf(initialType, addedType, extraType) + assertFalse(board.production.contains(moreResources)) + assertFalse(board.publicProduction.contains(moreResources)) + } + + @Theory + fun apply_boardContainsAddedResourceType_sellable( + initialType: ResourceType, + addedType: ResourceType, + extraType: ResourceType + ) { + val board = testBoard(initialType) + val effect = ProductionIncrease(fixedProduction(addedType), true) + + effect.applyTo(board) + + val resources = resourcesOf(initialType, addedType) + assertTrue(board.production.contains(resources)) + assertTrue(board.publicProduction.contains(resources)) + + val moreResources = resourcesOf(initialType, addedType, extraType) + assertFalse(board.production.contains(moreResources)) + assertFalse(board.publicProduction.contains(moreResources)) + } + + @Theory + fun computePoints_isAlwaysZero(addedType: ResourceType) { + val effect = ProductionIncrease(fixedProduction(addedType), false) + val player = SimplePlayer(0, testTable(5)) + assertEquals(0, effect.computePoints(player)) + } + + companion object { + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncreaseTest.kt new file mode 100644 index 00000000..eb48f4ed --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/RawPointsIncreaseTest.kt @@ -0,0 +1,29 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class RawPointsIncreaseTest { + + @Theory + fun computePoints_equalsNbOfPoints(points: Int) { + val rawPointsIncrease = RawPointsIncrease(points) + val player = SimplePlayer(0, testTable(5)) + assertEquals(points, rawPointsIncrease.computePoints(player)) + } + + companion object { + + @JvmStatic + @DataPoints + fun points(): IntArray { + return intArrayOf(0, 1, 2, 3, 5) + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgressTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgressTest.kt new file mode 100644 index 00000000..b7f5e152 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/ScienceProgressTest.kt @@ -0,0 +1,49 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.boards.ScienceType +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.engine.test.createScience +import org.luxons.sevenwonders.engine.test.createScienceProgress +import org.luxons.sevenwonders.engine.test.testBoard +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class ScienceProgressTest { + + @Theory + fun apply_initContainsAddedScience( + initCompasses: Int, + initWheels: Int, + initTablets: Int, + initJokers: Int, + compasses: Int, + wheels: Int, + tablets: Int, + jokers: Int + ) { + val board = testBoard(ResourceType.ORE) + val initialScience = createScience(initCompasses, initWheels, initTablets, initJokers) + board.science.addAll(initialScience) + + val effect = createScienceProgress(compasses, wheels, tablets, jokers) + effect.applyTo(board) + + assertEquals(initCompasses + compasses, board.science.getQuantity(ScienceType.COMPASS)) + assertEquals(initWheels + wheels, board.science.getQuantity(ScienceType.WHEEL)) + assertEquals(initTablets + tablets, board.science.getQuantity(ScienceType.TABLET)) + assertEquals(initJokers + jokers, board.science.jokers) + } + + companion object { + + @JvmStatic + @DataPoints + fun elementsCount(): IntArray { + return intArrayOf(0, 1, 2) + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivationTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivationTest.kt new file mode 100644 index 00000000..16fbeae6 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/effects/SpecialAbilityActivationTest.kt @@ -0,0 +1,93 @@ +package org.luxons.sevenwonders.engine.effects + +import org.junit.Assume +import org.junit.Test +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.engine.test.createGuildCard +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +@RunWith(Theories::class) +class SpecialAbilityActivationTest { + + @Theory + fun apply_addsAbility(ability: SpecialAbility) { + val effect = SpecialAbilityActivation(ability) + val player = SimplePlayer(0, testTable(5)) + + effect.applyTo(player) + + assertTrue(player.board.hasSpecial(ability)) + } + + @Theory + fun computePoints_zeroExceptForCopyGuild(ability: SpecialAbility) { + Assume.assumeTrue(ability !== SpecialAbility.COPY_GUILD) + + val effect = SpecialAbilityActivation(ability) + val player = SimplePlayer(0, testTable(5)) + + assertEquals(0, effect.computePoints(player)) + } + + @Theory + internal fun computePoints_copiedGuild(guildCard: Card, neighbour: RelativeBoardPosition) { + val effect = SpecialAbilityActivation(SpecialAbility.COPY_GUILD) + val player = SimplePlayer(0, testTable(5)) + + val neighbourBoard = player.getBoard(neighbour) + neighbourBoard.addCard(guildCard) + + player.board.copiedGuild = guildCard + + val directPointsFromGuildCard = guildCard.effects.stream().mapToInt { e -> e.computePoints(player) }.sum() + assertEquals(directPointsFromGuildCard, effect.computePoints(player)) + } + + @Test + fun computePoints_copyGuild_failWhenNoChosenGuild() { + val effect = SpecialAbilityActivation(SpecialAbility.COPY_GUILD) + val player = SimplePlayer(0, testTable(5)) + assertFailsWith { + effect.computePoints(player) + } + } + + companion object { + + @JvmStatic + @DataPoints + fun abilities(): Array = SpecialAbility.values() + + @JvmStatic + @DataPoints + fun neighbours(): Array = + arrayOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.RIGHT) + + @JvmStatic + @DataPoints + internal fun guilds(): Array { + val bonus = BonusPerBoardElement( + listOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.RIGHT), + BoardElementType.CARD, + points = 1, + colors = listOf(Color.GREY, Color.BROWN) + ) + val bonus2 = BonusPerBoardElement( + listOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.SELF, RelativeBoardPosition.RIGHT), + BoardElementType.BUILT_WONDER_STAGES, + points = 1 + ) + return arrayOf(createGuildCard(1, bonus), createGuildCard(2, bonus2)) + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMoveTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMoveTest.kt new file mode 100644 index 00000000..764d7890 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/moves/BuildWonderMoveTest.kt @@ -0,0 +1,83 @@ +package org.luxons.sevenwonders.engine.moves + +import org.junit.Test +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.model.MoveType +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.engine.test.createMove +import org.luxons.sevenwonders.engine.test.sampleCards +import org.luxons.sevenwonders.engine.test.testCard +import org.luxons.sevenwonders.engine.test.testSettings +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.fail + +class BuildWonderMoveTest { + + @Test + fun init_failsWhenCardNotInHand() { + val table = testTable(3) + val hand = sampleCards(7) + val playerContext = PlayerContext(0, table, hand) + val anotherCard = testCard("Card that is not in the hand") + + assertFailsWith { + createMove(playerContext, anotherCard, MoveType.UPGRADE_WONDER) + } + } + + @Test + fun init_failsWhenWonderIsCompletelyBuilt() { + val settings = testSettings(3) + val table = testTable(settings) + val hand = sampleCards(7) + + fillPlayerWonderLevels(settings, table, hand) + + // should fail because the wonder is already full + assertFailsWith { + buildOneWonderLevel(settings, table, hand, 4) + } + } + + private fun fillPlayerWonderLevels(settings: Settings, table: Table, hand: List) { + try { + val nbLevels = table.getBoard(0).wonder.stages.size + repeat(nbLevels) { + buildOneWonderLevel(settings, table, hand, it) + } + } catch (e: InvalidMoveException) { + fail("Building wonder levels should not fail before being full") + } + } + + private fun buildOneWonderLevel(settings: Settings, table: Table, hand: List, cardIndex: Int) { + val card = hand[cardIndex] + val playerContext = PlayerContext(0, table, hand) + val move = createMove(playerContext, card, MoveType.UPGRADE_WONDER) + move.place(mutableListOf(), settings) + move.activate(emptyList(), settings) + } + + @Test + fun place_increasesWonderLevel() { + val settings = testSettings(3) + val table = testTable(settings) + val hand = sampleCards(7) + val cardToUse = hand[0] + val playerContext = PlayerContext(0, table, hand) + val move = createMove(playerContext, cardToUse, MoveType.UPGRADE_WONDER) + + val initialStage = table.getBoard(0).wonder.nbBuiltStages + + move.place(mutableListOf(), settings) + + val newStage = table.getBoard(0).wonder.nbBuiltStages + + // we need to see the level increase before activation so that other players + assertEquals(initialStage + 1, newStage) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt new file mode 100644 index 00000000..a003a332 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/BestPriceCalculatorTest.kt @@ -0,0 +1,139 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Test +import org.luxons.sevenwonders.engine.SimplePlayer +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.model.resources.Provider.LEFT_PLAYER +import org.luxons.sevenwonders.model.resources.Provider.RIGHT_PLAYER +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.ResourceType.CLAY +import org.luxons.sevenwonders.model.resources.ResourceType.GLASS +import org.luxons.sevenwonders.model.resources.ResourceType.ORE +import org.luxons.sevenwonders.model.resources.ResourceType.STONE +import org.luxons.sevenwonders.model.resources.ResourceType.WOOD +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.test.createTransaction +import org.luxons.sevenwonders.engine.test.createTransactions +import org.luxons.sevenwonders.engine.test.testBoard +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +class BestPriceCalculatorTest { + + private fun solutions(price: Int, vararg resourceTransactions: ResourceTransactions) = + TransactionPlan(price, setOf(*resourceTransactions)) + + @Test + fun bestPrice_0forEmptyResources() { + val table = testTable(3) + val player0 = SimplePlayer(0, table) + val emptyResources = emptyResources() + val emptyTransactions = noTransactions() + assertEquals(solutions(0, emptyTransactions), bestSolution(emptyResources, player0)) + } + + @Test + fun bestPrice_fixedResources_defaultCost() { + val left = testBoard(STONE) + val main = testBoard(STONE) + val right = testBoard(WOOD) + val table = Table(listOf(main, right, left)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(STONE, STONE) + + val stoneLeftSingle = createTransaction(LEFT_PLAYER, STONE) + val stoneRightSingle = createTransaction(RIGHT_PLAYER, STONE) + + val stoneLeft = createTransactions(stoneLeftSingle) + val stoneRight = createTransactions(stoneRightSingle) + val stoneLeftAndRight = createTransactions(stoneLeftSingle, stoneRightSingle) + + assertEquals(solutions(2, stoneLeft), bestSolution(resources, player0)) + assertEquals(solutions(4, stoneLeftAndRight), bestSolution(resources, player1)) + assertEquals(solutions(2, stoneRight), bestSolution(resources, player2)) + } + + @Test + fun bestPrice_fixedResources_overridenCost() { + val main = testBoard(STONE) + main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) + + val left = testBoard(WOOD) + val right = testBoard(WOOD) + val opposite = testBoard(GLASS) + val table = Table(listOf(main, right, opposite, left)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + val player3 = SimplePlayer(3, table) + + val resources = resourcesOf(WOOD) + + val woodLeft = createTransactions(LEFT_PLAYER, WOOD) + val woodRight = createTransactions(RIGHT_PLAYER, WOOD) + + assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) + assertEquals(solutions(2, woodLeft, woodRight), bestSolution(resources, player2)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player3)) + } + + @Test + fun bestPrice_mixedResources_overridenCost() { + val left = testBoard(WOOD) + + val main = testBoard(STONE) + main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) + + val right = testBoard(ORE) + right.production.addChoice(WOOD, CLAY) + right.publicProduction.addChoice(WOOD, CLAY) + + val table = Table(listOf(main, right, left)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(WOOD) + val woodRight = createTransactions(RIGHT_PLAYER, WOOD) + + assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player2)) + } + + @Test + fun bestPrice_chooseCheapest() { + val left = testBoard(WOOD) + + val main = testBoard(WOOD) + main.production.addChoice(CLAY, ORE) + main.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) + + val right = testBoard(WOOD) + right.production.addFixedResource(ORE, 1) + right.production.addFixedResource(CLAY, 1) + right.publicProduction.addFixedResource(ORE, 1) + right.publicProduction.addFixedResource(CLAY, 1) + + val table = Table(listOf(main, right, left)) + + val player0 = SimplePlayer(0, table) + val player1 = SimplePlayer(1, table) + val player2 = SimplePlayer(2, table) + + val resources = resourcesOf(ORE, CLAY) + val oreAndClayLeft = createTransactions(LEFT_PLAYER, ORE, CLAY) + val clayRight = createTransactions(RIGHT_PLAYER, CLAY) + + assertEquals(solutions(1, clayRight), bestSolution(resources, player0)) + assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) + assertEquals(solutions(4, oreAndClayLeft), bestSolution(resources, player2)) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ProductionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ProductionTest.kt new file mode 100644 index 00000000..c449c784 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ProductionTest.kt @@ -0,0 +1,293 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Before +import org.junit.Test +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.ResourceType.CLAY +import org.luxons.sevenwonders.model.resources.ResourceType.GLASS +import org.luxons.sevenwonders.model.resources.ResourceType.LOOM +import org.luxons.sevenwonders.model.resources.ResourceType.ORE +import org.luxons.sevenwonders.model.resources.ResourceType.PAPYRUS +import org.luxons.sevenwonders.model.resources.ResourceType.STONE +import org.luxons.sevenwonders.model.resources.ResourceType.WOOD +import java.util.EnumSet +import java.util.HashSet +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ProductionTest { + + private lateinit var emptyResources: Resources + private lateinit var resources1Wood: Resources + private lateinit var resources1Stone: Resources + private lateinit var resources1Stone1Wood: Resources + private lateinit var resources2Stones: Resources + private lateinit var resources2Stones3Clay: Resources + + @Before + fun init() { + emptyResources = emptyResources() + resources1Wood = resourcesOf(WOOD) + resources1Stone = resourcesOf(STONE) + resources1Stone1Wood = resourcesOf(STONE to 1, WOOD to 1) + resources2Stones = resourcesOf(STONE to 2) + resources2Stones3Clay = resourcesOf(STONE to 2, CLAY to 3) + } + + @Test + fun contains_newProductionContainsEmpty() { + val production = Production() + assertTrue(production.contains(emptyResources)) + } + + @Test + fun contains_singleFixedResource_noneAtAll() { + val production = Production() + assertFalse(production.contains(resources2Stones)) + } + + @Test + fun contains_singleFixedResource_notEnough() { + val production = Production() + production.addFixedResource(STONE, 1) + assertFalse(production.contains(resources2Stones)) + } + + @Test + fun contains_singleFixedResource_justEnough() { + val production = Production() + production.addFixedResource(STONE, 2) + assertTrue(production.contains(resources2Stones)) + } + + @Test + fun contains_singleFixedResource_moreThanEnough() { + val production = Production() + production.addFixedResource(STONE, 3) + assertTrue(production.contains(resources2Stones)) + } + + @Test + fun contains_singleFixedResource_moreThanEnough_amongOthers() { + val production = Production() + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 2) + assertTrue(production.contains(resources2Stones)) + } + + @Test + fun contains_multipleFixedResources_notEnoughOfOne() { + val production = Production() + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 1) + assertFalse(production.contains(resources2Stones3Clay)) + } + + @Test + fun contains_multipleFixedResources_notEnoughOfBoth() { + val production = Production() + production.addFixedResource(STONE, 1) + production.addFixedResource(CLAY, 1) + assertFalse(production.contains(resources2Stones3Clay)) + } + + @Test + fun contains_multipleFixedResources_moreThanEnough() { + val production = Production() + production.addFixedResource(STONE, 3) + production.addFixedResource(CLAY, 5) + assertTrue(production.contains(resources2Stones3Clay)) + } + + @Test + fun contains_singleChoice_containsEmpty() { + val production = Production() + production.addChoice(STONE, CLAY) + assertTrue(production.contains(emptyResources)) + } + + @Test + fun contains_singleChoice_enough() { + val production = Production() + production.addChoice(STONE, WOOD) + assertTrue(production.contains(resources1Wood)) + assertTrue(production.contains(resources1Stone)) + } + + @Test + fun contains_multipleChoices_notBoth() { + val production = Production() + production.addChoice(STONE, CLAY) + production.addChoice(STONE, CLAY) + production.addChoice(STONE, CLAY) + assertFalse(production.contains(resources2Stones3Clay)) + } + + @Test + fun contains_multipleChoices_enough() { + val production = Production() + production.addChoice(STONE, ORE) + production.addChoice(STONE, WOOD) + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun contains_multipleChoices_enoughReverseOrder() { + val production = Production() + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun contains_multipleChoices_moreThanEnough() { + val production = Production() + production.addChoice(LOOM, GLASS, PAPYRUS) + production.addChoice(STONE, ORE) + production.addChoice(STONE, WOOD) + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun contains_mixedFixedAndChoice_enough() { + val production = Production() + production.addFixedResource(WOOD, 1) + production.addChoice(STONE, WOOD) + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun addAll_empty() { + val production = Production() + production.addAll(emptyResources) + assertTrue(production.contains(emptyResources)) + } + + @Test + fun addAll_singleResource() { + val production = Production() + production.addAll(resources1Stone) + assertTrue(production.contains(resources1Stone)) + } + + @Test + fun addAll_multipleResources() { + val production = Production() + production.addAll(resources2Stones3Clay) + assertTrue(production.contains(resources2Stones3Clay)) + } + + @Test + fun addAll_production_multipleFixedResources() { + val production = Production() + production.addAll(resources2Stones3Clay) + + val production2 = Production() + production2.addAll(production) + + assertTrue(production2.contains(resources2Stones3Clay)) + } + + @Test + fun addAll_production_multipleChoices() { + val production = Production() + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) + + val production2 = Production() + production2.addAll(production) + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun addAll_production_mixedFixedResourcesAndChoices() { + val production = Production() + production.addFixedResource(WOOD, 1) + production.addChoice(STONE, WOOD) + + val production2 = Production() + production2.addAll(production) + + assertTrue(production.contains(resources1Stone1Wood)) + } + + @Test + fun asChoices_empty() { + val production = Production() + assertTrue(production.asChoices().isEmpty()) + } + + @Test + fun asChoices_onlyChoices() { + val production = Production() + production.addChoice(STONE, WOOD) + production.addChoice(STONE, ORE) + production.addChoice(CLAY, LOOM, GLASS) + assertEquals(production.getAlternativeResources(), production.asChoices()) + } + + @Test + fun asChoices_onlyFixed() { + val production = Production() + production.addFixedResource(WOOD, 1) + production.addFixedResource(CLAY, 2) + + val expected = HashSet>() + expected.add(EnumSet.of(WOOD)) + expected.add(EnumSet.of(CLAY)) + expected.add(EnumSet.of(CLAY)) + + assertEquals(expected, production.asChoices()) + } + + @Test + fun asChoices_mixed() { + val production = Production() + production.addChoice(STONE, ORE) + production.addChoice(CLAY, LOOM, GLASS) + production.addFixedResource(WOOD, 1) + production.addFixedResource(CLAY, 2) + + val expected = HashSet>() + expected.add(EnumSet.of(STONE, ORE)) + expected.add(EnumSet.of(CLAY, LOOM, GLASS)) + expected.add(EnumSet.of(WOOD)) + expected.add(EnumSet.of(CLAY)) + expected.add(EnumSet.of(CLAY)) + + assertEquals(expected, production.asChoices()) + } + + @Test + fun equals_trueWhenSame() { + val production = Production() + assertEquals(production, production) + } + + @Test + fun equals_trueWhenSameContent() { + val production1 = Production() + val production2 = Production() + assertTrue(production1 == production2) + production1.addFixedResource(GLASS, 1) + production2.addFixedResource(GLASS, 1) + assertTrue(production1 == production2) + production1.addChoice(ORE, WOOD) + production2.addChoice(ORE, WOOD) + assertTrue(production1 == production2) + } + + @Test + fun hashCode_sameWhenSameContent() { + val production1 = Production() + val production2 = Production() + assertEquals(production1.hashCode(), production2.hashCode()) + production1.addFixedResource(GLASS, 1) + production2.addFixedResource(GLASS, 1) + assertEquals(production1.hashCode(), production2.hashCode()) + production1.addChoice(ORE, WOOD) + production2.addChoice(ORE, WOOD) + assertEquals(production1.hashCode(), production2.hashCode()) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt new file mode 100644 index 00000000..665612e3 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourceTransactionsTest.kt @@ -0,0 +1,29 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Test +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.ResourceType.CLAY +import org.luxons.sevenwonders.model.resources.ResourceType.WOOD +import org.luxons.sevenwonders.engine.test.createTransaction +import kotlin.test.assertEquals + +class ResourceTransactionsTest { + + @Test + fun toTransactions() { + val transactionMap = mapOf( + Provider.LEFT_PLAYER to (1 of WOOD) + (1 of CLAY), + Provider.RIGHT_PLAYER to (1 of WOOD) + ) + + val expectedNormalized = setOf( + createTransaction(Provider.LEFT_PLAYER, WOOD, CLAY), + createTransaction(Provider.RIGHT_PLAYER, WOOD) + ) + + assertEquals(expectedNormalized, transactionMap.toTransactions().toSet()) + } + + private infix fun Int.of(type: ResourceType): Resources = resourcesOf(type to this) +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourcesTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourcesTest.kt new file mode 100644 index 00000000..3aa5acd4 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/ResourcesTest.kt @@ -0,0 +1,436 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Test +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.ResourceType.CLAY +import org.luxons.sevenwonders.model.resources.ResourceType.GLASS +import org.luxons.sevenwonders.model.resources.ResourceType.LOOM +import org.luxons.sevenwonders.model.resources.ResourceType.ORE +import org.luxons.sevenwonders.model.resources.ResourceType.PAPYRUS +import org.luxons.sevenwonders.model.resources.ResourceType.STONE +import org.luxons.sevenwonders.model.resources.ResourceType.WOOD +import java.util.NoSuchElementException +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ResourcesTest { + + @Test + fun init_shouldBeEmpty() { + val resources = emptyResources() + for (resourceType in ResourceType.values()) { + assertEquals(0, resources[resourceType]) + } + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) + } + + @Test + fun add_zero() { + val resources = mutableResourcesOf() + resources.add(CLAY, 0) + assertEquals(0, resources[CLAY]) + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) + } + + @Test + fun add_simple() { + val resources = mutableResourcesOf() + resources.add(WOOD, 3) + assertEquals(3, resources[WOOD]) + assertEquals(3, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun add_multipleCallsStacked() { + val resources = mutableResourcesOf() + resources.add(ORE, 3) + resources.add(ORE, 2) + assertEquals(5, resources[ORE]) + assertEquals(5, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun add_interlaced() { + val resources = mutableResourcesOf() + resources.add(GLASS, 3) + resources.add(STONE, 1) + resources.add(WOOD, 4) + resources.add(GLASS, 2) + assertEquals(5, resources[GLASS]) + assertEquals(10, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun plus_zero() { + val resources = resourcesOf(CLAY to 2) + val resourcesPlusZero = resources + emptyResources() + val zeroPlusResources = emptyResources() + resources + + assertEquals(2, resourcesPlusZero[CLAY]) + assertEquals(2, resourcesPlusZero.size) + assertEquals(2, zeroPlusResources[CLAY]) + assertEquals(2, zeroPlusResources.size) + } + + @Test + fun plus_sameResource() { + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(WOOD to 3) + val sum = resources1 + resources2 + + assertEquals(1, resources1.size) + assertEquals(3, resources2.size) + assertEquals(4, sum[WOOD]) + assertEquals(4, sum.size) + } + + @Test + fun plus_differentemptyResources() { + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(ORE to 3) + val sum = resources1 + resources2 + + assertEquals(1, resources1.size) + assertEquals(3, resources2.size) + assertEquals(1, sum[WOOD]) + assertEquals(3, sum[ORE]) + assertEquals(4, sum.size) + } + + @Test + fun plus_overlappingemptyResources() { + val resources1 = resourcesOf(WOOD to 1) + val resources2 = resourcesOf(WOOD to 2, ORE to 4) + val sum = resources1 + resources2 + + assertEquals(1, resources1.size) + assertEquals(6, resources2.size) + assertEquals(3, sum[WOOD]) + assertEquals(4, sum[ORE]) + assertEquals(7, sum.size) + } + + @Test + fun remove_some() { + val resources = mutableResourcesOf(WOOD to 3) + resources.remove(WOOD, 2) + assertEquals(1, resources[WOOD]) + assertEquals(1, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun remove_all() { + val resources = mutableResourcesOf(WOOD to 3) + resources.remove(WOOD, 3) + assertEquals(0, resources[WOOD]) + assertEquals(0, resources.size) + assertTrue(resources.isEmpty()) + } + + @Test + fun remove_tooMany() { + val resources = mutableResourcesOf(WOOD to 2) + + assertFailsWith { + resources.remove(WOOD, 3) + } + } + + @Test + fun addAll_empty() { + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + + val emptyResources = emptyResources() + + resources.add(emptyResources) + assertEquals(1, resources[STONE]) + assertEquals(3, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(4, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun addAll_zeros() { + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + + val emptyResources = resourcesOf(STONE to 0, CLAY to 0) + + resources.add(emptyResources) + assertEquals(1, resources[STONE]) + assertEquals(3, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(4, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun addAll_same() { + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 2, CLAY to 6) + + resources.add(resources2) + assertEquals(3, resources[STONE]) + assertEquals(9, resources[CLAY]) + assertEquals(0, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(12, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun addAll_overlap() { + val resources = mutableResourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(CLAY to 6, ORE to 4) + + resources.add(resources2) + assertEquals(1, resources[STONE]) + assertEquals(9, resources[CLAY]) + assertEquals(4, resources[ORE]) + assertEquals(0, resources[GLASS]) + assertEquals(0, resources[LOOM]) + assertEquals(14, resources.size) + assertFalse(resources.isEmpty()) + } + + @Test + fun contains_emptyContainsEmpty() { + val emptyResources = emptyResources() + val emptyResources2 = emptyResources() + assertTrue(emptyResources.containsAll(emptyResources2)) + } + + @Test + fun contains_singleTypeContainsEmpty() { + val resources = resourcesOf(STONE to 1) + val emptyResources = emptyResources() + + assertTrue(resources.containsAll(emptyResources)) + } + + @Test + fun contains_multipleTypesContainsEmpty() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + val emptyResources = emptyResources() + + assertTrue(resources.containsAll(emptyResources)) + } + + @Test + fun contains_self() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + + assertTrue(resources.containsAll(resources)) + } + + @Test + fun contains_allOfEachType() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) + + assertTrue(resources.containsAll(resources2)) + } + + @Test + fun contains_someOfEachType() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) + + assertTrue(resources.containsAll(resources2)) + } + + @Test + fun contains_someOfSomeTypes() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 3) + + assertTrue(resources.containsAll(resources2)) + } + + @Test + fun contains_allOfSomeTypes() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 4) + + assertTrue(resources.containsAll(resources2)) + } + + @Test + fun minus_empty() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + val emptyResources = emptyResources() + + val diff = resources.minus(emptyResources) + assertEquals(1, diff[STONE]) + assertEquals(3, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_self() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + + val diff = resources.minus(resources) + assertEquals(0, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_allOfEachType() { + val resources = resourcesOf(STONE to 1, CLAY to 3) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) + + val diff = resources.minus(resources2) + assertEquals(0, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_someOfEachType() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(STONE to 1, CLAY to 3) + + val diff = resources.minus(resources2) + assertEquals(1, diff[STONE]) + assertEquals(1, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_someOfSomeTypes() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 3) + + val diff = resources.minus(resources2) + assertEquals(2, diff[STONE]) + assertEquals(1, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_allOfSomeTypes() { + val resources = resourcesOf(STONE to 2, CLAY to 4) + val resources2 = resourcesOf(CLAY to 4) + + val diff = resources.minus(resources2) + assertEquals(2, diff[STONE]) + assertEquals(0, diff[CLAY]) + assertEquals(0, diff[ORE]) + assertEquals(0, diff[GLASS]) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_tooMuchOfExistingType() { + val resources = resourcesOf(CLAY to 4) + val resources2 = resourcesOf(CLAY to 5) + + val diff = resources.minus(resources2) + assertEquals(0, diff[CLAY]) + } + + @Test + fun minus_someOfAnAbsentType() { + val resources = emptyResources() + val resources2 = resourcesOf(LOOM to 5) + + val diff = resources.minus(resources2) + assertEquals(0, diff[LOOM]) + } + + @Test + fun minus_someOfATypeWithZero() { + val resources = resourcesOf(LOOM to 0) + val resources2 = resourcesOf(LOOM to 5) + + val diff = resources.minus(resources2) + assertEquals(0, diff[LOOM]) + } + + @Test + fun isEmpty_noElement() { + val resources = emptyResources() + assertTrue(resources.isEmpty()) + } + + @Test + fun isEmpty_singleZeroElement() { + val resources = resourcesOf(LOOM to 0) + assertTrue(resources.isEmpty()) + } + + @Test + fun isEmpty_multipleZeroElements() { + val resources = resourcesOf(WOOD to 0, ORE to 0, LOOM to 0) + assertTrue(resources.isEmpty()) + } + + @Test + fun isEmpty_singleElementMoreThanZero() { + val resources = resourcesOf(LOOM to 3) + assertFalse(resources.isEmpty()) + } + + @Test + fun isEmpty_mixedZeroAndNonZeroElements() { + val resources = resourcesOf(WOOD to 0, LOOM to 3) + assertFalse(resources.isEmpty()) + } + + @Test + fun isEmpty_mixedZeroAndNonZeroElements_reverseOrder() { + val resources = resourcesOf(ORE to 3, PAPYRUS to 0) + assertFalse(resources.isEmpty()) + } + + @Test + fun equals_trueWhenSame() { + val resources = emptyResources() + assertEquals(resources, resources) + } + + @Test + fun equals_trueWhenSameContent() { + val resources1 = mutableResourcesOf() + val resources2 = mutableResourcesOf() + assertTrue(resources1 == resources2) + resources1.add(GLASS, 1) + resources2.add(GLASS, 1) + assertTrue(resources1 == resources2) + } + + @Test + fun hashCode_sameWhenSameContent() { + val resources1 = mutableResourcesOf() + val resources2 = mutableResourcesOf() + assertEquals(resources1.hashCode(), resources2.hashCode()) + resources1.add(GLASS, 1) + resources2.add(GLASS, 1) + assertEquals(resources1.hashCode(), resources2.hashCode()) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TradingRulesTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TradingRulesTest.kt new file mode 100644 index 00000000..0301b16c --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/resources/TradingRulesTest.kt @@ -0,0 +1,129 @@ +package org.luxons.sevenwonders.engine.resources + +import org.junit.Assume.assumeTrue +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.test.createTransaction +import org.luxons.sevenwonders.engine.test.createTransactions +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class TradingRulesTest { + + @Theory + fun setCost_overridesCost( + defaultCost: Int, + overriddenCost: Int, + overriddenProvider: Provider, + provider: Provider, + type: ResourceType + ) { + assumeTrue(defaultCost != overriddenCost) + assumeTrue(overriddenProvider != provider) + + val rules = TradingRules(defaultCost) + rules.setCost(type, overriddenProvider, overriddenCost) + + assertEquals(overriddenCost, rules.getCost(type, overriddenProvider)) + assertEquals(defaultCost, rules.getCost(type, provider)) + } + + @Theory + fun computeCost_zeroForNoResources(defaultCost: Int) { + val rules = TradingRules(defaultCost) + assertEquals(0, rules.computeCost(noTransactions())) + } + + @Theory + fun computeCost_defaultCostWhenNoOverride(defaultCost: Int, provider: Provider, type: ResourceType) { + val rules = TradingRules(defaultCost) + val transactions = createTransactions(provider, type) + assertEquals(defaultCost, rules.computeCost(transactions)) + } + + @Theory + fun computeCost_twiceDefaultFor2Resources(defaultCost: Int, provider: Provider, type: ResourceType) { + val rules = TradingRules(defaultCost) + val transactions = createTransactions(provider, type, type) + assertEquals(2 * defaultCost, rules.computeCost(transactions)) + } + + @Theory + fun computeCost_overriddenCost(defaultCost: Int, overriddenCost: Int, provider: Provider, type: ResourceType) { + val rules = TradingRules(defaultCost) + rules.setCost(type, provider, overriddenCost) + val transactions = createTransactions(provider, type) + assertEquals(overriddenCost, rules.computeCost(transactions)) + } + + @Theory + fun computeCost_defaultCostWhenOverrideOnOtherProviderOrType( + defaultCost: Int, + overriddenCost: Int, + overriddenProvider: Provider, + overriddenType: ResourceType, + provider: Provider, + type: ResourceType + ) { + assumeTrue(overriddenProvider != provider || overriddenType != type) + val rules = TradingRules(defaultCost) + rules.setCost(overriddenType, overriddenProvider, overriddenCost) + val transactions = createTransactions(provider, type) + assertEquals(defaultCost, rules.computeCost(transactions)) + } + + @Theory + fun computeCost_oneDefaultAndOneOverriddenType( + defaultCost: Int, + overriddenCost: Int, + overriddenType: ResourceType, + provider: Provider, + type: ResourceType + ) { + assumeTrue(overriddenType != type) + val rules = TradingRules(defaultCost) + rules.setCost(overriddenType, provider, overriddenCost) + val transactions = createTransactions(provider, overriddenType, type) + assertEquals(defaultCost + overriddenCost, rules.computeCost(transactions)) + } + + @Theory + fun computeCost_oneDefaultAndOneOverriddenProvider( + defaultCost: Int, + overriddenCost: Int, + overriddenProvider: Provider, + provider: Provider, + type: ResourceType + ) { + assumeTrue(overriddenProvider != provider) + val rules = TradingRules(defaultCost) + rules.setCost(type, overriddenProvider, overriddenCost) + + val boughtResources = createTransactions( + createTransaction(provider, type), + createTransaction(overriddenProvider, type) + ) + + assertEquals(defaultCost + overriddenCost, rules.computeCost(boughtResources)) + } + + companion object { + + @JvmStatic + @DataPoints + fun costs(): IntArray = intArrayOf(0, 1, 2) + + @JvmStatic + @DataPoints + fun providers(): Array = Provider.values() + + @JvmStatic + @DataPoints + fun resourceTypes(): Array = ResourceType.values() + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt new file mode 100644 index 00000000..7c1935ae --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/test/TestUtils.kt @@ -0,0 +1,142 @@ +package org.luxons.sevenwonders.engine.test + +import org.luxons.sevenwonders.engine.Player +import org.luxons.sevenwonders.engine.PlayerContext +import org.luxons.sevenwonders.engine.Settings +import org.luxons.sevenwonders.model.resources.CountedResource +import org.luxons.sevenwonders.model.CustomizableSettings +import org.luxons.sevenwonders.model.PlayerMove +import org.luxons.sevenwonders.engine.boards.Board +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.engine.boards.Science +import org.luxons.sevenwonders.engine.boards.ScienceType +import org.luxons.sevenwonders.engine.boards.Table +import org.luxons.sevenwonders.engine.cards.Card +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.engine.cards.Requirements +import org.luxons.sevenwonders.engine.effects.Effect +import org.luxons.sevenwonders.engine.effects.ScienceProgress +import org.luxons.sevenwonders.engine.moves.Move +import org.luxons.sevenwonders.model.MoveType +import org.luxons.sevenwonders.engine.moves.resolve +import org.luxons.sevenwonders.engine.resources.Production +import org.luxons.sevenwonders.model.resources.Provider +import org.luxons.sevenwonders.model.resources.ResourceTransaction +import org.luxons.sevenwonders.model.resources.ResourceTransactions +import org.luxons.sevenwonders.model.resources.ResourceType +import org.luxons.sevenwonders.model.resources.noTransactions +import org.luxons.sevenwonders.engine.resources.resourcesOf +import org.luxons.sevenwonders.engine.wonders.Wonder +import org.luxons.sevenwonders.engine.wonders.WonderStage + +private const val SEED: Long = 42 + +internal fun testCustomizableSettings(initialGold: Int = 0): CustomizableSettings = + CustomizableSettings(randomSeedForTests = SEED).copy(initialGold = initialGold) + +internal fun testSettings(nbPlayers: Int = 5, initialGold: Int = 0): Settings = + Settings(nbPlayers, testCustomizableSettings(initialGold)) + +internal fun testTable(nbPlayers: Int = 5): Table = testTable(testSettings(nbPlayers)) + +internal fun testTable(settings: Settings): Table = + Table(testBoards(settings.nbPlayers, settings)) + +private fun testBoards(count: Int, settings: Settings): List = List(count) { testBoard(settings) } + +internal fun testBoard( + initialResource: ResourceType = ResourceType.WOOD, + initialGold: Int = 0, + vararg production: ResourceType +): Board { + val settings = testSettings(initialGold = initialGold) + val board = testBoard(settings, initialResource) + board.production.addAll(fixedProduction(*production)) + return board +} + +private fun testBoard(settings: Settings, initialResource: ResourceType = ResourceType.WOOD): Board = + Board(testWonder(initialResource), 0, settings) + +internal fun testWonder(initialResource: ResourceType = ResourceType.WOOD): Wonder { + val stage1 = WonderStage(Requirements(), emptyList()) + val stage2 = WonderStage(Requirements(), emptyList()) + val stage3 = WonderStage(Requirements(), emptyList()) + return Wonder("Test Wonder ${initialResource.symbol}", initialResource, listOf(stage1, stage2, stage3), "") +} + +internal fun fixedProduction(vararg producedTypes: ResourceType): Production = + Production().apply { addAll(resourcesOf(*producedTypes)) } + +internal fun createTransactions(provider: Provider, vararg resources: ResourceType): ResourceTransactions = + createTransactions(createTransaction(provider, *resources)) + +internal fun createTransactions(vararg transactions: ResourceTransaction): ResourceTransactions = transactions.toSet() + +internal fun createTransaction(provider: Provider, vararg resources: ResourceType): ResourceTransaction = + ResourceTransaction(provider, + resources.map { CountedResource(1, it) }) + +internal fun createRequirements(vararg types: ResourceType): Requirements = Requirements(resources = resourcesOf(*types)) + +internal fun sampleCards(nbCards: Int, fromIndex: Int = 0, color: Color = Color.BLUE): List = + List(nbCards) { i -> testCard("Test Card ${fromIndex + i}", color) } + +internal fun createGuildCards(count: Int): List = List(count) { createGuildCard(it) } + +internal fun createGuildCard(num: Int, effect: Effect? = null): Card = + testCard("Test Guild $num", Color.PURPLE, effect = effect) + +internal fun testCard( + name: String = "Test Card", + color: Color = Color.BLUE, + requirements: Requirements = Requirements(), + effect: Effect? = null +): Card { + val effects = if (effect == null) emptyList() else listOf(effect) + return Card(name, color, requirements, effects, null, emptyList(), "path/to/card/image", + CardBack("image-III") + ) +} + +internal fun addCards(board: Board, nbCardsOfColor: Int, nbOtherCards: Int, color: Color) { + addCards(board, nbCardsOfColor, color) + addCards(board, nbOtherCards, getDifferentColorFrom(color)) +} + +internal fun addCards(board: Board, nbCards: Int, color: Color) { + sampleCards(nbCards, color = color).forEach { board.addCard(it) } +} + +internal fun getDifferentColorFrom(vararg colors: Color): Color = + Color.values().firstOrNull { it !in colors } ?: throw IllegalArgumentException("All colors are forbidden!") + +internal fun createScienceProgress(compasses: Int, wheels: Int, tablets: Int, jokers: Int): ScienceProgress = + ScienceProgress(createScience(compasses, wheels, tablets, jokers)) + +internal fun createScience(compasses: Int, wheels: Int, tablets: Int, jokers: Int): Science = Science().apply { + add(ScienceType.COMPASS, compasses) + add(ScienceType.WHEEL, wheels) + add(ScienceType.TABLET, tablets) + addJoker(jokers) +} + +internal fun playCardWithEffect(player: Player, color: Color, effect: Effect) { + val card = testCard(color = color, effect = effect) + player.board.addCard(card) + card.applyTo(player, noTransactions()) +} + +internal fun createMove(context: PlayerContext, card: Card, type: MoveType): Move = + type.resolve(PlayerMove(type, card.name), card, context) + +internal fun singleBoardPlayer(board: Board): Player = object : Player { + override val index = 0 + override val board = board + override fun getBoard(relativePosition: RelativeBoardPosition): Board = when (relativePosition) { + RelativeBoardPosition.LEFT -> throw RuntimeException("No LEFT board") + RelativeBoardPosition.SELF -> this.board + RelativeBoardPosition.RIGHT -> throw RuntimeException("No RIGHT board") + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/wonders/WonderTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/wonders/WonderTest.kt new file mode 100644 index 00000000..1857ebe8 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/engine/wonders/WonderTest.kt @@ -0,0 +1,34 @@ +package org.luxons.sevenwonders.engine.wonders + +import org.junit.Test +import org.luxons.sevenwonders.model.cards.CardBack +import org.luxons.sevenwonders.engine.test.testWonder +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class WonderTest { + + @Test + fun buildLevel_increasesNbBuiltStages() { + val wonder = testWonder() + assertEquals(0, wonder.nbBuiltStages) + wonder.placeCard(CardBack("img")) + assertEquals(1, wonder.nbBuiltStages) + wonder.placeCard(CardBack("img")) + assertEquals(2, wonder.nbBuiltStages) + wonder.placeCard(CardBack("img")) + assertEquals(3, wonder.nbBuiltStages) + } + + @Test + fun buildLevel_failsIfFull() { + val wonder = testWonder() + wonder.placeCard(CardBack("img")) + wonder.placeCard(CardBack("img")) + wonder.placeCard(CardBack("img")) + + assertFailsWith(IllegalStateException::class) { + wonder.placeCard(CardBack("img")) + } + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt deleted file mode 100644 index a3b5cf62..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -package org.luxons.sevenwonders.game - -import org.luxons.sevenwonders.game.api.Action -import org.luxons.sevenwonders.game.api.PlayedMove -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.api.PlayerTurnInfo -import org.luxons.sevenwonders.game.api.cards.HandCard -import org.luxons.sevenwonders.game.api.cards.TableCard -import org.luxons.sevenwonders.game.data.GameDefinition -import org.luxons.sevenwonders.game.data.LAST_AGE -import org.luxons.sevenwonders.game.api.MoveType -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.noTransactions -import org.luxons.sevenwonders.game.test.testCustomizableSettings -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class GameTest { - - @Test - fun testFullGame3Players() = playGame(nbPlayers = 3) - - @Test - fun testFullGame5Players() = playGame(nbPlayers = 6) - - @Test - fun testFullGame7Players() = playGame(nbPlayers = 7) - - private fun playGame(nbPlayers: Int) { - val game = createGame(nbPlayers) - - (1..LAST_AGE).forEach { playAge(nbPlayers, game, it) } - - game.computeScore() - } - - private fun playAge(nbPlayers: Int, game: Game, age: Int) { - repeat(6) { - playTurn(nbPlayers, game, age, 7 - it) - } - } - - private fun createGame(nbPlayers: Int): Game = - GameDefinition.load().initGame(0, testCustomizableSettings(), nbPlayers) - - private fun playTurn(nbPlayers: Int, game: Game, ageToCheck: Int, handSize: Int) { - val turnInfos = game.getCurrentTurnInfo() - assertEquals(nbPlayers, turnInfos.size) - turnInfos.forEach { - assertEquals(ageToCheck, it.currentAge) - assertEquals(handSize, it.hand.size) - } - - val moveExpectations = turnInfos.mapNotNull { it.firstAvailableMove() } - - moveExpectations.forEach { game.prepareMove(it.playerIndex, it.moveToSend) } - assertTrue(game.allPlayersPreparedTheirMove()) - - val table = game.playTurn() - - val expectedMoves = moveExpectations.map { it.expectedPlayedMove } - assertEquals(expectedMoves, table.lastPlayedMoves) - } - - private fun PlayerTurnInfo.firstAvailableMove(): MoveExpectation? = when (action) { - Action.PLAY, Action.PLAY_2, Action.PLAY_LAST -> createPlayCardMove(this) - Action.PICK_NEIGHBOR_GUILD -> createPickGuildMove(this) - Action.WAIT -> null - } - - private fun createPlayCardMove(turnInfo: PlayerTurnInfo): MoveExpectation { - val wonderBuildability = turnInfo.wonderBuildability - if (wonderBuildability.isBuildable) { - val transactions = wonderBuildability.cheapestTransactions.first() - return planMove(turnInfo, MoveType.UPGRADE_WONDER, turnInfo.hand.first(), transactions) - } - val playableCard = turnInfo.hand.firstOrNull { it.playability.isPlayable } - return if (playableCard != null) { - planMove(turnInfo, MoveType.PLAY, playableCard, playableCard.playability.cheapestTransactions.first()) - } else { - planMove(turnInfo, MoveType.DISCARD, turnInfo.hand.first(), - noTransactions() - ) - } - } - - private fun createPickGuildMove(turnInfo: PlayerTurnInfo): MoveExpectation { - val neighbourGuilds = turnInfo.neighbourGuildCards - - // the game should send action WAIT if no guild cards are available around - assertFalse(neighbourGuilds.isEmpty()) - return MoveExpectation( - turnInfo.playerIndex, - PlayerMove(MoveType.COPY_GUILD, neighbourGuilds.first().name), - PlayedMove(turnInfo.playerIndex, MoveType.COPY_GUILD, neighbourGuilds.first(), - noTransactions() - ) - ) - } - - data class MoveExpectation(val playerIndex: Int, val moveToSend: PlayerMove, val expectedPlayedMove: PlayedMove) - - private fun planMove( - turnInfo: PlayerTurnInfo, - moveType: MoveType, - card: HandCard, - transactions: ResourceTransactions - ): MoveExpectation = MoveExpectation( - turnInfo.playerIndex, - PlayerMove(moveType, card.name, transactions), - PlayedMove(turnInfo.playerIndex, moveType, card.toPlayedCard(), transactions) - ) - - private fun HandCard.toPlayedCard(): TableCard = - TableCard( - name, color, requirements, chainParent, chainChildren, image, back, true - ) -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt deleted file mode 100644 index ede165f7..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -package org.luxons.sevenwonders.game.api - -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.api.cards.TableCard -import org.luxons.sevenwonders.game.test.testCard -import kotlin.test.Test -import kotlin.test.assertEquals - -class BoardsKtTest { - - @Test - fun `toColumns on empty list should return no cols`() { - val cols = emptyList().toColumns() - assertEquals(emptyList>(), cols) - } - - @Test - fun `toColumns with single resource should return a single column`() { - val card = testCard(color = Color.BROWN).toTableCard() - val cols = listOf(card).toColumns() - assertEquals(listOf(listOf(card)), cols) - } - - @Test - fun `toColumns with single non-resource should return a single column`() { - val card = testCard(color = Color.BLUE).toTableCard() - val cols = listOf(card).toColumns() - assertEquals(listOf(listOf(card)), cols) - } - - @Test - fun `toColumns with two same-color cards should return a single column`() { - val card1 = testCard(color = Color.BLUE).toTableCard() - val card2 = testCard(color = Color.BLUE).toTableCard() - val cards = listOf(card1, card2) - val cols = cards.toColumns() - assertEquals(listOf(cards), cols) - } - - @Test - fun `toColumns with two resource cards should return a single column`() { - val card1 = testCard(color = Color.BROWN).toTableCard() - val card2 = testCard(color = Color.GREY).toTableCard() - val cards = listOf(card1, card2) - val cols = cards.toColumns() - assertEquals(listOf(cards), cols) - } - - @Test - fun `toColumns with 2 different non-resource cards should return 2 columns`() { - val card1 = testCard(color = Color.BLUE).toTableCard() - val card2 = testCard(color = Color.GREEN).toTableCard() - val cards = listOf(card1, card2) - val cols = cards.toColumns() - assertEquals(listOf(listOf(card1), listOf(card2)), cols) - } - - @Test - fun `toColumns with 1 res and 1 non-res card should return 2 columns`() { - val card1 = testCard(color = Color.BROWN).toTableCard() - val card2 = testCard(color = Color.GREEN).toTableCard() - val cards = listOf(card1, card2) - val cols = cards.toColumns() - assertEquals(listOf(listOf(card1), listOf(card2)), cols) - } - - @Test - fun `toColumns should return 1 col for res cards and 1 for each other color`() { - val res1 = testCard(color = Color.BROWN).toTableCard() - val res2 = testCard(color = Color.BROWN).toTableCard() - val res3 = testCard(color = Color.GREY).toTableCard() - val blue1 = testCard(color = Color.BLUE).toTableCard() - val green1 = testCard(color = Color.GREEN).toTableCard() - val green2 = testCard(color = Color.GREEN).toTableCard() - val cards = listOf(res1, green1, green2, res2, blue1, res3) - val cols = cards.toColumns() - val expectedCols = listOf( - listOf(res1, res2, res3), - listOf(blue1), - listOf(green1, green2) - ) - assertEquals(expectedCols, cols) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt deleted file mode 100644 index 65bd2b1b..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package org.luxons.sevenwonders.game.api - -import org.junit.Assume.assumeTrue -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.test.createGuildCards -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class TableTest { - - @Theory - fun getBoard_wrapLeft(nbPlayers: Int) { - assumeTrue(nbPlayers >= 2) - val table = testTable(nbPlayers) - val last = nbPlayers - 1 - assertEquals(table.getBoard(last), table.getBoard(0, RelativeBoardPosition.LEFT)) - assertEquals(table.getBoard(0), table.getBoard(0, RelativeBoardPosition.SELF)) - assertEquals(table.getBoard(1), table.getBoard(0, RelativeBoardPosition.RIGHT)) - } - - @Theory - fun getBoard_wrapRight(nbPlayers: Int) { - assumeTrue(nbPlayers >= 2) - val table = testTable(nbPlayers) - val last = nbPlayers - 1 - assertEquals(table.getBoard(last - 1), table.getBoard(last, RelativeBoardPosition.LEFT)) - assertEquals(table.getBoard(last), table.getBoard(last, RelativeBoardPosition.SELF)) - assertEquals(table.getBoard(0), table.getBoard(last, RelativeBoardPosition.RIGHT)) - } - - @Theory - fun getBoard_noWrap(nbPlayers: Int) { - assumeTrue(nbPlayers >= 3) - val table = testTable(nbPlayers) - assertEquals(table.getBoard(0), table.getBoard(1, RelativeBoardPosition.LEFT)) - assertEquals(table.getBoard(1), table.getBoard(1, RelativeBoardPosition.SELF)) - assertEquals(table.getBoard(2), table.getBoard(1, RelativeBoardPosition.RIGHT)) - } - - @Theory - fun getNeighbourGuildCards(nbPlayers: Int) { - assumeTrue(nbPlayers >= 4) - val table = testTable(nbPlayers) - val guildCards = createGuildCards(4) - table.getBoard(0).addCard(guildCards[0]) - table.getBoard(0).addCard(guildCards[1]) - table.getBoard(1).addCard(guildCards[2]) - table.getBoard(2).addCard(guildCards[3]) - - val neighbourCards0 = table.getNeighbourGuildCards(0) - assertEquals(listOf(guildCards[2]), neighbourCards0) - - val neighbourCards1 = table.getNeighbourGuildCards(1) - assertEquals(guildCards - guildCards[2], neighbourCards1) - - val neighbourCards2 = table.getNeighbourGuildCards(2) - assertEquals(listOf(guildCards[2]), neighbourCards2) - } - - companion object { - - @JvmStatic - @DataPoints - fun nbPlayers(): IntArray = intArrayOf(2, 3, 4, 5, 6, 7, 8) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt deleted file mode 100644 index 669934aa..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt +++ /dev/null @@ -1,211 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import junit.framework.TestCase.assertEquals -import org.junit.Assume.assumeTrue -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.FromDataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.boards.Board.InsufficientFundsException -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.effects.RawPointsIncrease -import org.luxons.sevenwonders.game.effects.SpecialAbility -import org.luxons.sevenwonders.game.effects.SpecialAbilityActivation -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.resourcesOf -import org.luxons.sevenwonders.game.score.ScoreCategory -import org.luxons.sevenwonders.game.test.addCards -import org.luxons.sevenwonders.game.test.getDifferentColorFrom -import org.luxons.sevenwonders.game.test.playCardWithEffect -import org.luxons.sevenwonders.game.test.singleBoardPlayer -import org.luxons.sevenwonders.game.test.testBoard -import org.luxons.sevenwonders.game.test.testCard -import org.luxons.sevenwonders.game.test.testSettings -import org.luxons.sevenwonders.game.test.testWonder -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertSame -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class BoardTest { - - @Theory - fun initialGold_respectsSettings(@FromDataPoints("gold") goldAmountInSettings: Int) { - val settings = testSettings(initialGold = goldAmountInSettings) - val board = Board(testWonder(), 0, settings) - assertEquals(goldAmountInSettings, board.gold) - } - - @Theory - fun initialProduction_containsInitialResource(type: ResourceType) { - val board = Board(testWonder(type), 0, testSettings()) - val resources = resourcesOf(type) - assertTrue(board.production.contains(resources)) - assertTrue(board.publicProduction.contains(resources)) - } - - @Theory - fun removeGold_successfulWhenNotTooMuch( - @FromDataPoints("gold") initialGold: Int, - @FromDataPoints("gold") goldRemoved: Int - ) { - assumeTrue(goldRemoved >= 0) - assumeTrue(initialGold >= goldRemoved) - - val board = Board(testWonder(), 0, testSettings(initialGold = initialGold)) - board.removeGold(goldRemoved) - assertEquals(initialGold - goldRemoved, board.gold) - } - - @Theory - fun removeGold_failsWhenTooMuch( - @FromDataPoints("gold") initialGold: Int, - @FromDataPoints("gold") goldRemoved: Int - ) { - assumeTrue(goldRemoved >= 0) - assumeTrue(initialGold < goldRemoved) - - assertFailsWith { - val board = Board(testWonder(), 0, testSettings(initialGold = initialGold)) - board.removeGold(goldRemoved) - } - } - - @Theory - fun getNbCardsOfColor_properCount_singleColor( - type: ResourceType, - @FromDataPoints("nbCards") nbCards: Int, - @FromDataPoints("nbCards") nbOtherCards: Int, - color: Color - ) { - val board = testBoard(initialResource = type) - addCards(board, nbCards, nbOtherCards, color) - assertEquals(nbCards, board.getNbCardsOfColor(listOf(color))) - } - - @Theory - fun getNbCardsOfColor_properCount_multiColors( - type: ResourceType, - @FromDataPoints("nbCards") nbCards1: Int, - @FromDataPoints("nbCards") nbCards2: Int, - @FromDataPoints("nbCards") nbOtherCards: Int, - color1: Color, - color2: Color - ) { - val board = testBoard(initialResource = type) - addCards(board, nbCards1, color1) - addCards(board, nbCards2, color2) - addCards(board, nbOtherCards, getDifferentColorFrom(color1, color2)) - assertEquals(nbCards1 + nbCards2, board.getNbCardsOfColor(listOf(color1, color2))) - } - - @Test - fun setCopiedGuild_succeedsOnPurpleCard() { - val board = testBoard() - val card = testCard(color = Color.PURPLE) - - board.copiedGuild = card - assertSame(card, board.copiedGuild) - } - - @Theory - fun setCopiedGuild_failsOnNonPurpleCard(color: Color) { - assumeTrue(color !== Color.PURPLE) - val board = testBoard() - val card = testCard(color = color) - - assertFailsWith { - board.copiedGuild = card - } - } - - @Theory - fun hasSpecial(applied: SpecialAbility, tested: SpecialAbility) { - val board = testBoard() - val special = SpecialAbilityActivation(applied) - - special.applyTo(singleBoardPlayer(board)) - - assertEquals(applied === tested, board.hasSpecial(tested)) - } - - @Test - fun canPlayFreeCard() { - val board = testBoard() - val special = SpecialAbilityActivation(SpecialAbility.ONE_FREE_PER_AGE) - - special.applyTo(singleBoardPlayer(board)) - - assertTrue(board.canPlayFreeCard(0)) - assertTrue(board.canPlayFreeCard(1)) - assertTrue(board.canPlayFreeCard(2)) - - board.consumeFreeCard(0) - - assertFalse(board.canPlayFreeCard(0)) - assertTrue(board.canPlayFreeCard(1)) - assertTrue(board.canPlayFreeCard(2)) - - board.consumeFreeCard(1) - - assertFalse(board.canPlayFreeCard(0)) - assertFalse(board.canPlayFreeCard(1)) - assertTrue(board.canPlayFreeCard(2)) - - board.consumeFreeCard(2) - - assertFalse(board.canPlayFreeCard(0)) - assertFalse(board.canPlayFreeCard(1)) - assertFalse(board.canPlayFreeCard(2)) - } - - @Theory - fun computePoints_gold(@FromDataPoints("gold") gold: Int) { - assumeTrue(gold >= 0) - val board = testBoard(initialGold = gold) - - val score = board.computeScore(singleBoardPlayer(board)) - assertEquals(gold / 3, score.pointsByCategory[ScoreCategory.GOLD]) - assertEquals(gold / 3, score.totalPoints) - } - - @Theory - fun computePoints_(@FromDataPoints("gold") gold: Int) { - assumeTrue(gold >= 0) - val board = testBoard(initialGold = gold) - - val effect = RawPointsIncrease(5) - playCardWithEffect(singleBoardPlayer(board), Color.BLUE, effect) - - val score = board.computeScore(singleBoardPlayer(board)) - assertEquals(gold / 3, score.pointsByCategory[ScoreCategory.GOLD]) - assertEquals(5, score.pointsByCategory[ScoreCategory.CIVIL]) - assertEquals(5 + gold / 3, score.totalPoints) - } - - companion object { - - @JvmStatic - @DataPoints("gold") - fun goldAmounts(): IntArray = intArrayOf(-3, -1, 0, 1, 2, 3) - - @JvmStatic - @DataPoints("nbCards") - fun nbCards(): IntArray = intArrayOf(0, 1, 2) - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - - @JvmStatic - @DataPoints - fun colors(): Array = Color.values() - - @JvmStatic - @DataPoints - fun specialAbilities(): Array = SpecialAbility.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt deleted file mode 100644 index 248d43dd..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.FromDataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.boards.Military.UnknownAgeException -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -@RunWith(Theories::class) -class MilitaryTest { - - @Theory - fun victory_addsCorrectPoints( - @FromDataPoints("ages") age: Int, - @FromDataPoints("points") nbPointsPerVictory: Int - ) { - val military = createMilitary(age, nbPointsPerVictory, 0) - val initialPoints = military.totalPoints - - military.victory(age) - assertEquals(initialPoints + nbPointsPerVictory, military.totalPoints) - } - - @Theory - fun victory_failsIfUnknownAge(@FromDataPoints("points") nbPointsPerVictory: Int) { - val military = createMilitary(0, nbPointsPerVictory, 0) - assertFailsWith { - military.victory(1) - } - } - - @Theory - fun defeat_removesCorrectPoints(@FromDataPoints("points") nbPointsLostPerDefeat: Int) { - val military = createMilitary(0, 0, nbPointsLostPerDefeat) - val initialPoints = military.totalPoints - - military.defeat() - assertEquals(initialPoints - nbPointsLostPerDefeat, military.totalPoints) - } - - companion object { - - @JvmStatic - @DataPoints("points") - fun points(): IntArray = intArrayOf(0, 1, 3, 5) - - @JvmStatic - @DataPoints("ages") - fun ages(): IntArray = intArrayOf(1, 2, 3) - - private fun createMilitary(age: Int, nbPointsPerVictory: Int, nbPointsPerDefeat: Int): Military = - Military(nbPointsPerDefeat, mapOf(age to nbPointsPerVictory)) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt deleted file mode 100644 index 75743b86..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.junit.Assume.assumeTrue -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class RelativeBoardPositionTest { - - @Theory - fun getIndexFrom_wrapLeft(nbPlayers: Int) { - assumeTrue(nbPlayers >= 2) - val last = nbPlayers - 1 - assertEquals(last, RelativeBoardPosition.LEFT.getIndexFrom(0, nbPlayers)) - assertEquals(0, RelativeBoardPosition.SELF.getIndexFrom(0, nbPlayers)) - assertEquals(1, RelativeBoardPosition.RIGHT.getIndexFrom(0, nbPlayers)) - } - - @Theory - fun getIndexFrom_wrapRight(nbPlayers: Int) { - assumeTrue(nbPlayers >= 2) - val last = nbPlayers - 1 - assertEquals(last - 1, RelativeBoardPosition.LEFT.getIndexFrom(last, nbPlayers)) - assertEquals(last, RelativeBoardPosition.SELF.getIndexFrom(last, nbPlayers)) - assertEquals(0, RelativeBoardPosition.RIGHT.getIndexFrom(last, nbPlayers)) - } - - @Theory - fun getIndexFrom_noWrap(nbPlayers: Int) { - assumeTrue(nbPlayers >= 3) - assertEquals(0, RelativeBoardPosition.LEFT.getIndexFrom(1, nbPlayers)) - assertEquals(1, RelativeBoardPosition.SELF.getIndexFrom(1, nbPlayers)) - assertEquals(2, RelativeBoardPosition.RIGHT.getIndexFrom(1, nbPlayers)) - } - - companion object { - - @JvmStatic - @DataPoints - fun nbPlayers(): IntArray = intArrayOf(1, 2, 3, 5, 7, 9) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt deleted file mode 100644 index 80d6773d..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt +++ /dev/null @@ -1,114 +0,0 @@ -package org.luxons.sevenwonders.game.boards - -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.test.createScience -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class ScienceTest { - - @Test - fun addAll_empty() { - val initial = createScience(3, 4, 5, 1) - val empty = Science() - initial.addAll(empty) - assertEquals(3, initial.getQuantity(ScienceType.COMPASS)) - assertEquals(4, initial.getQuantity(ScienceType.WHEEL)) - assertEquals(5, initial.getQuantity(ScienceType.TABLET)) - assertEquals(1, initial.jokers) - } - - @Test - fun addAll_noJoker() { - val initial = createScience(3, 4, 5, 1) - val other = createScience(1, 2, 3, 0) - initial.addAll(other) - assertEquals(4, initial.getQuantity(ScienceType.COMPASS)) - assertEquals(6, initial.getQuantity(ScienceType.WHEEL)) - assertEquals(8, initial.getQuantity(ScienceType.TABLET)) - assertEquals(1, initial.jokers) - } - - @Test - fun addAll_withJokers() { - val initial = createScience(3, 4, 5, 1) - val other = createScience(0, 0, 0, 3) - initial.addAll(other) - assertEquals(3, initial.getQuantity(ScienceType.COMPASS)) - assertEquals(4, initial.getQuantity(ScienceType.WHEEL)) - assertEquals(5, initial.getQuantity(ScienceType.TABLET)) - assertEquals(4, initial.jokers) - } - - @Test - fun addAll_mixed() { - val initial = createScience(3, 4, 5, 1) - val other = createScience(1, 2, 3, 4) - initial.addAll(other) - assertEquals(4, initial.getQuantity(ScienceType.COMPASS)) - assertEquals(6, initial.getQuantity(ScienceType.WHEEL)) - assertEquals(8, initial.getQuantity(ScienceType.TABLET)) - assertEquals(5, initial.jokers) - } - - @Theory - fun computePoints_compassesOnly_noJoker(compasses: Int) { - val science = createScience(compasses, 0, 0, 0) - assertEquals(compasses * compasses, science.computePoints()) - } - - @Theory - fun computePoints_wheelsOnly_noJoker(wheels: Int) { - val science = createScience(0, wheels, 0, 0) - assertEquals(wheels * wheels, science.computePoints()) - } - - @Theory - fun computePoints_tabletsOnly_noJoker(tablets: Int) { - val science = createScience(0, 0, tablets, 0) - assertEquals(tablets * tablets, science.computePoints()) - } - - @Theory - fun computePoints_allSameNoJoker(eachSymbol: Int) { - val science = createScience(eachSymbol, eachSymbol, eachSymbol, 0) - assertEquals(3 * eachSymbol * eachSymbol + 7 * eachSymbol, science.computePoints()) - } - - @Theory - fun computePoints_expectation(expectation: IntArray) { - val science = createScience(expectation[0], expectation[1], expectation[2], expectation[3]) - assertEquals(expectation[4], science.computePoints()) - } - - companion object { - - @JvmStatic - @DataPoints - fun quantitiesWithExpectedPoints(): Array = arrayOf( - // compasses, wheels, tablets, jokers, expected points - intArrayOf(0, 0, 0, 1, 1), - intArrayOf(0, 0, 1, 0, 1), - intArrayOf(0, 0, 0, 2, 4), - intArrayOf(0, 0, 1, 1, 4), - intArrayOf(0, 0, 2, 0, 4), - intArrayOf(0, 0, 0, 3, 10), - intArrayOf(0, 0, 1, 2, 10), - intArrayOf(0, 1, 1, 1, 10), - intArrayOf(1, 1, 1, 0, 10), - intArrayOf(0, 0, 0, 4, 16), - intArrayOf(0, 0, 1, 3, 16), - intArrayOf(0, 0, 2, 2, 16), - intArrayOf(0, 0, 3, 1, 16), - intArrayOf(0, 0, 4, 0, 16) - ) - - @JvmStatic - @DataPoints - fun quantitiesDataPoints(): IntArray = intArrayOf(0, 1, 3, 5, 8) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt deleted file mode 100644 index 967735b8..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Test -import org.luxons.sevenwonders.game.api.cards.CardBack -import kotlin.test.assertEquals - -class CardBackTest { - - @Test - fun initializedWithImage() { - val imagePath = "whateverimage.png" - val (image) = CardBack(imagePath) - assertEquals(imagePath, image) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt deleted file mode 100644 index 56fc9725..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Test -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.noTransactions -import org.luxons.sevenwonders.game.test.testCard -import org.luxons.sevenwonders.game.test.testSettings -import org.luxons.sevenwonders.game.wonders.Wonder -import kotlin.test.assertEquals - -class CardTest { - - @Test - fun playCardCostingMoney() { - val initialGold = 3 - val price = 1 - val settings = testSettings(3, initialGold) - - val boards = listOf( - Board(Wonder("TestWonder", ResourceType.WOOD, emptyList(), ""), 0, settings), - Board(Wonder("TestWonder", ResourceType.STONE, emptyList(), ""), 1, settings), - Board(Wonder("TestWonder", ResourceType.PAPYRUS, emptyList(), ""), 2, settings) - ) - val table = Table(boards) - - val treeFarmRequirements = Requirements(gold = price) - val treeFarmProduction = Production().apply { addChoice(ResourceType.WOOD, ResourceType.CLAY) } - val treeFarmEffect = ProductionIncrease(treeFarmProduction, false) - val treeFarmCard = testCard("Tree Farm", Color.BROWN, treeFarmRequirements, treeFarmEffect) - - treeFarmCard.applyTo(SimplePlayer(0, table), noTransactions()) - - assertEquals(initialGold - price, table.getBoard(0).gold) - assertEquals(initialGold, table.getBoard(1).gold) - assertEquals(initialGold, table.getBoard(2).gold) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt deleted file mode 100644 index f6c45720..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt +++ /dev/null @@ -1,104 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Assume.assumeTrue -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.cards.Decks.CardNotFoundException -import org.luxons.sevenwonders.game.test.sampleCards -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class DecksTest { - - @Test - fun getCard_failsOnEmptyNameWhenDeckIsEmpty() { - val decks = createDecks(0, 0) - assertFailsWith { - decks.getCard(0, "") - } - } - - @Test - fun getCard_failsWhenDeckIsEmpty() { - val decks = createDecks(0, 0) - assertFailsWith { - decks.getCard(0, "Any name") - } - } - - @Test - fun getCard_failsWhenCardIsNotFound() { - val decks = createDecks(3, 20) - assertFailsWith { - decks.getCard(1, "Unknown name") - } - } - - @Test - fun getCard_succeedsWhenCardIsFound() { - val decks = createDecks(3, 20) - val (name) = decks.getCard(1, "Test Card 3") - assertEquals("Test Card 3", name) - } - - @Test - fun deal_failsOnZeroPlayers() { - val decks = createDecks(3, 20) - assertFailsWith { - decks.deal(1, 0) - } - } - - @Test - fun deal_failsOnMissingAge() { - val decks = createDecks(2, 0) - assertFailsWith { - decks.deal(4, 10) - } - } - - @Theory - fun deal_failsWhenTooFewPlayers(nbPlayers: Int, nbCards: Int) { - assumeTrue(nbCards % nbPlayers != 0) - val decks = createDecks(1, nbCards) - assertFailsWith { - decks.deal(1, nbPlayers) - } - } - - @Theory - fun deal_succeedsOnZeroCards(nbPlayers: Int) { - val decks = createDecks(1, 0) - val hands = decks.deal(1, nbPlayers) - repeat(nbPlayers) { i -> - assertNotNull(hands[i]) - assertTrue(hands[i].isEmpty()) - } - } - - @Theory - fun deal_evenDistribution(nbPlayers: Int, nbCardsPerPlayer: Int) { - val nbCardsPerAge = nbPlayers * nbCardsPerPlayer - val decks = createDecks(1, nbCardsPerAge) - val hands = decks.deal(1, nbPlayers) - repeat(nbPlayers) { i -> - assertEquals(nbCardsPerPlayer, hands[i].size) - } - } - - companion object { - - @JvmStatic - @DataPoints - fun dataPoints(): IntArray = intArrayOf(1, 2, 3, 5, 10) - - private fun createDecks(nbAges: Int, nbCardsPerAge: Int): Decks = - Decks((1..nbAges).map { it to sampleCards(nbCardsPerAge) }.toMap()) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt deleted file mode 100644 index 2fbaf8ed..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Test -import org.luxons.sevenwonders.game.api.cards.HandRotationDirection -import kotlin.test.assertEquals - -class HandRotationDirectionTest { - - @Test - fun testAgesDirections() { - assertEquals(HandRotationDirection.LEFT, HandRotationDirection.forAge(1)) - assertEquals(HandRotationDirection.RIGHT, HandRotationDirection.forAge(2)) - assertEquals(HandRotationDirection.LEFT, HandRotationDirection.forAge(3)) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt deleted file mode 100644 index 4c9f258c..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt +++ /dev/null @@ -1,128 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Assume.assumeTrue -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.FromDataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.cards.HandRotationDirection -import org.luxons.sevenwonders.game.test.sampleCards -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class HandsTest { - - @Test - fun get_failsOnMissingPlayer() { - val hands = createHands(4, 7) - - assertFailsWith { hands[5] } - } - - @Test - fun get_retrievesCorrectCards() { - val hand0 = sampleCards(5, 0) - val hand1 = sampleCards(10, 5) - val hands = Hands(listOf(hand0, hand1)) - assertEquals(hand0, hands[0]) - assertEquals(hand1, hands[1]) - } - - @Theory - fun isEmpty_falseWhenAtLeast1_allSame( - @FromDataPoints("nbPlayers") nbPlayers: Int, - @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int - ) { - assumeTrue(nbCardsPerPlayer >= 1) - val hands = createHands(nbPlayers, nbCardsPerPlayer) - assertFalse(hands.isEmpty) - } - - @Theory - fun isEmpty_trueWhenAllEmpty(@FromDataPoints("nbPlayers") nbPlayers: Int) { - val hands = createHands(nbPlayers, 0) - assertTrue(hands.isEmpty) - } - - @Theory - fun maxOneCardRemains_falseWhenAtLeast2_allSame( - @FromDataPoints("nbPlayers") nbPlayers: Int, - @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int - ) { - assumeTrue(nbCardsPerPlayer >= 2) - val hands = createHands(nbPlayers, nbCardsPerPlayer) - assertFalse(hands.maxOneCardRemains()) - } - - @Theory - fun maxOneCardRemains_trueWhenAtMost1_allSame( - @FromDataPoints("nbPlayers") nbPlayers: Int, - @FromDataPoints("nbCardsPerPlayer") nbCardsPerPlayer: Int - ) { - assumeTrue(nbCardsPerPlayer <= 1) - val hands = createHands(nbPlayers, nbCardsPerPlayer) - assertTrue(hands.maxOneCardRemains()) - } - - @Theory - fun maxOneCardRemains_trueWhenAtMost1_someZero(@FromDataPoints("nbPlayers") nbPlayers: Int) { - val hands = createHands(nbPlayers, 1) - assertTrue(hands.maxOneCardRemains()) - } - - @Test - fun rotate_movesOfCorrectOffset_right() { - val hands = createHands(3, 7) - val rotated = hands.rotate(HandRotationDirection.RIGHT) - assertEquals(rotated[1], hands[0]) - assertEquals(rotated[2], hands[1]) - assertEquals(rotated[0], hands[2]) - } - - @Test - fun rotate_movesOfCorrectOffset_left() { - val hands = createHands(3, 7) - val rotated = hands.rotate(HandRotationDirection.LEFT) - assertEquals(rotated[2], hands[0]) - assertEquals(rotated[0], hands[1]) - assertEquals(rotated[1], hands[2]) - } - - @Test - fun createHand_containsAllCards() { - val hand0 = sampleCards(5, 0) - val hand1 = sampleCards(10, 5) - val hands = Hands(listOf(hand0, hand1)) - - val table = testTable(2) - val hand = hands.createHand(SimplePlayer(0, table)) - - assertEquals(hand0.map { it.name }, hand.map { it.name }) - } - - companion object { - - @JvmStatic - @DataPoints("nbCardsPerPlayer") - fun nbCardsPerPlayer(): IntArray { - return intArrayOf(0, 1, 2, 3, 4, 5, 6, 7) - } - - @JvmStatic - @DataPoints("nbPlayers") - fun nbPlayers(): IntArray { - return intArrayOf(3, 4, 5, 6, 7) - } - - private fun createHands(nbPlayers: Int, nbCardsPerPlayer: Int): Hands { - return sampleCards(nbCardsPerPlayer * nbPlayers, 0).deal(nbPlayers) - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt deleted file mode 100644 index 4bed43b3..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt +++ /dev/null @@ -1,168 +0,0 @@ -package org.luxons.sevenwonders.game.cards - -import org.junit.Assume.assumeTrue -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.emptyResources -import org.luxons.sevenwonders.game.api.resources.noTransactions -import org.luxons.sevenwonders.game.test.createRequirements -import org.luxons.sevenwonders.game.test.createTransactions -import org.luxons.sevenwonders.game.test.singleBoardPlayer -import org.luxons.sevenwonders.game.test.testBoard -import kotlin.test.assertEquals -import kotlin.test.assertSame -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class RequirementsTest { - - @Test - fun getResources_emptyAfterInit() { - val (_, resources) = Requirements() - assertTrue(resources.isEmpty()) - } - - @Test - fun setResources_success() { - val resources = emptyResources() - val requirements = Requirements(0, resources) - assertSame(resources, requirements.resources) - } - - @Theory - fun goldRequirement(boardGold: Int, requiredGold: Int) { - val requirements = Requirements(requiredGold) - - val board = testBoard(ResourceType.CLAY, boardGold) - val player = singleBoardPlayer(board) - - assertEquals(boardGold >= requiredGold, requirements.areMetWithHelpBy(board, - noTransactions() - )) - - val satisfaction = requirements.assess(player) - if (boardGold >= requiredGold) { - if (requiredGold == 0) { - assertEquals(RequirementsSatisfaction.noRequirements(), satisfaction) - } else { - assertEquals(RequirementsSatisfaction.enoughGold(requiredGold), satisfaction) - } - } else { - assertEquals(RequirementsSatisfaction.missingRequiredGold(requiredGold), satisfaction) - } - } - - @Theory - fun resourceRequirement_initialResource(initialResource: ResourceType, requiredResource: ResourceType) { - val requirements = createRequirements(requiredResource) - - val board = testBoard(initialResource, 0) - val player = singleBoardPlayer(board) - - assertEquals(initialResource == requiredResource, requirements.areMetWithHelpBy(board, - noTransactions() - )) - - if (initialResource == requiredResource) { - val satisfaction = requirements.assess(player) - assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction) - } - } - - @Theory - fun resourceRequirement_ownProduction( - initialResource: ResourceType, - producedResource: ResourceType, - requiredResource: ResourceType - ) { - assumeTrue(initialResource != requiredResource) - - val requirements = createRequirements(requiredResource) - - val board = testBoard(initialResource, 0) - board.production.addFixedResource(producedResource, 1) - val player = singleBoardPlayer(board) - - assertEquals(producedResource == requiredResource, requirements.areMetWithHelpBy(board, - noTransactions() - )) - - if (producedResource == requiredResource) { - val satisfaction = requirements.assess(player) - assertEquals(RequirementsSatisfaction.enoughResources(), satisfaction) - } - } - - @Theory - fun resourceRequirement_boughtResource( - initialResource: ResourceType, - boughtResource: ResourceType, - requiredResource: ResourceType - ) { - assumeTrue(initialResource != requiredResource) - - val requirements = createRequirements(requiredResource) - - val board = testBoard(initialResource, 2) - val neighbourBoard = testBoard(initialResource, 0) - neighbourBoard.publicProduction.addFixedResource(boughtResource, 1) - val table = Table(listOf(board, neighbourBoard)) - val player = SimplePlayer(0, table) - - val resources = createTransactions(Provider.RIGHT_PLAYER, boughtResource) - - val neighbourHasResource = boughtResource == requiredResource - assertEquals(neighbourHasResource, requirements.areMetWithHelpBy(board, resources)) - - val satisfaction = requirements.assess(player) - if (neighbourHasResource) { - val transactions = setOf( - createTransactions(Provider.LEFT_PLAYER, requiredResource), - createTransactions(Provider.RIGHT_PLAYER, requiredResource) - ) - assertEquals(RequirementsSatisfaction.metWithHelp(2, transactions), satisfaction) - } else { - assertEquals(RequirementsSatisfaction.unavailableResources(), satisfaction) - } - } - - @Theory - fun pay_boughtResource(initialResource: ResourceType, requiredResource: ResourceType) { - assumeTrue(initialResource != requiredResource) - - val requirements = createRequirements(requiredResource) - - val board = testBoard(initialResource, 2) - val neighbourBoard = testBoard(requiredResource, 0) - val table = Table(listOf(board, neighbourBoard)) - val player = SimplePlayer(0, table) - - val transactions = createTransactions(Provider.RIGHT_PLAYER, requiredResource) - - assertTrue(requirements.areMetWithHelpBy(board, transactions)) - assertTrue(requirements.assess(player).satisfied) - - requirements.pay(player, transactions) - - assertEquals(0, board.gold) - assertEquals(2, neighbourBoard.gold) - } - - companion object { - - @JvmStatic - @DataPoints - fun goldAmounts(): IntArray = intArrayOf(0, 1, 2, 5) - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt deleted file mode 100644 index 4317a933..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.luxons.sevenwonders.game.data - -import org.junit.Test -import org.luxons.sevenwonders.game.api.CustomizableSettings -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class GameDefinitionTest { - - @Test - fun successfulGameInit() { - val gameDefinition = GameDefinition.load() - assertNotNull(gameDefinition) - assertEquals(3, gameDefinition.minPlayers) - assertEquals(7, gameDefinition.maxPlayers) - - val game = gameDefinition.initGame(0, CustomizableSettings(), 7) - assertNotNull(game) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt deleted file mode 100644 index 9b44fad2..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt +++ /dev/null @@ -1,147 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.junit.Before -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.effects.GoldIncrease -import org.luxons.sevenwonders.game.effects.MilitaryReinforcements -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.effects.RawPointsIncrease -import org.luxons.sevenwonders.game.resources.Production -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -@RunWith(Theories::class) -class NumericEffectSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - gson = GsonBuilder().registerTypeAdapter(MilitaryReinforcements::class.java, NumericEffectSerializer()) - .registerTypeAdapter(RawPointsIncrease::class.java, NumericEffectSerializer()) - .registerTypeAdapter(GoldIncrease::class.java, NumericEffectSerializer()) - // ProductionIncrease is not a numeric effect, it is here for negative testing purpose - .registerTypeAdapter(ProductionIncrease::class.java, NumericEffectSerializer()).create() - } - - @Test - fun serialize_militaryReinforcements_null() { - assertEquals("null", gson.toJson(null, MilitaryReinforcements::class.java)) - } - - @Test - fun serialize_rawPointsIncrease_null() { - assertEquals("null", gson.toJson(null, RawPointsIncrease::class.java)) - } - - @Test - fun serialize_goldIncrease_null() { - assertEquals("null", gson.toJson(null, GoldIncrease::class.java)) - } - - @Test - fun serialize_failOnUnknownType() { - assertFailsWith { - gson.toJson(ProductionIncrease(Production(), false)) - } - } - - @Theory - fun serialize_militaryReinforcements(count: Int) { - val reinforcements = MilitaryReinforcements(count) - assertEquals(count.toString(), gson.toJson(reinforcements)) - } - - @Theory - fun serialize_rawPointsIncrease(count: Int) { - val points = RawPointsIncrease(count) - assertEquals(count.toString(), gson.toJson(points)) - } - - @Theory - fun serialize_goldIncrease(count: Int) { - val goldIncrease = GoldIncrease(count) - assertEquals(count.toString(), gson.toJson(goldIncrease)) - } - - @Theory - fun deserialize_militaryReinforcements(count: Int) { - val reinforcements = MilitaryReinforcements(count) - assertEquals(reinforcements, gson.fromJson(count.toString())) - } - - @Theory - fun deserialize_rawPointsIncrease(count: Int) { - val points = RawPointsIncrease(count) - assertEquals(points, gson.fromJson(count.toString())) - } - - @Theory - fun deserialize_goldIncrease(count: Int) { - val goldIncrease = GoldIncrease(count) - assertEquals(goldIncrease, gson.fromJson(count.toString())) - } - - @Test - fun deserialize_militaryReinforcements_failOnEmptyString() { - assertFailsWith { - gson.fromJson("\"\"") - } - } - - @Test - fun deserialize_rawPointsIncrease_failOnEmptyString() { - assertFailsWith { - gson.fromJson("\"\"") - } - } - - @Test - fun deserialize_goldIncrease_failOnEmptyString() { - assertFailsWith { - gson.fromJson("\"\"") - } - } - - @Test - fun deserialize_militaryReinforcements_failOnNonNumericString() { - assertFailsWith { - gson.fromJson("\"abc\"") - } - } - - @Test - fun deserialize_rawPointsIncrease_failOnNonNumericString() { - assertFailsWith { - gson.fromJson("\"abc\"") - } - } - - @Test - fun deserialize_goldIncrease_failOnNonNumericString() { - assertFailsWith { - gson.fromJson("\"abc\"") - } - } - - @Test - fun deserialize_failOnUnknownType() { - assertFailsWith { - gson.fromJson("\"2\"") - } - } - - companion object { - - @JvmStatic - @DataPoints - fun dataPoints(): IntArray = intArrayOf(-2, -1, 0, 1, 2, 5) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt deleted file mode 100644 index 5ea60d5d..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt +++ /dev/null @@ -1,192 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.effects.ProductionIncrease -import org.luxons.sevenwonders.game.resources.MutableResources -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull - -class ProductionIncreaseSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - val resourceTypeList = object : TypeToken>() {}.type - gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) - .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) - .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) - .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) - .registerTypeAdapter(Production::class.java, ProductionSerializer()) - .registerTypeAdapter(ProductionIncrease::class.java, ProductionIncreaseSerializer()) - .create() - } - - private fun create(sellable: Boolean, wood: Int, stone: Int, clay: Int): ProductionIncrease { - val production = Production() - if (wood > 0) { - production.addFixedResource(ResourceType.WOOD, wood) - } - if (stone > 0) { - production.addFixedResource(ResourceType.STONE, stone) - } - if (clay > 0) { - production.addFixedResource(ResourceType.CLAY, clay) - } - return ProductionIncrease(production, sellable) - } - - private fun createChoice(sellable: Boolean, vararg types: ResourceType): ProductionIncrease { - val production = Production() - production.addChoice(*types) - return ProductionIncrease(production, sellable) - } - - @Test - fun serialize_nullAsNull() { - assertEquals("null", gson.toJson(null, ProductionIncrease::class.java)) - } - - @Test - fun serialize_emptyProdIncreaseAsNull() { - val prodIncrease = ProductionIncrease(Production(), false) - assertEquals("null", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_singleType() { - val prodIncrease = create(true, 1, 0, 0) - assertEquals("\"W\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_mixedTypes() { - val prodIncrease = create(true, 1, 1, 1) - assertEquals("\"WSC\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_mixedTypes_notSellable() { - val prodIncrease = create(false, 1, 1, 1) - assertEquals("\"(WSC)\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice2() { - val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) - assertEquals("\"W/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice3() { - val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) - assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice3_notSellable() { - val prodIncrease = createChoice(false, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) - assertEquals("\"(W/O/C)\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice2_unordered() { - val prodIncrease = createChoice(true, ResourceType.CLAY, ResourceType.WOOD) - assertEquals("\"W/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice3_unordered() { - val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) - assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_failIfMultipleChoices() { - val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) - prodIncrease.production.addChoice(ResourceType.ORE, ResourceType.GLASS) - assertFailsWith { - gson.toJson(prodIncrease) - } - } - - @Test - fun serialize_failIfMixedFixedAndChoices() { - val prodIncrease = create(true, 1, 0, 0) - prodIncrease.production.addChoice(ResourceType.WOOD, ResourceType.CLAY) - assertFailsWith { - gson.toJson(prodIncrease) - } - } - - @Test - fun deserialize_nullFromNull() { - assertNull(gson.fromJson("null", ProductionIncrease::class.java)) - } - - @Test - fun deserialize_emptyList() { - val prodIncrease = ProductionIncrease(Production(), true) - assertEquals(prodIncrease, gson.fromJson("\"\"")) - } - - @Test - fun deserialize_failOnGarbageString() { - assertFailsWith(IllegalArgumentException::class) { - gson.fromJson("\"this is garbage\"") - } - } - - @Test - fun deserialize_failOnGarbageStringWithSlashes() { - assertFailsWith(IllegalArgumentException::class) { - gson.fromJson("\"this/is/garbage\"") - } - } - - @Test - fun deserialize_singleType() { - val prodIncrease = create(true, 1, 0, 0) - assertEquals(prodIncrease, gson.fromJson("\"W\"")) - } - - @Test - fun deserialize_multipleTimesSameType_notSellable() { - val prodIncrease = create(false, 3, 0, 0) - assertEquals(prodIncrease, gson.fromJson("\"(WWW)\"")) - } - - @Test - fun deserialize_mixedTypes() { - val prodIncrease = create(true, 1, 1, 1) - assertEquals(prodIncrease, gson.fromJson("\"WCS\"")) - } - - @Test - fun deserialize_choice2() { - val prodIncrease = createChoice(true, ResourceType.WOOD, ResourceType.CLAY) - assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) - } - - @Test - fun deserialize_choice3_notSellable() { - val prodIncrease = createChoice(false, ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) - assertEquals(prodIncrease, gson.fromJson("\"(W/O/C)\"")) - } - - @Test - fun deserialize_failOnMultipleResourcesInChoice() { - assertFailsWith(IllegalArgumentException::class) { - gson.fromJson("\"W/SS/C\"") - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt deleted file mode 100644 index 4fbd8373..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt +++ /dev/null @@ -1,207 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.resources.MutableResources -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.Resources -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull - -class ProductionSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - val resourceTypeList = object : TypeToken>() {}.type - gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer()) - .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) - .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()) - .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer()) - .registerTypeAdapter(Production::class.java, ProductionSerializer()).create() - } - - private fun create(wood: Int, stone: Int, clay: Int): Production { - val production = Production() - if (wood > 0) { - production.addFixedResource(ResourceType.WOOD, wood) - } - if (stone > 0) { - production.addFixedResource(ResourceType.STONE, stone) - } - if (clay > 0) { - production.addFixedResource(ResourceType.CLAY, clay) - } - return production - } - - private fun createChoice(vararg types: ResourceType): Production { - val production = Production() - production.addChoice(*types) - return production - } - - @Test - fun serialize_nullAsNull() { - assertEquals("null", gson.toJson(null, Production::class.java)) - } - - @Test - fun serialize_emptyProdIncreaseAsNull() { - val prodIncrease = Production() - assertEquals("null", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_singleType() { - val prodIncrease = create(1, 0, 0) - assertEquals("\"W\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_multipleTimesSameType() { - val prodIncrease = create(3, 0, 0) - assertEquals("\"WWW\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_mixedTypes() { - val prodIncrease = create(1, 1, 1) - assertEquals("\"WSC\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_mixedTypesMultiple() { - val prodIncrease = create(2, 1, 2) - assertEquals("\"WWSCC\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice2() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY) - assertEquals("\"W/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice3() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) - assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice2_unordered() { - val prodIncrease = createChoice(ResourceType.CLAY, ResourceType.WOOD) - assertEquals("\"W/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_choice3_unordered() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) - assertEquals("\"W/O/C\"", gson.toJson(prodIncrease)) - } - - @Test - fun serialize_failIfMultipleChoices() { - val production = createChoice(ResourceType.WOOD, ResourceType.CLAY) - production.addChoice(ResourceType.ORE, ResourceType.GLASS) - assertFailsWith { - gson.toJson(production) - } - } - - @Test - fun serialize_failIfMixedFixedAndChoices() { - val production = create(1, 0, 0) - production.addChoice(ResourceType.WOOD, ResourceType.CLAY) - assertFailsWith { - gson.toJson(production) - } - } - - @Test - fun deserialize_nullFromNull() { - assertNull(gson.fromJson("null", Production::class.java)) - } - - @Test - fun deserialize_emptyList() { - val prodIncrease = Production() - assertEquals(prodIncrease, gson.fromJson("\"\"")) - } - - @Test - fun deserialize_failOnGarbageString() { - assertFailsWith { - gson.fromJson("\"this is garbage\"") - } - } - - @Test - fun deserialize_failOnGarbageStringWithSlashes() { - assertFailsWith { - gson.fromJson("\"this/is/garbage\"") - } - } - - @Test - fun deserialize_singleType() { - val prodIncrease = create(1, 0, 0) - assertEquals(prodIncrease, gson.fromJson("\"W\"")) - } - - @Test - fun deserialize_multipleTimesSameType() { - val prodIncrease = create(3, 0, 0) - assertEquals(prodIncrease, gson.fromJson("\"WWW\"")) - } - - @Test - fun deserialize_mixedTypes() { - val prodIncrease = create(1, 1, 1) - assertEquals(prodIncrease, gson.fromJson("\"WCS\"")) - } - - @Test - fun deserialize_mixedTypes_unordered() { - val prodIncrease = create(1, 3, 2) - assertEquals(prodIncrease, gson.fromJson("\"SCWCSS\"")) - } - - @Test - fun deserialize_choice2() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY) - assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) - } - - @Test - fun deserialize_choice3() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.ORE, ResourceType.CLAY) - assertEquals(prodIncrease, gson.fromJson("\"W/O/C\"")) - } - - @Test - fun deserialize_choice2_unordered() { - val prodIncrease = createChoice(ResourceType.CLAY, ResourceType.WOOD) - assertEquals(prodIncrease, gson.fromJson("\"W/C\"")) - } - - @Test - fun deserialize_choice3_unordered() { - val prodIncrease = createChoice(ResourceType.WOOD, ResourceType.CLAY, ResourceType.ORE) - assertEquals(prodIncrease, gson.fromJson("\"W/O/C\"")) - } - - @Test - fun deserialize_failOnMultipleResourcesInChoice() { - assertFailsWith { - gson.fromJson("\"W/SS/C\"") - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt deleted file mode 100644 index c5e7b53d..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.api.resources.ResourceType -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNull - -class ResourceTypeSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - gson = GsonBuilder().registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer()).create() - } - - @Test - fun serialize_useSymbolForEachType() { - ResourceType.values().forEach { type -> - val expectedJson = "\"" + type.symbol + "\"" - assertEquals(expectedJson, gson.toJson(type)) - } - } - - @Test - fun deserialize_useSymbolForEachType() { - ResourceType.values().forEach { type -> - val typeInJson = "\"" + type.symbol + "\"" - assertEquals(type, gson.fromJson(typeInJson)) - } - } - - @Test - fun deserialize_nullFromNull() { - assertNull(gson.fromJson("null", ResourceType::class.java)) - } - - @Test - fun deserialize_failsOnEmptyString() { - assertFailsWith { - gson.fromJson("\"\"") - } - } - - @Test - fun deserialize_failsOnGarbageString() { - assertFailsWith { - gson.fromJson("\"thisisgarbage\"") - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt deleted file mode 100644 index 242f2e51..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.github.salomonbrys.kotson.typeToken -import com.github.salomonbrys.kotson.typedToJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.api.resources.ResourceType -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class ResourceTypesSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - gson = GsonBuilder().registerTypeAdapter(typeToken>(), ResourceTypesSerializer()).create() - } - - @Test - fun serialize_null() { - assertEquals("null", gson.toJson(null, typeToken>())) - } - - @Test - fun serialize_emptyList() { - val types = emptyList() - assertEquals("\"\"", gson.typedToJson(types)) - } - - @Test - fun serialize_singleType() { - val types = listOf(ResourceType.WOOD) - assertEquals("\"W\"", gson.typedToJson(types)) - } - - @Test - fun serialize_multipleTimesSameType() { - val types = List(3) { ResourceType.WOOD } - assertEquals("\"WWW\"", gson.typedToJson(types)) - } - - @Test - fun serialize_mixedTypes() { - val types = listOf(ResourceType.WOOD, ResourceType.CLAY, ResourceType.STONE) - assertEquals("\"WCS\"", gson.typedToJson(types)) - } - - @Test - fun deserialize_null() { - assertNull(gson.fromJson("null", typeToken>())) - } - - @Test - fun deserialize_emptyList() { - val types = emptyList() - assertEquals(types, gson.fromJson("\"\"")) - } - - @Test - fun deserialize_singleType() { - val types = listOf(ResourceType.WOOD) - assertEquals(types, gson.fromJson("\"W\"")) - } - - @Test - fun deserialize_multipleTimesSameType() { - val types = List(3) { ResourceType.WOOD } - assertEquals(types, gson.fromJson("\"WWW\"")) - } - - @Test - fun deserialize_mixedTypes() { - val types = listOf(ResourceType.WOOD, ResourceType.CLAY, ResourceType.STONE) - assertEquals(types, gson.fromJson("\"WCS\"")) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt deleted file mode 100644 index 58745841..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.resources.MutableResources -import org.luxons.sevenwonders.game.api.resources.ResourceType.CLAY -import org.luxons.sevenwonders.game.api.resources.ResourceType.STONE -import org.luxons.sevenwonders.game.api.resources.ResourceType.WOOD -import org.luxons.sevenwonders.game.resources.Resources -import org.luxons.sevenwonders.game.resources.emptyResources -import org.luxons.sevenwonders.game.resources.resourcesOf -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class ResourcesSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - gson = GsonBuilder() - .registerTypeAdapter(Resources::class.java, ResourcesSerializer()) - .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer()) - .create() - } - - @Test - fun serialize_null() { - assertEquals("null", gson.toJson(null, Resources::class.java)) - } - - @Test - fun serialize_emptyResourcesToNull() { - val resources = emptyResources() - assertEquals("null", gson.toJson(resources)) - } - - @Test - fun serialize_singleType() { - val resources = resourcesOf(WOOD) - assertEquals("\"W\"", gson.toJson(resources)) - } - - @Test - fun serialize_multipleTimesSameType() { - val resources = resourcesOf(WOOD to 3) - assertEquals("\"WWW\"", gson.toJson(resources)) - } - - @Test - fun serialize_mixedTypes() { - val resources = resourcesOf(WOOD, STONE, CLAY) - assertEquals("\"WSC\"", gson.toJson(resources)) - } - - @Test - fun serialize_mixedTypes_unordered() { - val resources = resourcesOf(CLAY to 1, WOOD to 2, CLAY to 1, STONE to 1) - assertEquals("\"CCWWS\"", gson.toJson(resources)) - } - - @Test - fun deserialize_null() { - assertNull(gson.fromJson("null", Resources::class.java)) - } - - @Test - fun deserialize_emptyList() { - val resources = emptyResources() - assertEquals(resources, gson.fromJson("\"\"")) - } - - @Test - fun deserialize_singleType() { - val resources = resourcesOf(WOOD) - assertEquals(resources, gson.fromJson("\"W\"")) - } - - @Test - fun deserialize_multipleTimesSameType() { - val resources = resourcesOf(WOOD to 3) - assertEquals(resources, gson.fromJson("\"WWW\"")) - } - - @Test - fun deserialize_mixedTypes() { - val resources = resourcesOf(WOOD, CLAY, STONE) - assertEquals(resources, gson.fromJson("\"WCS\"")) - } - - @Test - fun deserialize_mixedTypes_unordered() { - val resources = resourcesOf(WOOD to 1, CLAY to 2, STONE to 3) - assertEquals(resources, gson.fromJson("\"SCWCSS\"")) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt deleted file mode 100644 index 95d72517..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt +++ /dev/null @@ -1,157 +0,0 @@ -package org.luxons.sevenwonders.game.data.serializers - -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.boards.ScienceType -import org.luxons.sevenwonders.game.effects.ScienceProgress -import org.luxons.sevenwonders.game.test.createScienceProgress -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull - -private const val TABLET_STR = "\"TABLET\"" -private const val COMPASS_STR = "\"COMPASS\"" -private const val WHEEL_STR = "\"WHEEL\"" -private const val JOKER_STR = "\"any\"" - -class ScienceProgressSerializerTest { - - private lateinit var gson: Gson - - @Before - fun setUp() { - gson = GsonBuilder().registerTypeAdapter(ScienceProgress::class.java, ScienceProgressSerializer()).create() - } - - @Test - fun serialize_emptyToNull() { - val progress = createScienceProgress(0, 0, 0, 0) - val json = gson.toJson(progress) - assertEquals("null", json) - } - - @Test - fun serialize_oneCompass() { - val progress = createScienceProgress(1, 0, 0, 0) - val json = gson.toJson(progress) - assertEquals(COMPASS_STR, json) - } - - @Test - fun serialize_oneWheel() { - val progress = createScienceProgress(0, 1, 0, 0) - val json = gson.toJson(progress) - assertEquals(WHEEL_STR, json) - } - - @Test - fun serialize_oneTablet() { - val progress = createScienceProgress(0, 0, 1, 0) - val json = gson.toJson(progress) - assertEquals(TABLET_STR, json) - } - - @Test - fun serialize_oneJoker() { - val progress = createScienceProgress(0, 0, 0, 1) - val json = gson.toJson(progress) - assertEquals(JOKER_STR, json) - } - - @Test - fun serialize_failOnMultipleCompasses() { - assertFailsWith { - val progress = createScienceProgress(2, 0, 0, 0) - gson.toJson(progress) - } - } - - @Test - fun serialize_failOnMultipleWheels() { - assertFailsWith { - val progress = createScienceProgress(0, 2, 0, 0) - gson.toJson(progress) - } - } - - @Test - fun serialize_failOnMultipleTablets() { - assertFailsWith { - val progress = createScienceProgress(0, 0, 2, 0) - gson.toJson(progress) - } - } - - @Test - fun serialize_failOnMultipleJokers() { - assertFailsWith { - val progress = createScienceProgress(0, 0, 0, 2) - gson.toJson(progress) - } - } - - @Test - fun serialize_failOnMixedElements() { - assertFailsWith { - val progress = createScienceProgress(1, 1, 0, 0) - gson.toJson(progress) - } - } - - @Test - fun deserialize_failOnEmptyString() { - assertFailsWith { - gson.fromJson("\"\"") - } - } - - @Test - fun deserialize_failOnGarbageString() { - assertFailsWith { - gson.fromJson("thisisgarbage") - } - } - - @Test - fun deserialize_compass() { - val progress = gson.fromJson(COMPASS_STR) - assertNotNull(progress.science) - assertEquals(1, progress.science.getQuantity(ScienceType.COMPASS)) - assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) - assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) - assertEquals(0, progress.science.jokers) - } - - @Test - fun deserialize_wheel() { - val progress = gson.fromJson(WHEEL_STR) - assertNotNull(progress.science) - assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) - assertEquals(1, progress.science.getQuantity(ScienceType.WHEEL)) - assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) - assertEquals(0, progress.science.jokers) - } - - @Test - fun deserialize_tablet() { - val progress = gson.fromJson(TABLET_STR) - assertNotNull(progress.science) - assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) - assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) - assertEquals(1, progress.science.getQuantity(ScienceType.TABLET)) - assertEquals(0, progress.science.jokers) - } - - @Test - fun deserialize_joker() { - val progress = gson.fromJson(JOKER_STR) - assertNotNull(progress.science) - assertEquals(0, progress.science.getQuantity(ScienceType.COMPASS)) - assertEquals(0, progress.science.getQuantity(ScienceType.WHEEL)) - assertEquals(0, progress.science.getQuantity(ScienceType.TABLET)) - assertEquals(1, progress.science.jokers) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt deleted file mode 100644 index 3c72d0aa..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt +++ /dev/null @@ -1,142 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.Before -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.test.addCards -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class BonusPerBoardElementTest { - - private lateinit var table: Table - private lateinit var player0: Player - - @Before - fun setUp() { - table = testTable(4) - player0 = SimplePlayer(0, table) - } - - @Theory - fun computePoints_countsCards( - boardPosition: RelativeBoardPosition, - nbCards: Int, - nbOtherCards: Int, - points: Int, - gold: Int, - color: Color - ) { - val board = table.getBoard(0, boardPosition) - addCards(board, nbCards, nbOtherCards, color) - - val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.CARD, gold, points, listOf(color)) - - assertEquals(nbCards * points, bonus.computePoints(player0)) - } - - @Theory - fun computePoints_countsDefeatTokens( - boardPosition: RelativeBoardPosition, - nbDefeatTokens: Int, - points: Int, - gold: Int - ) { - val board = table.getBoard(0, boardPosition) - repeat(nbDefeatTokens) { - board.military.defeat() - } - - val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.DEFEAT_TOKEN, gold, points, listOf()) - - assertEquals(nbDefeatTokens * points, bonus.computePoints(player0)) - } - - @Theory - fun computePoints_countsWonderStages(boardPosition: RelativeBoardPosition, nbStages: Int, points: Int, gold: Int) { - val board = table.getBoard(0, boardPosition) - repeat(nbStages) { - board.wonder.placeCard(CardBack("")) - } - - val bonus = - BonusPerBoardElement(listOf(boardPosition), BoardElementType.BUILT_WONDER_STAGES, gold, points, listOf()) - - assertEquals(nbStages * points, bonus.computePoints(player0)) - } - - @Theory - fun apply_countsCards( - boardPosition: RelativeBoardPosition, - nbCards: Int, - nbOtherCards: Int, - points: Int, - gold: Int, - color: Color - ) { - val board = table.getBoard(0, boardPosition) - addCards(board, nbCards, nbOtherCards, color) - - val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.CARD, gold, points, listOf(color)) - - val selfBoard = table.getBoard(0) - val initialGold = selfBoard.gold - bonus.applyTo(player0) - assertEquals(initialGold + nbCards * gold, selfBoard.gold) - } - - @Theory - fun apply_countsDefeatTokens(boardPosition: RelativeBoardPosition, nbDefeatTokens: Int, points: Int, gold: Int) { - val board = table.getBoard(0, boardPosition) - repeat(nbDefeatTokens) { - board.military.defeat() - } - - val bonus = BonusPerBoardElement(listOf(boardPosition), BoardElementType.DEFEAT_TOKEN, gold, points, listOf()) - - val selfBoard = table.getBoard(0) - val initialGold = selfBoard.gold - bonus.applyTo(player0) - assertEquals(initialGold + nbDefeatTokens * gold, selfBoard.gold) - } - - @Theory - fun apply_countsWonderStages(boardPosition: RelativeBoardPosition, nbStages: Int, points: Int, gold: Int) { - val board = table.getBoard(0, boardPosition) - repeat(nbStages) { - board.wonder.placeCard(CardBack("")) - } - - val bonus = - BonusPerBoardElement(listOf(boardPosition), BoardElementType.BUILT_WONDER_STAGES, gold, points, listOf()) - - val selfBoard = table.getBoard(0) - val initialGold = selfBoard.gold - bonus.applyTo(player0) - assertEquals(initialGold + nbStages * gold, selfBoard.gold) - } - - companion object { - - @JvmStatic - @DataPoints - fun values(): IntArray = intArrayOf(0, 1, 2, 3) - - @JvmStatic - @DataPoints - fun colors(): Array = Color.values() - - @JvmStatic - @DataPoints - fun positions(): Array = RelativeBoardPosition.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt deleted file mode 100644 index 9773ec5c..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.Assume -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.test.createTransactions -import org.luxons.sevenwonders.game.test.testBoard -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class DiscountTest { - - @Theory - fun apply_givesDiscountedPrice(discountedPrice: Int, discountedType: ResourceType, provider: Provider) { - val board = testBoard(ResourceType.CLAY, 3) - val discount = Discount(listOf(discountedType), listOf(provider), discountedPrice) - discount.applyTo(board) - - val transactions = createTransactions(provider, discountedType) - assertEquals(discountedPrice, board.tradingRules.computeCost(transactions)) - } - - @Theory - fun apply_doesNotAffectOtherResources( - discountedPrice: Int, - discountedType: ResourceType, - provider: Provider, - otherType: ResourceType, - otherProvider: Provider - ) { - Assume.assumeTrue(otherProvider != provider) - Assume.assumeTrue(otherType != discountedType) - - val board = testBoard(ResourceType.CLAY, 3) - val discount = Discount(listOf(discountedType), listOf(provider), discountedPrice) - discount.applyTo(board) - - // this is the default in the settings used by TestUtilsKt.testBoard() - val normalPrice = 2 - - val fromOtherType = createTransactions(provider, otherType) - assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherType)) - - val fromOtherProvider = createTransactions(otherProvider, discountedType) - assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherProvider)) - - val fromOtherProviderAndType = createTransactions(otherProvider, otherType) - assertEquals(normalPrice, board.tradingRules.computeCost(fromOtherProviderAndType)) - } - - companion object { - - @JvmStatic - @DataPoints - fun discountedPrices(): IntArray = intArrayOf(0, 1, 2) - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - - @JvmStatic - @DataPoints - fun providers(): Array = Provider.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt deleted file mode 100644 index 3ade2773..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.test.testBoard -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class GoldIncreaseTest { - - @Theory - fun apply_increaseGoldWithRightAmount(initialAmount: Int, goldIncreaseAmount: Int, type: ResourceType) { - val board = testBoard(type, initialAmount) - val goldIncrease = GoldIncrease(goldIncreaseAmount) - - goldIncrease.applyTo(board) - - assertEquals(initialAmount + goldIncreaseAmount, board.gold) - } - - @Theory - fun computePoints_isAlwaysZero(gold: Int) { - val goldIncrease = GoldIncrease(gold) - val player = SimplePlayer(0, testTable(5)) - assertEquals(0, goldIncrease.computePoints(player)) - } - - companion object { - - @JvmStatic - @DataPoints - fun goldAmounts(): IntArray = intArrayOf(-5, -1, 0, 1, 2, 5, 10) - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt deleted file mode 100644 index c8a89aaf..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.test.testBoard -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class MilitaryReinforcementsTest { - - @Theory - fun apply_increaseGoldWithRightAmount(initialShields: Int, additionalShields: Int, type: ResourceType) { - val board = testBoard(type) - board.military.addShields(initialShields) - - val reinforcements = MilitaryReinforcements(additionalShields) - reinforcements.applyTo(board) - - assertEquals(initialShields + additionalShields, board.military.nbShields) - } - - @Theory - fun computePoints_isAlwaysZero(shields: Int) { - val reinforcements = MilitaryReinforcements(shields) - val player = SimplePlayer(0, testTable(5)) - assertEquals(0, reinforcements.computePoints(player)) - } - - companion object { - - @JvmStatic - @DataPoints - fun shieldCounts(): IntArray = intArrayOf(0, 1, 2, 3, 5) - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt deleted file mode 100644 index 15366fd0..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.resources.resourcesOf -import org.luxons.sevenwonders.game.test.fixedProduction -import org.luxons.sevenwonders.game.test.testBoard -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class ProductionIncreaseTest { - - @Theory - fun apply_boardContainsAddedResourceType( - initialType: ResourceType, - addedType: ResourceType, - extraType: ResourceType - ) { - val board = testBoard(initialType) - val effect = ProductionIncrease(fixedProduction(addedType), false) - - effect.applyTo(board) - - val resources = resourcesOf(initialType, addedType) - assertTrue(board.production.contains(resources)) - assertFalse(board.publicProduction.contains(resources)) - - val moreResources = resourcesOf(initialType, addedType, extraType) - assertFalse(board.production.contains(moreResources)) - assertFalse(board.publicProduction.contains(moreResources)) - } - - @Theory - fun apply_boardContainsAddedResourceType_sellable( - initialType: ResourceType, - addedType: ResourceType, - extraType: ResourceType - ) { - val board = testBoard(initialType) - val effect = ProductionIncrease(fixedProduction(addedType), true) - - effect.applyTo(board) - - val resources = resourcesOf(initialType, addedType) - assertTrue(board.production.contains(resources)) - assertTrue(board.publicProduction.contains(resources)) - - val moreResources = resourcesOf(initialType, addedType, extraType) - assertFalse(board.production.contains(moreResources)) - assertFalse(board.publicProduction.contains(moreResources)) - } - - @Theory - fun computePoints_isAlwaysZero(addedType: ResourceType) { - val effect = ProductionIncrease(fixedProduction(addedType), false) - val player = SimplePlayer(0, testTable(5)) - assertEquals(0, effect.computePoints(player)) - } - - companion object { - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt deleted file mode 100644 index 9cb10562..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class RawPointsIncreaseTest { - - @Theory - fun computePoints_equalsNbOfPoints(points: Int) { - val rawPointsIncrease = RawPointsIncrease(points) - val player = SimplePlayer(0, testTable(5)) - assertEquals(points, rawPointsIncrease.computePoints(player)) - } - - companion object { - - @JvmStatic - @DataPoints - fun points(): IntArray { - return intArrayOf(0, 1, 2, 3, 5) - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt deleted file mode 100644 index b782f6b3..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.boards.ScienceType -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.test.createScience -import org.luxons.sevenwonders.game.test.createScienceProgress -import org.luxons.sevenwonders.game.test.testBoard -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class ScienceProgressTest { - - @Theory - fun apply_initContainsAddedScience( - initCompasses: Int, - initWheels: Int, - initTablets: Int, - initJokers: Int, - compasses: Int, - wheels: Int, - tablets: Int, - jokers: Int - ) { - val board = testBoard(ResourceType.ORE) - val initialScience = createScience(initCompasses, initWheels, initTablets, initJokers) - board.science.addAll(initialScience) - - val effect = createScienceProgress(compasses, wheels, tablets, jokers) - effect.applyTo(board) - - assertEquals(initCompasses + compasses, board.science.getQuantity(ScienceType.COMPASS)) - assertEquals(initWheels + wheels, board.science.getQuantity(ScienceType.WHEEL)) - assertEquals(initTablets + tablets, board.science.getQuantity(ScienceType.TABLET)) - assertEquals(initJokers + jokers, board.science.jokers) - } - - companion object { - - @JvmStatic - @DataPoints - fun elementsCount(): IntArray { - return intArrayOf(0, 1, 2) - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt deleted file mode 100644 index f273783c..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.luxons.sevenwonders.game.effects - -import org.junit.Assume -import org.junit.Test -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.test.createGuildCard -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue - -@RunWith(Theories::class) -class SpecialAbilityActivationTest { - - @Theory - fun apply_addsAbility(ability: SpecialAbility) { - val effect = SpecialAbilityActivation(ability) - val player = SimplePlayer(0, testTable(5)) - - effect.applyTo(player) - - assertTrue(player.board.hasSpecial(ability)) - } - - @Theory - fun computePoints_zeroExceptForCopyGuild(ability: SpecialAbility) { - Assume.assumeTrue(ability !== SpecialAbility.COPY_GUILD) - - val effect = SpecialAbilityActivation(ability) - val player = SimplePlayer(0, testTable(5)) - - assertEquals(0, effect.computePoints(player)) - } - - @Theory - internal fun computePoints_copiedGuild(guildCard: Card, neighbour: RelativeBoardPosition) { - val effect = SpecialAbilityActivation(SpecialAbility.COPY_GUILD) - val player = SimplePlayer(0, testTable(5)) - - val neighbourBoard = player.getBoard(neighbour) - neighbourBoard.addCard(guildCard) - - player.board.copiedGuild = guildCard - - val directPointsFromGuildCard = guildCard.effects.stream().mapToInt { e -> e.computePoints(player) }.sum() - assertEquals(directPointsFromGuildCard, effect.computePoints(player)) - } - - @Test - fun computePoints_copyGuild_failWhenNoChosenGuild() { - val effect = SpecialAbilityActivation(SpecialAbility.COPY_GUILD) - val player = SimplePlayer(0, testTable(5)) - assertFailsWith { - effect.computePoints(player) - } - } - - companion object { - - @JvmStatic - @DataPoints - fun abilities(): Array = SpecialAbility.values() - - @JvmStatic - @DataPoints - fun neighbours(): Array = - arrayOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.RIGHT) - - @JvmStatic - @DataPoints - internal fun guilds(): Array { - val bonus = BonusPerBoardElement( - listOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.RIGHT), - BoardElementType.CARD, - points = 1, - colors = listOf(Color.GREY, Color.BROWN) - ) - val bonus2 = BonusPerBoardElement( - listOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.SELF, RelativeBoardPosition.RIGHT), - BoardElementType.BUILT_WONDER_STAGES, - points = 1 - ) - return arrayOf(createGuildCard(1, bonus), createGuildCard(2, bonus2)) - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt deleted file mode 100644 index 5d4f0236..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.luxons.sevenwonders.game.moves - -import org.junit.Test -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.MoveType -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.test.createMove -import org.luxons.sevenwonders.game.test.sampleCards -import org.luxons.sevenwonders.game.test.testCard -import org.luxons.sevenwonders.game.test.testSettings -import org.luxons.sevenwonders.game.test.testTable -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.fail - -class BuildWonderMoveTest { - - @Test - fun init_failsWhenCardNotInHand() { - val table = testTable(3) - val hand = sampleCards(7) - val playerContext = PlayerContext(0, table, hand) - val anotherCard = testCard("Card that is not in the hand") - - assertFailsWith { - createMove(playerContext, anotherCard, MoveType.UPGRADE_WONDER) - } - } - - @Test - fun init_failsWhenWonderIsCompletelyBuilt() { - val settings = testSettings(3) - val table = testTable(settings) - val hand = sampleCards(7) - - fillPlayerWonderLevels(settings, table, hand) - - // should fail because the wonder is already full - assertFailsWith { - buildOneWonderLevel(settings, table, hand, 4) - } - } - - private fun fillPlayerWonderLevels(settings: Settings, table: Table, hand: List) { - try { - val nbLevels = table.getBoard(0).wonder.stages.size - repeat(nbLevels) { - buildOneWonderLevel(settings, table, hand, it) - } - } catch (e: InvalidMoveException) { - fail("Building wonder levels should not fail before being full") - } - } - - private fun buildOneWonderLevel(settings: Settings, table: Table, hand: List, cardIndex: Int) { - val card = hand[cardIndex] - val playerContext = PlayerContext(0, table, hand) - val move = createMove(playerContext, card, MoveType.UPGRADE_WONDER) - move.place(mutableListOf(), settings) - move.activate(emptyList(), settings) - } - - @Test - fun place_increasesWonderLevel() { - val settings = testSettings(3) - val table = testTable(settings) - val hand = sampleCards(7) - val cardToUse = hand[0] - val playerContext = PlayerContext(0, table, hand) - val move = createMove(playerContext, cardToUse, MoveType.UPGRADE_WONDER) - - val initialStage = table.getBoard(0).wonder.nbBuiltStages - - move.place(mutableListOf(), settings) - - val newStage = table.getBoard(0).wonder.nbBuiltStages - - // we need to see the level increase before activation so that other players - assertEquals(initialStage + 1, newStage) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt deleted file mode 100644 index c531a866..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt +++ /dev/null @@ -1,139 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.junit.Test -import org.luxons.sevenwonders.game.SimplePlayer -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.api.resources.Provider.LEFT_PLAYER -import org.luxons.sevenwonders.game.api.resources.Provider.RIGHT_PLAYER -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.ResourceType.CLAY -import org.luxons.sevenwonders.game.api.resources.ResourceType.GLASS -import org.luxons.sevenwonders.game.api.resources.ResourceType.ORE -import org.luxons.sevenwonders.game.api.resources.ResourceType.STONE -import org.luxons.sevenwonders.game.api.resources.ResourceType.WOOD -import org.luxons.sevenwonders.game.api.resources.noTransactions -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 kotlin.test.assertEquals - -class BestPriceCalculatorTest { - - private fun solutions(price: Int, vararg resourceTransactions: ResourceTransactions) = - TransactionPlan(price, setOf(*resourceTransactions)) - - @Test - fun bestPrice_0forEmptyResources() { - val table = testTable(3) - val player0 = SimplePlayer(0, table) - val emptyResources = emptyResources() - val emptyTransactions = noTransactions() - assertEquals(solutions(0, emptyTransactions), bestSolution(emptyResources, player0)) - } - - @Test - fun bestPrice_fixedResources_defaultCost() { - val left = testBoard(STONE) - val main = testBoard(STONE) - val right = testBoard(WOOD) - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(STONE, STONE) - - val stoneLeftSingle = createTransaction(LEFT_PLAYER, STONE) - val stoneRightSingle = createTransaction(RIGHT_PLAYER, STONE) - - val stoneLeft = createTransactions(stoneLeftSingle) - val stoneRight = createTransactions(stoneRightSingle) - val stoneLeftAndRight = createTransactions(stoneLeftSingle, stoneRightSingle) - - assertEquals(solutions(2, stoneLeft), bestSolution(resources, player0)) - assertEquals(solutions(4, stoneLeftAndRight), bestSolution(resources, player1)) - assertEquals(solutions(2, stoneRight), bestSolution(resources, player2)) - } - - @Test - fun bestPrice_fixedResources_overridenCost() { - val main = testBoard(STONE) - main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) - - val left = testBoard(WOOD) - val right = testBoard(WOOD) - val opposite = testBoard(GLASS) - val table = Table(listOf(main, right, opposite, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - val player3 = SimplePlayer(3, table) - - val resources = resourcesOf(WOOD) - - val woodLeft = createTransactions(LEFT_PLAYER, WOOD) - val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - - assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(2, woodLeft, woodRight), bestSolution(resources, player2)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player3)) - } - - @Test - fun bestPrice_mixedResources_overridenCost() { - val left = testBoard(WOOD) - - val main = testBoard(STONE) - main.tradingRules.setCost(WOOD, RIGHT_PLAYER, 1) - - val right = testBoard(ORE) - right.production.addChoice(WOOD, CLAY) - right.publicProduction.addChoice(WOOD, CLAY) - - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(WOOD) - val woodRight = createTransactions(RIGHT_PLAYER, WOOD) - - assertEquals(solutions(1, woodRight), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player2)) - } - - @Test - fun bestPrice_chooseCheapest() { - val left = testBoard(WOOD) - - val main = testBoard(WOOD) - main.production.addChoice(CLAY, ORE) - main.tradingRules.setCost(CLAY, RIGHT_PLAYER, 1) - - val right = testBoard(WOOD) - right.production.addFixedResource(ORE, 1) - right.production.addFixedResource(CLAY, 1) - right.publicProduction.addFixedResource(ORE, 1) - right.publicProduction.addFixedResource(CLAY, 1) - - val table = Table(listOf(main, right, left)) - - val player0 = SimplePlayer(0, table) - val player1 = SimplePlayer(1, table) - val player2 = SimplePlayer(2, table) - - val resources = resourcesOf(ORE, CLAY) - val oreAndClayLeft = createTransactions(LEFT_PLAYER, ORE, CLAY) - val clayRight = createTransactions(RIGHT_PLAYER, CLAY) - - assertEquals(solutions(1, clayRight), bestSolution(resources, player0)) - assertEquals(solutions(0, noTransactions()), bestSolution(resources, player1)) - assertEquals(solutions(4, oreAndClayLeft), bestSolution(resources, player2)) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt deleted file mode 100644 index 13d7a10e..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt +++ /dev/null @@ -1,293 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.junit.Before -import org.junit.Test -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.ResourceType.CLAY -import org.luxons.sevenwonders.game.api.resources.ResourceType.GLASS -import org.luxons.sevenwonders.game.api.resources.ResourceType.LOOM -import org.luxons.sevenwonders.game.api.resources.ResourceType.ORE -import org.luxons.sevenwonders.game.api.resources.ResourceType.PAPYRUS -import org.luxons.sevenwonders.game.api.resources.ResourceType.STONE -import org.luxons.sevenwonders.game.api.resources.ResourceType.WOOD -import java.util.EnumSet -import java.util.HashSet -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class ProductionTest { - - private lateinit var emptyResources: Resources - private lateinit var resources1Wood: Resources - private lateinit var resources1Stone: Resources - private lateinit var resources1Stone1Wood: Resources - private lateinit var resources2Stones: Resources - private lateinit var resources2Stones3Clay: Resources - - @Before - fun init() { - emptyResources = emptyResources() - resources1Wood = resourcesOf(WOOD) - resources1Stone = resourcesOf(STONE) - resources1Stone1Wood = resourcesOf(STONE to 1, WOOD to 1) - resources2Stones = resourcesOf(STONE to 2) - resources2Stones3Clay = resourcesOf(STONE to 2, CLAY to 3) - } - - @Test - fun contains_newProductionContainsEmpty() { - val production = Production() - assertTrue(production.contains(emptyResources)) - } - - @Test - fun contains_singleFixedResource_noneAtAll() { - val production = Production() - assertFalse(production.contains(resources2Stones)) - } - - @Test - fun contains_singleFixedResource_notEnough() { - val production = Production() - production.addFixedResource(STONE, 1) - assertFalse(production.contains(resources2Stones)) - } - - @Test - fun contains_singleFixedResource_justEnough() { - val production = Production() - production.addFixedResource(STONE, 2) - assertTrue(production.contains(resources2Stones)) - } - - @Test - fun contains_singleFixedResource_moreThanEnough() { - val production = Production() - production.addFixedResource(STONE, 3) - assertTrue(production.contains(resources2Stones)) - } - - @Test - fun contains_singleFixedResource_moreThanEnough_amongOthers() { - val production = Production() - production.addFixedResource(STONE, 3) - production.addFixedResource(CLAY, 2) - assertTrue(production.contains(resources2Stones)) - } - - @Test - fun contains_multipleFixedResources_notEnoughOfOne() { - val production = Production() - production.addFixedResource(STONE, 3) - production.addFixedResource(CLAY, 1) - assertFalse(production.contains(resources2Stones3Clay)) - } - - @Test - fun contains_multipleFixedResources_notEnoughOfBoth() { - val production = Production() - production.addFixedResource(STONE, 1) - production.addFixedResource(CLAY, 1) - assertFalse(production.contains(resources2Stones3Clay)) - } - - @Test - fun contains_multipleFixedResources_moreThanEnough() { - val production = Production() - production.addFixedResource(STONE, 3) - production.addFixedResource(CLAY, 5) - assertTrue(production.contains(resources2Stones3Clay)) - } - - @Test - fun contains_singleChoice_containsEmpty() { - val production = Production() - production.addChoice(STONE, CLAY) - assertTrue(production.contains(emptyResources)) - } - - @Test - fun contains_singleChoice_enough() { - val production = Production() - production.addChoice(STONE, WOOD) - assertTrue(production.contains(resources1Wood)) - assertTrue(production.contains(resources1Stone)) - } - - @Test - fun contains_multipleChoices_notBoth() { - val production = Production() - production.addChoice(STONE, CLAY) - production.addChoice(STONE, CLAY) - production.addChoice(STONE, CLAY) - assertFalse(production.contains(resources2Stones3Clay)) - } - - @Test - fun contains_multipleChoices_enough() { - val production = Production() - production.addChoice(STONE, ORE) - production.addChoice(STONE, WOOD) - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun contains_multipleChoices_enoughReverseOrder() { - val production = Production() - production.addChoice(STONE, WOOD) - production.addChoice(STONE, ORE) - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun contains_multipleChoices_moreThanEnough() { - val production = Production() - production.addChoice(LOOM, GLASS, PAPYRUS) - production.addChoice(STONE, ORE) - production.addChoice(STONE, WOOD) - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun contains_mixedFixedAndChoice_enough() { - val production = Production() - production.addFixedResource(WOOD, 1) - production.addChoice(STONE, WOOD) - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun addAll_empty() { - val production = Production() - production.addAll(emptyResources) - assertTrue(production.contains(emptyResources)) - } - - @Test - fun addAll_singleResource() { - val production = Production() - production.addAll(resources1Stone) - assertTrue(production.contains(resources1Stone)) - } - - @Test - fun addAll_multipleResources() { - val production = Production() - production.addAll(resources2Stones3Clay) - assertTrue(production.contains(resources2Stones3Clay)) - } - - @Test - fun addAll_production_multipleFixedResources() { - val production = Production() - production.addAll(resources2Stones3Clay) - - val production2 = Production() - production2.addAll(production) - - assertTrue(production2.contains(resources2Stones3Clay)) - } - - @Test - fun addAll_production_multipleChoices() { - val production = Production() - production.addChoice(STONE, WOOD) - production.addChoice(STONE, ORE) - - val production2 = Production() - production2.addAll(production) - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun addAll_production_mixedFixedResourcesAndChoices() { - val production = Production() - production.addFixedResource(WOOD, 1) - production.addChoice(STONE, WOOD) - - val production2 = Production() - production2.addAll(production) - - assertTrue(production.contains(resources1Stone1Wood)) - } - - @Test - fun asChoices_empty() { - val production = Production() - assertTrue(production.asChoices().isEmpty()) - } - - @Test - fun asChoices_onlyChoices() { - val production = Production() - production.addChoice(STONE, WOOD) - production.addChoice(STONE, ORE) - production.addChoice(CLAY, LOOM, GLASS) - assertEquals(production.getAlternativeResources(), production.asChoices()) - } - - @Test - fun asChoices_onlyFixed() { - val production = Production() - production.addFixedResource(WOOD, 1) - production.addFixedResource(CLAY, 2) - - val expected = HashSet>() - expected.add(EnumSet.of(WOOD)) - expected.add(EnumSet.of(CLAY)) - expected.add(EnumSet.of(CLAY)) - - assertEquals(expected, production.asChoices()) - } - - @Test - fun asChoices_mixed() { - val production = Production() - production.addChoice(STONE, ORE) - production.addChoice(CLAY, LOOM, GLASS) - production.addFixedResource(WOOD, 1) - production.addFixedResource(CLAY, 2) - - val expected = HashSet>() - expected.add(EnumSet.of(STONE, ORE)) - expected.add(EnumSet.of(CLAY, LOOM, GLASS)) - expected.add(EnumSet.of(WOOD)) - expected.add(EnumSet.of(CLAY)) - expected.add(EnumSet.of(CLAY)) - - assertEquals(expected, production.asChoices()) - } - - @Test - fun equals_trueWhenSame() { - val production = Production() - assertEquals(production, production) - } - - @Test - fun equals_trueWhenSameContent() { - val production1 = Production() - val production2 = Production() - assertTrue(production1 == production2) - production1.addFixedResource(GLASS, 1) - production2.addFixedResource(GLASS, 1) - assertTrue(production1 == production2) - production1.addChoice(ORE, WOOD) - production2.addChoice(ORE, WOOD) - assertTrue(production1 == production2) - } - - @Test - fun hashCode_sameWhenSameContent() { - val production1 = Production() - val production2 = Production() - assertEquals(production1.hashCode(), production2.hashCode()) - production1.addFixedResource(GLASS, 1) - production2.addFixedResource(GLASS, 1) - assertEquals(production1.hashCode(), production2.hashCode()) - production1.addChoice(ORE, WOOD) - production2.addChoice(ORE, WOOD) - assertEquals(production1.hashCode(), production2.hashCode()) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt deleted file mode 100644 index 6ae4169a..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.junit.Test -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.ResourceType.CLAY -import org.luxons.sevenwonders.game.api.resources.ResourceType.WOOD -import org.luxons.sevenwonders.game.test.createTransaction -import kotlin.test.assertEquals - -class ResourceTransactionsTest { - - @Test - fun toTransactions() { - val transactionMap = mapOf( - Provider.LEFT_PLAYER to (1 of WOOD) + (1 of CLAY), - Provider.RIGHT_PLAYER to (1 of WOOD) - ) - - val expectedNormalized = setOf( - createTransaction(Provider.LEFT_PLAYER, WOOD, CLAY), - createTransaction(Provider.RIGHT_PLAYER, WOOD) - ) - - assertEquals(expectedNormalized, transactionMap.toTransactions().toSet()) - } - - private infix fun Int.of(type: ResourceType): Resources = resourcesOf(type to this) -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt deleted file mode 100644 index e53789b6..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt +++ /dev/null @@ -1,436 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.junit.Test -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.ResourceType.CLAY -import org.luxons.sevenwonders.game.api.resources.ResourceType.GLASS -import org.luxons.sevenwonders.game.api.resources.ResourceType.LOOM -import org.luxons.sevenwonders.game.api.resources.ResourceType.ORE -import org.luxons.sevenwonders.game.api.resources.ResourceType.PAPYRUS -import org.luxons.sevenwonders.game.api.resources.ResourceType.STONE -import org.luxons.sevenwonders.game.api.resources.ResourceType.WOOD -import java.util.NoSuchElementException -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class ResourcesTest { - - @Test - fun init_shouldBeEmpty() { - val resources = emptyResources() - for (resourceType in ResourceType.values()) { - assertEquals(0, resources[resourceType]) - } - assertEquals(0, resources.size) - assertTrue(resources.isEmpty()) - } - - @Test - fun add_zero() { - val resources = mutableResourcesOf() - resources.add(CLAY, 0) - assertEquals(0, resources[CLAY]) - assertEquals(0, resources.size) - assertTrue(resources.isEmpty()) - } - - @Test - fun add_simple() { - val resources = mutableResourcesOf() - resources.add(WOOD, 3) - assertEquals(3, resources[WOOD]) - assertEquals(3, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun add_multipleCallsStacked() { - val resources = mutableResourcesOf() - resources.add(ORE, 3) - resources.add(ORE, 2) - assertEquals(5, resources[ORE]) - assertEquals(5, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun add_interlaced() { - val resources = mutableResourcesOf() - resources.add(GLASS, 3) - resources.add(STONE, 1) - resources.add(WOOD, 4) - resources.add(GLASS, 2) - assertEquals(5, resources[GLASS]) - assertEquals(10, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun plus_zero() { - val resources = resourcesOf(CLAY to 2) - val resourcesPlusZero = resources + emptyResources() - val zeroPlusResources = emptyResources() + resources - - assertEquals(2, resourcesPlusZero[CLAY]) - assertEquals(2, resourcesPlusZero.size) - assertEquals(2, zeroPlusResources[CLAY]) - assertEquals(2, zeroPlusResources.size) - } - - @Test - fun plus_sameResource() { - val resources1 = resourcesOf(WOOD to 1) - val resources2 = resourcesOf(WOOD to 3) - val sum = resources1 + resources2 - - assertEquals(1, resources1.size) - assertEquals(3, resources2.size) - assertEquals(4, sum[WOOD]) - assertEquals(4, sum.size) - } - - @Test - fun plus_differentemptyResources() { - val resources1 = resourcesOf(WOOD to 1) - val resources2 = resourcesOf(ORE to 3) - val sum = resources1 + resources2 - - assertEquals(1, resources1.size) - assertEquals(3, resources2.size) - assertEquals(1, sum[WOOD]) - assertEquals(3, sum[ORE]) - assertEquals(4, sum.size) - } - - @Test - fun plus_overlappingemptyResources() { - val resources1 = resourcesOf(WOOD to 1) - val resources2 = resourcesOf(WOOD to 2, ORE to 4) - val sum = resources1 + resources2 - - assertEquals(1, resources1.size) - assertEquals(6, resources2.size) - assertEquals(3, sum[WOOD]) - assertEquals(4, sum[ORE]) - assertEquals(7, sum.size) - } - - @Test - fun remove_some() { - val resources = mutableResourcesOf(WOOD to 3) - resources.remove(WOOD, 2) - assertEquals(1, resources[WOOD]) - assertEquals(1, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun remove_all() { - val resources = mutableResourcesOf(WOOD to 3) - resources.remove(WOOD, 3) - assertEquals(0, resources[WOOD]) - assertEquals(0, resources.size) - assertTrue(resources.isEmpty()) - } - - @Test - fun remove_tooMany() { - val resources = mutableResourcesOf(WOOD to 2) - - assertFailsWith { - resources.remove(WOOD, 3) - } - } - - @Test - fun addAll_empty() { - val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - - val emptyResources = emptyResources() - - resources.add(emptyResources) - assertEquals(1, resources[STONE]) - assertEquals(3, resources[CLAY]) - assertEquals(0, resources[ORE]) - assertEquals(0, resources[GLASS]) - assertEquals(0, resources[LOOM]) - assertEquals(4, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun addAll_zeros() { - val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - - val emptyResources = resourcesOf(STONE to 0, CLAY to 0) - - resources.add(emptyResources) - assertEquals(1, resources[STONE]) - assertEquals(3, resources[CLAY]) - assertEquals(0, resources[ORE]) - assertEquals(0, resources[GLASS]) - assertEquals(0, resources[LOOM]) - assertEquals(4, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun addAll_same() { - val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - val resources2 = resourcesOf(STONE to 2, CLAY to 6) - - resources.add(resources2) - assertEquals(3, resources[STONE]) - assertEquals(9, resources[CLAY]) - assertEquals(0, resources[ORE]) - assertEquals(0, resources[GLASS]) - assertEquals(0, resources[LOOM]) - assertEquals(12, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun addAll_overlap() { - val resources = mutableResourcesOf(STONE to 1, CLAY to 3) - val resources2 = resourcesOf(CLAY to 6, ORE to 4) - - resources.add(resources2) - assertEquals(1, resources[STONE]) - assertEquals(9, resources[CLAY]) - assertEquals(4, resources[ORE]) - assertEquals(0, resources[GLASS]) - assertEquals(0, resources[LOOM]) - assertEquals(14, resources.size) - assertFalse(resources.isEmpty()) - } - - @Test - fun contains_emptyContainsEmpty() { - val emptyResources = emptyResources() - val emptyResources2 = emptyResources() - assertTrue(emptyResources.containsAll(emptyResources2)) - } - - @Test - fun contains_singleTypeContainsEmpty() { - val resources = resourcesOf(STONE to 1) - val emptyResources = emptyResources() - - assertTrue(resources.containsAll(emptyResources)) - } - - @Test - fun contains_multipleTypesContainsEmpty() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - val emptyResources = emptyResources() - - assertTrue(resources.containsAll(emptyResources)) - } - - @Test - fun contains_self() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - - assertTrue(resources.containsAll(resources)) - } - - @Test - fun contains_allOfEachType() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - val resources2 = resourcesOf(STONE to 1, CLAY to 3) - - assertTrue(resources.containsAll(resources2)) - } - - @Test - fun contains_someOfEachType() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(STONE to 1, CLAY to 3) - - assertTrue(resources.containsAll(resources2)) - } - - @Test - fun contains_someOfSomeTypes() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(CLAY to 3) - - assertTrue(resources.containsAll(resources2)) - } - - @Test - fun contains_allOfSomeTypes() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(CLAY to 4) - - assertTrue(resources.containsAll(resources2)) - } - - @Test - fun minus_empty() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - val emptyResources = emptyResources() - - val diff = resources.minus(emptyResources) - assertEquals(1, diff[STONE]) - assertEquals(3, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_self() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - - val diff = resources.minus(resources) - assertEquals(0, diff[STONE]) - assertEquals(0, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_allOfEachType() { - val resources = resourcesOf(STONE to 1, CLAY to 3) - val resources2 = resourcesOf(STONE to 1, CLAY to 3) - - val diff = resources.minus(resources2) - assertEquals(0, diff[STONE]) - assertEquals(0, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_someOfEachType() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(STONE to 1, CLAY to 3) - - val diff = resources.minus(resources2) - assertEquals(1, diff[STONE]) - assertEquals(1, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_someOfSomeTypes() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(CLAY to 3) - - val diff = resources.minus(resources2) - assertEquals(2, diff[STONE]) - assertEquals(1, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_allOfSomeTypes() { - val resources = resourcesOf(STONE to 2, CLAY to 4) - val resources2 = resourcesOf(CLAY to 4) - - val diff = resources.minus(resources2) - assertEquals(2, diff[STONE]) - assertEquals(0, diff[CLAY]) - assertEquals(0, diff[ORE]) - assertEquals(0, diff[GLASS]) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_tooMuchOfExistingType() { - val resources = resourcesOf(CLAY to 4) - val resources2 = resourcesOf(CLAY to 5) - - val diff = resources.minus(resources2) - assertEquals(0, diff[CLAY]) - } - - @Test - fun minus_someOfAnAbsentType() { - val resources = emptyResources() - val resources2 = resourcesOf(LOOM to 5) - - val diff = resources.minus(resources2) - assertEquals(0, diff[LOOM]) - } - - @Test - fun minus_someOfATypeWithZero() { - val resources = resourcesOf(LOOM to 0) - val resources2 = resourcesOf(LOOM to 5) - - val diff = resources.minus(resources2) - assertEquals(0, diff[LOOM]) - } - - @Test - fun isEmpty_noElement() { - val resources = emptyResources() - assertTrue(resources.isEmpty()) - } - - @Test - fun isEmpty_singleZeroElement() { - val resources = resourcesOf(LOOM to 0) - assertTrue(resources.isEmpty()) - } - - @Test - fun isEmpty_multipleZeroElements() { - val resources = resourcesOf(WOOD to 0, ORE to 0, LOOM to 0) - assertTrue(resources.isEmpty()) - } - - @Test - fun isEmpty_singleElementMoreThanZero() { - val resources = resourcesOf(LOOM to 3) - assertFalse(resources.isEmpty()) - } - - @Test - fun isEmpty_mixedZeroAndNonZeroElements() { - val resources = resourcesOf(WOOD to 0, LOOM to 3) - assertFalse(resources.isEmpty()) - } - - @Test - fun isEmpty_mixedZeroAndNonZeroElements_reverseOrder() { - val resources = resourcesOf(ORE to 3, PAPYRUS to 0) - assertFalse(resources.isEmpty()) - } - - @Test - fun equals_trueWhenSame() { - val resources = emptyResources() - assertEquals(resources, resources) - } - - @Test - fun equals_trueWhenSameContent() { - val resources1 = mutableResourcesOf() - val resources2 = mutableResourcesOf() - assertTrue(resources1 == resources2) - resources1.add(GLASS, 1) - resources2.add(GLASS, 1) - assertTrue(resources1 == resources2) - } - - @Test - fun hashCode_sameWhenSameContent() { - val resources1 = mutableResourcesOf() - val resources2 = mutableResourcesOf() - assertEquals(resources1.hashCode(), resources2.hashCode()) - resources1.add(GLASS, 1) - resources2.add(GLASS, 1) - assertEquals(resources1.hashCode(), resources2.hashCode()) - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt deleted file mode 100644 index 99f85ac2..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt +++ /dev/null @@ -1,129 +0,0 @@ -package org.luxons.sevenwonders.game.resources - -import org.junit.Assume.assumeTrue -import org.junit.experimental.theories.DataPoints -import org.junit.experimental.theories.Theories -import org.junit.experimental.theories.Theory -import org.junit.runner.RunWith -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.noTransactions -import org.luxons.sevenwonders.game.test.createTransaction -import org.luxons.sevenwonders.game.test.createTransactions -import kotlin.test.assertEquals - -@RunWith(Theories::class) -class TradingRulesTest { - - @Theory - fun setCost_overridesCost( - defaultCost: Int, - overriddenCost: Int, - overriddenProvider: Provider, - provider: Provider, - type: ResourceType - ) { - assumeTrue(defaultCost != overriddenCost) - assumeTrue(overriddenProvider != provider) - - val rules = TradingRules(defaultCost) - rules.setCost(type, overriddenProvider, overriddenCost) - - assertEquals(overriddenCost, rules.getCost(type, overriddenProvider)) - assertEquals(defaultCost, rules.getCost(type, provider)) - } - - @Theory - fun computeCost_zeroForNoResources(defaultCost: Int) { - val rules = TradingRules(defaultCost) - assertEquals(0, rules.computeCost(noTransactions())) - } - - @Theory - fun computeCost_defaultCostWhenNoOverride(defaultCost: Int, provider: Provider, type: ResourceType) { - val rules = TradingRules(defaultCost) - val transactions = createTransactions(provider, type) - assertEquals(defaultCost, rules.computeCost(transactions)) - } - - @Theory - fun computeCost_twiceDefaultFor2Resources(defaultCost: Int, provider: Provider, type: ResourceType) { - val rules = TradingRules(defaultCost) - val transactions = createTransactions(provider, type, type) - assertEquals(2 * defaultCost, rules.computeCost(transactions)) - } - - @Theory - fun computeCost_overriddenCost(defaultCost: Int, overriddenCost: Int, provider: Provider, type: ResourceType) { - val rules = TradingRules(defaultCost) - rules.setCost(type, provider, overriddenCost) - val transactions = createTransactions(provider, type) - assertEquals(overriddenCost, rules.computeCost(transactions)) - } - - @Theory - fun computeCost_defaultCostWhenOverrideOnOtherProviderOrType( - defaultCost: Int, - overriddenCost: Int, - overriddenProvider: Provider, - overriddenType: ResourceType, - provider: Provider, - type: ResourceType - ) { - assumeTrue(overriddenProvider != provider || overriddenType != type) - val rules = TradingRules(defaultCost) - rules.setCost(overriddenType, overriddenProvider, overriddenCost) - val transactions = createTransactions(provider, type) - assertEquals(defaultCost, rules.computeCost(transactions)) - } - - @Theory - fun computeCost_oneDefaultAndOneOverriddenType( - defaultCost: Int, - overriddenCost: Int, - overriddenType: ResourceType, - provider: Provider, - type: ResourceType - ) { - assumeTrue(overriddenType != type) - val rules = TradingRules(defaultCost) - rules.setCost(overriddenType, provider, overriddenCost) - val transactions = createTransactions(provider, overriddenType, type) - assertEquals(defaultCost + overriddenCost, rules.computeCost(transactions)) - } - - @Theory - fun computeCost_oneDefaultAndOneOverriddenProvider( - defaultCost: Int, - overriddenCost: Int, - overriddenProvider: Provider, - provider: Provider, - type: ResourceType - ) { - assumeTrue(overriddenProvider != provider) - val rules = TradingRules(defaultCost) - rules.setCost(type, overriddenProvider, overriddenCost) - - val boughtResources = createTransactions( - createTransaction(provider, type), - createTransaction(overriddenProvider, type) - ) - - assertEquals(defaultCost + overriddenCost, rules.computeCost(boughtResources)) - } - - companion object { - - @JvmStatic - @DataPoints - fun costs(): IntArray = intArrayOf(0, 1, 2) - - @JvmStatic - @DataPoints - fun providers(): Array = Provider.values() - - @JvmStatic - @DataPoints - fun resourceTypes(): Array = ResourceType.values() - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt deleted file mode 100644 index e666140e..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt +++ /dev/null @@ -1,143 +0,0 @@ -package org.luxons.sevenwonders.game.test - -import org.luxons.sevenwonders.game.Player -import org.luxons.sevenwonders.game.PlayerContext -import org.luxons.sevenwonders.game.Settings -import org.luxons.sevenwonders.game.api.resources.CountedResource -import org.luxons.sevenwonders.game.api.CustomizableSettings -import org.luxons.sevenwonders.game.api.PlayerMove -import org.luxons.sevenwonders.game.boards.Board -import org.luxons.sevenwonders.game.api.boards.RelativeBoardPosition -import org.luxons.sevenwonders.game.boards.Science -import org.luxons.sevenwonders.game.boards.ScienceType -import org.luxons.sevenwonders.game.boards.Table -import org.luxons.sevenwonders.game.cards.Card -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.api.cards.Color -import org.luxons.sevenwonders.game.cards.Requirements -import org.luxons.sevenwonders.game.effects.Effect -import org.luxons.sevenwonders.game.effects.ScienceProgress -import org.luxons.sevenwonders.game.moves.Move -import org.luxons.sevenwonders.game.api.MoveType -import org.luxons.sevenwonders.game.moves.resolve -import org.luxons.sevenwonders.game.resources.Production -import org.luxons.sevenwonders.game.api.resources.Provider -import org.luxons.sevenwonders.game.api.resources.ResourceTransaction -import org.luxons.sevenwonders.game.api.resources.ResourceTransactions -import org.luxons.sevenwonders.game.api.resources.ResourceType -import org.luxons.sevenwonders.game.api.resources.noTransactions -import org.luxons.sevenwonders.game.resources.resourcesOf -import org.luxons.sevenwonders.game.wonders.Wonder -import org.luxons.sevenwonders.game.wonders.WonderStage - -private const val SEED: Long = 42 - -internal fun testCustomizableSettings(initialGold: Int = 0): CustomizableSettings = - CustomizableSettings(randomSeedForTests = SEED).copy(initialGold = initialGold) - -internal fun testSettings(nbPlayers: Int = 5, initialGold: Int = 0): Settings = - Settings(nbPlayers, testCustomizableSettings(initialGold)) - -internal fun testTable(nbPlayers: Int = 5): Table = testTable(testSettings(nbPlayers)) - -internal fun testTable(settings: Settings): Table = - Table(testBoards(settings.nbPlayers, settings)) - -private fun testBoards(count: Int, settings: Settings): List = List(count) { testBoard(settings) } - -internal fun testBoard( - initialResource: ResourceType = ResourceType.WOOD, - initialGold: Int = 0, - vararg production: ResourceType -): Board { - val settings = testSettings(initialGold = initialGold) - val board = testBoard(settings, initialResource) - board.production.addAll(fixedProduction(*production)) - return board -} - -private fun testBoard(settings: Settings, initialResource: ResourceType = ResourceType.WOOD): Board = - Board(testWonder(initialResource), 0, settings) - -internal fun testWonder(initialResource: ResourceType = ResourceType.WOOD): Wonder { - val stage1 = WonderStage(Requirements(), emptyList()) - val stage2 = WonderStage(Requirements(), emptyList()) - val stage3 = WonderStage(Requirements(), emptyList()) - return Wonder("Test Wonder ${initialResource.symbol}", initialResource, listOf(stage1, stage2, stage3), "") -} - -internal fun fixedProduction(vararg producedTypes: ResourceType): Production = - Production().apply { addAll(resourcesOf(*producedTypes)) } - -internal fun createTransactions(provider: Provider, vararg resources: ResourceType): ResourceTransactions = - createTransactions(createTransaction(provider, *resources)) - -internal fun createTransactions(vararg transactions: ResourceTransaction): ResourceTransactions = transactions.toSet() - -internal fun createTransaction(provider: Provider, vararg resources: ResourceType): ResourceTransaction = - ResourceTransaction( - provider, - resources.map { CountedResource(1, it) }) - -internal fun createRequirements(vararg types: ResourceType): Requirements = Requirements(resources = resourcesOf(*types)) - -internal fun sampleCards(nbCards: Int, fromIndex: Int = 0, color: Color = Color.BLUE): List = - List(nbCards) { i -> testCard("Test Card ${fromIndex + i}", color) } - -internal fun createGuildCards(count: Int): List = List(count) { createGuildCard(it) } - -internal fun createGuildCard(num: Int, effect: Effect? = null): Card = - testCard("Test Guild $num", Color.PURPLE, effect = effect) - -internal fun testCard( - name: String = "Test Card", - color: Color = Color.BLUE, - requirements: Requirements = Requirements(), - effect: Effect? = null -): Card { - val effects = if (effect == null) emptyList() else listOf(effect) - return Card(name, color, requirements, effects, null, emptyList(), "path/to/card/image", - CardBack("image-III") - ) -} - -internal fun addCards(board: Board, nbCardsOfColor: Int, nbOtherCards: Int, color: Color) { - addCards(board, nbCardsOfColor, color) - addCards(board, nbOtherCards, getDifferentColorFrom(color)) -} - -internal fun addCards(board: Board, nbCards: Int, color: Color) { - sampleCards(nbCards, color = color).forEach { board.addCard(it) } -} - -internal fun getDifferentColorFrom(vararg colors: Color): Color = - Color.values().firstOrNull { it !in colors } ?: throw IllegalArgumentException("All colors are forbidden!") - -internal fun createScienceProgress(compasses: Int, wheels: Int, tablets: Int, jokers: Int): ScienceProgress = - ScienceProgress(createScience(compasses, wheels, tablets, jokers)) - -internal fun createScience(compasses: Int, wheels: Int, tablets: Int, jokers: Int): Science = Science().apply { - add(ScienceType.COMPASS, compasses) - add(ScienceType.WHEEL, wheels) - add(ScienceType.TABLET, tablets) - addJoker(jokers) -} - -internal fun playCardWithEffect(player: Player, color: Color, effect: Effect) { - val card = testCard(color = color, effect = effect) - player.board.addCard(card) - card.applyTo(player, noTransactions()) -} - -internal fun createMove(context: PlayerContext, card: Card, type: MoveType): Move = - type.resolve(PlayerMove(type, card.name), card, context) - -internal fun singleBoardPlayer(board: Board): Player = object : Player { - override val index = 0 - override val board = board - override fun getBoard(relativePosition: RelativeBoardPosition): Board = when (relativePosition) { - RelativeBoardPosition.LEFT -> throw RuntimeException("No LEFT board") - RelativeBoardPosition.SELF -> this.board - RelativeBoardPosition.RIGHT -> throw RuntimeException("No RIGHT board") - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt deleted file mode 100644 index 156055ff..00000000 --- a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.luxons.sevenwonders.game.wonders - -import org.junit.Test -import org.luxons.sevenwonders.game.api.cards.CardBack -import org.luxons.sevenwonders.game.test.testWonder -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class WonderTest { - - @Test - fun buildLevel_increasesNbBuiltStages() { - val wonder = testWonder() - assertEquals(0, wonder.nbBuiltStages) - wonder.placeCard(CardBack("img")) - assertEquals(1, wonder.nbBuiltStages) - wonder.placeCard(CardBack("img")) - assertEquals(2, wonder.nbBuiltStages) - wonder.placeCard(CardBack("img")) - assertEquals(3, wonder.nbBuiltStages) - } - - @Test - fun buildLevel_failsIfFull() { - val wonder = testWonder() - wonder.placeCard(CardBack("img")) - wonder.placeCard(CardBack("img")) - wonder.placeCard(CardBack("img")) - - assertFailsWith(IllegalStateException::class) { - wonder.placeCard(CardBack("img")) - } - } -} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/BoardsKtTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/BoardsKtTest.kt new file mode 100644 index 00000000..1dd19704 --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/BoardsKtTest.kt @@ -0,0 +1,86 @@ +package org.luxons.sevenwonders.model + +import org.luxons.sevenwonders.engine.converters.toColumns +import org.luxons.sevenwonders.engine.converters.toTableCard +import org.luxons.sevenwonders.engine.test.testCard +import org.luxons.sevenwonders.model.cards.Color +import org.luxons.sevenwonders.model.cards.TableCard +import kotlin.test.Test +import kotlin.test.assertEquals + +class BoardsKtTest { + + @Test + fun `toColumns on empty list should return no cols`() { + val cols = emptyList().toColumns() + assertEquals(emptyList(), cols) + } + + @Test + fun `toColumns with single resource should return a single column`() { + val card = testCard(color = Color.BROWN).toTableCard() + val cols = listOf(card).toColumns() + assertEquals(listOf(listOf(card)), cols) + } + + @Test + fun `toColumns with single non-resource should return a single column`() { + val card = testCard(color = Color.BLUE).toTableCard() + val cols = listOf(card).toColumns() + assertEquals(listOf(listOf(card)), cols) + } + + @Test + fun `toColumns with two same-color cards should return a single column`() { + val card1 = testCard(color = Color.BLUE).toTableCard() + val card2 = testCard(color = Color.BLUE).toTableCard() + val cards = listOf(card1, card2) + val cols = cards.toColumns() + assertEquals(listOf(cards), cols) + } + + @Test + fun `toColumns with two resource cards should return a single column`() { + val card1 = testCard(color = Color.BROWN).toTableCard() + val card2 = testCard(color = Color.GREY).toTableCard() + val cards = listOf(card1, card2) + val cols = cards.toColumns() + assertEquals(listOf(cards), cols) + } + + @Test + fun `toColumns with 2 different non-resource cards should return 2 columns`() { + val card1 = testCard(color = Color.BLUE).toTableCard() + val card2 = testCard(color = Color.GREEN).toTableCard() + val cards = listOf(card1, card2) + val cols = cards.toColumns() + assertEquals(listOf(listOf(card1), listOf(card2)), cols) + } + + @Test + fun `toColumns with 1 res and 1 non-res card should return 2 columns`() { + val card1 = testCard(color = Color.BROWN).toTableCard() + val card2 = testCard(color = Color.GREEN).toTableCard() + val cards = listOf(card1, card2) + val cols = cards.toColumns() + assertEquals(listOf(listOf(card1), listOf(card2)), cols) + } + + @Test + fun `toColumns should return 1 col for res cards and 1 for each other color`() { + val res1 = testCard(color = Color.BROWN).toTableCard() + val res2 = testCard(color = Color.BROWN).toTableCard() + val res3 = testCard(color = Color.GREY).toTableCard() + val blue1 = testCard(color = Color.BLUE).toTableCard() + val green1 = testCard(color = Color.GREEN).toTableCard() + val green2 = testCard(color = Color.GREEN).toTableCard() + val cards = listOf(res1, green1, green2, res2, blue1, res3) + val cols = cards.toColumns() + val expectedCols = listOf( + listOf(res1, res2, res3), + listOf(blue1), + listOf(green1, green2) + ) + assertEquals(expectedCols, cols) + } +} diff --git a/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/TableTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/TableTest.kt new file mode 100644 index 00000000..dc461c4d --- /dev/null +++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/model/TableTest.kt @@ -0,0 +1,71 @@ +package org.luxons.sevenwonders.model + +import org.junit.Assume.assumeTrue +import org.junit.experimental.theories.DataPoints +import org.junit.experimental.theories.Theories +import org.junit.experimental.theories.Theory +import org.junit.runner.RunWith +import org.luxons.sevenwonders.model.boards.RelativeBoardPosition +import org.luxons.sevenwonders.engine.test.createGuildCards +import org.luxons.sevenwonders.engine.test.testTable +import kotlin.test.assertEquals + +@RunWith(Theories::class) +class TableTest { + + @Theory + fun getBoard_wrapLeft(nbPlayers: Int) { + assumeTrue(nbPlayers >= 2) + val table = testTable(nbPlayers) + val last = nbPlayers - 1 + assertEquals(table.getBoard(last), table.getBoard(0, RelativeBoardPosition.LEFT)) + assertEquals(table.getBoard(0), table.getBoard(0, RelativeBoardPosition.SELF)) + assertEquals(table.getBoard(1), table.getBoard(0, RelativeBoardPosition.RIGHT)) + } + + @Theory + fun getBoard_wrapRight(nbPlayers: Int) { + assumeTrue(nbPlayers >= 2) + val table = testTable(nbPlayers) + val last = nbPlayers - 1 + assertEquals(table.getBoard(last - 1), table.getBoard(last, RelativeBoardPosition.LEFT)) + assertEquals(table.getBoard(last), table.getBoard(last, RelativeBoardPosition.SELF)) + assertEquals(table.getBoard(0), table.getBoard(last, RelativeBoardPosition.RIGHT)) + } + + @Theory + fun getBoard_noWrap(nbPlayers: Int) { + assumeTrue(nbPlayers >= 3) + val table = testTable(nbPlayers) + assertEquals(table.getBoard(0), table.getBoard(1, RelativeBoardPosition.LEFT)) + assertEquals(table.getBoard(1), table.getBoard(1, RelativeBoardPosition.SELF)) + assertEquals(table.getBoard(2), table.getBoard(1, RelativeBoardPosition.RIGHT)) + } + + @Theory + fun getNeighbourGuildCards(nbPlayers: Int) { + assumeTrue(nbPlayers >= 4) + val table = testTable(nbPlayers) + val guildCards = createGuildCards(4) + table.getBoard(0).addCard(guildCards[0]) + table.getBoard(0).addCard(guildCards[1]) + table.getBoard(1).addCard(guildCards[2]) + table.getBoard(2).addCard(guildCards[3]) + + val neighbourCards0 = table.getNeighbourGuildCards(0) + assertEquals(listOf(guildCards[2]), neighbourCards0) + + val neighbourCards1 = table.getNeighbourGuildCards(1) + assertEquals(guildCards - guildCards[2], neighbourCards1) + + val neighbourCards2 = table.getNeighbourGuildCards(2) + assertEquals(listOf(guildCards[2]), neighbourCards2) + } + + companion object { + + @JvmStatic + @DataPoints + fun nbPlayers(): IntArray = intArrayOf(2, 3, 4, 5, 6, 7, 8) + } +} -- cgit