summaryrefslogtreecommitdiff
path: root/sw-engine/src/test/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'sw-engine/src/test/kotlin')
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt123
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt84
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt71
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt211
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt57
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt45
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt114
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt14
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt42
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt104
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt14
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt127
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt162
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt20
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderSidePickMethodTest.kt96
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt147
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt192
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt207
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt56
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt80
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt99
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt157
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt142
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt69
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt43
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt44
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt73
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt29
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt49
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt93
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt82
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt137
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt292
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt27
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt435
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt126
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt137
-rw-r--r--sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt34
38 files changed, 4034 insertions, 0 deletions
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
new file mode 100644
index 00000000..a63ccdd6
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/GameTest.kt
@@ -0,0 +1,123 @@
+package org.luxons.sevenwonders.game
+
+import org.junit.Test
+import org.luxons.sevenwonders.game.api.Action
+import org.luxons.sevenwonders.game.cards.HandCard
+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.cards.TableCard
+import org.luxons.sevenwonders.game.data.GameDefinition
+import org.luxons.sevenwonders.game.data.LAST_AGE
+import org.luxons.sevenwonders.game.moves.MoveType
+import org.luxons.sevenwonders.game.resources.ResourceTransactions
+import org.luxons.sevenwonders.game.resources.noTransactions
+import org.luxons.sevenwonders.game.test.testCustomizableSettings
+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
new file mode 100644
index 00000000..244c30a8
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/BoardsKtTest.kt
@@ -0,0 +1,84 @@
+package org.luxons.sevenwonders.game.api
+
+import org.luxons.sevenwonders.game.cards.Color
+import org.luxons.sevenwonders.game.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<TableCard>().toColumns()
+ assertEquals(emptyList<List<TableCard>>(), 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
new file mode 100644
index 00000000..19e4e8e8
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/api/TableTest.kt
@@ -0,0 +1,71 @@
+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.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
new file mode 100644
index 00000000..d1b7c239
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/BoardTest.kt
@@ -0,0 +1,211 @@
+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.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.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<InsufficientFundsException> {
+ 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<IllegalArgumentException> {
+ 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> = ResourceType.values()
+
+ @JvmStatic
+ @DataPoints
+ fun colors(): Array<Color> = Color.values()
+
+ @JvmStatic
+ @DataPoints
+ fun specialAbilities(): Array<SpecialAbility> = 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
new file mode 100644
index 00000000..248d43dd
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/MilitaryTest.kt
@@ -0,0 +1,57 @@
+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<UnknownAgeException> {
+ 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
new file mode 100644
index 00000000..2038a676
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/RelativeBoardPositionTest.kt
@@ -0,0 +1,45 @@
+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 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
new file mode 100644
index 00000000..80d6773d
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/boards/ScienceTest.kt
@@ -0,0 +1,114 @@
+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<IntArray> = 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
new file mode 100644
index 00000000..66ff7a0e
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardBackTest.kt
@@ -0,0 +1,14 @@
+package org.luxons.sevenwonders.game.cards
+
+import org.junit.Test
+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
new file mode 100644
index 00000000..b6fecbd0
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/CardTest.kt
@@ -0,0 +1,42 @@
+package org.luxons.sevenwonders.game.cards
+
+import org.junit.Test
+import org.luxons.sevenwonders.game.SimplePlayer
+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.resources.ResourceType
+import org.luxons.sevenwonders.game.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
new file mode 100644
index 00000000..f6c45720
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/DecksTest.kt
@@ -0,0 +1,104 @@
+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<IllegalArgumentException> {
+ decks.getCard(0, "")
+ }
+ }
+
+ @Test
+ fun getCard_failsWhenDeckIsEmpty() {
+ val decks = createDecks(0, 0)
+ assertFailsWith<IllegalArgumentException> {
+ decks.getCard(0, "Any name")
+ }
+ }
+
+ @Test
+ fun getCard_failsWhenCardIsNotFound() {
+ val decks = createDecks(3, 20)
+ assertFailsWith<CardNotFoundException> {
+ 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<IllegalArgumentException> {
+ decks.deal(1, 0)
+ }
+ }
+
+ @Test
+ fun deal_failsOnMissingAge() {
+ val decks = createDecks(2, 0)
+ assertFailsWith<IllegalArgumentException> {
+ decks.deal(4, 10)
+ }
+ }
+
+ @Theory
+ fun deal_failsWhenTooFewPlayers(nbPlayers: Int, nbCards: Int) {
+ assumeTrue(nbCards % nbPlayers != 0)
+ val decks = createDecks(1, nbCards)
+ assertFailsWith<IllegalArgumentException> {
+ 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
new file mode 100644
index 00000000..4582c4a1
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandRotationDirectionTest.kt
@@ -0,0 +1,14 @@
+package org.luxons.sevenwonders.game.cards
+
+import org.junit.Test
+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
new file mode 100644
index 00000000..c7ff9106
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/HandsTest.kt
@@ -0,0 +1,127 @@
+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.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<IndexOutOfBoundsException> { 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
new file mode 100644
index 00000000..eccca3e7
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/cards/RequirementsTest.kt
@@ -0,0 +1,162 @@
+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.resources.Provider
+import org.luxons.sevenwonders.game.resources.ResourceType
+import org.luxons.sevenwonders.game.resources.emptyResources
+import org.luxons.sevenwonders.game.resources.noTransactions
+import org.luxons.sevenwonders.game.test.createRequirements
+import org.luxons.sevenwonders.game.test.createTransactions
+import org.luxons.sevenwonders.game.test.singleBoardPlayer
+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> = 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
new file mode 100644
index 00000000..4317a933
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/GameDefinitionTest.kt
@@ -0,0 +1,20 @@
+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/definitions/WonderSidePickMethodTest.kt b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderSidePickMethodTest.kt
new file mode 100644
index 00000000..0b561938
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/definitions/WonderSidePickMethodTest.kt
@@ -0,0 +1,96 @@
+package org.luxons.sevenwonders.game.data.definitions
+
+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.api.WonderSidePickMethod
+import java.util.Random
+import kotlin.test.assertEquals
+
+@RunWith(Theories::class)
+class WonderSidePickMethodTest {
+
+ private lateinit var random: Random
+
+ private lateinit var random2: Random
+
+ @Before
+ fun setUp() {
+ random = Random(123) // starts with TRUE
+ random2 = Random(123456) // starts with FALSE
+ }
+
+ @Test
+ fun pick_allA() {
+ var side: WonderSide? = null
+ repeat(10) {
+ side = WonderSidePickMethod.ALL_A.pickSide(random, side)
+ assertEquals(WonderSide.A, side)
+ }
+ }
+
+ @Test
+ fun pick_allB() {
+ var side: WonderSide? = null
+ repeat(10) {
+ side = WonderSidePickMethod.ALL_B.pickSide(random, side)
+ assertEquals(WonderSide.B, side)
+ }
+ }
+
+ @Test
+ fun pick_eachRandom() {
+ var side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, null)
+ assertEquals(WonderSide.A, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, side)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, side)
+ assertEquals(WonderSide.A, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, side)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, side)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random, side)
+ assertEquals(WonderSide.A, side)
+ }
+
+ @Test
+ fun pick_eachRandom2() {
+ var side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, null)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, side)
+ assertEquals(WonderSide.A, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, side)
+ assertEquals(WonderSide.A, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, side)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, side)
+ assertEquals(WonderSide.B, side)
+ side = WonderSidePickMethod.EACH_RANDOM.pickSide(random2, side)
+ assertEquals(WonderSide.B, side)
+ }
+
+ @Theory
+ fun pick_allSameRandom_sameAsFirst(firstSide: WonderSide) {
+ var side = firstSide
+ repeat(10) {
+ side = WonderSidePickMethod.SAME_RANDOM_FOR_ALL.pickSide(random, side)
+ assertEquals(firstSide, side)
+ }
+ }
+
+ @Test
+ fun pick_allSameRandom_firstIsRandom() {
+ assertEquals(WonderSide.A, WonderSidePickMethod.SAME_RANDOM_FOR_ALL.pickSide(random, null))
+ assertEquals(WonderSide.B, WonderSidePickMethod.SAME_RANDOM_FOR_ALL.pickSide(random2, null))
+ }
+
+ companion object {
+
+ @DataPoints
+ fun sides(): Array<WonderSide> = WonderSide.values()
+ }
+}
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
new file mode 100644
index 00000000..9b44fad2
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/NumericEffectSerializerTest.kt
@@ -0,0 +1,147 @@
+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<IllegalArgumentException> {
+ 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<MilitaryReinforcements>(count.toString()))
+ }
+
+ @Theory
+ fun deserialize_rawPointsIncrease(count: Int) {
+ val points = RawPointsIncrease(count)
+ assertEquals(points, gson.fromJson<RawPointsIncrease>(count.toString()))
+ }
+
+ @Theory
+ fun deserialize_goldIncrease(count: Int) {
+ val goldIncrease = GoldIncrease(count)
+ assertEquals(goldIncrease, gson.fromJson<GoldIncrease>(count.toString()))
+ }
+
+ @Test
+ fun deserialize_militaryReinforcements_failOnEmptyString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<MilitaryReinforcements>("\"\"")
+ }
+ }
+
+ @Test
+ fun deserialize_rawPointsIncrease_failOnEmptyString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<RawPointsIncrease>("\"\"")
+ }
+ }
+
+ @Test
+ fun deserialize_goldIncrease_failOnEmptyString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<GoldIncrease>("\"\"")
+ }
+ }
+
+ @Test
+ fun deserialize_militaryReinforcements_failOnNonNumericString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<MilitaryReinforcements>("\"abc\"")
+ }
+ }
+
+ @Test
+ fun deserialize_rawPointsIncrease_failOnNonNumericString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<RawPointsIncrease>("\"abc\"")
+ }
+ }
+
+ @Test
+ fun deserialize_goldIncrease_failOnNonNumericString() {
+ assertFailsWith<NumberFormatException> {
+ gson.fromJson<GoldIncrease>("\"abc\"")
+ }
+ }
+
+ @Test
+ fun deserialize_failOnUnknownType() {
+ assertFailsWith<IllegalArgumentException> {
+ gson.fromJson<ProductionIncrease>("\"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
new file mode 100644
index 00000000..31d695e8
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionIncreaseSerializerTest.kt
@@ -0,0 +1,192 @@
+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.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<List<ResourceType>>() {}.type
+ gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer())
+ .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer())
+ .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer())
+ .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer())
+ .registerTypeAdapter(Production::class.java, ProductionSerializer())
+ .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<IllegalArgumentException> {
+ gson.toJson(prodIncrease)
+ }
+ }
+
+ @Test
+ fun serialize_failIfMixedFixedAndChoices() {
+ val prodIncrease = create(true, 1, 0, 0)
+ prodIncrease.production.addChoice(ResourceType.WOOD, ResourceType.CLAY)
+ assertFailsWith<IllegalArgumentException> {
+ 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<ProductionIncrease>("\"this is garbage\"")
+ }
+ }
+
+ @Test
+ fun deserialize_failOnGarbageStringWithSlashes() {
+ assertFailsWith(IllegalArgumentException::class) {
+ gson.fromJson<ProductionIncrease>("\"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<ProductionIncrease>("\"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
new file mode 100644
index 00000000..265087ba
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ProductionSerializerTest.kt
@@ -0,0 +1,207 @@
+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.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<List<ResourceType>>() {}.type
+ gson = GsonBuilder().registerTypeAdapter(Resources::class.java, ResourcesSerializer())
+ .registerTypeAdapter(MutableResources::class.java, ResourcesSerializer())
+ .registerTypeAdapter(ResourceType::class.java, ResourceTypeSerializer())
+ .registerTypeAdapter(resourceTypeList, ResourceTypesSerializer())
+ .registerTypeAdapter(Production::class.java, ProductionSerializer()).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<IllegalArgumentException> {
+ gson.toJson(production)
+ }
+ }
+
+ @Test
+ fun serialize_failIfMixedFixedAndChoices() {
+ val production = create(1, 0, 0)
+ production.addChoice(ResourceType.WOOD, ResourceType.CLAY)
+ assertFailsWith<IllegalArgumentException> {
+ 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<IllegalArgumentException> {
+ gson.fromJson<Production>("\"this is garbage\"")
+ }
+ }
+
+ @Test
+ fun deserialize_failOnGarbageStringWithSlashes() {
+ assertFailsWith<IllegalArgumentException> {
+ gson.fromJson<Production>("\"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<IllegalArgumentException> {
+ gson.fromJson<Production>("\"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
new file mode 100644
index 00000000..f2b07e84
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypeSerializerTest.kt
@@ -0,0 +1,56 @@
+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.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<IllegalArgumentException> {
+ gson.fromJson<ResourceType>("\"\"")
+ }
+ }
+
+ @Test
+ fun deserialize_failsOnGarbageString() {
+ assertFailsWith<IllegalArgumentException> {
+ gson.fromJson<ResourceType>("\"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
new file mode 100644
index 00000000..8c1b421d
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourceTypesSerializerTest.kt
@@ -0,0 +1,80 @@
+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.resources.ResourceType
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class ResourceTypesSerializerTest {
+
+ private lateinit var gson: Gson
+
+ @Before
+ fun setUp() {
+ gson = GsonBuilder().registerTypeAdapter(typeToken<List<ResourceType>>(), ResourceTypesSerializer()).create()
+ }
+
+ @Test
+ fun serialize_null() {
+ assertEquals("null", gson.toJson(null, typeToken<List<ResourceType>>()))
+ }
+
+ @Test
+ fun serialize_emptyList() {
+ val types = emptyList<ResourceType>()
+ 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<List<ResourceType>>()))
+ }
+
+ @Test
+ fun deserialize_emptyList() {
+ val types = emptyList<ResourceType>()
+ 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
new file mode 100644
index 00000000..c146a948
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ResourcesSerializerTest.kt
@@ -0,0 +1,99 @@
+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.resources.ResourceType.CLAY
+import org.luxons.sevenwonders.game.resources.ResourceType.STONE
+import org.luxons.sevenwonders.game.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
new file mode 100644
index 00000000..95d72517
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/data/serializers/ScienceProgressSerializerTest.kt
@@ -0,0 +1,157 @@
+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<UnsupportedOperationException> {
+ val progress = createScienceProgress(2, 0, 0, 0)
+ gson.toJson(progress)
+ }
+ }
+
+ @Test
+ fun serialize_failOnMultipleWheels() {
+ assertFailsWith<UnsupportedOperationException> {
+ val progress = createScienceProgress(0, 2, 0, 0)
+ gson.toJson(progress)
+ }
+ }
+
+ @Test
+ fun serialize_failOnMultipleTablets() {
+ assertFailsWith<UnsupportedOperationException> {
+ val progress = createScienceProgress(0, 0, 2, 0)
+ gson.toJson(progress)
+ }
+ }
+
+ @Test
+ fun serialize_failOnMultipleJokers() {
+ assertFailsWith<UnsupportedOperationException> {
+ val progress = createScienceProgress(0, 0, 0, 2)
+ gson.toJson(progress)
+ }
+ }
+
+ @Test
+ fun serialize_failOnMixedElements() {
+ assertFailsWith<UnsupportedOperationException> {
+ val progress = createScienceProgress(1, 1, 0, 0)
+ gson.toJson(progress)
+ }
+ }
+
+ @Test
+ fun deserialize_failOnEmptyString() {
+ assertFailsWith<IllegalArgumentException> {
+ gson.fromJson<ScienceProgress>("\"\"")
+ }
+ }
+
+ @Test
+ fun deserialize_failOnGarbageString() {
+ assertFailsWith<IllegalArgumentException> {
+ gson.fromJson<ScienceProgress>("thisisgarbage")
+ }
+ }
+
+ @Test
+ fun deserialize_compass() {
+ val progress = gson.fromJson<ScienceProgress>(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<ScienceProgress>(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<ScienceProgress>(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<ScienceProgress>(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
new file mode 100644
index 00000000..700eddb1
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/BonusPerBoardElementTest.kt
@@ -0,0 +1,142 @@
+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.boards.RelativeBoardPosition
+import org.luxons.sevenwonders.game.boards.Table
+import org.luxons.sevenwonders.game.cards.CardBack
+import org.luxons.sevenwonders.game.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> = Color.values()
+
+ @JvmStatic
+ @DataPoints
+ fun positions(): Array<RelativeBoardPosition> = 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
new file mode 100644
index 00000000..d92c8d24
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/DiscountTest.kt
@@ -0,0 +1,69 @@
+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.resources.Provider
+import org.luxons.sevenwonders.game.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> = ResourceType.values()
+
+ @JvmStatic
+ @DataPoints
+ fun providers(): Array<Provider> = 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
new file mode 100644
index 00000000..993cc273
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/GoldIncreaseTest.kt
@@ -0,0 +1,43 @@
+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.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> = 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
new file mode 100644
index 00000000..0d5765da
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/MilitaryReinforcementsTest.kt
@@ -0,0 +1,44 @@
+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.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> = 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
new file mode 100644
index 00000000..c016ccc9
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ProductionIncreaseTest.kt
@@ -0,0 +1,73 @@
+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.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> = 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
new file mode 100644
index 00000000..9cb10562
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/RawPointsIncreaseTest.kt
@@ -0,0 +1,29 @@
+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
new file mode 100644
index 00000000..7e566a8c
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/ScienceProgressTest.kt
@@ -0,0 +1,49 @@
+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.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
new file mode 100644
index 00000000..aae3be8e
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/effects/SpecialAbilityActivationTest.kt
@@ -0,0 +1,93 @@
+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.boards.RelativeBoardPosition
+import org.luxons.sevenwonders.game.cards.Card
+import org.luxons.sevenwonders.game.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<IllegalStateException> {
+ effect.computePoints(player)
+ }
+ }
+
+ companion object {
+
+ @JvmStatic
+ @DataPoints
+ fun abilities(): Array<SpecialAbility> = SpecialAbility.values()
+
+ @JvmStatic
+ @DataPoints
+ fun neighbours(): Array<RelativeBoardPosition> =
+ arrayOf(RelativeBoardPosition.LEFT, RelativeBoardPosition.RIGHT)
+
+ @JvmStatic
+ @DataPoints
+ internal fun guilds(): Array<Card> {
+ 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
new file mode 100644
index 00000000..21b92872
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/moves/BuildWonderMoveTest.kt
@@ -0,0 +1,82 @@
+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.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<InvalidMoveException> {
+ 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<InvalidMoveException> {
+ buildOneWonderLevel(settings, table, hand, 4)
+ }
+ }
+
+ private fun fillPlayerWonderLevels(settings: Settings, table: Table, hand: List<Card>) {
+ 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<Card>, 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
new file mode 100644
index 00000000..b4c3b886
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt
@@ -0,0 +1,137 @@
+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.resources.Provider.LEFT_PLAYER
+import org.luxons.sevenwonders.game.resources.Provider.RIGHT_PLAYER
+import org.luxons.sevenwonders.game.resources.ResourceType.CLAY
+import org.luxons.sevenwonders.game.resources.ResourceType.GLASS
+import org.luxons.sevenwonders.game.resources.ResourceType.ORE
+import org.luxons.sevenwonders.game.resources.ResourceType.STONE
+import org.luxons.sevenwonders.game.resources.ResourceType.WOOD
+import org.luxons.sevenwonders.game.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
new file mode 100644
index 00000000..0e865921
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ProductionTest.kt
@@ -0,0 +1,292 @@
+package org.luxons.sevenwonders.game.resources
+
+import org.junit.Before
+import org.junit.Test
+import org.luxons.sevenwonders.game.resources.ResourceType.CLAY
+import org.luxons.sevenwonders.game.resources.ResourceType.GLASS
+import org.luxons.sevenwonders.game.resources.ResourceType.LOOM
+import org.luxons.sevenwonders.game.resources.ResourceType.ORE
+import org.luxons.sevenwonders.game.resources.ResourceType.PAPYRUS
+import org.luxons.sevenwonders.game.resources.ResourceType.STONE
+import org.luxons.sevenwonders.game.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<Set<ResourceType>>()
+ 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<Set<ResourceType>>()
+ 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
new file mode 100644
index 00000000..7e6d7816
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourceTransactionsTest.kt
@@ -0,0 +1,27 @@
+package org.luxons.sevenwonders.game.resources
+
+import org.junit.Test
+import org.luxons.sevenwonders.game.resources.ResourceType.CLAY
+import org.luxons.sevenwonders.game.resources.ResourceType.WOOD
+import org.luxons.sevenwonders.game.test.createTransaction
+import 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
new file mode 100644
index 00000000..634a25c7
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/ResourcesTest.kt
@@ -0,0 +1,435 @@
+package org.luxons.sevenwonders.game.resources
+
+import org.junit.Test
+import org.luxons.sevenwonders.game.resources.ResourceType.CLAY
+import org.luxons.sevenwonders.game.resources.ResourceType.GLASS
+import org.luxons.sevenwonders.game.resources.ResourceType.LOOM
+import org.luxons.sevenwonders.game.resources.ResourceType.ORE
+import org.luxons.sevenwonders.game.resources.ResourceType.PAPYRUS
+import org.luxons.sevenwonders.game.resources.ResourceType.STONE
+import org.luxons.sevenwonders.game.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<NoSuchElementException> {
+ 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
new file mode 100644
index 00000000..38953529
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/TradingRulesTest.kt
@@ -0,0 +1,126 @@
+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.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> = Provider.values()
+
+ @JvmStatic
+ @DataPoints
+ fun resourceTypes(): Array<ResourceType> = 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
new file mode 100644
index 00000000..78386b3d
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/test/TestUtils.kt
@@ -0,0 +1,137 @@
+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.CustomizableSettings
+import org.luxons.sevenwonders.game.api.PlayerMove
+import org.luxons.sevenwonders.game.boards.Board
+import org.luxons.sevenwonders.game.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.cards.CardBack
+import org.luxons.sevenwonders.game.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.moves.MoveType
+import org.luxons.sevenwonders.game.resources.Production
+import org.luxons.sevenwonders.game.resources.Provider
+import org.luxons.sevenwonders.game.resources.ResourceTransaction
+import org.luxons.sevenwonders.game.resources.ResourceTransactions
+import org.luxons.sevenwonders.game.resources.ResourceType
+import org.luxons.sevenwonders.game.resources.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<Board> = 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, resourcesOf(*resources))
+
+internal fun createRequirements(vararg types: ResourceType): Requirements = Requirements(resources = resourcesOf(*types))
+
+internal fun sampleCards(nbCards: Int, fromIndex: Int = 0, color: Color = Color.BLUE): List<Card> =
+ List(nbCards) { i -> testCard("Test Card ${fromIndex + i}", color) }
+
+internal fun createGuildCards(count: Int): List<Card> = 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
new file mode 100644
index 00000000..491d13fb
--- /dev/null
+++ b/sw-engine/src/test/kotlin/org/luxons/sevenwonders/game/wonders/WonderTest.kt
@@ -0,0 +1,34 @@
+package org.luxons.sevenwonders.game.wonders
+
+import org.junit.Test
+import org.luxons.sevenwonders.game.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"))
+ }
+ }
+}
bgstack15