Skip to content
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 #683

Open
wants to merge 9 commits into
base: gongwho
Choose a base branch
from
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# kotlin-blackjack

## step2
## step3
- [x] 초기 DeckCards 은 52장의 카드로 이루어진다
- [x] 초기 DeckCards 의 각 Suit 카드는 13장씩 있어야 한다
- [x] 초기 DeckCards 는 중복 없는 카드로 이루어져야 한다
Expand All @@ -17,3 +17,4 @@
- [x] Player 가 bust 상태가 되면 hit 할 수 없다
- [x] Player 가 blackjack 상태가 되면 hit 할 수 없다
- [x] Player 는 stay 후엔 다시 hit 할 수 없다
- [x] Dealer 와 Player 의 승패가 정확히 결정된다
35 changes: 24 additions & 11 deletions src/main/kotlin/blackjack/Blackjack.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
package blackjack

import blackjack.domain.BlackjackResult
import blackjack.domain.PlayerResult
import blackjack.domain.cards.Deck
import blackjack.domain.cards.HandCards
import blackjack.domain.player.Hand
import blackjack.domain.player.Dealer
import blackjack.domain.player.Player
import blackjack.domain.player.PlayerState
import blackjack.view.InputView
import blackjack.view.InputViewCommand
import blackjack.view.ResultView
import blackjack.view.UserInputView

