Deck tests
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -5,21 +5,23 @@ 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 {
|
||||||
cards
|
cards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
cards
|
cards
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/test/kotlin/eventDemo/Helpers.kt
Normal file
8
src/test/kotlin/eventDemo/Helpers.kt
Normal 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()
|
||||||
105
src/test/kotlin/eventDemo/app/entity/DeckTest.kt
Normal file
105
src/test/kotlin/eventDemo/app/entity/DeckTest.kt
Normal 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
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user