-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Step3] 블랙잭(딜러) #675
base: yibeomseok
Are you sure you want to change the base?
[Step3] 블랙잭(딜러) #675
Changes from all commits
b3acbfb
bc8dc6f
a8893d0
8ef13bf
c28a626
8ec8919
976609f
8b2ff4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package action | ||
|
||
enum class BlackJackAction { | ||
HIT, | ||
STAND, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackjack | ||
|
||
import blackjack.card.Card | ||
|
||
interface BlackjackParticipant { | ||
fun receiveCard(card: Card): BlackjackParticipant | ||
fun receiveCard(cards: List<Card>): BlackjackParticipant | ||
fun calculateBestValue(): Int | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package blackjack.dealer | ||
|
||
import action.BlackJackAction | ||
import blackjack.BlackjackParticipant | ||
import blackjack.card.Card | ||
import blackjack.deck.Deck | ||
import blackjack.hand.Hand | ||
import blackjack.hand.StandardHand | ||
|
||
data class Dealer( | ||
val dealerStrategy: DealerStrategy = DefaultDealerStrategy(), | ||
private val hand: Hand = StandardHand(), | ||
) : BlackjackParticipant { | ||
|
||
val cards: List<Card> get() = hand.cards() | ||
|
||
override fun receiveCard(card: Card): Dealer = copy(hand = hand.addCard(card)) | ||
|
||
override fun receiveCard(cards: List<Card>): Dealer = copy(hand = hand.addCard(cards)) | ||
|
||
override fun calculateBestValue(): Int = hand.calculateBestValue() | ||
|
||
fun decideAction(deck: Deck): BlackJackAction { | ||
return dealerStrategy.decideAction(hand, deck) | ||
} | ||
} |
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 | ||
} | ||
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.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 remainedScore = 21 - currentScore | ||
val safeCards = deck.remainingCards.count { isSafe(it, remainedScore) } | ||
|
||
return 1.0 - safeCards.toDouble() / deck.remainingCards.size | ||
} | ||
|
||
private fun isSafe(card: Card, remainedScore: Int): Boolean { | ||
val cardValue = when (card.rank) { | ||
CardRank.KING, CardRank.QUEEN, CardRank.JACK -> 10 | ||
CardRank.ACE -> 11 | ||
else -> card.rank.ordinal + 1 | ||
} | ||
return cardValue <= remainedScore | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ import blackjack.card.Card | |
import blackjack.card.CardRank | ||
import blackjack.card.CardSuit | ||
|
||
class StandardCardProvider : CardProvider { | ||
internal class StandardCardProvider : CardProvider { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. internal 키워드 활용! |
||
override fun provideCards(): List<Card> = | ||
CardSuit.values().flatMap { suit -> | ||
CardRank.values().map { rank -> Card(suit, rank) } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package blackjack.game | ||
|
||
import action.BlackJackAction | ||
import blackjack.BlackjackParticipant | ||
import blackjack.card.Card | ||
import blackjack.dealer.Dealer | ||
import blackjack.dealer.DealerStrategy | ||
import blackjack.dealer.DefaultDealerStrategy | ||
import blackjack.deck.Deck | ||
import blackjack.player.Player | ||
|
||
class BlackjackGame private constructor( | ||
players: List<Player>, | ||
dealer: Dealer = Dealer(), | ||
private val deck: Deck = Deck(), | ||
) { | ||
init { | ||
require(players.toSet().isNotEmpty()) { "플레이어가 최소 한 명은 존재해야 합니다." } | ||
} | ||
|
||
var state: GameState = GameState.InitialDeal(players, dealer) | ||
private set | ||
|
||
val players: List<Player> get() = state.players | ||
val dealer: Dealer get() = state.dealer | ||
|
||
fun dealInitialCards() { | ||
check(state is GameState.InitialDeal) { "Initial Deal 상태가 아닙니다." } | ||
val nPlayers = List(players.size) { players[it].receiveCard(deck.drawCard(2)) } | ||
val nDealer = dealer.receiveCard(deck.drawCard(2)) | ||
state = GameState.PlayerTurn(nPlayers, nDealer, currentPlayerIndex = 0) | ||
} | ||
|
||
fun dealPlayerTurn(player: Player, isDeal: Boolean) { | ||
val playerTurnState = state as? GameState.PlayerTurn ?: throw IllegalStateException("Player Turn이 아닙니다.") | ||
require(players.contains(player)) { "${player.name}이라는 플레이어는 없습니다." } | ||
require(player == playerTurnState.currentPlayer) { "현재 턴은 ${player.name}의 턴이 아닙니다." } | ||
|
||
if (isDeal.not()) { | ||
// 다음 플레이어로 넘어감 | ||
moveToNextPlayerOrDealerTurn(playerTurnState.currentPlayerIndex) | ||
} else { | ||
// 카드 받기 | ||
check(player.canHit() == BlackJackAction.HIT) { "해당 플레이어는 더 이상 카드를 받을 수 없습니다." } | ||
val nPlayers = players.map { if (it == player) it.receiveCard(deck.drawCard()) else it } | ||
Comment on lines
+39
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불필요한 주석은 제거해주세요! |
||
state = GameState.PlayerTurn(nPlayers, dealer, playerTurnState.currentPlayerIndex) | ||
} | ||
} | ||
|
||
fun dealDealerTurn(): BlackJackAction { | ||
check(state is GameState.DealerTurn) { "Dealer Turn이 아닙니다." } | ||
val dealerAction = dealer.decideAction(deck) | ||
return if (dealerAction == BlackJackAction.HIT) { | ||
val drawnCard = deck.drawCard() | ||
state = GameState.End(players, dealer.receiveCard(drawnCard)) | ||
BlackJackAction.HIT | ||
} else { | ||
state = GameState.End(players, dealer) | ||
BlackJackAction.STAND | ||
} | ||
} | ||
|
||
fun calculateResult(): Map<BlackjackParticipant, BlackjackResult> { | ||
val results = mutableMapOf<BlackjackParticipant, BlackjackResult>() | ||
results[dealer] = calculateDealerResult() | ||
players.forEach { results[it] = calculatePlayerResult(it) } | ||
return results | ||
} | ||
|
||
fun showPlayerCards(playerName: String): List<Card> { | ||
val player = state.players.find { it.name == playerName } | ||
?: throw IllegalArgumentException("${playerName}이라는 플레이어는 없습니다.") | ||
return player.cards | ||
} | ||
|
||
private fun calculateDealerResult(): BlackjackResult { | ||
val dealerScore = dealer.calculateBestValue() | ||
var win = 0 | ||
var loss = 0 | ||
players.forEach { | ||
if (dealerScore > 21) loss++ | ||
else if (it.calculateBestValue() > 21) win++ | ||
else if (dealerScore > it.calculateBestValue()) win++ | ||
else if (dealerScore <= it.calculateBestValue()) loss++ | ||
} | ||
return BlackjackResult(win, loss) | ||
} | ||
Comment on lines
+63
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 딜러기준 승패를 계산하는 로직을 손보기 위해선 |
||
|
||
private fun calculatePlayerResult(player: Player): BlackjackResult { | ||
val playerScore = player.calculateBestValue() | ||
val dealerScore = dealer.calculateBestValue() | ||
return if (dealerScore > 21) { | ||
BlackjackResult(1, 0) | ||
} else if (playerScore > 21) { | ||
BlackjackResult(0, 1) | ||
} else if (playerScore >= dealerScore) { | ||
BlackjackResult(1, 0) | ||
} else { | ||
BlackjackResult(0, 1) | ||
} | ||
} | ||
Comment on lines
+89
to
+101
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
객체지향 생활체조 원칙에 따라 요 코드도 개선해보면 좋겠네요! |
||
|
||
private fun moveToNextPlayerOrDealerTurn(currentPlayerIndex: Int) { | ||
val nextPlayerIndex = (currentPlayerIndex + 1) % players.size | ||
state = if (nextPlayerIndex == 0) { | ||
GameState.DealerTurn(players, dealer) | ||
} else { | ||
GameState.PlayerTurn(players, dealer, nextPlayerIndex) | ||
} | ||
} | ||
|
||
class BlackjackGameBuilder { | ||
private val players: MutableList<Player> = mutableListOf() | ||
private var dealerStrategy: DealerStrategy = DefaultDealerStrategy() | ||
|
||
fun join(name: String) { | ||
players.add(Player(name = name)) | ||
} | ||
|
||
fun join(names: List<String>) { | ||
names.forEach { | ||
join(it) | ||
} | ||
} | ||
|
||
fun dealerStrategy(strategy: DealerStrategyType) { | ||
when (strategy) { | ||
DealerStrategyType.DEFAULT_DEALER_STRATEGY -> dealerStrategy = DefaultDealerStrategy() | ||
// 다른 전략 추가 | ||
} | ||
} | ||
|
||
fun build(): BlackjackGame { | ||
return BlackjackGame( | ||
players = players.toList(), | ||
dealer = Dealer(dealerStrategy = dealerStrategy) | ||
) | ||
} | ||
} | ||
} | ||
|
||
enum class DealerStrategyType { | ||
DEFAULT_DEALER_STRATEGY | ||
} | ||
|
||
fun blackjackOpen(block: BlackjackGame.BlackjackGameBuilder.() -> Unit): BlackjackGame = | ||
BlackjackGame.BlackjackGameBuilder().apply(block).build() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package blackjack.game | ||
|
||
data class BlackjackResult( | ||
val win: Int, | ||
val lose: Int, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package blackjack.game | ||
|
||
import blackjack.dealer.Dealer | ||
import blackjack.player.Player | ||
|
||
sealed class GameState( | ||
val players: List<Player>, | ||
val dealer: Dealer, | ||
) { | ||
class InitialDeal( | ||
players: List<Player>, | ||
dealer: Dealer, | ||
) : GameState(players, dealer) | ||
|
||
class PlayerTurn( | ||
players: List<Player>, | ||
dealer: Dealer, | ||
val currentPlayerIndex: Int, | ||
) : GameState(players, dealer) { | ||
val currentPlayer: Player | ||
get() = players[currentPlayerIndex] | ||
} | ||
|
||
class DealerTurn( | ||
players: List<Player>, | ||
dealer: Dealer, | ||
) : GameState(players, dealer) | ||
|
||
class End( | ||
players: List<Player>, | ||
dealer: Dealer, | ||
) : GameState(players, dealer) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 DefaultDealerStrategy 만 사용되는데, 굳이 인터페이스로 분리가 필요했을까요? 🤔