diff --git a/README.md b/README.md
index 71f85ac028..946365e5c6 100644
--- a/README.md
+++ b/README.md
@@ -65,3 +65,26 @@ Player.hit() : 블랙잭에서 카드를 받는 행동은 "히트" 딜러가
- 매직넘버, 매직스트링을 상수로 분리해내기 (특히 view, model)
- 어떤 테스트코드가 필요한지 생각하고(뭘 구현해야하지, 뭐가 요구조건인지 리마인드) >> 테스트코드를 작성하고 >> 테스트할수 있도록 리팩토링하기
```
+
+
+
+## 🚀 3단계 - 블랙잭(딜러)
+
+- 요구사항 분석
+```text
+- 딜러 기능을 구현
+ - 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 기능을 구현
+ - 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다
+ - 딜러의 히트/스텐드 전략은 자동이라는 의미임. 테스트코드 작성 필요
+
+- 심판기능(Referee) : 점수계산 $& 승패를 판단
+ - 딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리한다.
+ - 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이긴다
+
+-입출력 기능
+ - 게임을 완료한 후 각 플레이어별로 승패를 출력
+
+```
+
+
+walkover - 부전승, 낙승 : 상대방(딜러의 몰수패로 이기는)
diff --git a/src/main/kotlin/blackjack/BlackJackGame.kt b/src/main/kotlin/blackjack/BlackJackGame.kt
new file mode 100644
index 0000000000..816de9ecbe
--- /dev/null
+++ b/src/main/kotlin/blackjack/BlackJackGame.kt
@@ -0,0 +1,38 @@
+package blackjack
+
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.Participants
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.player.playblestrategy.PlayingStrategy
+import blackjack.model.player.playblestrategy.impl.ConsoleInputStrategy
+import blackjack.model.player.playblestrategy.impl.DealerStrategy
+import blackjack.view.OutputView
+
+class BlackJackGame(
+ private val participants: Participants,
+ private val pack: Pack,
+) {
+ fun start() {
+ while (participants.isContinue()) {
+ playingBlackJackTurn(pack)
+ }
+ }
+
+ private fun playingBlackJackTurn(pack: Pack) {
+ playersTurn(pack)
+ val dealerScore = participants.dealer.score()
+ dealerTurn(DealerStrategy(dealerScore), pack)
+ }
+
+ private fun dealerTurn(playingStrategy: PlayingStrategy, pack: Pack) {
+ val playing = participants.dealer.playing(playingStrategy, pack)
+ OutputView.presentDealerAction(playing)
+ }
+
+ private fun playersTurn(pack: Pack) {
+ participants.players.values.forEach { player: Player ->
+ player.playing(ConsoleInputStrategy(player), pack)
+ OutputView.presentPlayer(player)
+ }
+ }
+}
diff --git a/src/main/kotlin/blackjack/Controller.kt b/src/main/kotlin/blackjack/Controller.kt
index 01b613427d..c2c243bdc1 100644
--- a/src/main/kotlin/blackjack/Controller.kt
+++ b/src/main/kotlin/blackjack/Controller.kt
@@ -1,30 +1,15 @@
package blackjack
-import blackjack.model.Participants
-import blackjack.model.Player
+import blackjack.model.card.pack.impl.ShuffledPack
import blackjack.view.InputView
import blackjack.view.OutputView
fun main() {
val participants = InputView.join()
- participants.dealing()
+ participants.dealing(ShuffledPack)
OutputView.dealing(participants)
OutputView.presentCards(participants)
- while (participants.isContinue()) {
- playingBlackJack(participants)
- }
- OutputView.result(participants)
-}
-
-fun playingBlackJack(participants: Participants) {
- participants.participants.forEach {
- it.hitOrStand()
- }
-}
-
-private fun Player.hitOrStand() {
- if (InputView.askHit(this)) {
- this.hit()
- }
- OutputView.playerCardPresent(this)
+ BlackJackGame(participants, ShuffledPack).start()
+ OutputView.presentScores(participants)
+ OutputView.presentResult(participants)
}
diff --git a/src/main/kotlin/blackjack/model/Participants.kt b/src/main/kotlin/blackjack/model/Participants.kt
deleted file mode 100644
index 875f0f922f..0000000000
--- a/src/main/kotlin/blackjack/model/Participants.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package blackjack.model
-
-class Participants(
- val participants: Set,
-) {
- fun count(): Int {
- return participants.size
- }
-
- fun dealing() {
- participants.forEach { it.deal() }
- }
-
- private fun isGameOver(): Boolean {
- return participants.none { Referee.isBlackJack(it) }
- }
-
- fun isContinue(): Boolean {
- return !isGameOver()
- }
-}
diff --git a/src/main/kotlin/blackjack/model/Player.kt b/src/main/kotlin/blackjack/model/Player.kt
deleted file mode 100644
index 4d98a6840d..0000000000
--- a/src/main/kotlin/blackjack/model/Player.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package blackjack.model
-
-import blackjack.model.pack.ShuffledPack
-
-data class Player(
- val name: String,
- val cards: Cards = Cards.emptyCards(),
-) {
- fun deal() {
- cards.add(ShuffledPack.pickCard())
- cards.add(ShuffledPack.pickCard())
- }
-
- fun hit() {
- cards.add(ShuffledPack.pickCard())
- }
-}
diff --git a/src/main/kotlin/blackjack/model/Referee.kt b/src/main/kotlin/blackjack/model/Referee.kt
deleted file mode 100644
index 15c30cbe6c..0000000000
--- a/src/main/kotlin/blackjack/model/Referee.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package blackjack.model
-
-object Referee {
- private const val BLACK_JACK_SCORE: Int = 21
- fun isBlackJack(it: Player): Boolean {
- return it.cards.totalScore() == BLACK_JACK_SCORE
- }
-}
diff --git a/src/main/kotlin/blackjack/model/Suit.kt b/src/main/kotlin/blackjack/model/Suit.kt
deleted file mode 100644
index a0e34e2069..0000000000
--- a/src/main/kotlin/blackjack/model/Suit.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package blackjack.model
-
-enum class Suit(
- val code: String,
- val alias: String,
-) {
- SPADE("♠", "스페이드"),
- HEART("♥", "하트"),
- DIAMOND("♦", "다이아"),
- CLOVER("♣", "클로버"),
- ;
-}
diff --git a/src/main/kotlin/blackjack/model/blackjack/BlackJackStatus.kt b/src/main/kotlin/blackjack/model/blackjack/BlackJackStatus.kt
new file mode 100644
index 0000000000..8eb40c78b8
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/blackjack/BlackJackStatus.kt
@@ -0,0 +1,6 @@
+package blackjack.model.blackjack
+
+enum class BlackJackStatus {
+ ALIVE,
+ STOP,
+}
diff --git a/src/main/kotlin/blackjack/model/blackjack/judgment/Judgment.kt b/src/main/kotlin/blackjack/model/blackjack/judgment/Judgment.kt
new file mode 100644
index 0000000000..add7ed7b31
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/blackjack/judgment/Judgment.kt
@@ -0,0 +1,8 @@
+package blackjack.model.blackjack.judgment
+
+import blackjack.model.player.playable.Playable
+import blackjack.model.result.PlayableResult
+
+interface Judgment {
+ fun sentence(thisPlayable: Playable, otherPlayable: Playable): PlayableResult
+}
diff --git a/src/main/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgment.kt b/src/main/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgment.kt
new file mode 100644
index 0000000000..5daf703bc7
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgment.kt
@@ -0,0 +1,20 @@
+package blackjack.model.blackjack.judgment.impl
+
+import blackjack.model.blackjack.judgment.Judgment
+import blackjack.model.player.playable.Playable
+import blackjack.model.result.PlayableResult
+
+object BlackJackJudgment : Judgment {
+ override fun sentence(thisPlayable: Playable, otherPlayable: Playable): PlayableResult {
+ if (thisPlayable.isBurst() && otherPlayable.isBurst()) {
+ return PlayableResult.DRAW
+ }
+ if (thisPlayable.isBurst() && !otherPlayable.isBurst()) {
+ return PlayableResult.LOSE
+ }
+ if (!thisPlayable.isBurst() && otherPlayable.isBurst()) {
+ return PlayableResult.WIN
+ }
+ return thisPlayable.score() vs otherPlayable.score()
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/Card.kt b/src/main/kotlin/blackjack/model/card/Card.kt
similarity index 52%
rename from src/main/kotlin/blackjack/model/Card.kt
rename to src/main/kotlin/blackjack/model/card/Card.kt
index 25808b1c81..7d17967bf9 100644
--- a/src/main/kotlin/blackjack/model/Card.kt
+++ b/src/main/kotlin/blackjack/model/card/Card.kt
@@ -1,20 +1,20 @@
-package blackjack.model
+package blackjack.model.card
class Card private constructor(
val suit: Suit,
val cardRank: CardRank,
) {
companion object {
- private val CARDS: Map, Card> by lazy {
+ private val CARDS: Map by lazy {
Suit.values().flatMap { suit ->
CardRank.values().map { rank ->
- (suit to rank) to Card(suit, rank)
+ Trump(suit to rank) to Card(suit, rank)
}
}.toMap()
}
fun of(suit: Suit, cardRank: CardRank): Card {
- return requireNotNull(this.CARDS[suit to cardRank]) { "입력된 suit=[$suit] , cardRank=[$cardRank] 는 찾을수 없습니다" }
+ return requireNotNull(CARDS[Trump(suit to cardRank)]) { "입력된 suit=[$suit] , cardRank=[$cardRank] 는 찾을수 없습니다" }
}
}
}
diff --git a/src/main/kotlin/blackjack/model/CardRank.kt b/src/main/kotlin/blackjack/model/card/CardRank.kt
similarity index 91%
rename from src/main/kotlin/blackjack/model/CardRank.kt
rename to src/main/kotlin/blackjack/model/card/CardRank.kt
index e900081fad..47d49e94e8 100644
--- a/src/main/kotlin/blackjack/model/CardRank.kt
+++ b/src/main/kotlin/blackjack/model/card/CardRank.kt
@@ -1,4 +1,4 @@
-package blackjack.model
+package blackjack.model.card
enum class CardRank(
val score: Int,
diff --git a/src/main/kotlin/blackjack/model/Cards.kt b/src/main/kotlin/blackjack/model/card/Cards.kt
similarity index 69%
rename from src/main/kotlin/blackjack/model/Cards.kt
rename to src/main/kotlin/blackjack/model/card/Cards.kt
index f678b87c39..30cf21ddf4 100644
--- a/src/main/kotlin/blackjack/model/Cards.kt
+++ b/src/main/kotlin/blackjack/model/card/Cards.kt
@@ -1,4 +1,6 @@
-package blackjack.model
+package blackjack.model.card
+
+import blackjack.model.player.BlackjackScore
class Cards(
cards: List = emptyList(),
@@ -10,11 +12,11 @@ class Cards(
_cards.add(card)
}
- fun totalScore(): Int {
+ fun totalScore(): BlackjackScore {
if (this.isContainsAce()) {
- return scoreWithAce()
+ return BlackjackScore(scoreWithAce())
}
- return simpleSumOfScore()
+ return BlackjackScore(simpleSumOfScore())
}
private fun simpleSumOfScore(): Int {
@@ -28,10 +30,10 @@ class Cards(
private fun scoreWithAce(): Int {
val simpleSum = simpleSumOfScore()
- if (simpleSum + ALTERNATIVE_ACE_SCORE_DIFF > HIGHEST_SCORE) {
+ if (simpleSum + ACE_BONUS_SCORE > HIGHEST_SCORE) {
return simpleSum
}
- return simpleSum + ALTERNATIVE_ACE_SCORE_DIFF
+ return simpleSum + ACE_BONUS_SCORE
}
fun count(): Int {
@@ -40,7 +42,7 @@ class Cards(
companion object {
private const val HIGHEST_SCORE: Int = 21
- private const val ALTERNATIVE_ACE_SCORE_DIFF: Int = 11 - 1
+ private const val ACE_BONUS_SCORE: Int = 10
fun emptyCards(): Cards {
return Cards(mutableListOf())
diff --git a/src/main/kotlin/blackjack/model/card/Suit.kt b/src/main/kotlin/blackjack/model/card/Suit.kt
new file mode 100644
index 0000000000..dd83ffda04
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/card/Suit.kt
@@ -0,0 +1,11 @@
+package blackjack.model.card
+
+enum class Suit(
+ val alias: String,
+) {
+ SPADE("스페이드"),
+ HEART("하트"),
+ DIAMOND("다이아"),
+ CLOVER("클로버"),
+ ;
+}
diff --git a/src/main/kotlin/blackjack/model/card/Trump.kt b/src/main/kotlin/blackjack/model/card/Trump.kt
new file mode 100644
index 0000000000..a152ad4bc2
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/card/Trump.kt
@@ -0,0 +1,6 @@
+package blackjack.model.card
+
+@JvmInline
+value class Trump(
+ val trump: Pair,
+)
diff --git a/src/main/kotlin/blackjack/model/card/pack/Pack.kt b/src/main/kotlin/blackjack/model/card/pack/Pack.kt
new file mode 100644
index 0000000000..beb091ae91
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/card/pack/Pack.kt
@@ -0,0 +1,7 @@
+package blackjack.model.card.pack
+
+import blackjack.model.card.Card
+
+interface Pack {
+ fun pickCard(): Card
+}
diff --git a/src/main/kotlin/blackjack/model/pack/ShuffledPack.kt b/src/main/kotlin/blackjack/model/card/pack/impl/ShuffledPack.kt
similarity index 68%
rename from src/main/kotlin/blackjack/model/pack/ShuffledPack.kt
rename to src/main/kotlin/blackjack/model/card/pack/impl/ShuffledPack.kt
index 897abc5a71..d24f0f1610 100644
--- a/src/main/kotlin/blackjack/model/pack/ShuffledPack.kt
+++ b/src/main/kotlin/blackjack/model/card/pack/impl/ShuffledPack.kt
@@ -1,8 +1,9 @@
-package blackjack.model.pack
+package blackjack.model.card.pack.impl
-import blackjack.model.Card
-import blackjack.model.CardRank
-import blackjack.model.Suit
+import blackjack.model.card.Card
+import blackjack.model.card.CardRank
+import blackjack.model.card.Suit
+import blackjack.model.card.pack.Pack
import kotlin.random.Random
object ShuffledPack : Pack {
@@ -21,6 +22,6 @@ object ShuffledPack : Pack {
}
private fun randomInRange(range: Int): Int {
- return this.random.nextInt(0, Int.MAX_VALUE) % range
+ return random.nextInt(0, Int.MAX_VALUE) % range
}
}
diff --git a/src/main/kotlin/blackjack/model/pack/Pack.kt b/src/main/kotlin/blackjack/model/pack/Pack.kt
deleted file mode 100644
index da8925a27a..0000000000
--- a/src/main/kotlin/blackjack/model/pack/Pack.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package blackjack.model.pack
-
-import blackjack.model.Card
-
-interface Pack {
- fun pickCard(): Card
-}
diff --git a/src/main/kotlin/blackjack/model/player/BlackjackScore.kt b/src/main/kotlin/blackjack/model/player/BlackjackScore.kt
new file mode 100644
index 0000000000..0bf9ff71e2
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/BlackjackScore.kt
@@ -0,0 +1,30 @@
+package blackjack.model.player
+
+import blackjack.model.result.PlayableResult
+
+@JvmInline
+value class BlackjackScore(
+ val value: Int
+) {
+ infix fun vs(other: BlackjackScore): PlayableResult {
+ if (this.value == other.value) {
+ return PlayableResult.DRAW
+ }
+ if (this.value > other.value) {
+ return PlayableResult.WIN
+ }
+ return PlayableResult.LOSE
+ }
+
+ fun isBurst(): Boolean {
+ return this.value > BLACK_JACK_SCORE
+ }
+
+ operator fun compareTo(other: BlackjackScore): Int {
+ return this.value.compareTo(other.value)
+ }
+
+ companion object {
+ private const val BLACK_JACK_SCORE: Int = 21
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/Participants.kt b/src/main/kotlin/blackjack/model/player/Participants.kt
new file mode 100644
index 0000000000..c0e15db1b1
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/Participants.kt
@@ -0,0 +1,28 @@
+package blackjack.model.player
+
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.result.DealerResult
+
+class Participants(
+ val players: Players,
+ val dealer: Dealer,
+) {
+
+ fun dealing(pack: Pack) {
+ players.dealing(pack)
+ dealer.dealing(pack)
+ }
+
+ fun isContinue(): Boolean {
+ return players.hasAnyAlivePlayer() && (!dealer.isBurst())
+ }
+
+ fun count(): Int {
+ return players.count() + 1
+ }
+
+ fun dealerResult(): DealerResult {
+ return this.dealer.dealerResult(this.players)
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/PlayableReaction.kt b/src/main/kotlin/blackjack/model/player/PlayableReaction.kt
new file mode 100644
index 0000000000..07f03c8333
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/PlayableReaction.kt
@@ -0,0 +1,6 @@
+package blackjack.model.player
+
+enum class PlayableReaction {
+ HIT,
+ STAND
+}
diff --git a/src/main/kotlin/blackjack/model/player/Players.kt b/src/main/kotlin/blackjack/model/player/Players.kt
new file mode 100644
index 0000000000..3448af29d4
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/Players.kt
@@ -0,0 +1,34 @@
+package blackjack.model.player
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.result.PlayableResult
+
+class Players(
+ val values: List,
+) {
+ init {
+ require(values.map { it.name }.distinct().size == values.size) { "Player 들의 이름은 중복이 허용되지 않습니다" }
+ }
+
+ constructor(vararg players: Player) : this(players.toList())
+
+ fun dealing(pack: Pack) {
+ values.forEach { it.dealing(pack) }
+ }
+
+ fun hasAnyAlivePlayer(): Boolean {
+ val burstPlayers: List = values.map { it.status() == BlackJackStatus.ALIVE }.toList()
+ return burstPlayers.contains(true)
+ }
+
+ fun count(): Int {
+ return values.size
+ }
+
+ fun results(dealer: Dealer): List> {
+ return this.values.map { player -> player to player.result(dealer) }
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playable/Playable.kt b/src/main/kotlin/blackjack/model/player/playable/Playable.kt
new file mode 100644
index 0000000000..1de3a8b8fd
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playable/Playable.kt
@@ -0,0 +1,17 @@
+package blackjack.model.player.playable
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.PlayableReaction
+import blackjack.model.player.playblestrategy.PlayingStrategy
+import blackjack.model.result.PlayableResult
+
+interface Playable {
+ fun score(): BlackjackScore
+ fun dealing(pack: Pack)
+ fun playing(playingStrategy: PlayingStrategy, pack: Pack): PlayableReaction
+ fun result(playable: Playable): PlayableResult
+ fun isBurst(): Boolean
+ fun status(): BlackJackStatus
+}
diff --git a/src/main/kotlin/blackjack/model/player/playable/impl/Dealer.kt b/src/main/kotlin/blackjack/model/player/playable/impl/Dealer.kt
new file mode 100644
index 0000000000..e1bcb0ba15
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playable/impl/Dealer.kt
@@ -0,0 +1,63 @@
+package blackjack.model.player.playable.impl
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.blackjack.judgment.impl.BlackJackJudgment
+import blackjack.model.card.Cards
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.PlayableReaction
+import blackjack.model.player.Players
+import blackjack.model.player.playable.Playable
+import blackjack.model.player.playblestrategy.PlayingStrategy
+import blackjack.model.result.DealerResult
+import blackjack.model.result.PlayableResult
+
+class Dealer(
+ val cards: Cards = Cards(),
+) : Playable {
+
+ override fun score(): BlackjackScore {
+ return cards.totalScore()
+ }
+
+ override fun dealing(pack: Pack) {
+ this.cards.add(pack.pickCard())
+ this.cards.add(pack.pickCard())
+ }
+
+ fun countOfCards(): Int {
+ return cards.count()
+ }
+
+ override fun playing(playingStrategy: PlayingStrategy, pack: Pack): PlayableReaction {
+ if (playingStrategy.isHit()) {
+ cards.add(pack.pickCard())
+ return PlayableReaction.HIT
+ }
+ return PlayableReaction.STAND
+ }
+
+ override fun result(playable: Playable): PlayableResult {
+ return BlackJackJudgment.sentence(this, playable)
+ }
+
+ override fun status(): BlackJackStatus {
+ if (this.score().isBurst()) {
+ return BlackJackStatus.STOP
+ }
+ return BlackJackStatus.ALIVE
+ }
+
+ override fun isBurst(): Boolean {
+ return this.score().isBurst()
+ }
+
+ fun dealerResult(players: Players): DealerResult {
+ val results = players.values.map { player -> this.result(player) }
+ return DealerResult(
+ winningCount = results.count { it == PlayableResult.WIN },
+ drawingCount = results.count { it == PlayableResult.DRAW },
+ losingCount = results.count { it == PlayableResult.LOSE },
+ )
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playable/impl/Player.kt b/src/main/kotlin/blackjack/model/player/playable/impl/Player.kt
new file mode 100644
index 0000000000..1acf47a52b
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playable/impl/Player.kt
@@ -0,0 +1,59 @@
+package blackjack.model.player.playable.impl
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.blackjack.judgment.impl.BlackJackJudgment
+import blackjack.model.card.Cards
+import blackjack.model.card.pack.Pack
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.PlayableReaction
+import blackjack.model.player.playable.Playable
+import blackjack.model.player.playblestrategy.PlayingStrategy
+import blackjack.model.result.PlayableResult
+
+class Player(
+ val name: String,
+ val cards: Cards = Cards.emptyCards(),
+ var status: BlackJackStatus,
+) : Playable {
+
+ override fun score(): BlackjackScore {
+ return this.cards.totalScore()
+ }
+
+ override fun dealing(pack: Pack) {
+ cards.add(pack.pickCard())
+ cards.add(pack.pickCard())
+ }
+
+ override fun playing(playingStrategy: PlayingStrategy, pack: Pack): PlayableReaction {
+ if (playingStrategy.isHit()) {
+ this.hit(pack)
+ return PlayableReaction.HIT
+ }
+ this.status = BlackJackStatus.STOP
+ return PlayableReaction.STAND
+ }
+
+ override fun result(playable: Playable): PlayableResult {
+ return BlackJackJudgment.sentence(this, playable)
+ }
+
+ override fun isBurst(): Boolean {
+ if (this.score().isBurst()) {
+ this.status = BlackJackStatus.STOP
+ return true
+ }
+ return false
+ }
+
+ fun hit(pack: Pack) {
+ cards.add(pack.pickCard())
+ }
+
+ override fun status(): BlackJackStatus {
+ if (this.isBurst()) {
+ return BlackJackStatus.STOP
+ }
+ return this.status
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playblestrategy/PlayingStrategy.kt b/src/main/kotlin/blackjack/model/player/playblestrategy/PlayingStrategy.kt
new file mode 100644
index 0000000000..4e18ce86b4
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playblestrategy/PlayingStrategy.kt
@@ -0,0 +1,5 @@
+package blackjack.model.player.playblestrategy
+
+interface PlayingStrategy {
+ fun isHit(): Boolean
+}
diff --git a/src/main/kotlin/blackjack/model/player/playblestrategy/impl/ConsoleInputStrategy.kt b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/ConsoleInputStrategy.kt
new file mode 100644
index 0000000000..0499e43933
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/ConsoleInputStrategy.kt
@@ -0,0 +1,22 @@
+package blackjack.model.player.playblestrategy.impl
+
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.player.playblestrategy.PlayingStrategy
+
+class ConsoleInputStrategy(
+ val player: Player,
+) : PlayingStrategy {
+
+ override fun isHit(): Boolean {
+ println("${player.name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)")
+ return this.isHitInput()
+ }
+
+ private fun isHitInput(): Boolean {
+ return (readlnOrNull() ?: "") == HIT_COMMAND
+ }
+
+ companion object {
+ private const val HIT_COMMAND: String = "y"
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playblestrategy/impl/DealerStrategy.kt b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/DealerStrategy.kt
new file mode 100644
index 0000000000..19a2e22435
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/DealerStrategy.kt
@@ -0,0 +1,17 @@
+package blackjack.model.player.playblestrategy.impl
+
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.playblestrategy.PlayingStrategy
+
+data class DealerStrategy(
+ private val currentScore: BlackjackScore,
+) : PlayingStrategy {
+
+ override fun isHit(): Boolean {
+ return currentScore <= DEALER_PICK_THRESHOLD
+ }
+
+ companion object {
+ private val DEALER_PICK_THRESHOLD = BlackjackScore(16)
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playblestrategy/impl/HitStrategy.kt b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/HitStrategy.kt
new file mode 100644
index 0000000000..9458e9ee52
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/HitStrategy.kt
@@ -0,0 +1,9 @@
+package blackjack.model.player.playblestrategy.impl
+
+import blackjack.model.player.playblestrategy.PlayingStrategy
+
+object HitStrategy : PlayingStrategy {
+ override fun isHit(): Boolean {
+ return true
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/player/playblestrategy/impl/StandStrategy.kt b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/StandStrategy.kt
new file mode 100644
index 0000000000..d2a7dbc43a
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/player/playblestrategy/impl/StandStrategy.kt
@@ -0,0 +1,9 @@
+package blackjack.model.player.playblestrategy.impl
+
+import blackjack.model.player.playblestrategy.PlayingStrategy
+
+object StandStrategy : PlayingStrategy {
+ override fun isHit(): Boolean {
+ return false
+ }
+}
diff --git a/src/main/kotlin/blackjack/model/result/DealerResult.kt b/src/main/kotlin/blackjack/model/result/DealerResult.kt
new file mode 100644
index 0000000000..db8f13cd0f
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/result/DealerResult.kt
@@ -0,0 +1,7 @@
+package blackjack.model.result
+
+data class DealerResult(
+ val winningCount: Int,
+ val drawingCount: Int,
+ val losingCount: Int,
+)
diff --git a/src/main/kotlin/blackjack/model/result/PlayableResult.kt b/src/main/kotlin/blackjack/model/result/PlayableResult.kt
new file mode 100644
index 0000000000..e2e94cda8f
--- /dev/null
+++ b/src/main/kotlin/blackjack/model/result/PlayableResult.kt
@@ -0,0 +1,7 @@
+package blackjack.model.result
+
+enum class PlayableResult {
+ WIN,
+ LOSE,
+ DRAW,
+}
diff --git a/src/main/kotlin/blackjack/view/Console.kt b/src/main/kotlin/blackjack/view/Console.kt
deleted file mode 100644
index a57cba1bfe..0000000000
--- a/src/main/kotlin/blackjack/view/Console.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package blackjack.view
-
-import blackjack.model.Cards
-import blackjack.model.Player
-
-object Console {
-
- fun Cards.present(): String {
- return cards.joinToString(separator = ", ") { "${it.cardRank.alias}${it.suit.alias}" }
- }
-
- fun Player.present(): String {
- return "${this.name}카드 : ${this.cards.present()}"
- }
-}
diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt
index 08fd3a1cd6..bec3904164 100644
--- a/src/main/kotlin/blackjack/view/InputView.kt
+++ b/src/main/kotlin/blackjack/view/InputView.kt
@@ -1,19 +1,25 @@
package blackjack.view
-import blackjack.model.Participants
-import blackjack.model.Player
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.player.Participants
+import blackjack.model.player.Players
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
object InputView {
private const val PLAYER_NAMES_DELIMITER: String = ","
- private const val HIT_COMMAND: String = "y"
+
const val PARTICIPANTS_PRESENT_SEPARATOR: String = ", "
private fun joinPlayers(input: String): Participants {
return Participants(
- input.split(PLAYER_NAMES_DELIMITER)
- .asSequence()
- .map { Player(it) }
- .toSet()
+ Players(
+ input.split(PLAYER_NAMES_DELIMITER)
+ .asSequence()
+ .map { Player(name = it, status = BlackJackStatus.ALIVE) }
+ .toList()
+ ),
+ Dealer()
)
}
@@ -21,13 +27,4 @@ object InputView {
println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)")
return joinPlayers(readlnOrNull() ?: "")
}
-
- private fun isHitInput(): Boolean {
- return (readlnOrNull() ?: "") == HIT_COMMAND
- }
-
- fun askHit(it: Player): Boolean {
- println("${it.name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)")
- return this.isHitInput()
- }
}
diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt
index 24527e6393..2adba6969c 100644
--- a/src/main/kotlin/blackjack/view/OutputView.kt
+++ b/src/main/kotlin/blackjack/view/OutputView.kt
@@ -1,41 +1,93 @@
package blackjack.view
-import blackjack.model.Participants
-import blackjack.model.Player
-import blackjack.view.Console.present
+import blackjack.model.card.Cards
+import blackjack.model.player.Participants
+import blackjack.model.player.PlayableReaction
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.result.DealerResult
+import blackjack.model.result.PlayableResult
object OutputView {
fun presentCards(participants: Participants) {
- println(participants.present())
+ println(participants.presentDealer())
+ println(participants.presentPlayers())
}
- fun result(participants: Participants) {
+ fun presentScores(participants: Participants) {
presentCardsWitScore(participants)
}
private fun presentCardsWitScore(participants: Participants) {
- println()
- println(participants.presentWithScore())
+ print(System.lineSeparator())
+ println(participants.presentDealerWithScore())
+ println(participants.presentPlayerWithScore())
}
fun dealing(participants: Participants) {
- println("${participants.names()} 에게 2 장씩 나누었습니다.")
+ println("딜러와 ${participants.names()} 에게 2 장씩 나누었습니다.")
}
- fun playerCardPresent(it: Player) {
- println(it.present())
+ fun presentResult(participants: Participants) {
+ println("## 최종 승패")
+ presentDealerResult(participants.dealerResult())
+ presentPlayersResult(participants.players.results(participants.dealer))
+ }
+
+ private fun presentPlayersResult(playersResult: List>) {
+ playersResult
+ .forEach { presentPlayerResult(it.first, it.second) }
+ }
+
+ private fun presentPlayerResult(player: Player, result: PlayableResult) {
+ println("${player.name}: ${result.name}")
+ }
+
+ fun presentPlayer(player: Player) {
+ println(player.presentPlayers())
+ }
+
+ private fun presentDealerResult(dealerResult: DealerResult) {
+ println("딜러 ${dealerResult.winningCount}승 ${dealerResult.drawingCount}무 ${dealerResult.losingCount}패")
+ }
+
+ fun presentDealerAction(playableReaction: PlayableReaction) {
+ when (playableReaction) {
+ PlayableReaction.HIT -> println("딜러는 16이하라 한장의 카드를 더 받았습니다.")
+ PlayableReaction.STAND -> println("딜러는 17이상이라 카드를 받지 않았습니다.")
+ }
}
}
-private fun Participants.present(): String {
- return this.participants.joinToString(separator = "\n") { it.present() }
+private fun Participants.presentPlayers(): String {
+ return this.players.values.joinToString(separator = "\n") { it.presentPlayers() }
}
-private fun Participants.presentWithScore(): String {
- return this.participants.joinToString(separator = "\n") { "${it.present()} - 결과: ${it.cards.totalScore()}" }
+private fun Participants.presentDealer(): String {
+ return this.dealer.presentDealers()
+}
+
+private fun Participants.presentDealerWithScore(): String {
+ return "${this.dealer.presentDealers()} - 결과: ${this.dealer.score().value}"
+}
+
+private fun Participants.presentPlayerWithScore(): String {
+ return this.players.values.joinToString(separator = "\n") { "${it.presentPlayers()} - 결과: ${it.cards.totalScore().value}" }
}
private fun Participants.names(): String {
- return participants.joinToString(separator = InputView.PARTICIPANTS_PRESENT_SEPARATOR) { it.name }
+ return players.values.joinToString(separator = InputView.PARTICIPANTS_PRESENT_SEPARATOR) { it.name }
+}
+
+fun Cards.presentPlayers(): String {
+ return cards.joinToString(separator = ", ") { "${it.cardRank.alias}${it.suit.alias}" }
+}
+
+fun Player.presentPlayers(): String {
+ return "${this.name}카드 : ${this.cards.presentPlayers()}"
+}
+
+fun Dealer.presentDealers(): String {
+ return "딜러 카드 : ${this.cards.presentPlayers()}"
}
diff --git a/src/test/kotlin/blackjack/model/CardsTest.kt b/src/test/kotlin/blackjack/model/CardsTest.kt
deleted file mode 100644
index 0bba60f724..0000000000
--- a/src/test/kotlin/blackjack/model/CardsTest.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package blackjack.model
-
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-
-class CardsTest : StringSpec({
-
- "Cards 의 모든 카드의 점수의 합을 반환해야 한다" {
- val actual = Cards(
- listOf(
- Card.of(Suit.CLOVER, CardRank.THREE),
- Card.of(Suit.DIAMOND, CardRank.FIVE),
- Card.of(Suit.HEART, CardRank.EIGHT),
- Card.of(Suit.HEART, CardRank.KING)
- )
- ).totalScore()
- actual shouldBe (3 + 5 + 8 + 10)
- }
-
- "합계점수가 21를 초과 하는 경우, ACE 가 1로 인식되어야 한다" {
- val actual = Cards(
- listOf(
- Card.of(Suit.HEART, CardRank.ACE),
- Card.of(Suit.DIAMOND, CardRank.FIVE),
- Card.of(Suit.HEART, CardRank.EIGHT),
- Card.of(Suit.HEART, CardRank.KING)
- )
- ).totalScore()
- actual shouldBe (1 + 5 + 8 + 10)
- }
-
- "합계점수가 21를 이하인 경우, ACE 가 11로 인식되어야 한다" {
- val actual = Cards(
- listOf(
- Card.of(Suit.HEART, CardRank.ACE),
- Card.of(Suit.HEART, CardRank.THREE),
- Card.of(Suit.DIAMOND, CardRank.FIVE),
- )
- ).totalScore()
- actual shouldBe (11 + 3 + 5)
- }
-})
diff --git a/src/test/kotlin/blackjack/model/PlayerTest.kt b/src/test/kotlin/blackjack/model/PlayerTest.kt
deleted file mode 100644
index 41efb3eb5f..0000000000
--- a/src/test/kotlin/blackjack/model/PlayerTest.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package blackjack.model
-
-import io.kotest.assertions.throwables.shouldNotThrow
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-
-class PlayerTest : StringSpec({
- "플레이어는 dealing 시 2장의 카드를 받을 수 있다" {
- shouldNotThrow {
- val player = Player("구글")
- player.deal()
- player.cards.count() shouldBe 2
- }
- }
-
- "플레이어는 hit 시 1장의 카드를 받을 수 있다" {
- shouldNotThrow {
- val player = Player("애플")
- player.hit()
- player.cards.count() shouldBe 1
- }
- }
-})
diff --git a/src/test/kotlin/blackjack/model/PlayersTest.kt b/src/test/kotlin/blackjack/model/PlayersTest.kt
new file mode 100644
index 0000000000..127ae76dd0
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/PlayersTest.kt
@@ -0,0 +1,23 @@
+package blackjack.model
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.player.Players
+import blackjack.model.player.playable.impl.Player
+import io.kotest.assertions.throwables.shouldNotThrow
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.StringSpec
+
+class PlayersTest : StringSpec({
+
+ "플레이어들의 이름은 중복을 허용하지 않는다. 중복시 throw IllegalArgumentException" {
+ shouldThrow {
+ Players(Player("hana", status = BlackJackStatus.ALIVE), Player("hana", status = BlackJackStatus.ALIVE))
+ }
+ }
+
+ "플레이어들의 이름이 중복되지 않으면 정상 동작 해야한다" {
+ shouldNotThrow {
+ Players(Player("hana", status = BlackJackStatus.ALIVE), Player("numa", status = BlackJackStatus.ALIVE))
+ }
+ }
+})
diff --git a/src/test/kotlin/blackjack/model/RefereeTest.kt b/src/test/kotlin/blackjack/model/RefereeTest.kt
deleted file mode 100644
index 2da25b0950..0000000000
--- a/src/test/kotlin/blackjack/model/RefereeTest.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package blackjack.model
-
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-
-class RefereeTest : StringSpec({
-
- "ACE+ACE+KING 이 들어온 경우 ACE 를 1로 인식해, 블랙잭이 아니어야만 한다" {
- val player = Player(
- "kim",
- Cards(
- listOf(
- Card.of(Suit.HEART, CardRank.ACE),
- Card.of(Suit.DIAMOND, CardRank.ACE),
- Card.of(Suit.CLOVER, CardRank.KING)
- )
- )
- )
- Referee.isBlackJack(player) shouldBe false
- }
-
- "ACE+KING 이 들어온 경우 ACE 를 11로 인식해, 블랙잭으로 계산 되어야 한다" {
- val player = Player(
- "kim",
- Cards(
- listOf(
- Card.of(Suit.DIAMOND, CardRank.ACE),
- Card.of(Suit.CLOVER, CardRank.KING)
- )
- )
- )
- Referee.isBlackJack(player) shouldBe true
- }
-})
diff --git a/src/test/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgmentTest.kt b/src/test/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgmentTest.kt
new file mode 100644
index 0000000000..7c165e00ad
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/blackjack/judgment/impl/BlackJackJudgmentTest.kt
@@ -0,0 +1,52 @@
+package blackjack.model.blackjack.judgment.impl
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.card.CardFixture
+import blackjack.model.card.CardFixture.makeCards
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.result.PlayableResult
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
+
+class BlackJackJudgmentTest : StringSpec({
+
+ "플레이어와 딜러 모두 burst가 아니라면 점수가 높은 쪽이 승리해야 한다" {
+ val player = Player("pi", makeCards(CardFixture.two), status = BlackJackStatus.ALIVE)
+ val dealer = Dealer(makeCards(CardFixture.three))
+
+ player.result(dealer) shouldBe PlayableResult.LOSE
+ dealer.result(player) shouldBe PlayableResult.WIN
+ }
+ "플레이어와 딜러 모두 burst 라면 DRAW 이어야한다" {
+ val player = Player("pi", makeCards(CardFixture.jack, CardFixture.queen, CardFixture.five), status = BlackJackStatus.ALIVE)
+ val dealer = Dealer(makeCards(CardFixture.three, CardFixture.ten, CardFixture.nine))
+
+ player.result(dealer) shouldBe PlayableResult.DRAW
+ dealer.result(player) shouldBe PlayableResult.DRAW
+ }
+
+ "플레이어가 burst 이고, 딜러는 burst 가 아니라면 딜러가 승리해야 한다" {
+ val player = Player("pi", makeCards(CardFixture.jack, CardFixture.queen, CardFixture.five), status = BlackJackStatus.ALIVE)
+ val dealer = Dealer(makeCards(CardFixture.three, CardFixture.ten, CardFixture.four))
+
+ player.result(dealer) shouldBe PlayableResult.LOSE
+ dealer.result(player) shouldBe PlayableResult.WIN
+ }
+
+ "딜러가 burst 이고, 플레이어는 burst 가 아니라면 플레이어가 승리해야 한다" {
+ val player = Player("pi", makeCards(CardFixture.jack, CardFixture.two, CardFixture.five), status = BlackJackStatus.ALIVE)
+ val dealer = Dealer(makeCards(CardFixture.three, CardFixture.ten, CardFixture.nine))
+
+ player.result(dealer) shouldBe PlayableResult.WIN
+ dealer.result(player) shouldBe PlayableResult.LOSE
+ }
+
+ "플레이어와 딜러 모두 BlackJack(21점) 이라면 DRAW 이어야한다" {
+ val player = Player("pi", makeCards(CardFixture.jack, CardFixture.queen, CardFixture.ace2), status = BlackJackStatus.ALIVE)
+ val dealer = Dealer(makeCards(CardFixture.three, CardFixture.ten, CardFixture.eight))
+
+ player.result(dealer) shouldBe PlayableResult.DRAW
+ dealer.result(player) shouldBe PlayableResult.DRAW
+ }
+})
diff --git a/src/test/kotlin/blackjack/model/card/CardFixture.kt b/src/test/kotlin/blackjack/model/card/CardFixture.kt
new file mode 100644
index 0000000000..29c802fc55
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/card/CardFixture.kt
@@ -0,0 +1,22 @@
+package blackjack.model.card
+
+object CardFixture {
+ fun makeCards(vararg cardArray: Card): Cards {
+ return Cards(cardArray.toList())
+ }
+
+ val ace1 = Card.of(Suit.HEART, CardRank.ACE)
+ val ace2 = Card.of(Suit.DIAMOND, CardRank.ACE)
+ val two = Card.of(Suit.DIAMOND, CardRank.TWO)
+ val three = Card.of(Suit.DIAMOND, CardRank.THREE)
+ val four = Card.of(Suit.DIAMOND, CardRank.FOUR)
+ val five = Card.of(Suit.DIAMOND, CardRank.FIVE)
+ val six = Card.of(Suit.DIAMOND, CardRank.SIX)
+ val seven = Card.of(Suit.DIAMOND, CardRank.SEVEN)
+ val eight = Card.of(Suit.DIAMOND, CardRank.EIGHT)
+ val nine = Card.of(Suit.DIAMOND, CardRank.NINE)
+ val ten = Card.of(Suit.DIAMOND, CardRank.TEN)
+ val jack = Card.of(Suit.CLOVER, CardRank.JACK)
+ val queen = Card.of(Suit.CLOVER, CardRank.QUEEN)
+ val king = Card.of(Suit.CLOVER, CardRank.KING)
+}
diff --git a/src/test/kotlin/blackjack/model/card/CardsTest.kt b/src/test/kotlin/blackjack/model/card/CardsTest.kt
new file mode 100644
index 0000000000..392415f1db
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/card/CardsTest.kt
@@ -0,0 +1,91 @@
+package blackjack.model.card
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.playable.impl.Player
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.collections.shouldBeIn
+import io.kotest.matchers.shouldBe
+
+class CardsTest : StringSpec({
+
+ "Cards 의 모든 카드의 점수의 합을 반환해야 한다" {
+ val actual = Cards(
+ listOf(
+ Card.of(Suit.CLOVER, CardRank.THREE),
+ Card.of(Suit.DIAMOND, CardRank.FIVE),
+ Card.of(Suit.HEART, CardRank.EIGHT),
+ Card.of(Suit.HEART, CardRank.KING)
+ )
+ ).totalScore()
+ actual shouldBe BlackjackScore(3 + 5 + 8 + 10)
+ }
+
+ "합계점수가 21를 초과 하는 경우, ACE 가 1로 인식되어야 한다" {
+ val actual = Cards(
+ listOf(
+ Card.of(Suit.HEART, CardRank.ACE),
+ Card.of(Suit.DIAMOND, CardRank.FIVE),
+ Card.of(Suit.HEART, CardRank.EIGHT),
+ Card.of(Suit.HEART, CardRank.KING)
+ )
+ ).totalScore()
+ actual shouldBe BlackjackScore(1 + 5 + 8 + 10)
+ }
+
+ "합계점수가 21를 이하인 경우, ACE 가 11로 인식되어야 한다" {
+ val actual = Cards(
+ listOf(
+ Card.of(Suit.HEART, CardRank.ACE),
+ Card.of(Suit.HEART, CardRank.THREE),
+ Card.of(Suit.DIAMOND, CardRank.FIVE),
+ )
+ ).totalScore()
+ actual shouldBe BlackjackScore(11 + 3 + 5)
+ }
+
+ "ACE+ACE+KING 이 들어온 경우 ACE 를 1로 인식해, 스코어가 12 이어야 만 한다" {
+ val player = Player(
+ "kim",
+ CardFixture.makeCards(CardFixture.ace1, CardFixture.ace2, CardFixture.king),
+ status = BlackJackStatus.ALIVE
+ )
+ player.status() shouldBe BlackJackStatus.ALIVE
+ player.isBurst() shouldBe false
+ player.score() shouldBe BlackjackScore(12)
+ }
+
+ "ACE+KING 이 들어온 경우 ACE 를 11로 인식해, 블랙잭 점수인 21 로 계산 되어야 한다" {
+ val player = Player(
+ "kong",
+ CardFixture.makeCards(CardFixture.ace1, CardFixture.king),
+ status = BlackJackStatus.ALIVE
+ )
+ player.status() shouldBe BlackJackStatus.ALIVE
+ player.isBurst() shouldBe false
+ player.score() shouldBe BlackjackScore(21)
+ }
+
+ "카드가 여러장 들어있는 상황에서도 합계 점수를 정확히 구해낼 수 있어야한다" {
+ val player = Player(
+ "kong",
+ CardFixture.makeCards(
+ CardFixture.ace1,
+ CardFixture.two,
+ CardFixture.three,
+ CardFixture.four,
+ CardFixture.five,
+ CardFixture.six,
+ CardFixture.seven,
+ CardFixture.eight,
+ CardFixture.nine,
+ CardFixture.ten,
+ CardFixture.king,
+ CardFixture.jack,
+ CardFixture.queen
+ ),
+ status = BlackJackStatus.ALIVE
+ )
+ player.score() shouldBeIn listOf(BlackjackScore(85), BlackjackScore(95))
+ }
+})
diff --git a/src/test/kotlin/blackjack/model/pack/ShuffledPackTest.kt b/src/test/kotlin/blackjack/model/pack/impl/ShuffledPackTest.kt
similarity index 80%
rename from src/test/kotlin/blackjack/model/pack/ShuffledPackTest.kt
rename to src/test/kotlin/blackjack/model/pack/impl/ShuffledPackTest.kt
index 69143f7624..890dd308a2 100644
--- a/src/test/kotlin/blackjack/model/pack/ShuffledPackTest.kt
+++ b/src/test/kotlin/blackjack/model/pack/impl/ShuffledPackTest.kt
@@ -1,5 +1,6 @@
-package blackjack.model.pack
+package blackjack.model.pack.impl
+import blackjack.model.card.pack.impl.ShuffledPack
import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.core.spec.style.StringSpec
diff --git a/src/test/kotlin/blackjack/model/playable/BlackjackScoreTest.kt b/src/test/kotlin/blackjack/model/playable/BlackjackScoreTest.kt
new file mode 100644
index 0000000000..a9d3d002f3
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/playable/BlackjackScoreTest.kt
@@ -0,0 +1,20 @@
+package blackjack.model.playable
+
+import blackjack.model.player.BlackjackScore
+import blackjack.model.result.PlayableResult
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
+
+class BlackjackScoreTest : StringSpec({
+ "이겨야한다" {
+ BlackjackScore(4) vs BlackjackScore(2) shouldBe PlayableResult.WIN
+ }
+
+ "져야한다" {
+ BlackjackScore(1) vs BlackjackScore(2) shouldBe PlayableResult.LOSE
+ }
+
+ "비겨야한다" {
+ BlackjackScore(7) vs BlackjackScore(7) shouldBe PlayableResult.DRAW
+ }
+})
diff --git a/src/test/kotlin/blackjack/model/playable/impl/DealerTest.kt b/src/test/kotlin/blackjack/model/playable/impl/DealerTest.kt
new file mode 100644
index 0000000000..b77c9361b9
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/playable/impl/DealerTest.kt
@@ -0,0 +1,140 @@
+package blackjack.model.playable.impl
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.card.CardFixture
+import blackjack.model.card.pack.impl.ShuffledPack
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.Players
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.player.playblestrategy.impl.DealerStrategy
+import blackjack.model.result.PlayableResult
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.collections.shouldContain
+import io.kotest.matchers.shouldBe
+
+class DealerTest : StringSpec({
+
+ "딜러는 처음에 받은 2장의 카드 점수 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 한다" {
+ val dealer = Dealer(
+ CardFixture.makeCards(CardFixture.king, CardFixture.six)
+ )
+ dealer.playing(DealerStrategy(BlackjackScore(16)), ShuffledPack)
+
+ dealer.cards.cards shouldContain CardFixture.king
+ dealer.cards.cards shouldContain CardFixture.six
+ dealer.countOfCards() shouldBe 3
+ }
+
+ "딜러는 카드의 점수 합계가 17점 이상이면 추가로 받을 수 없다" {
+ val dealer = Dealer(
+ CardFixture.makeCards(CardFixture.king, CardFixture.seven)
+ )
+ dealer.playing(DealerStrategy(BlackjackScore(17)), ShuffledPack)
+
+ dealer.cards.cards shouldContain CardFixture.king
+ dealer.cards.cards shouldContain CardFixture.seven
+ dealer.countOfCards() shouldBe 2
+ }
+
+ "딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리해야 한다" {
+ val dealer = Dealer( // 22점
+ CardFixture.makeCards(CardFixture.five, CardFixture.seven, CardFixture.king)
+ )
+ val player1 = Player(
+ "seoul", // 13점
+ CardFixture.makeCards(CardFixture.ace1, CardFixture.king, CardFixture.two), status = BlackJackStatus.ALIVE
+ )
+ val player2 = Player(
+ "wonju", // 21점
+ CardFixture.makeCards(CardFixture.queen, CardFixture.king, CardFixture.ace2), status = BlackJackStatus.ALIVE
+ )
+ val actualDealerResult = dealer.dealerResult(
+ Players(player1, player2)
+ )
+
+ actualDealerResult.winningCount shouldBe 0
+ actualDealerResult.drawingCount shouldBe 0
+ actualDealerResult.losingCount shouldBe 2
+ dealer.result(player1) shouldBe PlayableResult.LOSE
+ dealer.result(player2) shouldBe PlayableResult.LOSE
+ }
+
+ "딜러와 플레이어 모두가 Burst 상황이라면 경기 결과는 DRAW 이어야한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.nine, CardFixture.three))
+ val player1 = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.jack, CardFixture.four), status = BlackJackStatus.ALIVE
+ )
+ val player2 = Player(
+ "martini",
+ CardFixture.makeCards(CardFixture.nine, CardFixture.eight, CardFixture.seven),
+ status = BlackJackStatus.ALIVE
+ )
+
+ player1.result(dealer) shouldBe PlayableResult.DRAW
+ player2.result(dealer) shouldBe PlayableResult.DRAW
+ dealer.result(player1) shouldBe PlayableResult.DRAW
+ dealer.result(player2) shouldBe PlayableResult.DRAW
+ }
+ "딜러는 플레이어보다 점수가 높은경우 WIN 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.ten)) // 20
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.five), // 15
+ status = BlackJackStatus.ALIVE
+ )
+
+ dealer.result(player) shouldBe PlayableResult.WIN
+ player.result(dealer) shouldBe PlayableResult.LOSE
+ }
+
+ "딜러는 플레이어보다 점수가 낮은경우 LOSE 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.three, CardFixture.four))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.seven, CardFixture.five), status = BlackJackStatus.ALIVE
+ )
+
+ dealer.result(player) shouldBe PlayableResult.LOSE
+ player.result(dealer) shouldBe PlayableResult.WIN
+ }
+
+ "딜러는 플레이어와 점수가 같은 경우 DRAW 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.nine, CardFixture.two))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.jack, CardFixture.ace2), status = BlackJackStatus.ALIVE
+ )
+
+ dealer.score() shouldBe BlackjackScore(21)
+ player.score() shouldBe BlackjackScore(21)
+ player.result(dealer) shouldBe PlayableResult.DRAW
+ dealer.result(player) shouldBe PlayableResult.DRAW
+ }
+
+ "딜러가 2승1패인 경우 경기결과가 잘 표현되어야한다" {
+ val dealer = Dealer( // 15점
+ CardFixture.makeCards(CardFixture.two, CardFixture.two, CardFixture.eight, CardFixture.three)
+ )
+ val player1 = Player(
+ "saml", // 21점
+ CardFixture.makeCards(CardFixture.five, CardFixture.king, CardFixture.six), status = BlackJackStatus.ALIVE
+ )
+ val player2 = Player(
+ "ldap", // 18점
+ CardFixture.makeCards(CardFixture.eight, CardFixture.queen, CardFixture.ace2), status = BlackJackStatus.ALIVE
+ )
+ val player3 = Player(
+ "oauth", // 10점
+ CardFixture.makeCards(CardFixture.two, CardFixture.eight), status = BlackJackStatus.ALIVE
+ )
+ val actual = dealer.dealerResult(
+ Players(player1, player2, player3)
+ )
+
+ actual.winningCount shouldBe 1
+ actual.drawingCount shouldBe 0
+ actual.losingCount shouldBe 2
+ }
+})
diff --git a/src/test/kotlin/blackjack/model/playable/impl/PlayerTest.kt b/src/test/kotlin/blackjack/model/playable/impl/PlayerTest.kt
new file mode 100644
index 0000000000..86b1073f4f
--- /dev/null
+++ b/src/test/kotlin/blackjack/model/playable/impl/PlayerTest.kt
@@ -0,0 +1,92 @@
+package blackjack.model.playable.impl
+
+import blackjack.model.blackjack.BlackJackStatus
+import blackjack.model.card.CardFixture
+import blackjack.model.card.pack.impl.ShuffledPack
+import blackjack.model.player.BlackjackScore
+import blackjack.model.player.playable.impl.Dealer
+import blackjack.model.player.playable.impl.Player
+import blackjack.model.result.PlayableResult
+import io.kotest.assertions.throwables.shouldNotThrow
+import io.kotest.core.spec.style.StringSpec
+import io.kotest.matchers.shouldBe
+
+class PlayerTest : StringSpec({
+
+ "플레이어는 dealing 시 2장의 카드를 받을 수 있다" {
+ shouldNotThrow {
+ val player = Player("구글", status = BlackJackStatus.ALIVE)
+ player.dealing(ShuffledPack)
+ player.cards.count() shouldBe 2
+ }
+ }
+
+ "플레이어는 hit 시 1장의 카드를 받을 수 있다" {
+ shouldNotThrow {
+ val player = Player("애플", status = BlackJackStatus.ALIVE)
+ player.hit(ShuffledPack)
+ player.cards.count() shouldBe 1
+ }
+ }
+
+ "21이 넘는 점수의 카드를 가지고 있는 경우 Burst 상태로써 BlackJackStatus.DIE 상태 이어야한다" {
+ val player = Player(
+ "gin-tonic",
+ CardFixture.makeCards(
+ CardFixture.two,
+ CardFixture.three,
+ CardFixture.four,
+ CardFixture.eight,
+ CardFixture.nine
+ ),
+ status = BlackJackStatus.ALIVE
+ )
+
+ player.isBurst() shouldBe true
+ player.status() shouldBe BlackJackStatus.STOP
+ player.score() shouldBe BlackjackScore(2 + 3 + 4 + 8 + 9)
+ }
+
+ "플레이어가 BlackJack 이고, 딜러가 Burst 라면 플레이어가 승리하는 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.nine, CardFixture.three))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.jack, CardFixture.ace2), status = BlackJackStatus.ALIVE
+ )
+
+ player.result(dealer) shouldBe PlayableResult.WIN
+ dealer.result(player) shouldBe PlayableResult.LOSE
+ }
+
+ "플레이어는 딜러보다 점수가 높은 경우 Win 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.three))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.jack), status = BlackJackStatus.ALIVE
+ )
+
+ player.result(dealer) shouldBe PlayableResult.WIN
+ }
+
+ "플레이어는 (Burst 가 아닌 상황에서, burst와 관계없이) 딜러보다 점수가 낮은경우 LOSE 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.ten))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.five), status = BlackJackStatus.ALIVE
+ )
+ val actual = player.result(dealer)
+ actual shouldBe PlayableResult.LOSE
+ }
+
+ "플레이어는 딜러와 점수가 같은 경우 DRAW 결과를 반환 해야 한다" {
+ val dealer = Dealer(CardFixture.makeCards(CardFixture.queen, CardFixture.nine, CardFixture.two))
+ val player = Player(
+ "malibu",
+ CardFixture.makeCards(CardFixture.ten, CardFixture.jack, CardFixture.ace2), status = BlackJackStatus.ALIVE
+ )
+
+ dealer.score() shouldBe BlackjackScore(21)
+ player.score() shouldBe BlackjackScore(21)
+ player.result(dealer) shouldBe PlayableResult.DRAW
+ }
+})
diff --git a/src/test/kotlin/study/DefensiveCopyLearn.kt b/src/test/kotlin/study/DefensiveCopyLearn.kt
index d8aa5eede9..0bef41a000 100644
--- a/src/test/kotlin/study/DefensiveCopyLearn.kt
+++ b/src/test/kotlin/study/DefensiveCopyLearn.kt
@@ -1,21 +1,47 @@
package study
-import blackjack.model.Card
-import blackjack.model.Cards
-import blackjack.model.pack.ShuffledPack
-import blackjack.view.Console.present
import io.kotest.core.spec.style.StringSpec
class DefensiveCopyLearn : StringSpec({
- "sdf" {
- val list = mutableListOf()
- list.add(ShuffledPack.pickCard())
- list.add(ShuffledPack.pickCard())
- list.add(ShuffledPack.pickCard())
- val cards = Cards(list)
- println("BEFORE : ${cards.present()}")
+ "방어적 복사를 하지 않았을 때의 문제가 되는 코드 예제" {
+ val list = mutableListOf()
+ list.add("goodNight")
+ list.add("goodMorning")
+ list.add("niceYoMeetYou")
+ val nonDefensiveClass = NonDefensiveClass(list)
+ println("BEFORE : ${nonDefensiveClass.present()}")
list.clear()
- println("AFTER : ${cards.present()}")
+ println("AFTER : ${nonDefensiveClass.present()}")
+ }
+
+ "방어적 복사를 적용한 코드 예제" {
+ val list = mutableListOf()
+ list.add("goodNight")
+ list.add("goodMorning")
+ list.add("niceYoMeetYou")
+ val defensiveClass = DefensiveClass(list.toList())
+ println("BEFORE : ${defensiveClass.present()}")
+ list.clear()
+ println("AFTER : ${defensiveClass.present()}")
}
})
+
+class NonDefensiveClass(
+ val strings: MutableList
+) {
+ fun present(): String {
+ return strings.toString()
+ }
+}
+
+class DefensiveClass(
+ strings: List
+) {
+ private val _strings: MutableList = strings.toMutableList()
+ val strings: List get() = _strings.toList()
+
+ fun present(): String {
+ return strings.toString()
+ }
+}