Deck tests

This commit is contained in:
2025-03-05 18:20:49 +01:00
parent 729e7f6adc
commit 7043e1c6e7
6 changed files with 167 additions and 45 deletions

View File

@@ -4,27 +4,21 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Deck( data class Deck(
val stack: Set<Card> = emptySet(), val stack: Stack = emptySet(),
val discard: Set<Card> = emptySet(), val discard: Set<Card> = emptySet(),
val playersHands: PlayerHands = emptyMap(), val playersHands: PlayerHands = emptyMap(),
) { ) {
constructor(players: List<Player>) : this(playersHands = players.associateWith { emptyList<Card>() }) constructor(players: List<Player>) : this(playersHands = players.associateWith { emptyList<Card>() })
fun putOneCardOnDiscard(): Deck { fun placeFirstCardOnDiscard(): Deck {
val takenCard = stack.first() val takenCard = stack.first()
val newStack = stack.filterNot { it != takenCard }.toSet() return copy(
return copy(stack = newStack) stack = stack - takenCard,
discard = discard + takenCard,
)
} }
private fun take(n: Int): Pair<Deck, List<Card>> { fun takeOneCardFromStackTo(player: Player): Deck =
val takenCards = stack.take(n)
val newStack = stack.filterNot { takenCards.contains(it) }.toSet()
return Pair(copy(stack = newStack), takenCards)
}
private fun takeOne(): Pair<Deck, Card> = take(1).let { (deck, cards) -> Pair(deck, cards.first()) }
fun takeOneCardTo(player: Player): Deck =
takeOne().let { (deck, newPlayerCard) -> takeOne().let { (deck, newPlayerCard) ->
val newHands = val newHands =
deck.playersHands.mapValues { (p, cards) -> deck.playersHands.mapValues { (p, cards) ->
@@ -52,26 +46,20 @@ data class Deck(
}.let { }.let {
copy( copy(
discard = discard + card, discard = discard + card,
playersHands = playersHands.addCard(player, card), playersHands = playersHands.removeCard(player, card),
) )
} }
private fun take(n: Int): Pair<Deck, List<Card>> {
val takenCards = stack.take(n)
val newStack = stack.filterNot { takenCards.contains(it) }.toSet()
return Pair(copy(stack = newStack), takenCards)
}
private fun takeOne(): Pair<Deck, Card> = take(1).let { (deck, cards) -> Pair(deck, cards.first()) }
companion object { companion object {
fun initHands( fun newWithoutPlayers(): Deck =
players: Set<Player>,
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 =
listOf(Card.Color.Red, Card.Color.Blue, Card.Color.Yellow, Card.Color.Green) listOf(Card.Color.Red, Card.Color.Blue, Card.Color.Yellow, Card.Color.Green)
.flatMap { color -> .flatMap { color ->
((0..9) + (1..9)).map { Card.NumericCard(it, 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.ReverseCard(color) } +
(1..2).map { Card.PassCard(color) } (1..2).map { Card.PassCard(color) }
}.let { }.let {
(1..4).map { Card.Plus4Card() } it + (1..4).map { Card.Plus4Card() }
}.shuffled() }.shuffled()
.toSet() .toSet()
.let { Deck(it) } .let { Deck(it) }
} }
} }
fun Deck.initHands(
players: Set<Player>,
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<Card>

View File

@@ -5,7 +5,8 @@ typealias PlayerHands = Map<Player, List<Card>>
fun PlayerHands.removeCard( fun PlayerHands.removeCard(
player: Player, player: Player,
card: Card, card: Card,
) = mapValues { (p, cards) -> ): PlayerHands =
mapValues { (p, cards) ->
if (p == player) { if (p == player) {
cards - card cards - card
} else { } else {
@@ -16,7 +17,8 @@ fun PlayerHands.removeCard(
fun PlayerHands.addCards( fun PlayerHands.addCards(
player: Player, player: Player,
newCards: List<Card>, newCards: List<Card>,
) = mapValues { (p, cards) -> ): PlayerHands =
mapValues { (p, cards) ->
if (p == player) { if (p == player) {
cards + newCards cards + newCards
} else { } else {

View File

@@ -58,7 +58,7 @@ private fun GameId.buildStateFromEvents(events: List<GameEvent>): GameState =
is PlayerHavePassEvent -> { is PlayerHavePassEvent -> {
state.copy( state.copy(
lastPlayer = event.player, lastPlayer = event.player,
deck = state.deck.takeOneCardTo(event.player), deck = state.deck.takeOneCardFromStackTo(event.player),
) )
} }

View File

@@ -3,9 +3,10 @@ package eventDemo.app.event.event
import eventDemo.app.entity.Deck import eventDemo.app.entity.Deck
import eventDemo.app.entity.GameId import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player 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( data class GameStartedEvent(
override val id: GameId, override val id: GameId,
@@ -20,7 +21,7 @@ data class GameStartedEvent(
GameStartedEvent( GameStartedEvent(
id = id, id = id,
firstPlayer = players.random(), firstPlayer = players.random(),
deck = Deck.initHands(players).putOneCardOnDiscard(), deck = Deck.newWithoutPlayers().initHands(players).placeFirstCardOnDiscard(),
) )
} }
} }

View File

@@ -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<Card> = stack + discard + playersHands.values.flatten()

View File

@@ -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
}
})