class Blackjack(
private val inputView: InputView,
private val resultView: ResultView,
) {
private val deck = Deck.fullDeck()

init {
deck.shuffle()
}
private val dealer = Dealer(Deck.fullDeck())

fun simulate() {
val playerNames = inputView.getPlayerNames()

dealer.initHand()

val players = createPlayers(playerNames)

resultView.printInitialState(players)
Expand All @@ -30,26 +30,39 @@ class Blackjack(
processPlayerTurn(player)
}

dealer.processTurn {
resultView.printDealerTurn(it)
}

resultView.printPlayer(dealer.asPlayer)
resultView.printResult(players)

val gameResult = BlackjackResult(
players.map { player ->
PlayerResult(player, dealer.wins(player))
}
)

resultView.printBlackjackResult(gameResult)
}

private fun processPlayerTurn(player: Player) {
while (player.state == PlayerState.Hit) {
val command = inputView.getPlayerCommand(player.name)
player.play(command == "y")
player.play(command == InputViewCommand.Yes)
resultView.printPlayer(player)
}
}

private fun createPlayers(playerNames: List<String>): List<Player> {
return playerNames.map { Player(it, Hand(HandCards(mutableListOf(deck.draw(), deck.draw())))) }
return playerNames.map { Player(it, dealer.createInitialHand()) }
}

private fun Player.play(isHit: Boolean) {
if (isHit) {
hit()
val card = deck.draw()
val card = dealer.provideCard()
addCard(card)
println("$name: ${hand.handCards}")
} else {
stay()
}
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/blackjack/domain/BlackjackResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package blackjack.domain

import blackjack.domain.player.Player

data class BlackjackResult(
val playerResult: List<PlayerResult>
) {
val dealerWin get() = playerResult.count { it.isWin.not() }
val dealerLose get() = playerResult.count { it.isWin }
}

data class PlayerResult(
val player: Player,
val isWin: Boolean,
)
42 changes: 42 additions & 0 deletions src/main/kotlin/blackjack/domain/player/Dealer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package blackjack.domain.player

import blackjack.domain.cards.Deck
import blackjack.domain.cards.HandCards

class Dealer(private val deck: Deck) {
private lateinit var _player: Player
val asPlayer get() = _player

init {
deck.shuffle()
}

fun provideCard() = deck.draw()

fun createInitialHand(): Hand = Hand(HandCards(mutableListOf(deck.draw(), deck.draw())))

fun initHand() {
_player = Player(
"딜러",
hand = createInitialHand()
)
}

fun processTurn(onResult: (Boolean) -> Unit) {
val addCard = _player.hand.valueSum() <= 16

if (addCard) _player.addCard(deck.draw())

onResult(addCard)
}

fun wins(player: Player): Boolean {
return if (asPlayer.state.isBust()) {
false
} else if (asPlayer.state.isBust().not() && player.state.isBust()) {
true
} else {
asPlayer.hand.blackjackDiff() < player.hand.blackjackDiff()
}
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/blackjack/domain/player/Hand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package blackjack.domain.player

import blackjack.domain.card.Card
import blackjack.domain.cards.HandCards
import kotlin.math.abs

data class Hand(val handCards: HandCards) {
fun addCard(card: Card) {
handCards.add(card)
}

fun valueSum(): Int = handCards.cardList.sumOf { it.character.value }

fun blackjackDiff() = abs(valueSum() - BLACK_JACK)

fun isBlackjack() = valueSum() == BLACK_JACK

fun isBust() = valueSum() > BLACK_JACK
Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/blackjack/domain/player/PlayerState.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package blackjack.domain.player

enum class PlayerState {
Hit, Stay, Bust, Blackjack
Hit, Stay, Bust, Blackjack;

fun isHit() = this == Hit
fun isBust() = this == Bust
}
14 changes: 13 additions & 1 deletion src/main/kotlin/blackjack/view/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,17 @@ package blackjack.view
interface InputView {
fun getPlayerNames(): List<String>

fun getPlayerCommand(playerName: String): String
fun getPlayerCommand(playerName: String): InputViewCommand
}

sealed interface InputViewCommand {
object Yes : InputViewCommand
object No : InputViewCommand

companion object {
private val validYesCommands: Set<String> = setOf("y")
fun get(commandString: String): InputViewCommand {
return if (commandString in validYesCommands) Yes else No
}
}
}
19 changes: 18 additions & 1 deletion src/main/kotlin/blackjack/view/ResultView.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package blackjack.view

import blackjack.domain.BlackjackResult
import blackjack.domain.player.Player

class ResultView {
Expand All @@ -16,7 +17,23 @@ class ResultView {
}
}

private fun printPlayer(player: Player) {
fun printPlayer(player: Player) {
println("${player.name}: ${player.hand}")
}

fun printDealerTurn(addedCard: Boolean) {
if (addedCard) {
println("딜러는 16이하라 한장의 카드를 더 받았습니다.")
} else {
println("딜러는 17이상이라 카드를 받지 않았습니다.")
}
}

fun printBlackjackResult(blackjackResult: BlackjackResult) {
println("## 최종 승패")
println("딜러: ${blackjackResult.dealerWin}승 ${blackjackResult.dealerLose}패")
blackjackResult.playerResult.forEach { playerResult ->
println("${playerResult.player.name}: ${if (playerResult.isWin) "승" else "패"}")
}
}
}
5 changes: 3 additions & 2 deletions src/main/kotlin/blackjack/view/UserInputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ class UserInputView : InputView {
return readln().split(",").map { it.trim() }
}

override fun getPlayerCommand(playerName: String): String {
override fun getPlayerCommand(playerName: String): InputViewCommand {
println("${playerName}은 한 장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)")
return readln().trim()
val cmd = readln().trim()
return InputViewCommand.get(cmd)
}
}
44 changes: 44 additions & 0 deletions src/test/kotlin/blackjack/domain/BlackjackResultTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package blackjack.domain

import blackjack.domain.card.Card
import blackjack.domain.card.Character
import blackjack.domain.card.Suit
import blackjack.domain.cards.HandCards
import blackjack.domain.player.Hand
import blackjack.domain.player.Player
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

private fun randomCard() = Card(Suit.values().random(), Character.values().random())

class BlackjackResultTest : StringSpec({
"blackjackResult 테스트" {
val player1 = Player("a", Hand(HandCards(mutableListOf(randomCard(), randomCard()))))
val player2 = Player("b", Hand(HandCards(mutableListOf(randomCard(), randomCard()))))
val player3 = Player("c", Hand(HandCards(mutableListOf(randomCard(), randomCard()))))
val player4 = Player("d", Hand(HandCards(mutableListOf(randomCard(), randomCard()))))
val player5 = Player("e", Hand(HandCards(mutableListOf(randomCard(), randomCard()))))

val playerResults = listOf(
PlayerResult(
player1, true
),
PlayerResult(
player2, false
),
PlayerResult(
player3, true
),
PlayerResult(
player4, false
),
PlayerResult(
player5, false
),
)

val blackjackResult = BlackjackResult(playerResults)
blackjackResult.dealerWin shouldBe 3
blackjackResult.dealerLose shouldBe 2
}
})
77 changes: 77 additions & 0 deletions src/test/kotlin/blackjack/domain/player/DealerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package blackjack.domain.player

import blackjack.domain.card.Card
import blackjack.domain.card.Character
import blackjack.domain.card.Suit
import blackjack.domain.cards.Deck
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class DealerTest : StringSpec({
"Dealer 는 Bust 패배한다" {
val dealer = Dealer(Deck.fullDeck())
dealer.initHand()

while (dealer.asPlayer.state.isHit()) {
dealer.asPlayer.addCard(dealer.provideCard())
}

val player = Player("pp", dealer.createInitialHand())

dealer.asPlayer.state.isBust() shouldBe true
dealer.wins(player) shouldBe false
}

"Dealer 는 동점이면 패배한다" {
val dealer = Dealer(Deck.fullDeck())
dealer.initHand()

dealer.asPlayer.toTargetValueOver19(20)

val player = Player("pp", dealer.createInitialHand())

player.toTargetValueOver19(20)

dealer.asPlayer.hand.valueSum() shouldBe 20
player.hand.valueSum() shouldBe 20

dealer.wins(player) shouldBe false
}

"Dealer 승리 테스트" {
val dealer = Dealer(Deck.fullDeck())
dealer.initHand()

dealer.asPlayer.toTargetValueOver19(21)

val player = Player("pp", dealer.createInitialHand())

player.toTargetValueOver19(20)

dealer.asPlayer.hand.valueSum() shouldBe 21
player.hand.valueSum() shouldBe 20

dealer.wins(player) shouldBe true
}

"Dealer process turn 테스트" {
val dealer = Dealer(Deck.fullDeck())
dealer.initHand()

dealer.asPlayer.toTargetValueOver19(20)

dealer.processTurn {
it shouldBe false
}
}
})

private fun Player.toTargetValueOver19(targetValue: Int) {
var remain = targetValue - hand.valueSum()

while (remain != 0) {
val cardValue = if (remain > 10) remain - 10 else remain
addCard(Card(Suit.Diamond, Character.values().first { it.value == cardValue }))
remain -= cardValue
}
}
7 changes: 7 additions & 0 deletions src/test/kotlin/blackjack/domain/player/HandTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,11 @@ class HandTest : StringSpec({
hand.isBlackjack() shouldBe false
hand.isBust() shouldBe true
}

"blackjackDiff 테스트" {
val hand = Hand(HandCards(mutableListOf(Card(Suit.Spade, Character.Jack), Card(Suit.Clover, Character.Jack))))

hand.valueSum() shouldBe 20
hand.blackjackDiff() shouldBe 1
}
})