From 7043e1c6e73480638b573ea1ab5df9c260e5b3f8 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Wed, 5 Mar 2025 18:20:49 +0100 Subject: [PATCH] Deck tests --- src/main/kotlin/eventDemo/app/entity/Deck.kt | 66 ++++++----- .../kotlin/eventDemo/app/entity/PlayerHand.kt | 26 +++-- .../eventDemo/app/event/GameStateBuilder.kt | 2 +- .../app/event/event/GameStartedEvent.kt | 5 +- src/test/kotlin/eventDemo/Helpers.kt | 8 ++ .../kotlin/eventDemo/app/entity/DeckTest.kt | 105 ++++++++++++++++++ 6 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 src/test/kotlin/eventDemo/Helpers.kt create mode 100644 src/test/kotlin/eventDemo/app/entity/DeckTest.kt diff --git a/src/main/kotlin/eventDemo/app/entity/Deck.kt b/src/main/kotlin/eventDemo/app/entity/Deck.kt index 55ae55d..5b319dc 100644 --- a/src/main/kotlin/eventDemo/app/entity/Deck.kt +++ b/src/main/kotlin/eventDemo/app/entity/Deck.kt @@ -4,27 +4,21 @@ import kotlinx.serialization.Serializable @Serializable data class Deck( - val stack: Set = emptySet(), + val stack: Stack = emptySet(), val discard: Set = emptySet(), val playersHands: PlayerHands = emptyMap(), ) { constructor(players: List) : this(playersHands = players.associateWith { emptyList() }) - fun putOneCardOnDiscard(): Deck { + fun placeFirstCardOnDiscard(): Deck { val takenCard = stack.first() - val newStack = stack.filterNot { it != takenCard }.toSet() - return copy(stack = newStack) + return copy( + stack = stack - takenCard, + discard = discard + takenCard, + ) } - private fun take(n: Int): Pair> { - val takenCards = stack.take(n) - val newStack = stack.filterNot { takenCards.contains(it) }.toSet() - return Pair(copy(stack = newStack), takenCards) - } - - private fun takeOne(): Pair = take(1).let { (deck, cards) -> Pair(deck, cards.first()) } - - fun takeOneCardTo(player: Player): Deck = + fun takeOneCardFromStackTo(player: Player): Deck = takeOne().let { (deck, newPlayerCard) -> val newHands = deck.playersHands.mapValues { (p, cards) -> @@ -52,26 +46,20 @@ data class Deck( }.let { copy( discard = discard + card, - playersHands = playersHands.addCard(player, card), + playersHands = playersHands.removeCard(player, card), ) } + private fun take(n: Int): Pair> { + val takenCards = stack.take(n) + val newStack = stack.filterNot { takenCards.contains(it) }.toSet() + return Pair(copy(stack = newStack), takenCards) + } + + private fun takeOne(): Pair = take(1).let { (deck, cards) -> Pair(deck, cards.first()) } + companion object { - fun initHands( - players: Set, - handSize: Int = 7, - ): Deck { - val deck = new() - val playersHands = players.associateWith { deck.stack.take(handSize) } - val allTakenCards = playersHands.flatMap { it.value } - val newStack = deck.stack.filterNot { allTakenCards.contains(it) }.toSet() - return deck.copy( - stack = newStack, - playersHands = playersHands, - ) - } - - private fun new(): Deck = + fun newWithoutPlayers(): Deck = listOf(Card.Color.Red, Card.Color.Blue, Card.Color.Yellow, Card.Color.Green) .flatMap { color -> ((0..9) + (1..9)).map { Card.NumericCard(it, color) } + @@ -79,9 +67,27 @@ data class Deck( (1..2).map { Card.ReverseCard(color) } + (1..2).map { Card.PassCard(color) } }.let { - (1..4).map { Card.Plus4Card() } + it + (1..4).map { Card.Plus4Card() } }.shuffled() .toSet() .let { Deck(it) } } } + +fun Deck.initHands( + players: Set, + handSize: Int = 7, +): Deck { + // Copy cards from stack to the player hands + val deckWithEmptyHands = copy(playersHands = players.associateWith { listOf() }) + return players.fold(deckWithEmptyHands) { acc: Deck, player: Player -> + val hand = acc.stack.take(handSize) + val newStack = acc.stack.filterNot { card: Card -> hand.contains(card) }.toSet() + copy( + stack = newStack, + playersHands = acc.playersHands.addCards(player, hand), + ) + } +} + +typealias Stack = Set diff --git a/src/main/kotlin/eventDemo/app/entity/PlayerHand.kt b/src/main/kotlin/eventDemo/app/entity/PlayerHand.kt index 6c15bde..74b1b10 100644 --- a/src/main/kotlin/eventDemo/app/entity/PlayerHand.kt +++ b/src/main/kotlin/eventDemo/app/entity/PlayerHand.kt @@ -5,21 +5,23 @@ typealias PlayerHands = Map> fun PlayerHands.removeCard( player: Player, card: Card, -) = mapValues { (p, cards) -> - if (p == player) { - cards - card - } else { - cards +): PlayerHands = + mapValues { (p, cards) -> + if (p == player) { + cards - card + } else { + cards + } } -} fun PlayerHands.addCards( player: Player, newCards: List, -) = mapValues { (p, cards) -> - if (p == player) { - cards + newCards - } else { - cards +): PlayerHands = + mapValues { (p, cards) -> + if (p == player) { + cards + newCards + } else { + cards + } } -} diff --git a/src/main/kotlin/eventDemo/app/event/GameStateBuilder.kt b/src/main/kotlin/eventDemo/app/event/GameStateBuilder.kt index 2f0cdbe..488a652 100644 --- a/src/main/kotlin/eventDemo/app/event/GameStateBuilder.kt +++ b/src/main/kotlin/eventDemo/app/event/GameStateBuilder.kt @@ -58,7 +58,7 @@ private fun GameId.buildStateFromEvents(events: List): GameState = is PlayerHavePassEvent -> { state.copy( lastPlayer = event.player, - deck = state.deck.takeOneCardTo(event.player), + deck = state.deck.takeOneCardFromStackTo(event.player), ) } diff --git a/src/main/kotlin/eventDemo/app/event/event/GameStartedEvent.kt b/src/main/kotlin/eventDemo/app/event/event/GameStartedEvent.kt index 9a6beb8..cc3b37c 100644 --- a/src/main/kotlin/eventDemo/app/event/event/GameStartedEvent.kt +++ b/src/main/kotlin/eventDemo/app/event/event/GameStartedEvent.kt @@ -3,9 +3,10 @@ package eventDemo.app.event.event import eventDemo.app.entity.Deck import eventDemo.app.entity.GameId import eventDemo.app.entity.Player +import eventDemo.app.entity.initHands /** - * This [GameEvent] is sent when all players is ready. + * This [GameEvent] is sent when all players are ready. */ data class GameStartedEvent( override val id: GameId, @@ -20,7 +21,7 @@ data class GameStartedEvent( GameStartedEvent( id = id, firstPlayer = players.random(), - deck = Deck.initHands(players).putOneCardOnDiscard(), + deck = Deck.newWithoutPlayers().initHands(players).placeFirstCardOnDiscard(), ) } } diff --git a/src/test/kotlin/eventDemo/Helpers.kt b/src/test/kotlin/eventDemo/Helpers.kt new file mode 100644 index 0000000..984ea75 --- /dev/null +++ b/src/test/kotlin/eventDemo/Helpers.kt @@ -0,0 +1,8 @@ +package eventDemo + +import eventDemo.app.entity.Card +import eventDemo.app.entity.Deck + +fun Deck.allCardCount(): Int = stack.size + discard.size + playersHands.values.flatten().size + +fun Deck.allCards(): Set = stack + discard + playersHands.values.flatten() diff --git a/src/test/kotlin/eventDemo/app/entity/DeckTest.kt b/src/test/kotlin/eventDemo/app/entity/DeckTest.kt new file mode 100644 index 0000000..3db861e --- /dev/null +++ b/src/test/kotlin/eventDemo/app/entity/DeckTest.kt @@ -0,0 +1,105 @@ +package eventDemo.app.entity + +import eventDemo.allCardCount +import eventDemo.allCards +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldBeUnique +import io.kotest.matchers.ints.shouldBeExactly +import kotlin.test.assertNotNull + +class DeckTest : + FunSpec({ + val totalCardsNumber = 104 + test("newWithoutPlayers") { + // When + val deck = Deck.newWithoutPlayers() + + // Then + deck.stack.size shouldBeExactly totalCardsNumber + deck.discard.size shouldBeExactly 0 + deck.playersHands.size shouldBeExactly 0 + + deck.allCardCount() shouldBeExactly totalCardsNumber + deck.allCards().shouldBeUnique() + deck.allCards().map { it.id }.shouldBeUnique() + } + + test("initHands") { + // Given + val playerNumbers = 4 + val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet() + val deck = Deck.newWithoutPlayers() + + // When + val initDeck = deck.initHands(players) + + // Then + initDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) + initDeck.discard.size shouldBeExactly 0 + initDeck.playersHands.size shouldBeExactly playerNumbers + initDeck.playersHands.forEach { (_, cards) -> cards.size shouldBeExactly 7 } + initDeck.allCardCount() shouldBeExactly totalCardsNumber + } + + test("takeOneCardFromStackTo") { + // Given + val playerNumbers = 4 + val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet() + val deck = Deck.newWithoutPlayers().initHands(players) + val firstPlayer = players.first() + + // When + val modifiedDeck = deck.takeOneCardFromStackTo(firstPlayer) + + // Then + modifiedDeck.discard.size shouldBeExactly 0 + modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1 + modifiedDeck.playersHands.size shouldBeExactly playerNumbers + assertNotNull(modifiedDeck.playersHands[firstPlayer]).size shouldBeExactly 7 + 1 + modifiedDeck.playersHands + .filterKeys { it != firstPlayer } + .forEach { (_, cards) -> cards.size shouldBeExactly 7 } + modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber + } + + test("putOneCardFromHand") { + // Given + val playerNumbers = 4 + val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet() + val deck = Deck.newWithoutPlayers().initHands(players) + val firstPlayer = players.first() + + // When + val card = deck.playersHands[firstPlayer]!!.first() + val modifiedDeck = deck.putOneCardFromHand(firstPlayer, card) + + // Then + modifiedDeck.discard.size shouldBeExactly 1 + modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) + modifiedDeck.playersHands.size shouldBeExactly playerNumbers + assertNotNull(modifiedDeck.playersHands[firstPlayer]).size shouldBeExactly 6 + modifiedDeck.playersHands + .filterKeys { it != firstPlayer } + .forEach { (_, cards) -> cards.size shouldBeExactly 7 } + modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber + } + + test("placeFirstCardOnDiscard") { + // Given + val playerNumbers = 4 + val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet() + val deck = Deck.newWithoutPlayers().initHands(players) + val firstPlayer = players.first() + + // When + val modifiedDeck = deck.placeFirstCardOnDiscard() + + // Then + modifiedDeck.discard.size shouldBeExactly 1 + modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1 + modifiedDeck.playersHands.size shouldBeExactly playerNumbers + modifiedDeck.playersHands + .forEach { (_, cards) -> cards.size shouldBeExactly 7 } + modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber + } + })