diff options
10 files changed, 79 insertions, 76 deletions
diff --git a/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt index 173ced64..8498ce85 100644 --- a/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt +++ b/backend/src/main/kotlin/org/luxons/sevenwonders/lobby/Lobby.kt @@ -3,7 +3,6 @@ package org.luxons.sevenwonders.lobby import org.luxons.sevenwonders.game.Game import org.luxons.sevenwonders.game.api.CustomizableSettings import org.luxons.sevenwonders.game.data.GameDefinition -import java.util.ArrayList enum class State { LOBBY, PLAYING @@ -15,7 +14,7 @@ class Lobby( var owner: Player, @field:Transient private val gameDefinition: GameDefinition ) { - private var players: MutableList<Player> = ArrayList(gameDefinition.maxPlayers) + private val players: MutableList<Player> = ArrayList(gameDefinition.maxPlayers) var settings: CustomizableSettings = CustomizableSettings() @@ -64,7 +63,11 @@ class Lobby( @Synchronized fun reorderPlayers(orderedUsernames: List<String>) { - players = orderedUsernames.map { find(it) }.toMutableList() + val usernames = players.map { it.username } + if (orderedUsernames.toSet() != usernames.toSet()) { + throw PlayerListMismatchException(orderedUsernames) + } + players.sortBy { orderedUsernames.indexOf(it.username) } } private fun find(username: String): Player = @@ -98,8 +101,11 @@ class Lobby( IllegalStateException("Minimum $min players required to start a game") internal class PlayerNameAlreadyUsedException(displayName: String, gameName: String) : - IllegalStateException("Name '$displayName' is already used by a player in game '$gameName'") + IllegalArgumentException("Name '$displayName' is already used by a player in game '$gameName'") internal class UnknownPlayerException(username: String) : - IllegalStateException("Unknown player '$username'") + IllegalArgumentException("Unknown player '$username'") + + internal class PlayerListMismatchException(usernames: List<String>) : + IllegalArgumentException("Newly ordered usernames $usernames don't match the current player list") } diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt index 2d37b0f4..6e417970 100644 --- a/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt +++ b/backend/src/test/kotlin/org/luxons/sevenwonders/controllers/LobbyControllerTest.kt @@ -15,7 +15,6 @@ import org.luxons.sevenwonders.repositories.LobbyRepository import org.luxons.sevenwonders.repositories.PlayerNotFoundException import org.luxons.sevenwonders.repositories.PlayerRepository import org.luxons.sevenwonders.test.mockSimpMessagingTemplate -import java.util.Arrays import java.util.HashMap import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -99,10 +98,10 @@ class LobbyControllerTest { val player3 = addPlayer(lobby, "testuser3") val player4 = addPlayer(lobby, "testuser4") - val players = Arrays.asList(player, player2, player3, player4) + val players = listOf(player, player2, player3, player4) assertEquals(players, lobby.getPlayers()) - val reorderedPlayers = Arrays.asList(player3, player, player2, player4) + val reorderedPlayers = listOf(player3, player, player2, player4) val playerNames = reorderedPlayers.map { it.username } val reorderPlayersAction = ReorderPlayersAction(playerNames) @@ -120,7 +119,7 @@ class LobbyControllerTest { val player2 = addPlayer(lobby, "testuser2") val player3 = addPlayer(lobby, "testuser3") - val reorderedPlayers = Arrays.asList(player3, player, player2) + val reorderedPlayers = listOf(player3, player, player2) val playerNames = reorderedPlayers.map { it.username } val reorderPlayersAction = ReorderPlayersAction(playerNames) diff --git a/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt b/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt index 85c89595..98e154da 100644 --- a/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt +++ b/backend/src/test/kotlin/org/luxons/sevenwonders/lobby/LobbyTest.kt @@ -13,11 +13,11 @@ import org.junit.runner.RunWith import org.luxons.sevenwonders.game.api.CustomizableSettings import org.luxons.sevenwonders.game.data.GameDefinition import org.luxons.sevenwonders.lobby.Lobby.GameAlreadyStartedException +import org.luxons.sevenwonders.lobby.Lobby.PlayerListMismatchException import org.luxons.sevenwonders.lobby.Lobby.PlayerNameAlreadyUsedException import org.luxons.sevenwonders.lobby.Lobby.PlayerOverflowException import org.luxons.sevenwonders.lobby.Lobby.PlayerUnderflowException import org.luxons.sevenwonders.lobby.Lobby.UnknownPlayerException -import java.util.Arrays import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotNull @@ -150,21 +150,38 @@ class LobbyTest { lobby.addPlayer(player1) lobby.addPlayer(player2) lobby.addPlayer(player3) - lobby.reorderPlayers(Arrays.asList("testuser3", "testuser1", "testuser2")) - assertEquals("testuser3", lobby.getPlayers()[0].username) - assertEquals("testuser1", lobby.getPlayers()[1].username) - assertEquals("testuser2", lobby.getPlayers()[2].username) + + val reorderedUsernames = listOf("testuser3", "gameowner", "testuser1", "testuser2") + lobby.reorderPlayers(reorderedUsernames) + + assertEquals(reorderedUsernames, lobby.getPlayers().map { it.username }) } - @Test(expected = UnknownPlayerException::class) + @Test(expected = PlayerListMismatchException::class) fun reorderPlayers_failsOnUnknownPlayer() { val player1 = Player("testuser1", "Test User 1") val player2 = Player("testuser2", "Test User 2") - val player3 = Player("testuser3", "Test User 3") lobby.addPlayer(player1) lobby.addPlayer(player2) - lobby.addPlayer(player3) - lobby.reorderPlayers(Arrays.asList("unknown", "testuser1", "testuser2")) + lobby.reorderPlayers(listOf("unknown", "testuser2", "gameowner")) + } + + @Test(expected = PlayerListMismatchException::class) + fun reorderPlayers_failsOnExtraPlayer() { + val player1 = Player("testuser1", "Test User 1") + val player2 = Player("testuser2", "Test User 2") + lobby.addPlayer(player1) + lobby.addPlayer(player2) + lobby.reorderPlayers(listOf("testuser2", "onemore", "testuser1", "gameowner")) + } + + @Test(expected = PlayerListMismatchException::class) + fun reorderPlayers_failsOnMissingPlayer() { + val player1 = Player("testuser1", "Test User 1") + val player2 = Player("testuser2", "Test User 2") + lobby.addPlayer(player1) + lobby.addPlayer(player2) + lobby.reorderPlayers(listOf("testuser2", "gameowner")) } @Theory @@ -203,11 +220,11 @@ class LobbyTest { @Test fun startGame_switchesState() { - assertTrue(lobby.state === State.LOBBY) + assertEquals(State.LOBBY, lobby.state) // there is already the owner addPlayers(gameDefinition.minPlayers - 1) lobby.startGame() - assertTrue(lobby.state === State.PLAYING) + assertEquals(State.PLAYING, lobby.state) } @Test diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt index f92177c8..124cf435 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/Game.kt @@ -26,8 +26,8 @@ class Game internal constructor( ) { private val table: Table = Table(boards) private val players: List<Player> = boards.map { SimplePlayer(it.playerIndex, table) } - private val discardedCards: MutableList<Card> = ArrayList() - private val preparedMoves: MutableMap<Int, Move> = HashMap() + private val discardedCards: MutableList<Card> = mutableListOf() + private val preparedMoves: MutableMap<Int, Move> = mutableMapOf() private var currentTurnInfo: List<PlayerTurnInfo> = emptyList() private var hands: Hands = Hands(emptyList()) diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt index 22b9ac93..04dd2d25 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/boards/Board.kt @@ -23,7 +23,7 @@ internal class Board(val wonder: Wonder, val playerIndex: Int, settings: Setting private val pointsPer3Gold: Int = settings.pointsPer3Gold - private val playedCards: MutableList<Card> = arrayListOf() + private val playedCards: MutableList<Card> = mutableListOf() private val specialAbilities: MutableSet<SpecialAbility> = hashSetOf() private val consumedFreeCards: MutableMap<Age, Boolean> = mutableMapOf() diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt index 23a8d3ee..4d0348ea 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/data/definitions/EffectsDefinition.kt @@ -10,7 +10,6 @@ import org.luxons.sevenwonders.game.effects.RawPointsIncrease import org.luxons.sevenwonders.game.effects.ScienceProgress import org.luxons.sevenwonders.game.effects.SpecialAbility import org.luxons.sevenwonders.game.effects.SpecialAbilityActivation -import java.util.ArrayList internal class EffectsDefinition( private val gold: GoldIncrease? = null, @@ -22,32 +21,14 @@ internal class EffectsDefinition( private val points: RawPointsIncrease? = null, private val action: SpecialAbility? = null ) { - fun create(): List<Effect> { - val effects = ArrayList<Effect>() - if (gold != null) { - effects.add(gold) - } - if (military != null) { - effects.add(military) - } - if (science != null) { - effects.add(science) - } - if (discount != null) { - effects.add(discount) - } - if (perBoardElement != null) { - effects.add(perBoardElement) - } - if (production != null) { - effects.add(production) - } - if (points != null) { - effects.add(points) - } - if (action != null) { - effects.add(SpecialAbilityActivation(action)) - } - return effects + fun create(): List<Effect> = mutableListOf<Effect>().apply { + gold?.let { add(it) } + military?.let { add(it) } + science?.let { add(it) } + discount?.let { add(it) } + perBoardElement?.let { add(it) } + production?.let { add(it) } + points?.let { add(it) } + action?.let { add(SpecialAbilityActivation(it)) } } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt index 3d051732..dea6f2c2 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculator.kt @@ -1,7 +1,6 @@ package org.luxons.sevenwonders.game.resources import org.luxons.sevenwonders.game.Player -import java.util.ArrayList import java.util.EnumSet internal fun bestSolution(resources: Resources, player: Player): TransactionPlan = @@ -12,8 +11,10 @@ data class TransactionPlan(val price: Int, val possibleTransactions: Set<Resourc private class ResourcePool( val provider: Provider?, private val rules: TradingRules, - val choices: Set<MutableSet<ResourceType>> + choices: Set<Set<ResourceType>> ) { + val choices: Set<MutableSet<ResourceType>> = choices.map { it.toMutableSet() }.toSet() + fun getCost(type: ResourceType): Int = if (provider == null) 0 else rules.getCost(type, provider) } @@ -34,23 +35,18 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { } private fun createResourcePools(player: Player): List<ResourcePool> { - val providers = Provider.values() + // we only take alternative resources here, because fixed resources were already removed for optimization + val ownBoardChoices = player.board.production.getAlternativeResources() + val ownPool = ResourcePool(null, player.board.tradingRules, ownBoardChoices) + val providerPools = Provider.values().map { it.toResourcePoolFor(player) } - val board = player.board - val rules = board.tradingRules + return providerPools + ownPool + } - val pools = ArrayList<ResourcePool>(providers.size + 1) - // we only take alternative resources here, because fixed resources were already removed for optimization - val ownBoardChoices = board.production.getAlternativeResources() - pools.add(ResourcePool(null, rules, ownBoardChoices.map { it.toMutableSet() }.toSet())) - - for (provider in providers) { - val providerBoard = player.getBoard(provider.boardPosition) - val choices = providerBoard.publicProduction.asChoices().map { it.toMutableSet() }.toSet() - val pool = ResourcePool(provider, rules, choices) - pools.add(pool) - } - return pools + private fun Provider.toResourcePoolFor(player: Player): ResourcePool { + val providerBoard = player.getBoard(boardPosition) + val choices = providerBoard.publicProduction.asChoices() + return ResourcePool(this, player.board.tradingRules, choices) } fun computeBestSolution(): TransactionPlan { @@ -120,6 +116,6 @@ private class BestPriceCalculator(resourcesToPay: Resources, player: Player) { bestSolutions.clear() } // avoid mutating the resources from the transactions - bestSolutions.add(boughtResources.mapValues { MutableResources(HashMap(it.value.quantities)) }.toTransactions()) + bestSolutions.add(boughtResources.mapValues { (_, res) -> res.copy() }.toTransactions()) } } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt index 0b069677..66a63463 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Production.kt @@ -1,6 +1,5 @@ package org.luxons.sevenwonders.game.resources -import java.util.Arrays import java.util.EnumSet data class Production internal constructor( @@ -14,7 +13,7 @@ data class Production internal constructor( fun addFixedResource(type: ResourceType, quantity: Int) = fixedResources.add(type, quantity) fun addChoice(vararg options: ResourceType) { - val optionSet = EnumSet.copyOf(Arrays.asList(*options)) + val optionSet = EnumSet.copyOf(options.toList()) alternativeResources.add(optionSet) } diff --git a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt index c02fd260..a91a2af1 100644 --- a/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt +++ b/game-engine/src/main/kotlin/org/luxons/sevenwonders/game/resources/Resources.kt @@ -10,6 +10,10 @@ fun resourcesOf(vararg resources: Pair<ResourceType, Int>): Resources = mutableR fun Iterable<Pair<ResourceType, Int>>.toResources(): Resources = toMutableResources() +/** + * Creates [Resources] from a copy of the given map. Future modifications to the input map won't affect the return + * resources. + */ fun Map<ResourceType, Int>.toResources(): Resources = toMutableResources() fun Iterable<Resources>.merge(): Resources = fold(MutableResources()) { r1, r2 -> r1.add(r2); r1 } @@ -53,6 +57,8 @@ interface Resources { quantities.mapValues { (type, q) -> Math.max(0, q - resources[type]) }.toResources() fun toList(): List<ResourceType> = quantities.flatMap { (type, quantity) -> List(quantity) { type } } + + fun copy(): Resources = quantities.toResources() } class MutableResources( diff --git a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt index 5d99ae00..b4c3b886 100644 --- a/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt +++ b/game-engine/src/test/kotlin/org/luxons/sevenwonders/game/resources/BestPriceCalculatorTest.kt @@ -14,7 +14,6 @@ import org.luxons.sevenwonders.game.test.createTransaction import org.luxons.sevenwonders.game.test.createTransactions import org.luxons.sevenwonders.game.test.testBoard import org.luxons.sevenwonders.game.test.testTable -import java.util.Arrays import kotlin.test.assertEquals class BestPriceCalculatorTest { @@ -36,7 +35,7 @@ class BestPriceCalculatorTest { val left = testBoard(STONE) val main = testBoard(STONE) val right = testBoard(WOOD) - val table = Table(Arrays.asList(main, right, left)) + val table = Table(listOf(main, right, left)) val player0 = SimplePlayer(0, table) val player1 = SimplePlayer(1, table) @@ -64,7 +63,7 @@ class BestPriceCalculatorTest { val left = testBoard(WOOD) val right = testBoard(WOOD) val opposite = testBoard(GLASS) - val table = Table(Arrays.asList(main, right, opposite, left)) + val table = Table(listOf(main, right, opposite, left)) val player0 = SimplePlayer(0, table) val player1 = SimplePlayer(1, table) @@ -93,7 +92,7 @@ class BestPriceCalculatorTest { right.production.addChoice(WOOD, CLAY) right.publicProduction.addChoice(WOOD, CLAY) - val table = Table(Arrays.asList(main, right, left)) + val table = Table(listOf(main, right, left)) val player0 = SimplePlayer(0, table) val player1 = SimplePlayer(1, table) @@ -121,7 +120,7 @@ class BestPriceCalculatorTest { right.publicProduction.addFixedResource(ORE, 1) right.publicProduction.addFixedResource(CLAY, 1) - val table = Table(Arrays.asList(main, right, left)) + val table = Table(listOf(main, right, left)) val player0 = SimplePlayer(0, table) val player1 = SimplePlayer(1, table) |