-
Notifications
You must be signed in to change notification settings - Fork 313
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4b7a27d
commit b3acbfb
Showing
16 changed files
with
215 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package action | ||
|
||
enum class BlackJackAction { | ||
HIT, | ||
STAND, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack | ||
|
||
import blackjack.card.Card | ||
import blackjack.hand.Hand | ||
|
||
interface BlackjackParticipant { | ||
val hand: Hand | ||
fun receiveCard(card: Card): BlackjackParticipant | ||
fun calculateBestValue(): Int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package blackjack.dealer | ||
|
||
import action.BlackJackAction | ||
import blackjack.BlackjackParticipant | ||
import blackjack.card.Card | ||
import blackjack.deck.Deck | ||
import blackjack.hand.Hand | ||
|
||
data class Dealer( | ||
override val hand: Hand, | ||
private val dealerStrategy: DealerStrategy = DefaultDealerStrategy() | ||
) : BlackjackParticipant { | ||
|
||
val cards: List<Card> | ||
get() = hand.cards.toList() | ||
|
||
override fun receiveCard(card: Card): Dealer = copy(hand = hand.addCard(card)) | ||
|
||
override fun calculateBestValue(): Int = hand.calculateBestValue() | ||
|
||
fun decideAction(deck: Deck): BlackJackAction { | ||
return dealerStrategy.decideAction(hand, deck) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackjack.dealer | ||
|
||
import action.BlackJackAction | ||
import blackjack.deck.Deck | ||
import blackjack.hand.Hand | ||
|
||
interface DealerStrategy { | ||
fun decideAction(hand: Hand, deck: Deck): BlackJackAction | ||
} |
34 changes: 34 additions & 0 deletions
34
domain/src/main/kotlin/blackjack/dealer/DefaultDealerStrategy.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package blackjack.dealer | ||
|
||
import action.BlackJackAction | ||
import blackjack.card.CardRank | ||
import blackjack.deck.Deck | ||
import blackjack.hand.Hand | ||
|
||
internal class DefaultDealerStrategy : DealerStrategy { | ||
override fun decideAction(hand: Hand, deck: Deck): BlackJackAction { | ||
val dealerScore = hand.calculateBestValue() | ||
val dealerMinScore = hand.calculateMinValue() | ||
|
||
val bustingProbability = maxOf( | ||
calculateProbabilityOfBusting(dealerScore, deck), | ||
calculateProbabilityOfBusting(dealerMinScore, deck) | ||
) | ||
|
||
return if (bustingProbability > 0.5) BlackJackAction.STAND else BlackJackAction.HIT | ||
} | ||
|
||
private fun calculateProbabilityOfBusting(currentScore: Int, deck: Deck): Double { | ||
val scoreNeededToAvoidBust = 21 - currentScore | ||
val safeCards = deck.remainingCards.count { card -> | ||
val cardValue = when (card.rank) { | ||
CardRank.KING, CardRank.QUEEN, CardRank.JACK -> 10 | ||
CardRank.ACE -> 11 | ||
else -> card.rank.ordinal + 1 | ||
} | ||
cardValue <= scoreNeededToAvoidBust | ||
} | ||
|
||
return 1.0 - safeCards.toDouble() / deck.size.toDouble() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,10 @@ | ||
package blackjack.hand | ||
|
||
import blackjack.card.Card | ||
import blackjack.card.CardRank | ||
|
||
data class Hand( | ||
val cards: List<Card> = emptyList() | ||
) { | ||
fun addCard(card: Card): Hand = copy(cards = cards + card) | ||
|
||
fun calculateBestValue(): Int { | ||
val sumWithoutAces = cards.filter { it.rank != CardRank.ACE }.sumOf { cardValue(it) } | ||
val aceCount = cards.count { it.rank == CardRank.ACE } | ||
return calculateBestAceValue(sumWithoutAces, aceCount) | ||
} | ||
|
||
private fun cardValue(card: Card): Int = when (card.rank) { | ||
CardRank.KING, CardRank.QUEEN, CardRank.JACK -> FACE_CARD_VALUE | ||
else -> card.rank.ordinal + 1 | ||
} | ||
|
||
private fun calculateBestAceValue(sumWithoutAces: Int, aceCount: Int): Int { | ||
var sum = sumWithoutAces | ||
repeat(aceCount) { | ||
sum += if (sum + ACE_HIGH_VALUE > MAX_HAND_VALUE) ACE_LOW_VALUE else ACE_HIGH_VALUE | ||
} | ||
return sum | ||
} | ||
|
||
companion object { | ||
private const val FACE_CARD_VALUE = 10 | ||
private const val MAX_HAND_VALUE = 21 | ||
private const val ACE_HIGH_VALUE = 11 | ||
private const val ACE_LOW_VALUE = 1 | ||
} | ||
interface Hand { | ||
val cards: Set<Card> | ||
fun addCard(card: Card): Hand | ||
fun calculateMinValue(): Int | ||
fun calculateBestValue(): Int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package blackjack.hand | ||
|
||
import blackjack.card.Card | ||
import blackjack.card.CardRank | ||
|
||
internal class StandardHand( | ||
override val cards: Set<Card> = emptySet() | ||
) : Hand { | ||
override fun addCard(card: Card): StandardHand = StandardHand(cards = cards + card) | ||
|
||
override fun calculateBestValue(): Int { | ||
val sumWithoutAces = cards.filter { it.rank != CardRank.ACE }.sumOf { cardValue(it) } | ||
val aceCount = cards.count { it.rank == CardRank.ACE } | ||
return calculateBestAceValue(sumWithoutAces, aceCount) | ||
} | ||
|
||
override fun calculateMinValue(): Int { | ||
val sumWithoutAces = cards.filter { it.rank != CardRank.ACE }.sumOf { cardValue(it) } | ||
val aceCount = cards.count { it.rank == CardRank.ACE } | ||
return calculateMinAceValue(sumWithoutAces, aceCount) | ||
} | ||
|
||
private fun cardValue(card: Card): Int = when (card.rank) { | ||
CardRank.KING, CardRank.QUEEN, CardRank.JACK -> FACE_CARD_VALUE | ||
else -> card.rank.ordinal + 1 | ||
} | ||
|
||
private fun calculateBestAceValue(sumWithoutAces: Int, aceCount: Int): Int { | ||
var sum = sumWithoutAces | ||
repeat(aceCount) { | ||
sum += if (sum + ACE_HIGH_VALUE > MAX_HAND_VALUE) ACE_LOW_VALUE else ACE_HIGH_VALUE | ||
} | ||
return sum | ||
} | ||
|
||
private fun calculateMinAceValue(sumWithoutAces: Int, aceCount: Int): Int { | ||
var sum = sumWithoutAces | ||
repeat(aceCount) { | ||
sum += ACE_LOW_VALUE | ||
} | ||
return sum | ||
} | ||
|
||
companion object { | ||
private const val FACE_CARD_VALUE = 10 | ||
private const val MAX_HAND_VALUE = 21 | ||
private const val ACE_HIGH_VALUE = 11 | ||
private const val ACE_LOW_VALUE = 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,27 @@ | ||
package blackjack.player | ||
|
||
import action.BlackJackAction | ||
import blackjack.BlackjackParticipant | ||
import blackjack.card.Card | ||
import blackjack.hand.Hand | ||
|
||
data class Player( | ||
val name: String, | ||
private val hand: Hand, | ||
) { | ||
override val hand: Hand, | ||
) : BlackjackParticipant { | ||
|
||
val cards: List<Card> | ||
get() = hand.cards | ||
get() = hand.cards.toList() | ||
|
||
fun canHit(): BlackJackAction = if (hand.calculateMinValue() <= 21) { | ||
BlackJackAction.HIT | ||
} else { | ||
BlackJackAction.STAND | ||
} | ||
|
||
fun canReceiveCard(): Boolean = hand.calculateBestValue() <= 21 | ||
fun receiveCard(card: Card): Player { | ||
override fun receiveCard(card: Card): Player { | ||
return copy(hand = hand.addCard(card)) | ||
} | ||
fun calculateBestValue(): Int = hand.calculateBestValue() | ||
|
||
override fun calculateBestValue(): Int = hand.calculateBestValue() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package blackjack.dealer | ||
|
||
import action.BlackJackAction | ||
import blackjack.card.Card | ||
import blackjack.card.CardRank | ||
import blackjack.card.CardSuit | ||
import blackjack.deck.Deck | ||
import blackjack.hand.StandardHand | ||
import io.kotest.core.spec.style.FunSpec | ||
import io.kotest.matchers.shouldBe | ||
|
||
class DealerTest : FunSpec({ | ||
|
||
test("처음 딜러의 손패 수는 0이다.") { | ||
val dealer = Dealer(hand = StandardHand()) | ||
dealer.cards.size shouldBe 0 | ||
} | ||
|
||
test("손패의 수가 0일 때 결정할 액션은 HIT이다") { | ||
val dealer = Dealer(hand = StandardHand()) | ||
dealer.decideAction(deck = Deck()) shouldBe BlackJackAction.HIT | ||
} | ||
|
||
test("딜러는 카드를 받으면 손패의 수가 1 증가한다.") { | ||
Dealer(hand = StandardHand()).also { | ||
it.cards.size shouldBe 0 | ||
}.receiveCard(card = Card(suit = CardSuit.CLUBS, rank = CardRank.ACE)) | ||
.cards.size shouldBe 1 | ||
} | ||
|
||
test("ACE와 JACK을 가지고 있을 때, 베스트는 21이다.") { | ||
Dealer(hand = StandardHand()) | ||
.receiveCard(card = Card(suit = CardSuit.CLUBS, rank = CardRank.ACE)) | ||
.receiveCard(card = Card(suit = CardSuit.DIAMONDS, rank = CardRank.JACK)) | ||
.calculateBestValue() shouldBe 21 | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters