From ed22ad51c0818b31f473ae3ab80a8aa319c50f18 Mon Sep 17 00:00:00 2001 From: bongbong Date: Sat, 2 Dec 2023 17:01:49 +0900 Subject: [PATCH 01/19] =?UTF-8?q?[step4]=20docs:=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 50232ec0d..9bd6a9685 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,14 @@ - 쉼표 기준으로 분리한다 - 2명의 이름을 입력 받는다 - 참가자의 이름은 공백일 수 없다. +- [ ] 플레이어는 게임을 시작할 때 베팅 금액을 정해야 한다 게임 세팅 - [x] 입력 받은 사람의 이름으로 참가자를 생성한다 - [x] 카드 52장 (1덱)을 세팅한다 - 스페이스, 하트, 다이아, 클로버 4개의 각 문양이 2~10, jack, queen, king, ace 13장으로 구성 게임 진행 +- 베팅 + - [ ] 각 플레이어의 베팅 금액은 딜러에게 주어진다 - 처음 카드 배분 - [x] 참가자와 딜러에게 각각 2장의 카드를 나눠 준다 - [x] 받은 카드를 출력한다 @@ -35,16 +38,25 @@ - 결과 출력 - [x] 게임이 종료되면 딜러와 플레이어의 카드 점수를 출력한다 - [x] 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다 - - 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. -- 승패 계산 - - [x] 승패를 가른다 - - 각 플레이어와 딜러의 승패를 가른다 - - 버스트 : 21점 초과 / 푸시 : 딜러와 동점 (무승부) - - 딜러가 각각 딜러와 다 겨뤄 21에 더 가까운 사람이 승리한다 - - 버스트시에는 점수가 0점이다 - - 플레이어와 딜러가 둘 다 버스트라면 딜러가 승리한다 - - 딜러와 플레이어가 버스트가 아니면서 동일 점수라면 무승부 - - 가른 승패의 결과를 출력한다 + - 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. +- 베팅 결과 계산 + - [ ] 베팅 결과를 계산한다 + - 블랙잭 : 처음 2장의 카드의 합이 21 / 버스트 : 카드의 합이 21점 초과 / 푸시 : 딜러와 동점 (무승부) + - 블랙잭일 경우 1.5배를 딜러로부터 받는다 + - 딜러도 블랙잭일 경우 무승부로 베팅한 금액을 돌려 받는다 + - 버스트일 경우 베팅 금액을 모두 잃는다 + - 딜러도 버스트일 경우 동일하게 딜러가 승리한다 (베팅 금액은 딜러에게) + - 그 외의 경우 + - 딜러와의 승패 결과에 따라 이길 경우 베팅 금액을 돌려 받고, 패할 경우 해당 돈은 딜러에게 귀속된다. + - 이길 경우 : 베팅 금액을 돌려 받는다 + - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 버스트 + - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 낮은 1~21 사이의 수 + - 패할 경우 : 해당 돈은 딜러에게 귀속된다 + - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 블랙잭 + - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 높은 1~21 사이의 수 + - 무승부일 경우 : 베팅 금액을 돌려 받는다 + - 경우1) 딜러와 플레이어 모두 동일한 1~21 사이의 수 + - [ ] 베팅 결과를 출력한다 - 게임 종료 - [x] 게임을 종료시킨다 - 카드 배분이 끝나면 (DistributionEnd) 게임을 종료시킨다 From ea2d901cbcf2a0fef817931b69ad28d30ddac698 Mon Sep 17 00:00:00 2001 From: bongbong Date: Sat, 2 Dec 2023 17:33:21 +0900 Subject: [PATCH 02/19] =?UTF-8?q?[step4]=20feat:=2021=EC=A0=90=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20=EB=8D=94=EC=9D=B4=EC=83=81=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=B0=9B=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/domain/card/HandScore.kt | 2 + .../domain/distirbution/DealToPlayer.kt | 2 +- .../blackjack/domain/player/CardPlayer.kt | 6 +-- .../kotlin/blackjack/domain/player/Player.kt | 2 +- .../kotlin/blackjack/domain/player/Players.kt | 3 -- .../kotlin/blackjack/domain/GameTableTest.kt | 6 +-- .../blackjack/domain/card/HandScoreTest.kt | 24 +++++++++++ .../domain/distirbution/DealToPlayerTest.kt | 35 ++++++++++++++-- .../blackjack/domain/player/PlayerTest.kt | 42 +++++++++++++++++-- .../blackjack/domain/player/PlayersTest.kt | 22 ---------- 10 files changed, 105 insertions(+), 39 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/card/HandScore.kt b/src/main/kotlin/blackjack/domain/card/HandScore.kt index 42a68213b..7570714bb 100644 --- a/src/main/kotlin/blackjack/domain/card/HandScore.kt +++ b/src/main/kotlin/blackjack/domain/card/HandScore.kt @@ -6,6 +6,8 @@ class HandScore( val isBust: Boolean get() = cardScore > BUST_THRESHOLD + val isGreaterOrEqualToMaxScore: Boolean = cardScore >= BUST_THRESHOLD + val gameScore: Int get() { if (isBust) return BUST_SCORE diff --git a/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt b/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt index bb7252259..8d7b89ebb 100644 --- a/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt +++ b/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt @@ -24,7 +24,7 @@ class DealToPlayer( } } - val isSystemStand = (action == Action.STAND && playerInTurn.isBust) + val isSystemStand = (action == Action.STAND && playerInTurn.isGreaterOrEqualToMaxScore) return DealToPlayerResult(playerInTurn, isSystemStand) } diff --git a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt index 16fab5ae8..ca83d2509 100644 --- a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt +++ b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt @@ -11,14 +11,14 @@ interface CardPlayer { val score: HandScore get() = HandScore.from(hand) - val isBust: Boolean - get() = score.isBust + val isGreaterOrEqualToMaxScore: Boolean + get() = score.isGreaterOrEqualToMaxScore infix fun isGreaterCardScoreThan(other: Int): Boolean = score isGreaterCardScoreThan other fun addCard(card: Card) { - require(!isBust) { "버스트라 카드를 더 받을 수 없습니다" } + require(score.isGreaterOrEqualToMaxScore.not()) { "최대 점수 이상이라 카드를 더 받을 수 없습니다" } hand.add(card) } diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index 3e391cf13..c32892c6a 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -10,7 +10,7 @@ class Player( ) : CardPlayer { override fun hitOrStand(): Action { - if (isBust) return Action.STAND + if (isGreaterOrEqualToMaxScore) return Action.STAND return getDesiredAction(this) } } diff --git a/src/main/kotlin/blackjack/domain/player/Players.kt b/src/main/kotlin/blackjack/domain/player/Players.kt index 5fa2dd6aa..d54352f42 100644 --- a/src/main/kotlin/blackjack/domain/player/Players.kt +++ b/src/main/kotlin/blackjack/domain/player/Players.kt @@ -15,9 +15,6 @@ data class Players( var inTurn: Player = value.first() private set - val isPlayerInTurnOverMaxScore: Boolean - get() = inTurn.isBust - val isLastTurn: Boolean get() = value.indexOf(inTurn) == value.lastIndex diff --git a/src/test/kotlin/blackjack/domain/GameTableTest.kt b/src/test/kotlin/blackjack/domain/GameTableTest.kt index 9e8edc842..ea9b379d0 100644 --- a/src/test/kotlin/blackjack/domain/GameTableTest.kt +++ b/src/test/kotlin/blackjack/domain/GameTableTest.kt @@ -145,7 +145,7 @@ class GameTableTest : DescribeSpec({ } describe("playerInTurnAction") { - context("이번 턴의 플레이어가 HIT를 하면(player: HIT, 점수: 21이하)") { + context("이번 턴의 플레이어가 HIT를 하면(player: HIT, 점수: 21미만)") { val hitPlayer = player(action = Action.HIT, hand = Hand()) val players = players(hitPlayer, player("other")) val table = GameTable(Dealer(), players) @@ -158,7 +158,7 @@ class GameTableTest : DescribeSpec({ } } - context("이번 턴의 플레이어가 STAND를 하면(player: STAND, 점수: 21이하)") { + context("이번 턴의 플레이어가 STAND를 하면(player: STAND, 점수: 21미만)") { val standPlayer = player(action = Action.STAND, hand = Hand()) val players = players(standPlayer, player("other")) val table = GameTable(Dealer(), players) @@ -172,7 +172,7 @@ class GameTableTest : DescribeSpec({ } context("이번 턴의 플레이어가 STAND를 하면(player: HIT, 점수: 21이상)") { - val standPlayer = player(action = Action.HIT, hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN))) + val standPlayer = player(action = Action.HIT, hand = hand(card(Rank.TEN), card(Rank.ACE))) val players = players(standPlayer, player("other")) val table = GameTable(Dealer(), players) table.playerInTurn shouldBe standPlayer diff --git a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt index 9901edc47..653a42900 100644 --- a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt @@ -73,6 +73,30 @@ class HandScoreTest : DescribeSpec({ } } + describe("isGreaterOrEqualToMaxScore") { + context("21점 미만일 때") { + val handScore = HandScore(20) + + it("false 반환") { + handScore.isGreaterOrEqualToMaxScore shouldBe false + } + } + context("21점일 때") { + val handScore = HandScore(21) + + it("true 반환") { + handScore.isGreaterOrEqualToMaxScore shouldBe true + } + } + context("21점 초과일 때") { + val handScore = HandScore(22) + + it("true 반환") { + handScore.isGreaterOrEqualToMaxScore shouldBe true + } + } + } + describe("isGreaterCardScoreThan") { val score = HandScore(16) context("보다 작은 점수를 비교") { diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt index cd92c70f4..eae46b2b8 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt @@ -14,7 +14,7 @@ import io.kotest.matchers.types.shouldBeTypeOf class DealToPlayerTest : DescribeSpec({ describe("deal") { - context("플레이어가 HIT (player: HIT, 점수 : bust 아님) 을 하면") { + context("플레이어가 HIT (player: HIT, 점수 : 21점 미만) 을 하면") { val players = players(player("currentPlayer", Action.HIT), player()) val table = table(inputAction = Action.HIT, players = players) val deckCount = table.dealer.deck.cards.size @@ -41,7 +41,7 @@ class DealToPlayerTest : DescribeSpec({ } } - context("모든 플레이어는 항상 STAND를 응답(player: STAND)할 때") { + context("플레이어가 STAND를 응답(player: STAND)할 때") { val player1Cards = hand(card(Rank.TEN), card(Rank.TEN)) val players = players( player("currentPlayer", Action.STAND, player1Cards), @@ -90,7 +90,7 @@ class DealToPlayerTest : DescribeSpec({ } context("카드 배분시 게임 룰에 의한 (21점 이상) STAND을 한 경우") { - val cards = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN)) + val cards = hand(card(Rank.TEN), card(Rank.ACE)) val players = players(player("currentPlayer", hand = cards, action = Action.HIT), player("nextPlayer")) val table = table(inputAction = Action.HIT, players = players) @@ -117,5 +117,34 @@ class DealToPlayerTest : DescribeSpec({ } } } + + context("플레이어가 HIT를 했지만 STAND를 응답할 때(점수 21점 이상)") { + val player1Cards = hand(card(Rank.TEN), card(Rank.ACE)) + val players = players( + player("currentPlayer", Action.STAND, player1Cards), + player("nextPlayer", Action.STAND), + ) + val table = table(inputAction = Action.HIT, players = players) + + val dealToPlayer = DealToPlayer(table) + val result = dealToPlayer.deal() + + it("게임의 다음 상태는 다음 플레이어 배분 차례") { + dealToPlayer.nextDistributor.shouldBeTypeOf() + table.players.inTurn.name shouldBe PlayerName("nextPlayer") + } + + it("플레이어 카드는 변화 없음") { + table.players.value.first().hand shouldBe player1Cards + } + + it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { + result.player.name shouldBe PlayerName("currentPlayer") + } + + it("게임 룰에 의한 STAND인지 여부는 TRUE") { + result.isSystemStand shouldBe true + } + } } }) diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index 5d04b0400..3627ed39f 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -59,13 +59,31 @@ class PlayerTest : DescribeSpec({ } } } + + context("플레이어가 21점이라면") { + val score21Cards = hand(card(Rank.TEN), card(Rank.ACE)) + val player = player(hand = score21Cards) + + it("카드를 추가할 수 없다") { + shouldThrowExactly { + player.addCard(Card(Suit.CLUB, Rank.TEN)) + } + } + } } - describe("isBust") { + describe("isGreaterOrEqualToMaxScore") { context("21을 넘었을 때") { val player = player(hand = hand(card(Rank.THREE), card(Rank.TEN), card(Rank.TEN))) it("true 반환") { - player.isBust shouldBe true + player.isGreaterOrEqualToMaxScore shouldBe true + } + } + + context("21점일 때") { + val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN))) + it("true 반환") { + player.isGreaterOrEqualToMaxScore shouldBe true } } @@ -73,7 +91,7 @@ class PlayerTest : DescribeSpec({ val player = player(hand = hand(card(Rank.ACE), card(Rank.ACE), card(Rank.ACE))) it("false 반환") { - player.isBust shouldBe false + player.isGreaterOrEqualToMaxScore shouldBe false } } } @@ -108,6 +126,24 @@ class PlayerTest : DescribeSpec({ } } + context("이미 점수가 최대 점수 21점 이라면") { + val score21Cards = hand(card(Rank.QUEEN), card(Rank.ACE)) + it("HIT을 받아도 STAND가 반환") { + val player = player(action = Action.HIT, hand = score21Cards) + + val result = player.hitOrStand() + + result shouldBe Action.STAND + } + it("STAND를 받으면 STAND가 반환") { + val player = player(action = Action.STAND, hand = score21Cards) + + val result = player.hitOrStand() + + result shouldBe Action.STAND + } + } + context("최대 점수 21을 넘지 않았을 때") { val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) diff --git a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt index 3b658779f..acf5047d6 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt @@ -1,11 +1,7 @@ package blackjack.domain.player import blackjack.domain.Action -import blackjack.domain.card.Rank -import blackjack.mock.card -import blackjack.mock.hand import blackjack.mock.player -import blackjack.mock.players import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -37,24 +33,6 @@ class PlayersTest : DescribeSpec({ } } - describe("현재 플레이어가 최대 점수를 넘었는지 여부 반환") { - context("현재 플레이어가 최대 점수를 넘었을 경우") { - val playerOverMaxScore = player(hand = hand(card(Rank.THREE), card(Rank.TEN), card(Rank.TEN))) - val players = players(playerOverMaxScore, player()) - it("true 반환") { - players.isPlayerInTurnOverMaxScore shouldBe true - } - } - - context("현재 플레이어가 최대 점수를 넘지 않았을 경우") { - val playerUnderMaxScore = player(hand = hand(card(Rank.ACE), card(Rank.TEN))) - val players = players(playerUnderMaxScore, player()) - it("false 반환") { - players.isPlayerInTurnOverMaxScore shouldBe false - } - } - } - describe("다음 플레이어에게 차례 넘김") { val playerList = listOf(player("kim"), player("lee")) val players = Players(playerList) From d095226db7f57fc8c92b1889bcecdb7c62f0e6fe Mon Sep 17 00:00:00 2001 From: bongbong Date: Sat, 2 Dec 2023 17:57:43 +0900 Subject: [PATCH 03/19] =?UTF-8?q?[step3]=20chore:=20getDesiredAction=20->?= =?UTF-8?q?=20desiredAction=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/domain/player/Player.kt | 4 ++-- src/main/kotlin/blackjack/domain/player/Players.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index c32892c6a..e5e8fd027 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -5,12 +5,12 @@ import blackjack.domain.card.Hand class Player( val name: PlayerName, - val getDesiredAction: (player: Player) -> Action, + val desiredAction: (player: Player) -> Action, override val hand: Hand = Hand(), ) : CardPlayer { override fun hitOrStand(): Action { if (isGreaterOrEqualToMaxScore) return Action.STAND - return getDesiredAction(this) + return desiredAction(this) } } diff --git a/src/main/kotlin/blackjack/domain/player/Players.kt b/src/main/kotlin/blackjack/domain/player/Players.kt index d54352f42..32683b734 100644 --- a/src/main/kotlin/blackjack/domain/player/Players.kt +++ b/src/main/kotlin/blackjack/domain/player/Players.kt @@ -29,10 +29,10 @@ data class Players( private const val PLAYER_COUNT = 2 fun of( names: PlayerNames, - actionOf: (player: Player) -> Action, - ) = names.value.map { name -> createPlayer(name, actionOf) }.let(::Players) + desiredAction: (player: Player) -> Action, + ) = names.value.map { name -> createPlayer(name, desiredAction) }.let(::Players) - private fun createPlayer(name: PlayerName, actionOf: (player: Player) -> Action) = - Player(name, actionOf) + private fun createPlayer(name: PlayerName, desiredAction: (player: Player) -> Action) = + Player(name, desiredAction) } } From b1ebc8a11c66c5222e9cd6dbb636b8e11e89dbad Mon Sep 17 00:00:00 2001 From: bongbong Date: Sat, 2 Dec 2023 18:05:42 +0900 Subject: [PATCH 04/19] =?UTF-8?q?[step4]=20feat:=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EC=8B=9D?= =?UTF-8?q?=EB=B3=84=EC=9E=90=EB=A1=9C=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++ .../kotlin/blackjack/domain/player/Player.kt | 15 ++++++++++ .../blackjack/domain/player/PlayerNames.kt | 4 +++ .../domain/player/PlayerNamesTest.kt | 11 +++++++ .../blackjack/domain/player/PlayerTest.kt | 29 +++++++++++++++++-- 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9bd6a9685..09ed26130 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ - [x] 입력 받은 사람의 이름으로 참가자를 생성한다 - [x] 카드 52장 (1덱)을 세팅한다 - 스페이스, 하트, 다이아, 클로버 4개의 각 문양이 2~10, jack, queen, king, ace 13장으로 구성 +PlayerNames : 플레이어 이름 리스트 + - [x] 각 플레이어는 다른 이름이어야 한다 +Player : 플레이어 + - [x] 플레이어는 이름 같으면 동일한 플레이어로 취급 게임 진행 - 베팅 - [ ] 각 플레이어의 베팅 금액은 딜러에게 주어진다 diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index e5e8fd027..00b714612 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -13,4 +13,19 @@ class Player( if (isGreaterOrEqualToMaxScore) return Action.STAND return desiredAction(this) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Player + + if (name != other.name) return false + + return true + } + + override fun hashCode(): Int { + return name.hashCode() + } } diff --git a/src/main/kotlin/blackjack/domain/player/PlayerNames.kt b/src/main/kotlin/blackjack/domain/player/PlayerNames.kt index 47b284708..bbe78c8c0 100644 --- a/src/main/kotlin/blackjack/domain/player/PlayerNames.kt +++ b/src/main/kotlin/blackjack/domain/player/PlayerNames.kt @@ -4,6 +4,10 @@ package blackjack.domain.player value class PlayerNames( val value: List, ) { + init { + require(value.distinct().size == value.size) { "플레이어는 각기 다른 이름이어야 합니다" } + } + companion object { fun from(names: List): PlayerNames = names.map(::PlayerName).let(::PlayerNames) diff --git a/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt index b3f3e42d9..d978814f1 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt @@ -1,5 +1,6 @@ package blackjack.domain.player +import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -15,5 +16,15 @@ class PlayerNamesTest : DescribeSpec({ result.value[1].value shouldBe "백상어" } } + + context("두 플레이어 이름이 동일하면") { + val names = listOf("홍길동", "홍길동") + + it("플레이어 이름 생성 실패") { + shouldThrowExactly { + PlayerNames.from(names) + } + } + } } }) diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index 3627ed39f..af70803fd 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -11,7 +11,7 @@ import blackjack.mock.player import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe -import java.lang.IllegalArgumentException +import io.kotest.matchers.shouldNotBe class PlayerTest : DescribeSpec({ describe("Player()") { @@ -109,7 +109,7 @@ class PlayerTest : DescribeSpec({ describe("hitOrStand") { context("이미 점수가 최대 점수 21을 넘었다면") { - val score30Cards = hand(card(Rank.QUEEN), card(Rank.QUEEN), card(Rank.QUEEN),) + val score30Cards = hand(card(Rank.QUEEN), card(Rank.QUEEN), card(Rank.QUEEN)) it("HIT을 받아도 STAND가 반환") { val player = player(action = Action.HIT, hand = score30Cards) @@ -163,4 +163,29 @@ class PlayerTest : DescribeSpec({ } } } + + describe("equals") { + context("두 플레이어의 이름이 다르면") { + val action = { _: Player -> Action.HIT } + val hand = hand() + + val player1 = Player(PlayerName("kim"), action, hand) + val player2 = Player(PlayerName("lee"), action, hand) + + it("다른 플레이어로 취급") { + player1 shouldNotBe player2 + } + } + } + + context("두 플레이어의 이름이 같다면") { + val action = { _: Player -> Action.HIT } + + val player1 = Player(PlayerName("kim"), action, hand(card(Rank.ACE))) + val player2 = Player(PlayerName("kim"), action, hand(card(Rank.TEN))) + + it("같은 플레이어로 취급") { + player1 shouldBe player2 + } + } }) From 158af44c1329407135feb2fde26fb64e6586670b Mon Sep 17 00:00:00 2001 From: bongbong Date: Sat, 2 Dec 2023 20:40:01 +0900 Subject: [PATCH 05/19] =?UTF-8?q?[step4]=20feat:=20=ED=94=8C=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=96=B4=EB=B3=84=20=EB=B2=A0=ED=8C=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 140 +++++++++++------- .../blackjack/controller/InputProcessor.kt | 3 + .../controller/ViewInputProcessor.kt | 9 +- .../kotlin/blackjack/domain/BlackJackGame.kt | 15 +- src/main/kotlin/blackjack/domain/GameTable.kt | 2 +- .../kotlin/blackjack/domain/batting/Amount.kt | 12 ++ .../blackjack/domain/batting/BetBoard.kt | 20 +++ .../blackjack/domain/batting/PlayerBet.kt | 18 +++ .../kotlin/blackjack/view/input/InputView.kt | 7 + .../blackjack/domain/BlackJackGameTest.kt | 2 + .../kotlin/blackjack/domain/GameTableTest.kt | 22 +-- .../blackjack/domain/betting/AmountTest.kt | 25 ++++ .../blackjack/domain/betting/BetBoardTest.kt | 72 +++++++++ .../blackjack/mock/InputProcessorMock.kt | 3 + .../kotlin/blackjack/mock/TestFactories.kt | 2 +- 15 files changed, 276 insertions(+), 76 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/batting/Amount.kt create mode 100644 src/main/kotlin/blackjack/domain/batting/BetBoard.kt create mode 100644 src/main/kotlin/blackjack/domain/batting/PlayerBet.kt create mode 100644 src/test/kotlin/blackjack/domain/betting/AmountTest.kt create mode 100644 src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt diff --git a/README.md b/README.md index 09ed26130..cc00d72a2 100644 --- a/README.md +++ b/README.md @@ -9,61 +9,93 @@ - git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다. ### 기능 목록 -게임 입력 -- [x] 게임에 참여할 사람의 이름을 입력 받는다 +InputView : 게임에 필요한 입력값을 입력 받는다 +- 게임에 참여할 사람의 이름을 입력 받는다 - 쉼표 기준으로 분리한다 - 2명의 이름을 입력 받는다 - 참가자의 이름은 공백일 수 없다. -- [ ] 플레이어는 게임을 시작할 때 베팅 금액을 정해야 한다 -게임 세팅 -- [x] 입력 받은 사람의 이름으로 참가자를 생성한다 -- [x] 카드 52장 (1덱)을 세팅한다 +- [x] 플레이어는 게임을 시작할 때 베팅 금액을 정해야 한다 + +InputProcessor : 입력 금액을 검증하고 도메인 레벨로 변환한다 +- 게임에 참여할 사람의 이름을 처리한다 + - 게임 참여자는 2명이여야 한다 + - 게임 참여자의 이름은 서로 달라야 한다 + - 게임 참여자 이름은 공백일 수 없다. +- [x] 베팅 금액을 처리한다 + - 베팅 금액은 0보다 커야 한다 + +BlackJackGame : 게임을 진행한다 +- distributor 가 dealEnd 가 될 때까지 카드 배분을 진행한다. +- 각 카드 배분의 결과를 result 처리기에 넘긴다 +- 초기 카드 배분자는 DealIntialCards()이다 +- 카드 배분 횟수를 초과하면 종료시킨다 + - 한 사람이 최대 가질 수 있는 카드 수 11장 ( Ace(1) * 4 + 2 * 4 + 3 * 3) : 배분 9번 진행 + - 한 게임당 가질 수 있는 최대 카드 배분 횟수 : 초키 카드 배분 + 9번 진행 * 2 + 딜러 카드 배분 = 20 + +GameTable +- 딜러와 플레이어를 갖고 상호간의 카드 이동을 담당한다 + +Players +- 플레이어를 관리하고 어떤 플레이어의 턴인지 관리한다 + - 플레이어 이름으로 플레이어를 생성한다 + - PlayerNames : 플레이어 이름 리스트 + - [x] 각 플레이어는 다른 이름이어야 함 + +Dealer +- 카드 52장 (1덱)을 세팅한다 - 스페이스, 하트, 다이아, 클로버 4개의 각 문양이 2~10, jack, queen, king, ace 13장으로 구성 -PlayerNames : 플레이어 이름 리스트 - - [x] 각 플레이어는 다른 이름이어야 한다 -Player : 플레이어 - - [x] 플레이어는 이름 같으면 동일한 플레이어로 취급 -게임 진행 -- 베팅 - - [ ] 각 플레이어의 베팅 금액은 딜러에게 주어진다 -- 처음 카드 배분 - - [x] 참가자와 딜러에게 각각 2장의 카드를 나눠 준다 - - [x] 받은 카드를 출력한다 - - 딜러의 카드는 한 장만 출력한다 -- 플레이어 카드 배분 - - [x] 참가자 중 1명에게 카드를 더 받을지 묻고, 참가자는 y 또는 n 으로 대답한다. - - [x] 참가자가 y를 입력하면 카드 한 장을 더 지급한다 - - [x] 한 사람이 받은 카드를 계산했을 때 21을 넘는다면 더이상 카드를 받을 수 없다. - - [x] 참가자가 n을 입력하면 다른 참가자에게 턴이 넘어간다. 두번째 참가자라면 딜러에게 턴이 넘어간다 - - [x] 참가자가 y, n을 입력하면 카드 지급 여부에 관계 없이 결과를 출력한다 -- 딜러 카드 배분 - - [x] 처음 받은 2장의 카드의 합이 16이하이면 1장의 카드를 받고, 17이상이면 카드를 받지 않는다 - - [x] 딜러가 카드를 받았는지 여부를 출력한다 -- 결과 출력 - - [x] 게임이 종료되면 딜러와 플레이어의 카드 점수를 출력한다 - - [x] 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다 - - 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. -- 베팅 결과 계산 - - [ ] 베팅 결과를 계산한다 - - 블랙잭 : 처음 2장의 카드의 합이 21 / 버스트 : 카드의 합이 21점 초과 / 푸시 : 딜러와 동점 (무승부) - - 블랙잭일 경우 1.5배를 딜러로부터 받는다 - - 딜러도 블랙잭일 경우 무승부로 베팅한 금액을 돌려 받는다 - - 버스트일 경우 베팅 금액을 모두 잃는다 - - 딜러도 버스트일 경우 동일하게 딜러가 승리한다 (베팅 금액은 딜러에게) - - 그 외의 경우 - - 딜러와의 승패 결과에 따라 이길 경우 베팅 금액을 돌려 받고, 패할 경우 해당 돈은 딜러에게 귀속된다. - - 이길 경우 : 베팅 금액을 돌려 받는다 - - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 버스트 - - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 낮은 1~21 사이의 수 - - 패할 경우 : 해당 돈은 딜러에게 귀속된다 - - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 블랙잭 - - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 높은 1~21 사이의 수 - - 무승부일 경우 : 베팅 금액을 돌려 받는다 - - 경우1) 딜러와 플레이어 모두 동일한 1~21 사이의 수 - - [ ] 베팅 결과를 출력한다 -- 게임 종료 - - [x] 게임을 종료시킨다 - - 카드 배분이 끝나면 (DistributionEnd) 게임을 종료시킨다 - - 카드 배분 횟수를 초과하면 종료시킨다 - - 한 사람이 최대 가질 수 있는 카드 수 11장 ( Ace(1) * 4 + 2 * 4 + 3 * 3) : 배분 9번 진행 - - 한 게임당 가질 수 있는 최대 카드 배분 횟수 : 초키 카드 배분 + 9번 진행 * 2 + 딜러 카드 배분 = 20 +- 자신의 카드 보유에 따라 hit or stand 를 반환한다 + - 처음 받은 2장의 카드의 합이 16이하이면 1장의 카드를 받고, 17이상이면 카드를 받지 않는다 + +Player +- [x] 플레이어는 이름이 같으면 동일한 플레이어로 취급된다 +- hitOrStand: 사용자의 의견에 따라 카드를 더 받을지(hit) 그만할지(stand)를 반환한다 + - 사용자가 hit 를 요청했더라도 카드 점수가 21점 이상이면 stand 를 반환한다 + +BettingBoard : 베팅 보드 +- [x] 플레이어별 베팅 금액을 저장 + +DealInitialCards : 처음 카드 배분 +- 참가자와 딜러에게 각각 2장의 카드를 나눠 준다 + +DealToPlayer +- 참가자 중 1명에게 카드를 더 받을지 묻고, 결과에 따라 카드를 지급한다 + - 참가자가 hit 라면 카드 한 장을 더 지급한다 + - 참가자가 stand 라면 다른 참가자에게 턴이 넘어간다. 두번째 참가자라면 딜러에게 턴이 넘어간다 + - 참가자의 hit or stand 마다 배분 결과를 반환한다 + - 참가자가 stand 였을 때 점수가 21점 이상이라면 시스템으로 인한 stand로 판별한다 + +DealToDealer +- 딜러의 카드 상태에 따라 hit or stand 를 조회하고 결과에 따라 카드를 지급한다 +- 딜러까지 카드 배분이 끝나면 카드 배분을 종료한다 + +ResultProcessor +- 발생한 Result 이벤트에 따라 해당 이벤트를 알맞은 ResultHandler 에 전달한다 +- DealToPlayerResult 인 경우 시스템상의 stand 라면 뷰에 결과를 전달하지 않는다 + +ViewResultProcessor +- 딜러의 카드는 한 장만 출력한다 +- 게임이 종료되면 (GameResult 인 경우) 딜러와 플레이어의 카드 점수를 출력한다 + +GameResult +- 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다 +- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. + +베팅 결과 계산 +- [ ] 베팅 결과를 계산한다 + - 블랙잭 : 처음 2장의 카드의 합이 21 / 버스트 : 카드의 합이 21점 초과 / 푸시 : 딜러와 동점 (무승부) + - 블랙잭일 경우 1.5배를 딜러로부터 받는다 + - 딜러도 블랙잭일 경우 무승부로 베팅한 금액을 돌려 받는다 + - 버스트일 경우 베팅 금액을 모두 잃는다 + - 딜러도 버스트일 경우 동일하게 딜러가 승리한다 (베팅 금액은 딜러에게) + - 그 외의 경우 + - 딜러와의 승패 결과에 따라 이길 경우 베팅 금액을 돌려 받고, 패할 경우 해당 돈은 딜러에게 귀속된다. + - 이길 경우 : 베팅 금액을 돌려 받는다 + - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 버스트 + - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 낮은 1~21 사이의 수 + - 패할 경우 : 해당 돈은 딜러에게 귀속된다 + - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 블랙잭 + - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 높은 1~21 사이의 수 + - 무승부일 경우 : 베팅 금액을 돌려 받는다 + - 경우1) 딜러와 플레이어 모두 동일한 1~21 사이의 수 +- [ ] 베팅 결과를 출력한다 diff --git a/src/main/kotlin/blackjack/controller/InputProcessor.kt b/src/main/kotlin/blackjack/controller/InputProcessor.kt index 55f3fb8ac..612bdcf92 100644 --- a/src/main/kotlin/blackjack/controller/InputProcessor.kt +++ b/src/main/kotlin/blackjack/controller/InputProcessor.kt @@ -1,11 +1,14 @@ package blackjack.controller import blackjack.domain.Action +import blackjack.domain.batting.Amount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames interface InputProcessor { fun playerNames(): PlayerNames + fun playerBet(player: Player): Amount + fun playerAction(player: Player): Action } diff --git a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt index 6db2bcdc3..7bb9fd549 100644 --- a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt @@ -1,6 +1,7 @@ package blackjack.controller import blackjack.domain.Action +import blackjack.domain.batting.Amount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames import blackjack.view.dto.PlayerNameDto @@ -10,11 +11,17 @@ class ViewInputProcessor : InputProcessor { override fun playerNames(): PlayerNames = InputView.playerNames().let(PlayerNames::from) + override fun playerBet(player: Player): Amount = + InputView.playerBet(player.nameDto()) + .let { Amount.betAmount(it.toInt()) } + override fun playerAction(player: Player): Action { - val action = InputView.playerAction(player.let(PlayerNameDto::from)) + val action = InputView.playerAction(player.nameDto()) return toPlayerAction(action) } + private fun Player.nameDto(): PlayerNameDto = this.let(PlayerNameDto::from) + private fun toPlayerAction(action: String): Action = when (action) { "y" -> Action.HIT "n" -> Action.STAND diff --git a/src/main/kotlin/blackjack/domain/BlackJackGame.kt b/src/main/kotlin/blackjack/domain/BlackJackGame.kt index 15e12aeb0..56b98cc94 100644 --- a/src/main/kotlin/blackjack/domain/BlackJackGame.kt +++ b/src/main/kotlin/blackjack/domain/BlackJackGame.kt @@ -2,6 +2,7 @@ package blackjack.domain import blackjack.controller.InputProcessor import blackjack.controller.ResultProcessor +import blackjack.domain.batting.BetBoard import blackjack.domain.distirbution.CardDistributor import blackjack.domain.distirbution.DealEnd import blackjack.domain.distirbution.DealInitialCards @@ -9,16 +10,14 @@ import blackjack.domain.player.Players import blackjack.domain.result.Result class BlackJackGame( - private val inputProcessor: InputProcessor, - private val resultProcessor: ResultProcessor = ResultProcessor(), + inputProcessor: InputProcessor, + players: Players = Players.of(inputProcessor.playerNames()) { player -> inputProcessor.playerAction(player) }, + private val resultProcessor: ResultProcessor, ) { - var dealCards: CardDistributor = DealInitialCards( - GameTable( - Dealer(), - Players.of(inputProcessor.playerNames()) { player -> inputProcessor.playerAction(player) } - ), - ) + + var dealCards: CardDistributor = DealInitialCards(GameTable(players)) private set + val betBoard: BetBoard = BetBoard.of(players) { player -> inputProcessor.playerBet(player) } fun run() { var distributionCount = 0 diff --git a/src/main/kotlin/blackjack/domain/GameTable.kt b/src/main/kotlin/blackjack/domain/GameTable.kt index d71ca8c01..34924ea26 100644 --- a/src/main/kotlin/blackjack/domain/GameTable.kt +++ b/src/main/kotlin/blackjack/domain/GameTable.kt @@ -4,8 +4,8 @@ import blackjack.domain.player.Player import blackjack.domain.player.Players data class GameTable( - val dealer: Dealer, val players: Players, + val dealer: Dealer = Dealer(), ) { val isLastPlayerTurn: Boolean get() = players.isLastTurn diff --git a/src/main/kotlin/blackjack/domain/batting/Amount.kt b/src/main/kotlin/blackjack/domain/batting/Amount.kt new file mode 100644 index 000000000..73cf174d0 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/batting/Amount.kt @@ -0,0 +1,12 @@ +package blackjack.domain.batting + +data class Amount( + val value: Int, +) { + companion object { + fun betAmount(amount: Int): Amount { + require(amount > 0) { "베팅 금액은 0보다 커야 합니다" } + return Amount(amount) + } + } +} diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt new file mode 100644 index 000000000..0b1189822 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -0,0 +1,20 @@ +package blackjack.domain.batting + +import blackjack.domain.player.Player +import blackjack.domain.player.PlayerName +import blackjack.domain.player.Players + +data class BetBoard( + val playerBets: Map +) { + fun playerBet(name: PlayerName): PlayerBet = + playerBets[name] ?: throw IllegalArgumentException("해당 하는 플레이어 베팅을 찾을 수 없습니다") + + companion object { + fun of(players: Players, betAmount: (player: Player) -> Amount): BetBoard = + players.value.associate { it.name to it.playerBet(betAmount) }.let(::BetBoard) + + private fun Player.playerBet(betAmount: (player: Player) -> Amount): PlayerBet = + PlayerBet.BetPlaced(this.name, betAmount(this)) + } +} diff --git a/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt new file mode 100644 index 000000000..eb7815eb9 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt @@ -0,0 +1,18 @@ +package blackjack.domain.batting + +import blackjack.domain.player.PlayerName + +sealed interface PlayerBet { + val playerName: PlayerName + + data class BetPlaced( + override val playerName: PlayerName, + val betAmount: Amount, + ) : PlayerBet + + data class BetFinished( + override val playerName: PlayerName, + val betAmount: Amount, + val resultAmount: Amount, + ) : PlayerBet +} diff --git a/src/main/kotlin/blackjack/view/input/InputView.kt b/src/main/kotlin/blackjack/view/input/InputView.kt index a4e3c2339..d93bbe7ae 100644 --- a/src/main/kotlin/blackjack/view/input/InputView.kt +++ b/src/main/kotlin/blackjack/view/input/InputView.kt @@ -4,6 +4,7 @@ import blackjack.view.dto.PlayerNameDto object InputView { private const val PLAYER_NAMES_MESSAGE = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)" + private const val PLAYER_BET_MESSAGE = "%s의 베팅 금액은?" private const val PLAYER_ACTION_MESSAGE = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)" fun playerNames(): List { @@ -16,6 +17,12 @@ object InputView { return readInput() } + fun playerBet(player: PlayerNameDto): String { + println() + println(PLAYER_BET_MESSAGE.format(player.name)) + return readInput() + } + private fun readInput(): String = readlnOrNull() ?: throw IllegalArgumentException("입력 값이 없습니다") } diff --git a/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt index 1ed5dac72..28857df7c 100644 --- a/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt +++ b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt @@ -1,5 +1,6 @@ package blackjack.domain +import blackjack.controller.ResultProcessor import blackjack.domain.player.PlayerName import blackjack.mock.InputProcessorMock import io.kotest.core.spec.style.DescribeSpec @@ -14,6 +15,7 @@ class BlackJackGameTest : DescribeSpec({ inputProcessor = InputProcessorMock( playerNames = listOf(name1, name2) ), + resultProcessor = ResultProcessor(), ) it("전달된 이름으로 플레이어 세팅") { diff --git a/src/test/kotlin/blackjack/domain/GameTableTest.kt b/src/test/kotlin/blackjack/domain/GameTableTest.kt index ea9b379d0..c9cf32f68 100644 --- a/src/test/kotlin/blackjack/domain/GameTableTest.kt +++ b/src/test/kotlin/blackjack/domain/GameTableTest.kt @@ -19,7 +19,7 @@ class GameTableTest : DescribeSpec({ context("카드가 모두 한 장도 없는 상태일 때 모두에게 2장씩 지급") { val dealer = Dealer() val players = players(player(hand = Hand()), player(hand = Hand())) - val table = GameTable(dealer, players) + val table = GameTable(players, dealer) table.dealToAll(2) @@ -36,7 +36,7 @@ class GameTableTest : DescribeSpec({ val dealer = Dealer() val deckCount = dealer.deck.cards.size val players = players(player(hand = Hand()), player()) - val table = GameTable(dealer, players) + val table = GameTable(players, dealer) context("카드 1장 배분") { val playerInTurn = players.inTurn @@ -56,7 +56,7 @@ class GameTableTest : DescribeSpec({ describe("passPlayerTurnIfNotLastTurn") { val players = players(player("kim"), player("lee")) - val table = GameTable(Dealer(), players) + val table = GameTable(players) context("플레이어 1이 차례인 경우") { players.inTurn shouldBe players.value.first() @@ -86,7 +86,7 @@ class GameTableTest : DescribeSpec({ ) val dealer = Dealer(deck(cards)) val deckCount = dealer.deck.cards.size - val table = GameTable(dealer, players()) + val table = GameTable(players(), dealer) val count = 2 context("딜러 카드가 한 장도 없을 때 카드 ${count}장 배분") { @@ -106,7 +106,7 @@ class GameTableTest : DescribeSpec({ describe("isLastPlayerTurn") { val players = listOf(player("kim"), player("lee")) - val table = GameTable(Dealer(), Players(players)) + val table = GameTable(Players(players)) context("첫 번째 플레이어 턴") { table.players.inTurn shouldBe players.first() @@ -126,7 +126,7 @@ class GameTableTest : DescribeSpec({ describe("playerInTurn") { val players = listOf(player("kim"), player("lee")) - val table = GameTable(Dealer(), Players(players)) + val table = GameTable(Players(players)) context("첫 번쨰 플레이어 턴") { table.players.inTurn shouldBe players.first() @@ -148,7 +148,7 @@ class GameTableTest : DescribeSpec({ context("이번 턴의 플레이어가 HIT를 하면(player: HIT, 점수: 21미만)") { val hitPlayer = player(action = Action.HIT, hand = Hand()) val players = players(hitPlayer, player("other")) - val table = GameTable(Dealer(), players) + val table = GameTable(players) table.playerInTurn shouldBe hitPlayer it("HIT 반환") { @@ -161,7 +161,7 @@ class GameTableTest : DescribeSpec({ context("이번 턴의 플레이어가 STAND를 하면(player: STAND, 점수: 21미만)") { val standPlayer = player(action = Action.STAND, hand = Hand()) val players = players(standPlayer, player("other")) - val table = GameTable(Dealer(), players) + val table = GameTable(players) table.playerInTurn shouldBe standPlayer it("STAND 반환") { @@ -174,7 +174,7 @@ class GameTableTest : DescribeSpec({ context("이번 턴의 플레이어가 STAND를 하면(player: HIT, 점수: 21이상)") { val standPlayer = player(action = Action.HIT, hand = hand(card(Rank.TEN), card(Rank.ACE))) val players = players(standPlayer, player("other")) - val table = GameTable(Dealer(), players) + val table = GameTable(players) table.playerInTurn shouldBe standPlayer it("STAND 반환") { @@ -188,7 +188,7 @@ class GameTableTest : DescribeSpec({ describe("dealerAction") { context("딜러가 HIT를 하면 (딜러 16점 이하)") { val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TWO), card(Rank.THREE)))) - val table = GameTable(dealer, players()) + val table = GameTable(players(), dealer) dealer.hitOrStand() shouldBe Action.HIT it("HIT 반환") { @@ -200,7 +200,7 @@ class GameTableTest : DescribeSpec({ context("딜러가 STAND를 하면 (딜러 17점 이상)") { val dealer = Dealer(player = DealerPlayer(hand(card(Rank.QUEEN), card(Rank.QUEEN)))) - val table = GameTable(dealer, players()) + val table = GameTable(players(), dealer) dealer.hitOrStand() shouldBe Action.STAND it("STAND 반환") { diff --git a/src/test/kotlin/blackjack/domain/betting/AmountTest.kt b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt new file mode 100644 index 000000000..72181e55c --- /dev/null +++ b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt @@ -0,0 +1,25 @@ +package blackjack.domain.betting + +import blackjack.domain.batting.Amount +import io.kotest.assertions.throwables.shouldThrowExactly +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import java.lang.IllegalArgumentException + +class AmountTest : StringSpec({ + "0보다 큰 베팅 금액이 생성된다" { + val amount = 3_000 + + val result = Amount.betAmount(amount) + + result.value shouldBe amount + } + + "0이하의 금액은 베팅 금액이 될 수 없다" { + val amount = 0 + + shouldThrowExactly { + Amount.betAmount(amount) + } + } +}) diff --git a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt new file mode 100644 index 000000000..970eb4797 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt @@ -0,0 +1,72 @@ +package blackjack.domain.betting + +import blackjack.domain.batting.Amount +import blackjack.domain.batting.BetBoard +import blackjack.domain.batting.PlayerBet +import blackjack.domain.player.Player +import blackjack.domain.player.PlayerName +import blackjack.mock.player +import blackjack.mock.players +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.inspectors.forAll +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeTypeOf + +class BetBoardTest : DescribeSpec({ + describe("BettingBoard") { + context("플레이어 이름으로 베팅 금액 등록 (kim: 3_000원, lee: 4_000원)") { + val players = players(player("kim"), player("lee")) + val kimAmount = Amount(3_000) + val leeAmount = Amount(4_000) + val betAmount = { player: Player -> + if (player.name.value == "kim") kimAmount + else leeAmount + } + + val betBoard: BetBoard = BetBoard.of(players, betAmount) + + it("등록된 플레이어 베팅은 2개") { + betBoard.playerBets.size shouldBe 2 + } + + it("플레이어 베팅은 모두 BetPlaced 상태") { + betBoard.playerBets.values.forAll { it is PlayerBet.BetPlaced } + } + + it("플레이어 kim 의 베팅 금액은 3_000") { + val name = PlayerName("kim") + val playerBet = betBoard.playerBet(name) + + playerBet.playerName shouldBe name + (playerBet as? PlayerBet.BetPlaced)?.betAmount shouldBe kimAmount + } + + it("플레이어 lee 의 베팅 금액은 4_000") { + val name = PlayerName("lee") + val playerBet = betBoard.playerBet(name) + + playerBet.playerName shouldBe name + (playerBet as? PlayerBet.BetPlaced)?.betAmount shouldBe leeAmount + } + } + } + + describe("playerBet") { + context("kim 이름으로 플레이어 베팅을 등록하면") { + val kim = player("kim") + val amount = Amount(3_000) + + val board = BetBoard.of(players(kim, player())) { player: Player -> + if (player == kim) amount + else Amount(5_000) + } + + it("kim의 베팅 금액이 조회 된다") { + val result = board.playerBet(PlayerName("kim")) + + result.shouldBeTypeOf() + result.betAmount shouldBe amount + } + } + } +}) diff --git a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt index 9a36366a2..564fc325d 100644 --- a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt +++ b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt @@ -2,15 +2,18 @@ package blackjack.mock import blackjack.controller.InputProcessor import blackjack.domain.Action +import blackjack.domain.batting.Amount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames class InputProcessorMock( private val playerNames: List = listOf("kim", "lee"), private val action: Action = Action.HIT, + private val betAmount: Amount = Amount(3_000) ) : InputProcessor { override fun playerNames(): PlayerNames = playerNames.let(PlayerNames::from) + override fun playerBet(player: Player): Amount = betAmount override fun playerAction(player: Player): Action = action } diff --git a/src/test/kotlin/blackjack/mock/TestFactories.kt b/src/test/kotlin/blackjack/mock/TestFactories.kt index 350ef0de5..439caf705 100644 --- a/src/test/kotlin/blackjack/mock/TestFactories.kt +++ b/src/test/kotlin/blackjack/mock/TestFactories.kt @@ -37,6 +37,6 @@ fun table( players: Players? = null, ): GameTable = GameTable( + players ?: players(player("kim", inputAction), player("lee", inputAction)), dealer, - players ?: players(player("kim", inputAction), player("lee", inputAction)) ) From c8eca3119565b9e0d8a9833b34a9952ec005f817 Mon Sep 17 00:00:00 2001 From: bongbong Date: Mon, 4 Dec 2023 00:30:12 +0900 Subject: [PATCH 06/19] =?UTF-8?q?[step4]=20feat:=20=EB=B2=A0=ED=8C=85=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98?= =?UTF-8?q?=EC=9D=B5=20=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++- .../blackjack/controller/ResultProcessor.kt | 6 +- .../controller/ViewResultProcessor.kt | 37 ++- .../kotlin/blackjack/domain/BlackJackGame.kt | 9 +- .../kotlin/blackjack/domain/batting/Amount.kt | 18 +- .../blackjack/domain/batting/BetBoard.kt | 59 ++++- .../blackjack/domain/batting/PlayerBet.kt | 19 +- src/main/kotlin/blackjack/domain/card/Hand.kt | 6 + .../kotlin/blackjack/domain/card/HandScore.kt | 3 + .../blackjack/domain/distirbution/DealEnd.kt | 6 +- .../blackjack/domain/player/CardPlayer.kt | 3 + .../kotlin/blackjack/domain/player/Player.kt | 10 + .../result/distribution/DealEndResult.kt | 10 + .../domain/result/game/DealerGameResult.kt | 19 ++ .../domain/result/game/DealerResult.kt | 16 -- .../domain/result/game/GameEndResult.kt | 29 +++ .../domain/result/game/GameResult.kt | 21 -- .../domain/result/game/PlayerGameResult.kt | 27 +++ .../domain/result/game/PlayerResult.kt | 23 -- .../blackjack/domain/result/game/Profit.kt | 19 ++ .../domain/result/game/VictoryStatues.kt | 15 -- .../view/dto/DealerCardsResultDto.kt | 8 + .../blackjack/view/dto/DealerProfitDto.kt | 5 + .../blackjack/view/dto/FinalDealerStateDto.kt | 10 - ...yerStateDto.kt => PlayerCardsResultDto.kt} | 4 +- .../blackjack/view/dto/PlayerProfitDto.kt | 6 + .../blackjack/view/output/OutputView.kt | 46 ++-- .../view/output/VictoryStatusView.kt | 17 -- .../blackjack/domain/betting/AmountTest.kt | 21 +- .../blackjack/domain/betting/BetBoardTest.kt | 215 +++++++++++++++--- .../blackjack/domain/betting/PlayerBetTest.kt | 32 +++ .../blackjack/domain/card/HandScoreTest.kt | 16 ++ .../kotlin/blackjack/domain/card/HandTest.kt | 17 ++ .../domain/distirbution/DealEndTest.kt | 6 +- .../blackjack/domain/player/PlayerTest.kt | 113 +++++++++ .../VictoryStatusTest.kt | 3 +- .../domain/result/game/GameResultTest.kt | 142 ------------ .../domain/result/game/ProfitTest.kt | 22 ++ .../blackjack/mock/InputProcessorMock.kt | 2 +- .../kotlin/blackjack/mock/TestFactories.kt | 7 +- 40 files changed, 720 insertions(+), 361 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/result/distribution/DealEndResult.kt create mode 100644 src/main/kotlin/blackjack/domain/result/game/DealerGameResult.kt delete mode 100644 src/main/kotlin/blackjack/domain/result/game/DealerResult.kt create mode 100644 src/main/kotlin/blackjack/domain/result/game/GameEndResult.kt delete mode 100644 src/main/kotlin/blackjack/domain/result/game/GameResult.kt create mode 100644 src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt delete mode 100644 src/main/kotlin/blackjack/domain/result/game/PlayerResult.kt create mode 100644 src/main/kotlin/blackjack/domain/result/game/Profit.kt delete mode 100644 src/main/kotlin/blackjack/domain/result/game/VictoryStatues.kt create mode 100644 src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt create mode 100644 src/main/kotlin/blackjack/view/dto/DealerProfitDto.kt delete mode 100644 src/main/kotlin/blackjack/view/dto/FinalDealerStateDto.kt rename src/main/kotlin/blackjack/view/dto/{FinalPlayerStateDto.kt => PlayerCardsResultDto.kt} (53%) create mode 100644 src/main/kotlin/blackjack/view/dto/PlayerProfitDto.kt delete mode 100644 src/main/kotlin/blackjack/view/output/VictoryStatusView.kt create mode 100644 src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt rename src/test/kotlin/blackjack/domain/result/{game => distribution}/VictoryStatusTest.kt (89%) delete mode 100644 src/test/kotlin/blackjack/domain/result/game/GameResultTest.kt create mode 100644 src/test/kotlin/blackjack/domain/result/game/ProfitTest.kt diff --git a/README.md b/README.md index cc00d72a2..1a8cc9305 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ InputProcessor : 입력 금액을 검증하고 도메인 레벨로 변환한다 - 베팅 금액은 0보다 커야 한다 BlackJackGame : 게임을 진행한다 -- distributor 가 dealEnd 가 될 때까지 카드 배분을 진행한다. +- distributor 가 dealEnd 가 될 때까지 카드 배분을 진행한다 - 각 카드 배분의 결과를 result 처리기에 넘긴다 - 초기 카드 배분자는 DealIntialCards()이다 - 카드 배분 횟수를 초과하면 종료시킨다 @@ -34,6 +34,7 @@ BlackJackGame : 게임을 진행한다 GameTable - 딜러와 플레이어를 갖고 상호간의 카드 이동을 담당한다 +- 배팅 보드를 가지고 게임이 끝나면 게임 결과로 베팅 결과를 산출한다 Players - 플레이어를 관리하고 어떤 플레이어의 턴인지 관리한다 @@ -51,9 +52,20 @@ Player - [x] 플레이어는 이름이 같으면 동일한 플레이어로 취급된다 - hitOrStand: 사용자의 의견에 따라 카드를 더 받을지(hit) 그만할지(stand)를 반환한다 - 사용자가 hit 를 요청했더라도 카드 점수가 21점 이상이면 stand 를 반환한다 +- 딜러와의 점수를 비교해서 승패를 가를 수 있다 + - 버스트 : 딜러 점수와 상관 없이 패 + - 그 외 딜러보다 점수가 높으면 승, 같으면 무, 낮으면 패 BettingBoard : 베팅 보드 - [x] 플레이어별 베팅 금액을 저장 +- [x] 게임이 종료되면 플레이어별 승패 결과에 따라 각 플레이어가 받을 금액을 결정한다 + - 플레이어가 이겼을 때 + - 블랙잭으로 이겼을 때 : 베팅금액 회수 + 베팅금액 * 1.5 + - 일반 수로 이겼을 때 : 베팅금액 회수 + 베팅금액 * 1 + - 무승부일 때 : 베팅금액 회수 + - 졌을 때: 베팅금액 뺏김 + - PlayerBet + - 플레이어의 베팅 금액과 받은 금액으로 최종 수익을 계산한다 DealInitialCards : 처음 카드 배분 - 참가자와 딜러에게 각각 2장의 카드를 나눠 준다 @@ -76,26 +88,8 @@ ResultProcessor ViewResultProcessor - 딜러의 카드는 한 장만 출력한다 - 게임이 종료되면 (GameResult 인 경우) 딜러와 플레이어의 카드 점수를 출력한다 +- 베팅 결과를 최종 수익을 출력한다 GameResult - 게임 최종 결과는 21을 넘지 않으면서, 21에 가까운 방식으로 계산한다 - 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. - -베팅 결과 계산 -- [ ] 베팅 결과를 계산한다 - - 블랙잭 : 처음 2장의 카드의 합이 21 / 버스트 : 카드의 합이 21점 초과 / 푸시 : 딜러와 동점 (무승부) - - 블랙잭일 경우 1.5배를 딜러로부터 받는다 - - 딜러도 블랙잭일 경우 무승부로 베팅한 금액을 돌려 받는다 - - 버스트일 경우 베팅 금액을 모두 잃는다 - - 딜러도 버스트일 경우 동일하게 딜러가 승리한다 (베팅 금액은 딜러에게) - - 그 외의 경우 - - 딜러와의 승패 결과에 따라 이길 경우 베팅 금액을 돌려 받고, 패할 경우 해당 돈은 딜러에게 귀속된다. - - 이길 경우 : 베팅 금액을 돌려 받는다 - - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 버스트 - - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 낮은 1~21 사이의 수 - - 패할 경우 : 해당 돈은 딜러에게 귀속된다 - - 경우1) 플레이어 : 1~21 사이의 수 / 딜러 : 블랙잭 - - 경우2) 플레이어 : 1~21 사이의 수 / 딜러 : 플레이어보다 높은 1~21 사이의 수 - - 무승부일 경우 : 베팅 금액을 돌려 받는다 - - 경우1) 딜러와 플레이어 모두 동일한 1~21 사이의 수 -- [ ] 베팅 결과를 출력한다 diff --git a/src/main/kotlin/blackjack/controller/ResultProcessor.kt b/src/main/kotlin/blackjack/controller/ResultProcessor.kt index c2ebdb331..f663aa917 100644 --- a/src/main/kotlin/blackjack/controller/ResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ResultProcessor.kt @@ -1,10 +1,11 @@ package blackjack.controller import blackjack.domain.result.Result +import blackjack.domain.result.distribution.DealEndResult import blackjack.domain.result.distribution.DealInitialCardResult import blackjack.domain.result.distribution.DealToDealerResult import blackjack.domain.result.distribution.DealToPlayerResult -import blackjack.domain.result.game.GameResult +import blackjack.domain.result.game.GameEndResult class ResultProcessor { fun handle(result: Result) { @@ -14,8 +15,9 @@ class ResultProcessor { if (result.isSystemStand) return ViewResultProcessor.drawPlayerState(result) } + is DealEndResult -> {} is DealToDealerResult -> ViewResultProcessor.drawDealerState(result) - is GameResult -> ViewResultProcessor.drawGameResult(result) + is GameEndResult -> ViewResultProcessor.drawGameResult(result) } } } diff --git a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt index 6ff37233c..d4db5c0b4 100644 --- a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt @@ -3,11 +3,13 @@ package blackjack.controller import blackjack.domain.result.distribution.DealInitialCardResult import blackjack.domain.result.distribution.DealToDealerResult import blackjack.domain.result.distribution.DealToPlayerResult -import blackjack.domain.result.game.GameResult +import blackjack.domain.result.game.GameEndResult +import blackjack.view.dto.DealerCardsResultDto import blackjack.view.dto.DealerHitDto -import blackjack.view.dto.FinalDealerStateDto -import blackjack.view.dto.FinalPlayerStateDto +import blackjack.view.dto.DealerProfitDto +import blackjack.view.dto.PlayerCardsResultDto import blackjack.view.dto.PlayerDto +import blackjack.view.dto.PlayerProfitDto import blackjack.view.output.OutputView object ViewResultProcessor { @@ -26,15 +28,26 @@ object ViewResultProcessor { OutputView.drawDealerHitStatus(DealerHitDto(result.isHit)) } - fun drawGameResult(result: GameResult) { + fun drawGameResult(result: GameEndResult) { + drawCardResult(result) + drawProfitResult(result) + } + + private fun drawCardResult(result: GameEndResult) { + val dealerDto = + DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.cardScore) + val playersDto = result.playerResults.map { + PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.cardScore) + } + OutputView.drawCardsResults(dealerDto, playersDto) + } + + private fun drawProfitResult(result: GameEndResult) { val dealerDto = - result.dealerResults.let { - FinalDealerStateDto(it.dealer.hand.cards, it.dealer.score.cardScore, it.status) - } - val playersDto = - result.playersResult.map { - FinalPlayerStateDto(it.player.name.value, it.player.hand.cards, it.player.score.cardScore, it.status) - } - OutputView.drawFinalResults(dealerDto, playersDto) + DealerProfitDto(result.dealerProfit.value.toString()) + val playersDto = result.playerResults.map { + PlayerProfitDto(it.name.value, it.profit.value.toString()) + } + OutputView.drawProfitResults(dealerDto, playersDto) } } diff --git a/src/main/kotlin/blackjack/domain/BlackJackGame.kt b/src/main/kotlin/blackjack/domain/BlackJackGame.kt index 56b98cc94..ddfc33e44 100644 --- a/src/main/kotlin/blackjack/domain/BlackJackGame.kt +++ b/src/main/kotlin/blackjack/domain/BlackJackGame.kt @@ -8,6 +8,8 @@ import blackjack.domain.distirbution.DealEnd import blackjack.domain.distirbution.DealInitialCards import blackjack.domain.player.Players import blackjack.domain.result.Result +import blackjack.domain.result.distribution.DealEndResult +import blackjack.domain.result.game.GameEndResult class BlackJackGame( inputProcessor: InputProcessor, @@ -17,6 +19,7 @@ class BlackJackGame( var dealCards: CardDistributor = DealInitialCards(GameTable(players)) private set + val betBoard: BetBoard = BetBoard.of(players) { player -> inputProcessor.playerBet(player) } fun run() { @@ -39,8 +42,10 @@ class BlackJackGame( } private fun endDeal() { - val result = dealCards.deal() - emitResult(result) + val dealEndResult = dealCards.deal() as? DealEndResult + ?: throw IllegalArgumentException("배분이 종료되지 않았습니다") + betBoard.closeBetting(dealEndResult) + emitResult(GameEndResult.of(dealEndResult, betBoard)) } companion object { diff --git a/src/main/kotlin/blackjack/domain/batting/Amount.kt b/src/main/kotlin/blackjack/domain/batting/Amount.kt index 73cf174d0..43d5bebdc 100644 --- a/src/main/kotlin/blackjack/domain/batting/Amount.kt +++ b/src/main/kotlin/blackjack/domain/batting/Amount.kt @@ -1,12 +1,24 @@ package blackjack.domain.batting +import java.math.BigDecimal + data class Amount( - val value: Int, -) { + val value: BigDecimal, +) : Comparable { + + operator fun plus(other: Amount): Amount = value.plus(other.value).let(::Amount) + operator fun times(count: Int): Amount = value.times(count.toBigDecimal()).let(::Amount) + + operator fun times(count: BigDecimal): Amount = + Amount(value * count) + override fun compareTo(other: Amount): Int = + this.value.compareTo(other.value) + companion object { + val ZERO = Amount(BigDecimal.ZERO) fun betAmount(amount: Int): Amount { require(amount > 0) { "베팅 금액은 0보다 커야 합니다" } - return Amount(amount) + return Amount(amount.toBigDecimal()) } } } diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt index 0b1189822..7149298be 100644 --- a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -1,20 +1,69 @@ package blackjack.domain.batting +import blackjack.domain.Dealer import blackjack.domain.player.Player import blackjack.domain.player.PlayerName import blackjack.domain.player.Players +import blackjack.domain.result.distribution.DealEndResult +import blackjack.domain.result.game.Profit +import blackjack.domain.result.game.VictoryStatus +import java.math.BigDecimal data class BetBoard( - val playerBets: Map + private val playerBets: MutableMap ) { fun playerBet(name: PlayerName): PlayerBet = - playerBets[name] ?: throw IllegalArgumentException("해당 하는 플레이어 베팅을 찾을 수 없습니다") + findPlayerBet(name) + + fun playerProfit(name: PlayerName): Profit = + findFinishedPlayerBet(name).profit + + fun dealerProfit(): Profit = + playerBets.map { + val bet = it.value as? PlayerBet.Finished + ?: throw IllegalStateException("모든 플레이어가 베팅을 끝내지 않았습니다") + bet.profit + }.reduce(Profit::plus).negative + + fun closeBetting(dealEndResult: DealEndResult) { + dealEndResult.players.value.forEach { player -> + closeBet(player, dealEndResult.dealer) + } + } + + private fun closeBet(player: Player, dealer: Dealer) { + val betToFinish = findPlacedPlayerBet(player.name) + val payoutAmount = player.payout(dealer, betToFinish.betAmount) + val finishedBet = PlayerBet.Finished.of(betToFinish, payoutAmount) + playerBets[player.name] = finishedBet + } + + private fun findPlacedPlayerBet(name: PlayerName): PlayerBet.Placed = + findPlayerBet(name) as? PlayerBet.Placed ?: throw IllegalStateException("해당 플레이어의 베팅이 이미 종료되었습니다") + + private fun findFinishedPlayerBet(name: PlayerName): PlayerBet.Finished = + findPlayerBet(name) as? PlayerBet.Finished ?: throw IllegalStateException("해당 플레이어의 베팅이 종료 되지 않았습니다") + + private fun findPlayerBet(name: PlayerName): PlayerBet = + playerBets[name] ?: throw IllegalArgumentException("베팅에 참여한 플레이어가 아닙니다") + + private fun Player.payout(dealer: Dealer, betAmount: Amount): Amount = + when (this.judgeVictory(dealer)) { + VictoryStatus.WIN -> calculateWinAmount(this, betAmount) + VictoryStatus.LOSS -> Amount.ZERO + VictoryStatus.PUSH -> betAmount + } + + private fun calculateWinAmount(player: Player, betAmount: Amount): Amount = + if (player.isBlackJack) betAmount * BLACK_JACK_MULTIPLIER + betAmount + else betAmount * 2 companion object { + private val BLACK_JACK_MULTIPLIER = BigDecimal(1.5) fun of(players: Players, betAmount: (player: Player) -> Amount): BetBoard = - players.value.associate { it.name to it.playerBet(betAmount) }.let(::BetBoard) + players.value.associate { it.name to it.placeBet(betAmount) }.toMutableMap().let(::BetBoard) - private fun Player.playerBet(betAmount: (player: Player) -> Amount): PlayerBet = - PlayerBet.BetPlaced(this.name, betAmount(this)) + private fun Player.placeBet(betAmount: (player: Player) -> Amount): PlayerBet = + PlayerBet.Placed(this.name, betAmount(this)) } } diff --git a/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt index eb7815eb9..2e40410e3 100644 --- a/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt +++ b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt @@ -1,18 +1,29 @@ package blackjack.domain.batting import blackjack.domain.player.PlayerName +import blackjack.domain.result.game.Profit sealed interface PlayerBet { val playerName: PlayerName - data class BetPlaced( + data class Placed( override val playerName: PlayerName, val betAmount: Amount, ) : PlayerBet - data class BetFinished( + data class Finished( override val playerName: PlayerName, val betAmount: Amount, - val resultAmount: Amount, - ) : PlayerBet + val payoutAmount: Amount, + ) : PlayerBet { + + val profit: Profit = Profit.of(betAmount, payoutAmount) + companion object { + fun of(betPlaced: Placed, payoutAmount: Amount): Finished = Finished( + playerName = betPlaced.playerName, + betAmount = betPlaced.betAmount, + payoutAmount = payoutAmount + ) + } + } } diff --git a/src/main/kotlin/blackjack/domain/card/Hand.kt b/src/main/kotlin/blackjack/domain/card/Hand.kt index 8ccf5126b..e79784e29 100644 --- a/src/main/kotlin/blackjack/domain/card/Hand.kt +++ b/src/main/kotlin/blackjack/domain/card/Hand.kt @@ -9,7 +9,13 @@ data class Hand( val ranks: List get() = _cards.map { it.rank } + val isBlackJackCardSize: Boolean = _cards.size == BLACK_JACK_CARD_SIZE + fun add(card: Card) { _cards.add(card) } + + companion object { + private const val BLACK_JACK_CARD_SIZE = 2 + } } diff --git a/src/main/kotlin/blackjack/domain/card/HandScore.kt b/src/main/kotlin/blackjack/domain/card/HandScore.kt index 7570714bb..d54bedbaf 100644 --- a/src/main/kotlin/blackjack/domain/card/HandScore.kt +++ b/src/main/kotlin/blackjack/domain/card/HandScore.kt @@ -8,6 +8,8 @@ class HandScore( val isGreaterOrEqualToMaxScore: Boolean = cardScore >= BUST_THRESHOLD + val isBlackJackScore: Boolean = cardScore == BLACK_JACK_SCORE + val gameScore: Int get() { if (isBust) return BUST_SCORE @@ -20,6 +22,7 @@ class HandScore( companion object { private const val BUST_THRESHOLD = 21 + private const val BLACK_JACK_SCORE = 21 private const val BUST_SCORE = 0 private const val ACE_BONUS_SCORE = 10 diff --git a/src/main/kotlin/blackjack/domain/distirbution/DealEnd.kt b/src/main/kotlin/blackjack/domain/distirbution/DealEnd.kt index d86d88df0..ba6d1879f 100644 --- a/src/main/kotlin/blackjack/domain/distirbution/DealEnd.kt +++ b/src/main/kotlin/blackjack/domain/distirbution/DealEnd.kt @@ -1,14 +1,14 @@ package blackjack.domain.distirbution import blackjack.domain.GameTable -import blackjack.domain.result.game.GameResult +import blackjack.domain.result.distribution.DealEndResult class DealEnd( override val table: GameTable ) : CardDistributor() { - override fun deal(): GameResult = - GameResult.of(table.players, table.dealer) + override fun deal(): DealEndResult = + DealEndResult(table.players, table.dealer) override fun nextDistributor(): CardDistributor { throw IllegalArgumentException("마지막 카드 배분입니다") diff --git a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt index ca83d2509..27d521e66 100644 --- a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt +++ b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt @@ -14,6 +14,9 @@ interface CardPlayer { val isGreaterOrEqualToMaxScore: Boolean get() = score.isGreaterOrEqualToMaxScore + val isBlackJack: Boolean + get() = hand.isBlackJackCardSize && score.isBlackJackScore + infix fun isGreaterCardScoreThan(other: Int): Boolean = score isGreaterCardScoreThan other diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index 00b714612..7a1264864 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -1,7 +1,9 @@ package blackjack.domain.player import blackjack.domain.Action +import blackjack.domain.Dealer import blackjack.domain.card.Hand +import blackjack.domain.result.game.VictoryStatus class Player( val name: PlayerName, @@ -9,6 +11,14 @@ class Player( override val hand: Hand = Hand(), ) : CardPlayer { + fun judgeVictory(dealer: Dealer): VictoryStatus = + when { + score.isBust -> VictoryStatus.LOSS + score isGreaterGameScoreThan dealer.score -> VictoryStatus.WIN + score isSameGameScoreTo dealer.score -> VictoryStatus.PUSH + else -> VictoryStatus.LOSS + } + override fun hitOrStand(): Action { if (isGreaterOrEqualToMaxScore) return Action.STAND return desiredAction(this) diff --git a/src/main/kotlin/blackjack/domain/result/distribution/DealEndResult.kt b/src/main/kotlin/blackjack/domain/result/distribution/DealEndResult.kt new file mode 100644 index 000000000..2c9651731 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/distribution/DealEndResult.kt @@ -0,0 +1,10 @@ +package blackjack.domain.result.distribution + +import blackjack.domain.Dealer +import blackjack.domain.player.Players +import blackjack.domain.result.Result + +data class DealEndResult( + val players: Players, + val dealer: Dealer, +) : Result() diff --git a/src/main/kotlin/blackjack/domain/result/game/DealerGameResult.kt b/src/main/kotlin/blackjack/domain/result/game/DealerGameResult.kt new file mode 100644 index 000000000..7f23c4d05 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/game/DealerGameResult.kt @@ -0,0 +1,19 @@ +package blackjack.domain.result.game + +import blackjack.domain.Dealer +import blackjack.domain.batting.BetBoard +import blackjack.domain.player.DealerPlayer + +data class DealerGameResult( + val dealer: DealerPlayer, + val profit: Profit, +) { + + companion object { + fun of(dealer: Dealer, betBoard: BetBoard): DealerGameResult = + DealerGameResult( + dealer.player, + betBoard.dealerProfit() + ) + } +} diff --git a/src/main/kotlin/blackjack/domain/result/game/DealerResult.kt b/src/main/kotlin/blackjack/domain/result/game/DealerResult.kt deleted file mode 100644 index b93cbc3ec..000000000 --- a/src/main/kotlin/blackjack/domain/result/game/DealerResult.kt +++ /dev/null @@ -1,16 +0,0 @@ -package blackjack.domain.result.game - -import blackjack.domain.Dealer - -data class DealerResult( - val dealer: Dealer, - val status: VictoryStatues, -) { - companion object { - fun of(dealer: Dealer, playerResults: List): DealerResult = - DealerResult( - dealer, - playerResults.map { it.status.opponentResult }.let(::VictoryStatues) - ) - } -} diff --git a/src/main/kotlin/blackjack/domain/result/game/GameEndResult.kt b/src/main/kotlin/blackjack/domain/result/game/GameEndResult.kt new file mode 100644 index 000000000..e15d12a81 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/game/GameEndResult.kt @@ -0,0 +1,29 @@ +package blackjack.domain.result.game + +import blackjack.domain.batting.BetBoard +import blackjack.domain.card.Hand +import blackjack.domain.card.HandScore +import blackjack.domain.result.Result +import blackjack.domain.result.distribution.DealEndResult + +data class GameEndResult( + val playerResults: List, + val dealerResult: DealerGameResult, +) : Result() { + + val dealerHand: Hand = dealerResult.dealer.hand + + val dealerScore: HandScore = dealerResult.dealer.score + + val dealerProfit: Profit = dealerResult.profit + + companion object { + fun of(dealEndResult: DealEndResult, betBoard: BetBoard): GameEndResult { + val playerResults = dealEndResult.players.value.map { player -> + PlayerGameResult.of(player, betBoard) + } + val dealerResult = DealerGameResult.of(dealEndResult.dealer, betBoard) + return GameEndResult(playerResults, dealerResult) + } + } +} diff --git a/src/main/kotlin/blackjack/domain/result/game/GameResult.kt b/src/main/kotlin/blackjack/domain/result/game/GameResult.kt deleted file mode 100644 index ef81e0598..000000000 --- a/src/main/kotlin/blackjack/domain/result/game/GameResult.kt +++ /dev/null @@ -1,21 +0,0 @@ -package blackjack.domain.result.game - -import blackjack.domain.Dealer -import blackjack.domain.player.Players -import blackjack.domain.result.Result - -data class GameResult( - val dealerResults: DealerResult, - val playersResult: List, -) : Result() { - - companion object { - fun of(players: Players, dealer: Dealer): GameResult { - val playersResult = players.value.map { PlayerResult.of(it, dealer.player) } - return GameResult( - DealerResult.of(dealer, playersResult), - playersResult - ) - } - } -} diff --git a/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt b/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt new file mode 100644 index 000000000..a9c9727a3 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt @@ -0,0 +1,27 @@ +package blackjack.domain.result.game + +import blackjack.domain.batting.BetBoard +import blackjack.domain.card.Hand +import blackjack.domain.card.HandScore +import blackjack.domain.player.Player +import blackjack.domain.player.PlayerName + +data class PlayerGameResult( + val player: Player, + val profit: Profit, +) { + + val name: PlayerName = player.name + + val hand: Hand = player.hand + + val score: HandScore = player.score + + companion object { + fun of(player: Player, betBoard: BetBoard): PlayerGameResult = + PlayerGameResult( + player, + betBoard.playerProfit(player.name) + ) + } +} diff --git a/src/main/kotlin/blackjack/domain/result/game/PlayerResult.kt b/src/main/kotlin/blackjack/domain/result/game/PlayerResult.kt deleted file mode 100644 index 6ba21af13..000000000 --- a/src/main/kotlin/blackjack/domain/result/game/PlayerResult.kt +++ /dev/null @@ -1,23 +0,0 @@ -package blackjack.domain.result.game - -import blackjack.domain.player.DealerPlayer -import blackjack.domain.player.Player - -data class PlayerResult( - val player: Player, - val status: VictoryStatus, -) { - companion object { - fun of(player: Player, dealerPlayer: DealerPlayer): PlayerResult { - val playerScore = player.score - val dealerScore = dealerPlayer.score - val status = when { - playerScore.isBust -> VictoryStatus.LOSS - playerScore isGreaterGameScoreThan dealerScore -> VictoryStatus.WIN - playerScore isSameGameScoreTo dealerScore -> VictoryStatus.PUSH - else -> VictoryStatus.LOSS - } - return PlayerResult(player, status) - } - } -} diff --git a/src/main/kotlin/blackjack/domain/result/game/Profit.kt b/src/main/kotlin/blackjack/domain/result/game/Profit.kt new file mode 100644 index 000000000..04f9ca2a4 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/game/Profit.kt @@ -0,0 +1,19 @@ +package blackjack.domain.result.game + +import blackjack.domain.batting.Amount +import java.math.BigDecimal + +data class Profit( + val value: BigDecimal, +) { + + val negative: Profit + get() = Profit(value.negate()) + + operator fun plus(other: Profit): Profit = value.plus(other.value).let(::Profit) + + companion object { + fun of(betAmount: Amount, payoutAmount: Amount): Profit = + payoutAmount.value.minus(betAmount.value).let(::Profit) + } +} diff --git a/src/main/kotlin/blackjack/domain/result/game/VictoryStatues.kt b/src/main/kotlin/blackjack/domain/result/game/VictoryStatues.kt deleted file mode 100644 index f4e3c57bd..000000000 --- a/src/main/kotlin/blackjack/domain/result/game/VictoryStatues.kt +++ /dev/null @@ -1,15 +0,0 @@ -package blackjack.domain.result.game - -@JvmInline -value class VictoryStatues( - val value: List, -) { - val winCount: Int - get() = value.count { it == VictoryStatus.WIN } - - val pushCount: Int - get() = value.count { it == VictoryStatus.PUSH } - - val lossCount: Int - get() = value.count { it == VictoryStatus.LOSS } -} diff --git a/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt b/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt new file mode 100644 index 000000000..b1e81f88f --- /dev/null +++ b/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt @@ -0,0 +1,8 @@ +package blackjack.view.dto + +import blackjack.domain.card.Card + +data class DealerCardsResultDto( + val cards: List, + val cardScore: Int, +) diff --git a/src/main/kotlin/blackjack/view/dto/DealerProfitDto.kt b/src/main/kotlin/blackjack/view/dto/DealerProfitDto.kt new file mode 100644 index 000000000..86ac102d7 --- /dev/null +++ b/src/main/kotlin/blackjack/view/dto/DealerProfitDto.kt @@ -0,0 +1,5 @@ +package blackjack.view.dto + +data class DealerProfitDto( + val profit: String, +) diff --git a/src/main/kotlin/blackjack/view/dto/FinalDealerStateDto.kt b/src/main/kotlin/blackjack/view/dto/FinalDealerStateDto.kt deleted file mode 100644 index 881f4d505..000000000 --- a/src/main/kotlin/blackjack/view/dto/FinalDealerStateDto.kt +++ /dev/null @@ -1,10 +0,0 @@ -package blackjack.view.dto - -import blackjack.domain.card.Card -import blackjack.domain.result.game.VictoryStatues - -data class FinalDealerStateDto( - val cards: List, - val cardScore: Int, - val victoryStatus: VictoryStatues, -) diff --git a/src/main/kotlin/blackjack/view/dto/FinalPlayerStateDto.kt b/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt similarity index 53% rename from src/main/kotlin/blackjack/view/dto/FinalPlayerStateDto.kt rename to src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt index 34b5b6219..2cca62028 100644 --- a/src/main/kotlin/blackjack/view/dto/FinalPlayerStateDto.kt +++ b/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt @@ -1,11 +1,9 @@ package blackjack.view.dto import blackjack.domain.card.Card -import blackjack.domain.result.game.VictoryStatus -data class FinalPlayerStateDto( +data class PlayerCardsResultDto( val name: String, val cards: List, val cardScore: Int, - val victoryStatus: VictoryStatus, ) diff --git a/src/main/kotlin/blackjack/view/dto/PlayerProfitDto.kt b/src/main/kotlin/blackjack/view/dto/PlayerProfitDto.kt new file mode 100644 index 000000000..b20af73fb --- /dev/null +++ b/src/main/kotlin/blackjack/view/dto/PlayerProfitDto.kt @@ -0,0 +1,6 @@ +package blackjack.view.dto + +data class PlayerProfitDto( + val name: String, + val profit: String, +) diff --git a/src/main/kotlin/blackjack/view/output/OutputView.kt b/src/main/kotlin/blackjack/view/output/OutputView.kt index a8486ccea..22c184ad5 100644 --- a/src/main/kotlin/blackjack/view/output/OutputView.kt +++ b/src/main/kotlin/blackjack/view/output/OutputView.kt @@ -1,12 +1,12 @@ package blackjack.view.output import blackjack.domain.card.Card -import blackjack.domain.result.game.VictoryStatues -import blackjack.domain.result.game.VictoryStatus +import blackjack.view.dto.DealerCardsResultDto import blackjack.view.dto.DealerHitDto -import blackjack.view.dto.FinalDealerStateDto -import blackjack.view.dto.FinalPlayerStateDto +import blackjack.view.dto.DealerProfitDto +import blackjack.view.dto.PlayerCardsResultDto import blackjack.view.dto.PlayerDto +import blackjack.view.dto.PlayerProfitDto object OutputView { private const val INITIAL_DISTRIBUTION_MSG = "딜러와 %s에게 2장씩 나누었습니다." @@ -16,9 +16,9 @@ object OutputView { private const val DEALER_STAND_MSG = "딜러는 17이상이라 카드를 받지 않았습니다." private const val DEALER_FINAL_CARDS_MSG = "딜러 카드: %s - 결과: %d" private const val PLAYER_FINAL_CARDS_MSG = "%s카드: %s - 결과: %d" - private const val VICTORY_MSG = "## 최종 승패" - private const val DEALER_VICTORY_MSG = "딜러: %s" - private const val PLAYER_VICTORY_MSG = "%s: %s" + private const val PROFIT_MSG = "## 최종 수익" + private const val DEALER_PROFIT_MSG = "딜러: %s" + private const val PLAYER_PROFIT_MSG = "%s: %s" fun drawInitialDistributionResult( players: List, @@ -49,17 +49,9 @@ object OutputView { } } - fun drawFinalResults( - dealer: FinalDealerStateDto, - players: List, - ) { - drawFinalCards(dealer, players) - drawVictoryStatus(dealer, players) - } - - private fun drawFinalCards( - dealer: FinalDealerStateDto, - players: List, + fun drawCardsResults( + dealer: DealerCardsResultDto, + players: List, ) { println() println(DEALER_FINAL_CARDS_MSG.format(extractCardsState(dealer.cards), dealer.cardScore)) @@ -68,15 +60,15 @@ object OutputView { } } - private fun drawVictoryStatus( - dealer: FinalDealerStateDto, - players: List, + fun drawProfitResults( + dealer: DealerProfitDto, + players: List, ) { println() - println(VICTORY_MSG) - println(DEALER_VICTORY_MSG.format(extractVictoryStates(dealer.victoryStatus))) + println(PROFIT_MSG) + println(DEALER_PROFIT_MSG.format(dealer.profit)) players.forEach { - println(PLAYER_VICTORY_MSG.format(it.name, extractVictoryState(it.victoryStatus))) + println(PLAYER_PROFIT_MSG.format(it.name, it.profit)) } } @@ -85,10 +77,4 @@ object OutputView { private fun extractCardsState(cards: List): String = cards.joinToString(", ") { CardView.from(it) } - - private fun extractVictoryStates(states: VictoryStatues): String = - VictoryStatusView.from(states) - - private fun extractVictoryState(state: VictoryStatus): String = - VictoryStatusView.from(state) } diff --git a/src/main/kotlin/blackjack/view/output/VictoryStatusView.kt b/src/main/kotlin/blackjack/view/output/VictoryStatusView.kt deleted file mode 100644 index f4268e394..000000000 --- a/src/main/kotlin/blackjack/view/output/VictoryStatusView.kt +++ /dev/null @@ -1,17 +0,0 @@ -package blackjack.view.output - -import blackjack.domain.result.game.VictoryStatues -import blackjack.domain.result.game.VictoryStatus - -object VictoryStatusView { - fun from(status: VictoryStatus): String = when (status) { - VictoryStatus.WIN -> "승" - VictoryStatus.PUSH -> "무" - VictoryStatus.LOSS -> "패" - } - - fun from(statuses: VictoryStatues): String = - "${statuses.winCount}${from(VictoryStatus.WIN)} " + - "${statuses.pushCount}${from(VictoryStatus.PUSH)} " + - "${statuses.lossCount}${from(VictoryStatus.LOSS)}" -} diff --git a/src/test/kotlin/blackjack/domain/betting/AmountTest.kt b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt index 72181e55c..b679689d4 100644 --- a/src/test/kotlin/blackjack/domain/betting/AmountTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt @@ -5,6 +5,7 @@ import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import java.lang.IllegalArgumentException +import java.math.BigDecimal class AmountTest : StringSpec({ "0보다 큰 베팅 금액이 생성된다" { @@ -12,7 +13,7 @@ class AmountTest : StringSpec({ val result = Amount.betAmount(amount) - result.value shouldBe amount + result.value shouldBe amount.toBigDecimal() } "0이하의 금액은 베팅 금액이 될 수 없다" { @@ -22,4 +23,22 @@ class AmountTest : StringSpec({ Amount.betAmount(amount) } } + + "금액 끼리는 더할수 있다" { + val result = Amount(BigDecimal(3_000)) + Amount(BigDecimal(5000)) + + result shouldBe Amount(BigDecimal(8_000)) + } + + "금액의 배수(Int)를 구할수 있다" { + val result = Amount(BigDecimal(3_000)) * 3 + + result shouldBe Amount(BigDecimal(9_000)) + } + + "금액의 곱(BigDecimal)을 구할수 있다" { + val result = Amount(BigDecimal(3_000)) * BigDecimal(3) + + result shouldBe Amount(BigDecimal(9_000)) + } }) diff --git a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt index 970eb4797..4ab0a8d69 100644 --- a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt @@ -1,23 +1,31 @@ package blackjack.domain.betting -import blackjack.domain.batting.Amount +import blackjack.domain.Dealer import blackjack.domain.batting.BetBoard import blackjack.domain.batting.PlayerBet +import blackjack.domain.card.Rank +import blackjack.domain.player.DealerPlayer import blackjack.domain.player.Player import blackjack.domain.player.PlayerName +import blackjack.domain.result.distribution.DealEndResult +import blackjack.domain.result.game.Profit +import blackjack.mock.amount +import blackjack.mock.card +import blackjack.mock.hand import blackjack.mock.player import blackjack.mock.players +import blackjack.mock.profit +import io.kotest.assertions.throwables.shouldThrowExactly import io.kotest.core.spec.style.DescribeSpec -import io.kotest.inspectors.forAll import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeTypeOf +import java.math.BigDecimal class BetBoardTest : DescribeSpec({ describe("BettingBoard") { context("플레이어 이름으로 베팅 금액 등록 (kim: 3_000원, lee: 4_000원)") { val players = players(player("kim"), player("lee")) - val kimAmount = Amount(3_000) - val leeAmount = Amount(4_000) + val kimAmount = amount(3_000) + val leeAmount = amount(4_000) val betAmount = { player: Player -> if (player.name.value == "kim") kimAmount else leeAmount @@ -25,20 +33,12 @@ class BetBoardTest : DescribeSpec({ val betBoard: BetBoard = BetBoard.of(players, betAmount) - it("등록된 플레이어 베팅은 2개") { - betBoard.playerBets.size shouldBe 2 - } - - it("플레이어 베팅은 모두 BetPlaced 상태") { - betBoard.playerBets.values.forAll { it is PlayerBet.BetPlaced } - } - it("플레이어 kim 의 베팅 금액은 3_000") { val name = PlayerName("kim") val playerBet = betBoard.playerBet(name) playerBet.playerName shouldBe name - (playerBet as? PlayerBet.BetPlaced)?.betAmount shouldBe kimAmount + (playerBet as? PlayerBet.Placed)?.betAmount shouldBe kimAmount } it("플레이어 lee 의 베팅 금액은 4_000") { @@ -46,26 +46,189 @@ class BetBoardTest : DescribeSpec({ val playerBet = betBoard.playerBet(name) playerBet.playerName shouldBe name - (playerBet as? PlayerBet.BetPlaced)?.betAmount shouldBe leeAmount + (playerBet as? PlayerBet.Placed)?.betAmount shouldBe leeAmount } } } - describe("playerBet") { - context("kim 이름으로 플레이어 베팅을 등록하면") { - val kim = player("kim") - val amount = Amount(3_000) + describe("playerProfit") { + context("베팅이 완료된 플레이어 (베팅: 1000, 받은돈: 0)") { + val name = PlayerName("kim") + val betAmount = amount(1000) + val payoutAmount = amount(0) + val finishedBet = PlayerBet.Finished(name, betAmount, payoutAmount) + val betBord = BetBoard( + mutableMapOf( + name to finishedBet, + PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), amount(5_000), amount(5_000)) + ) + ) + + val result = betBord.playerProfit(name) + + it("베팅 수익 조회(-1000)") { + result.value shouldBe BigDecimal(-1000) + } + } + + context("베팅이 완료되지 않은 플레이어") { + val name = PlayerName("kim") + val betAmount = amount(1000) + val placedBet = PlayerBet.Placed(name, betAmount) + val betBord = BetBoard( + mutableMapOf( + name to placedBet, + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + it("수익 조회 실패") { + shouldThrowExactly { + betBord.playerProfit(name) + } + } + } + + describe("dealerProfit") { + context("플레이어1의 수익: 2000, 플레이어2의 수익: -1000") { + val betBoard = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Finished(PlayerName("kim"), amount(2_000), amount(4_000)), + PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), amount(1_000), amount(0)), + ) + ) + + betBoard.playerProfit(PlayerName("kim")) shouldBe profit(2_000) + betBoard.playerProfit(PlayerName("lee")) shouldBe profit(-1000) - val board = BetBoard.of(players(kim, player())) { player: Player -> - if (player == kim) amount - else Amount(5_000) + it("딜러의 수익 = -(두 플레이어 수익의 합) (-1000)") { + betBoard.dealerProfit() shouldBe profit(-1000) + } } + context("모든 플레이어 베팅이 끝나지 않았다면") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + it("수익 조회 실패") { + shouldThrowExactly { + betBord.dealerProfit() + } + } + } + } + + describe("closeBetting") { + context("딜러를 블랙잭으로 이긴 플레이어") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.ACE), card(Rank.TEN))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN)))) + + it("베팅 종료시 수익이 1.5배") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) + + val expect = (5_000 * 1.5).toBigDecimal() + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(expect) + } + } + + context("딜러를 일반 숫자로 이긴 플레이어") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN)))) + + it("베팅 종료시 수익이 1배") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) + + val expect = (5_000 * 1).toBigDecimal() + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(expect) + } + } + + context("딜러와 둘다 블랙잭으로 무승부인 플레이어") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.ACE))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.ACE)))) + + it("베팅 종료시 수익은 0") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) + + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(0)) + } + } + + context("딜러와 둘다 일반 점수로 무승부인 플레이어") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.TWO), card(Rank.ACE))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TWO), card(Rank.ACE)))) + + it("베팅 종료시 수익은 0") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) + + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(0)) + } + } + + context("버스트인 플레이어") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TWO), card(Rank.ACE)))) + + it("베팅 종료시 수익은 -베팅금액") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) + + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(-5000)) + } + } + + context("버스트인 플레이어와 딜러도 버스트") { + val betBord = BetBoard( + mutableMapOf( + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + ) + ) + + val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN))) + val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN)))) - it("kim의 베팅 금액이 조회 된다") { - val result = board.playerBet(PlayerName("kim")) + it("베팅 종료시 수익은 -베팅금액") { + betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) - result.shouldBeTypeOf() - result.betAmount shouldBe amount + betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(-5000)) + } } } } diff --git a/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt b/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt new file mode 100644 index 000000000..569326cf6 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt @@ -0,0 +1,32 @@ +package blackjack.domain.betting + +import blackjack.domain.batting.PlayerBet +import blackjack.domain.player.PlayerName +import blackjack.mock.amount +import blackjack.mock.profit +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class PlayerBetTest : StringSpec({ + "BetPlaced와 수익 정보로 BetFinished 생성" { + val placed = PlayerBet.Placed( + playerName = PlayerName("kim"), + betAmount = amount(3_000) + ) + val payoutAmount = amount(0) + + val result = PlayerBet.Finished.of(placed, payoutAmount) + + result.playerName shouldBe placed.playerName + result.betAmount shouldBe placed.betAmount + result.payoutAmount shouldBe payoutAmount + } + + "BetFinished의 수익 조회" { + val betAmount = amount(10000) + val payoutAmount = amount(25000) + val bet = PlayerBet.Finished(PlayerName("lee"), betAmount, payoutAmount) + + bet.profit shouldBe profit(15000) + } +}) diff --git a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt index 653a42900..5ce681b74 100644 --- a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt @@ -212,4 +212,20 @@ class HandScoreTest : DescribeSpec({ } } } + + describe("isBlackJackScore") { + context("블랙잭 점수인 21점이면") { + val handScore = HandScore(21) + it("true 반환") { + handScore.isBlackJackScore shouldBe true + } + } + + context("블랙잭 점수인 21점이 아니면") { + val handScore = HandScore(22) + it("false 반환") { + handScore.isBlackJackScore shouldBe false + } + } + } }) diff --git a/src/test/kotlin/blackjack/domain/card/HandTest.kt b/src/test/kotlin/blackjack/domain/card/HandTest.kt index 5f41bb38a..e72520804 100644 --- a/src/test/kotlin/blackjack/domain/card/HandTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandTest.kt @@ -1,5 +1,6 @@ package blackjack.domain.card +import blackjack.mock.card import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -41,4 +42,20 @@ class HandTest : DescribeSpec({ } } } + + describe("isBlackJackCardSize") { + context("블랙잭 조건인 카드가 2장이라면") { + val hand = Hand(mutableListOf(card(), card())) + it("true 반환") { + hand.isBlackJackCardSize shouldBe true + } + } + + context("블랙잭 조건인 카드가 2장이 아니라면") { + val hand = Hand(mutableListOf(card(), card(), card())) + it("false 반환") { + hand.isBlackJackCardSize shouldBe false + } + } + } }) diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt index da38b63cb..11b130985 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt @@ -20,13 +20,13 @@ class DealEndTest : DescribeSpec({ val result = dealEnd.deal() it("결과의 플레이어는 테이블의 플레이어") { - result.playersResult.forEachIndexed { index, playerResult -> - playerResult.player shouldBe players.value[index] + result.players.value.forEachIndexed { index, player -> + player shouldBe players.value[index] } } it("결과의 딜러는 테이블의 딜러") { - result.dealerResults.dealer shouldBe dealer + result.dealer shouldBe dealer } } } diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index af70803fd..cd3dcb029 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -1,10 +1,12 @@ package blackjack.domain.player import blackjack.domain.Action +import blackjack.domain.Dealer import blackjack.domain.card.Card import blackjack.domain.card.Hand import blackjack.domain.card.Rank import blackjack.domain.card.Suit +import blackjack.domain.result.game.VictoryStatus import blackjack.mock.card import blackjack.mock.hand import blackjack.mock.player @@ -188,4 +190,115 @@ class PlayerTest : DescribeSpec({ player1 shouldBe player2 } } + + describe("isBlackJack") { + context("플레이어가 블랙잭이면(카드가 2장이며, 해당 카드 합이 21)") { + val player = player(hand = hand(card(Rank.TEN), card(Rank.ACE))) + it("true 반환") { + player.isBlackJack shouldBe true + } + } + + context("플레이어 카드가 2장이지만 21점이 아니면") { + val player = player(hand = hand(card(Rank.TEN), card(Rank.TWO))) + it("false 반환") { + player.isBlackJack shouldBe false + } + } + + context("플레이어 카드 합이 21이지만 카드 수가 2장 초과면") { + val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN), card(Rank.TEN))) + it("false 반환") { + player.isBlackJack shouldBe false + } + } + } + + describe("judgeVictory") { + val score22Cards = + hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TWO)) + val blackJackCards = + hand(card(Rank.TEN), card(Rank.ACE)) + val score21NotBlackJackCards = + hand(card(Rank.TEN), card(Rank.FIVE), card(Rank.SIX)) + val score10Cards = hand(card(Rank.FIVE), card(Rank.FIVE)) + val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) + + context("플레이어: 버스트") { + val player = player(hand = score22Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("LOSS") { + player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어: 버스트, 딜러: 버스트") { + val player = player(hand = score22Cards) + val dealer = Dealer(player = DealerPlayer(hand = score22Cards)) + + it("LOSS") { + player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어(5) < 딜러(10)") { + val player = player(hand = score5Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("LOSS") { + player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어(21, 블랙잭) > 딜러(5)") { + val player = player(hand = blackJackCards) + val dealer = Dealer(player = DealerPlayer(score5Cards)) + + it("WIN") { + player.judgeVictory(dealer) shouldBe VictoryStatus.WIN + } + } + context("플레이어(10) > 딜러(5)") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score5Cards)) + + it("WIN") { + player.judgeVictory(dealer) shouldBe VictoryStatus.WIN + } + } + + context("플레이어(10), 딜러: 버스트") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score22Cards)) + + it("WIN") { + player.judgeVictory(dealer) shouldBe VictoryStatus.WIN + } + } + + context("플레이어(10) == 딜러(10)") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("PUSH") { + player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH + } + } + + context("플레이어(블랙잭) == 딜러(블랙잭)") { + val player = player(hand = blackJackCards) + val dealer = Dealer(player = DealerPlayer(blackJackCards)) + + it("PUSH") { + player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH + } + } + + context("플레이어(21) == 딜러(블랙잭)") { + val player = player(hand = score21NotBlackJackCards) + val dealer = Dealer(player = DealerPlayer(blackJackCards)) + + it("PUSH") { + player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH + } + } + } }) diff --git a/src/test/kotlin/blackjack/domain/result/game/VictoryStatusTest.kt b/src/test/kotlin/blackjack/domain/result/distribution/VictoryStatusTest.kt similarity index 89% rename from src/test/kotlin/blackjack/domain/result/game/VictoryStatusTest.kt rename to src/test/kotlin/blackjack/domain/result/distribution/VictoryStatusTest.kt index f469a4778..de72aca58 100644 --- a/src/test/kotlin/blackjack/domain/result/game/VictoryStatusTest.kt +++ b/src/test/kotlin/blackjack/domain/result/distribution/VictoryStatusTest.kt @@ -1,5 +1,6 @@ -package blackjack.domain.result.game +package blackjack.domain.result.distribution +import blackjack.domain.result.game.VictoryStatus import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe diff --git a/src/test/kotlin/blackjack/domain/result/game/GameResultTest.kt b/src/test/kotlin/blackjack/domain/result/game/GameResultTest.kt deleted file mode 100644 index de5fe1c56..000000000 --- a/src/test/kotlin/blackjack/domain/result/game/GameResultTest.kt +++ /dev/null @@ -1,142 +0,0 @@ -package blackjack.domain.result.game - -import blackjack.domain.Dealer -import blackjack.domain.card.Rank -import blackjack.domain.player.DealerPlayer -import blackjack.domain.player.Players -import blackjack.mock.card -import blackjack.mock.hand -import blackjack.mock.player -import io.kotest.core.spec.style.DescribeSpec -import io.kotest.matchers.shouldBe - -class GameResultTest : DescribeSpec({ - describe("GameResult of (dealer, players)") { - val score25Cards = - hand(card(Rank.TEN), card(Rank.TEN), card(Rank.FIVE)) - val score22Cards = - hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TWO)) - val score21Cards = - hand(card(Rank.TEN), card(Rank.ACE)) - val score10Cards = hand(card(Rank.TEN)) - val score5Cards = hand(card(Rank.FIVE)) - - context("dealer(21) > player1(10) > player2(5)") { - val dealer = Dealer(player = DealerPlayer(score21Cards)) - val player1 = player(hand = score10Cards) - val player2 = player(hand = score5Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : LOSS") { - result.playersResult.first().status shouldBe VictoryStatus.LOSS - } - it("player2 : LOSS") { - result.playersResult.last().status shouldBe VictoryStatus.LOSS - } - it("dealer : (WIN, WIN)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.WIN, VictoryStatus.WIN) - } - } - - context("player1(21) > player2(10) > dealer(5)") { - val dealer = Dealer(player = DealerPlayer(score5Cards)) - val player1 = player(hand = score21Cards) - val player2 = player(hand = score10Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : WIN") { - result.playersResult.first().status shouldBe VictoryStatus.WIN - } - - it("player2 : WIN") { - result.playersResult.last().status shouldBe VictoryStatus.WIN - } - - it("dealer : (LOSS, LOSS)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.LOSS, VictoryStatus.LOSS) - } - } - - context("player1(21) > dealer(10) > player2(5)") { - val dealer = Dealer(player = DealerPlayer(score10Cards)) - val player1 = player(hand = score21Cards) - val player2 = player(hand = score5Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : WIN") { - result.playersResult.first().status shouldBe VictoryStatus.WIN - } - - it("player2 : LOSS") { - result.playersResult.last().status shouldBe VictoryStatus.LOSS - } - - it("dealer : (LOSS, WIN)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.LOSS, VictoryStatus.WIN) - } - } - - context("dealer(5) > player1(22, BUST) == player2(22, BUST)") { - val dealer = Dealer(player = DealerPlayer(score5Cards)) - val player1 = player(hand = score22Cards) - val player2 = player(hand = score22Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : LOSS") { - result.playersResult.first().status shouldBe VictoryStatus.LOSS - } - - it("player2 : LOSS") { - result.playersResult.last().status shouldBe VictoryStatus.LOSS - } - - it("dealer : (WIN, WIN)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.WIN, VictoryStatus.WIN) - } - } - - context("player1(5) == player2(5) > dealer(22, BUST)") { - val dealer = Dealer(player = DealerPlayer(score22Cards)) - val player1 = player(hand = score5Cards) - val player2 = player(hand = score5Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : WIN") { - result.playersResult.first().status shouldBe VictoryStatus.WIN - } - - it("player2 : WIN") { - result.playersResult.last().status shouldBe VictoryStatus.WIN - } - - it("dealer : (LOSS, LOSS)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.LOSS, VictoryStatus.LOSS) - } - } - - context("player1(22, BUST) == player2(25, BUST) == dealer(22, BUST)") { - val dealer = Dealer(player = DealerPlayer(score22Cards)) - val player1 = player(hand = score22Cards) - val player2 = player(hand = score25Cards) - - val result = GameResult.of(Players(listOf(player1, player2)), dealer) - - it("player1 : LOSS") { - result.playersResult.first().status shouldBe VictoryStatus.LOSS - } - - it("player2 : LOSS") { - result.playersResult.last().status shouldBe VictoryStatus.LOSS - } - - it("dealer : (WIN, WIN)") { - result.dealerResults.status.value shouldBe listOf(VictoryStatus.WIN, VictoryStatus.WIN) - } - } - } -}) diff --git a/src/test/kotlin/blackjack/domain/result/game/ProfitTest.kt b/src/test/kotlin/blackjack/domain/result/game/ProfitTest.kt new file mode 100644 index 000000000..af67f3281 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/result/game/ProfitTest.kt @@ -0,0 +1,22 @@ +package blackjack.domain.result.game + +import blackjack.mock.profit +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class ProfitTest : StringSpec({ + "profit의 반대 (-profit) 금액을 구할 수 있다" { + val profit = profit(1_000) + + profit.negative shouldBe profit(-1_000) + } + + "profit끼리 덧셈이 가능하다" { + val profit1 = profit(1_000) + val profit2 = profit(1_000) + + val result = profit1 + profit2 + + result shouldBe profit(2_000) + } +}) diff --git a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt index 564fc325d..b56415c7a 100644 --- a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt +++ b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt @@ -9,7 +9,7 @@ import blackjack.domain.player.PlayerNames class InputProcessorMock( private val playerNames: List = listOf("kim", "lee"), private val action: Action = Action.HIT, - private val betAmount: Amount = Amount(3_000) + private val betAmount: Amount = amount(3_000) ) : InputProcessor { override fun playerNames(): PlayerNames = playerNames.let(PlayerNames::from) diff --git a/src/test/kotlin/blackjack/mock/TestFactories.kt b/src/test/kotlin/blackjack/mock/TestFactories.kt index 439caf705..2277775b9 100644 --- a/src/test/kotlin/blackjack/mock/TestFactories.kt +++ b/src/test/kotlin/blackjack/mock/TestFactories.kt @@ -3,6 +3,7 @@ package blackjack.mock import blackjack.domain.Action import blackjack.domain.Dealer import blackjack.domain.GameTable +import blackjack.domain.batting.Amount import blackjack.domain.card.Card import blackjack.domain.card.Deck import blackjack.domain.card.Hand @@ -11,8 +12,9 @@ import blackjack.domain.card.Suit import blackjack.domain.player.Player import blackjack.domain.player.PlayerName import blackjack.domain.player.Players +import blackjack.domain.result.game.Profit -fun card(rank: Rank, suit: Suit = Suit.CLUB): Card = Card(suit, rank) +fun card(rank: Rank = Rank.TEN, suit: Suit = Suit.CLUB): Card = Card(suit, rank) fun hand(vararg cards: Card): Hand = Hand(cards.toMutableList()) @@ -40,3 +42,6 @@ fun table( players ?: players(player("kim", inputAction), player("lee", inputAction)), dealer, ) + +fun amount(amount: Int): Amount = amount.toBigDecimal().let(::Amount) +fun profit(profit: Int): Profit = profit.toBigDecimal().let(::Profit) From 3d30c05b7b8e167d88da071a03150f71ae199fa2 Mon Sep 17 00:00:00 2001 From: bongbong Date: Mon, 4 Dec 2023 00:37:05 +0900 Subject: [PATCH 07/19] =?UTF-8?q?[step3]=20refactor:=20View=EB=A5=BC=20?= =?UTF-8?q?=EB=B3=B4=EC=97=AC=EC=A4=84=EC=A7=80=20=EB=A7=90=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20ViewResultProcessor=EC=97=90=EC=84=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/controller/ResultProcessor.kt | 5 +---- src/main/kotlin/blackjack/controller/ViewResultProcessor.kt | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/blackjack/controller/ResultProcessor.kt b/src/main/kotlin/blackjack/controller/ResultProcessor.kt index f663aa917..584843905 100644 --- a/src/main/kotlin/blackjack/controller/ResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ResultProcessor.kt @@ -11,10 +11,7 @@ class ResultProcessor { fun handle(result: Result) { when (result) { is DealInitialCardResult -> ViewResultProcessor.drawInitialDistribution(result) - is DealToPlayerResult -> { - if (result.isSystemStand) return - ViewResultProcessor.drawPlayerState(result) - } + is DealToPlayerResult -> ViewResultProcessor.drawPlayerState(result) is DealEndResult -> {} is DealToDealerResult -> ViewResultProcessor.drawDealerState(result) is GameEndResult -> ViewResultProcessor.drawGameResult(result) diff --git a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt index d4db5c0b4..b32c49391 100644 --- a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt @@ -20,6 +20,7 @@ object ViewResultProcessor { } fun drawPlayerState(result: DealToPlayerResult) { + if (result.isSystemStand) return val dto = result.player.let { PlayerDto(it.name.value, it.hand.cards) } OutputView.drawPlayerCurrentState(dto) } From 5181cef999aba560eeb8c9870fd4bc95a56de311 Mon Sep 17 00:00:00 2001 From: bongbong Date: Mon, 4 Dec 2023 00:41:12 +0900 Subject: [PATCH 08/19] =?UTF-8?q?[step3]=20refactor:=20requireNotNul()?= =?UTF-8?q?=EC=9D=84=20=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/domain/distirbution/CardDistributor.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/distirbution/CardDistributor.kt b/src/main/kotlin/blackjack/domain/distirbution/CardDistributor.kt index 0c6c9fca4..3469f0f48 100644 --- a/src/main/kotlin/blackjack/domain/distirbution/CardDistributor.kt +++ b/src/main/kotlin/blackjack/domain/distirbution/CardDistributor.kt @@ -7,12 +7,12 @@ abstract class CardDistributor { protected var _nextDistributor: CardDistributor? = null val nextDistributor: CardDistributor get() { - require(_nextDistributor != null) { "아직 배분이 진행 되지 않았습니다" } + requireNotNull(_nextDistributor) { "아직 배분이 진행 되지 않았습니다" } return _nextDistributor as CardDistributor } open fun nextDistributor(): CardDistributor { - require(_nextDistributor != null) { "아직 배분이 진행 되지 않았습니다" } + requireNotNull(_nextDistributor) { "아직 배분이 진행 되지 않았습니다" } return _nextDistributor as CardDistributor } From fc1d9ae193427f32638ae8890ff491ced2295f3d Mon Sep 17 00:00:00 2001 From: bongbong Date: Mon, 4 Dec 2023 00:44:12 +0900 Subject: [PATCH 09/19] =?UTF-8?q?[step3]=20refactor:=20step3=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/domain/distirbution/DealToPlayer.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt b/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt index 8d7b89ebb..7486a07a8 100644 --- a/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt +++ b/src/main/kotlin/blackjack/domain/distirbution/DealToPlayer.kt @@ -29,10 +29,9 @@ class DealToPlayer( } private fun endPlayerDistributionIfLastTurn() { - _nextDistributor = when (table.isLastPlayerTurn) { - true -> DealToDealer(table) - false -> DealToPlayer(table) - } + _nextDistributor = + if (table.isLastPlayerTurn) DealToDealer(table) + else DealToPlayer(table) } companion object { From a4db3dbad4a606221090435646c120a42f127e2e Mon Sep 17 00:00:00 2001 From: bongbong Date: Wed, 6 Dec 2023 22:25:06 +0900 Subject: [PATCH 10/19] =?UTF-8?q?[step4]=20chore:=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=95=9E=EB=8B=A8=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/domain/batting/Amount.kt | 4 ++-- src/main/kotlin/blackjack/domain/batting/BetBoard.kt | 2 +- src/main/kotlin/blackjack/domain/player/PlayerNames.kt | 2 +- src/main/kotlin/blackjack/domain/player/Players.kt | 2 +- src/main/kotlin/blackjack/domain/result/game/Profit.kt | 4 ++-- src/test/kotlin/blackjack/mock/TestFactories.kt | 8 ++++---- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/batting/Amount.kt b/src/main/kotlin/blackjack/domain/batting/Amount.kt index 43d5bebdc..d814a1279 100644 --- a/src/main/kotlin/blackjack/domain/batting/Amount.kt +++ b/src/main/kotlin/blackjack/domain/batting/Amount.kt @@ -6,8 +6,8 @@ data class Amount( val value: BigDecimal, ) : Comparable { - operator fun plus(other: Amount): Amount = value.plus(other.value).let(::Amount) - operator fun times(count: Int): Amount = value.times(count.toBigDecimal()).let(::Amount) + operator fun plus(other: Amount): Amount = Amount(value.plus(other.value)) + operator fun times(count: Int): Amount = Amount(value.times(count.toBigDecimal())) operator fun times(count: BigDecimal): Amount = Amount(value * count) diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt index 7149298be..cce279fb9 100644 --- a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -61,7 +61,7 @@ data class BetBoard( companion object { private val BLACK_JACK_MULTIPLIER = BigDecimal(1.5) fun of(players: Players, betAmount: (player: Player) -> Amount): BetBoard = - players.value.associate { it.name to it.placeBet(betAmount) }.toMutableMap().let(::BetBoard) + BetBoard(players.value.associate { it.name to it.placeBet(betAmount) }.toMutableMap()) private fun Player.placeBet(betAmount: (player: Player) -> Amount): PlayerBet = PlayerBet.Placed(this.name, betAmount(this)) diff --git a/src/main/kotlin/blackjack/domain/player/PlayerNames.kt b/src/main/kotlin/blackjack/domain/player/PlayerNames.kt index bbe78c8c0..1036bc33c 100644 --- a/src/main/kotlin/blackjack/domain/player/PlayerNames.kt +++ b/src/main/kotlin/blackjack/domain/player/PlayerNames.kt @@ -10,6 +10,6 @@ value class PlayerNames( companion object { fun from(names: List): PlayerNames = - names.map(::PlayerName).let(::PlayerNames) + PlayerNames(names.map(::PlayerName)) } } diff --git a/src/main/kotlin/blackjack/domain/player/Players.kt b/src/main/kotlin/blackjack/domain/player/Players.kt index 32683b734..fca124970 100644 --- a/src/main/kotlin/blackjack/domain/player/Players.kt +++ b/src/main/kotlin/blackjack/domain/player/Players.kt @@ -30,7 +30,7 @@ data class Players( fun of( names: PlayerNames, desiredAction: (player: Player) -> Action, - ) = names.value.map { name -> createPlayer(name, desiredAction) }.let(::Players) + ) = Players(names.value.map { name -> createPlayer(name, desiredAction) }) private fun createPlayer(name: PlayerName, desiredAction: (player: Player) -> Action) = Player(name, desiredAction) diff --git a/src/main/kotlin/blackjack/domain/result/game/Profit.kt b/src/main/kotlin/blackjack/domain/result/game/Profit.kt index 04f9ca2a4..7fc59d1d2 100644 --- a/src/main/kotlin/blackjack/domain/result/game/Profit.kt +++ b/src/main/kotlin/blackjack/domain/result/game/Profit.kt @@ -10,10 +10,10 @@ data class Profit( val negative: Profit get() = Profit(value.negate()) - operator fun plus(other: Profit): Profit = value.plus(other.value).let(::Profit) + operator fun plus(other: Profit): Profit = Profit(value.plus(other.value)) companion object { fun of(betAmount: Amount, payoutAmount: Amount): Profit = - payoutAmount.value.minus(betAmount.value).let(::Profit) + Profit(payoutAmount.value.minus(betAmount.value)) } } diff --git a/src/test/kotlin/blackjack/mock/TestFactories.kt b/src/test/kotlin/blackjack/mock/TestFactories.kt index 2277775b9..d5ce58a01 100644 --- a/src/test/kotlin/blackjack/mock/TestFactories.kt +++ b/src/test/kotlin/blackjack/mock/TestFactories.kt @@ -18,9 +18,9 @@ fun card(rank: Rank = Rank.TEN, suit: Suit = Suit.CLUB): Card = Card(suit, rank) fun hand(vararg cards: Card): Hand = Hand(cards.toMutableList()) -fun deck(vararg cards: Card): Deck = Deck(cards.toMutableList().let(::ArrayDeque)) +fun deck(vararg cards: Card): Deck = Deck(ArrayDeque(cards.toMutableList())) -fun deck(cards: List): Deck = Deck(cards.let(::ArrayDeque)) +fun deck(cards: List): Deck = Deck(ArrayDeque(cards)) fun player( name: String = "kim", @@ -43,5 +43,5 @@ fun table( dealer, ) -fun amount(amount: Int): Amount = amount.toBigDecimal().let(::Amount) -fun profit(profit: Int): Profit = profit.toBigDecimal().let(::Profit) +fun amount(amount: Int): Amount = Amount(amount.toBigDecimal()) +fun profit(profit: Int): Profit = Profit(profit.toBigDecimal()) From 780067a7304b8142441777623a4431bec3f02efd Mon Sep 17 00:00:00 2001 From: bongbong Date: Wed, 6 Dec 2023 22:27:34 +0900 Subject: [PATCH 11/19] =?UTF-8?q?[step4]=20feat:=20PlayerGameResult=20?= =?UTF-8?q?=EC=9D=98=20player=20=EC=A0=91=EA=B7=BC=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=EC=9E=90=20->=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/domain/result/game/PlayerGameResult.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt b/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt index a9c9727a3..9ed86b4a7 100644 --- a/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt +++ b/src/main/kotlin/blackjack/domain/result/game/PlayerGameResult.kt @@ -7,7 +7,7 @@ import blackjack.domain.player.Player import blackjack.domain.player.PlayerName data class PlayerGameResult( - val player: Player, + private val player: Player, val profit: Profit, ) { From d95162a84dd4794772d375b9eccc43f6aff02fc6 Mon Sep 17 00:00:00 2001 From: bongbong Date: Wed, 6 Dec 2023 23:15:34 +0900 Subject: [PATCH 12/19] =?UTF-8?q?[step4]=20test:=20DCI=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=EC=9D=84=20=EA=B3=A0=EB=A0=A4=ED=95=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blackjack/domain/BlackJackGameTest.kt | 6 +- .../kotlin/blackjack/domain/DealerTest.kt | 28 +++---- .../kotlin/blackjack/domain/GameTableTest.kt | 44 +++++------ .../blackjack/domain/betting/BetBoardTest.kt | 44 +++++------ .../kotlin/blackjack/domain/card/CardTest.kt | 14 ++-- .../kotlin/blackjack/domain/card/DeckTest.kt | 14 ++-- .../blackjack/domain/card/HandScoreTest.kt | 2 +- .../domain/distirbution/DealEndTest.kt | 10 +-- .../distirbution/DealInitialCardsTest.kt | 8 +- .../domain/distirbution/DealToDealerTest.kt | 8 +- .../domain/distirbution/DealToPlayerTest.kt | 79 ++++++------------- .../domain/player/DealerPlayerTest.kt | 5 +- .../domain/player/PlayerNamesTest.kt | 6 +- .../blackjack/domain/player/PlayerTest.kt | 77 +++++++++--------- .../blackjack/domain/player/PlayersTest.kt | 18 ++--- 15 files changed, 167 insertions(+), 196 deletions(-) diff --git a/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt index 28857df7c..1f4887157 100644 --- a/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt +++ b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt @@ -7,8 +7,8 @@ import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe class BlackJackGameTest : DescribeSpec({ - describe("게임 생성") { - context("게임에 참여할 2명의 이름 전달") { + describe("BlackJackGame()") { + context("게임에 참여할 2명의 이름 전달하면") { val name1 = "Hong" val name2 = "Kim" val game = BlackJackGame( @@ -18,7 +18,7 @@ class BlackJackGameTest : DescribeSpec({ resultProcessor = ResultProcessor(), ) - it("전달된 이름으로 플레이어 세팅") { + it("전달된 이름으로 플레이어가 세팅된다") { val playerNames = game.dealCards.table.players.value.map { it.name } playerNames[0] shouldBe PlayerName(name1) diff --git a/src/test/kotlin/blackjack/domain/DealerTest.kt b/src/test/kotlin/blackjack/domain/DealerTest.kt index 3cbb27ff0..a55d3cc6e 100644 --- a/src/test/kotlin/blackjack/domain/DealerTest.kt +++ b/src/test/kotlin/blackjack/domain/DealerTest.kt @@ -15,7 +15,7 @@ import io.kotest.matchers.shouldBe class DealerTest : DescribeSpec({ describe("dealCards") { val count = 2 - context("플레이어 1명에게 ${count}장 카드 배분") { + context("플레이어 1명에게 ${count}장 카드를 배분하면") { val cards = listOf( Card(Suit.CLUB, Rank.ACE), Card(Suit.CLUB, Rank.TWO), @@ -27,16 +27,16 @@ class DealerTest : DescribeSpec({ dealer.dealCards(count, player) - it("플레이어에게 카드 전달") { + it("플레이어에게 카드가 전달된다") { player.hand.cards shouldBe listOf(cards[3], cards[2]) } - it("덱에서 카드에서 제거") { + it("덱에서 카드가 제거된다") { dealer.deck.cards shouldBe listOf(cards[0], cards[1]) } } - context("플레이어 2명에게 ${count}장씩 카드 배분") { + context("플레이어 2명에게 ${count}장씩 카드 배분하면") { val deckCards = deck( Card(Suit.CLUB, Rank.ACE), Card(Suit.CLUB, Rank.TWO), @@ -53,12 +53,12 @@ class DealerTest : DescribeSpec({ dealer.dealCards(count, *players.toTypedArray()) - it("플레이어에게 카드 전달") { + it("플레이어에게 해당 카드가 전달된다") { players[0].hand.cards.size shouldBe 2 players[1].hand.cards.size shouldBe 2 } - it("덱에서 카드에서 제거") { + it("덱에서 카드는 제거된다") { dealer.deck.cards.size shouldBe 2 } } @@ -74,14 +74,14 @@ class DealerTest : DescribeSpec({ val dealer = Dealer(deck(cards)) val count = 2 - context("자신에게 ${count}장 카드 배분") { + context("딜러가 자신에게 ${count}장 카드를 배분하면") { dealer dealToSelf count - it("플레이어에게 카드 전달") { + it("플레이어에게 해당 카드기 전달된다") { dealer.hand.cards shouldBe listOf(cards[3], cards[2]) } - it("덱에서 카드 제거") { + it("덱에서 카드가 제거된다") { dealer.deck.cards shouldBe listOf(cards[0], cards[1]) } } @@ -91,10 +91,10 @@ class DealerTest : DescribeSpec({ val dealer = Dealer( player = DealerPlayer(hand(card(Rank.ACE), card(Rank.QUEEN))) ) - context("딜러가 가진 카드의 점수 조회") { + context("딜러가 가진 카드의 점수를 조회하면") { val result = dealer.score - it("계산된 점수 반환") { + it("계산된 점수가 반환된다") { result.cardScore shouldBe 21 } } @@ -106,21 +106,21 @@ class DealerTest : DescribeSpec({ context("딜러보다 낮은 점수로 비교하면") { val result = dealer isGreaterCardScoreThan 16 - it("참을 반환") { + it("참을 반환한다") { result shouldBe true } } context("딜러보다 높은 점수로 비교하면") { val result = dealer isGreaterCardScoreThan 21 - it("거짓을 반환") { + it("거짓을 반환한다") { result shouldBe false } } context("딜러와 같은 점수로 비교하면") { val result = dealer isGreaterCardScoreThan 20 - it("거짓을 반환") { + it("거짓을 반환한다") { result shouldBe false } } diff --git a/src/test/kotlin/blackjack/domain/GameTableTest.kt b/src/test/kotlin/blackjack/domain/GameTableTest.kt index c9cf32f68..47edd9655 100644 --- a/src/test/kotlin/blackjack/domain/GameTableTest.kt +++ b/src/test/kotlin/blackjack/domain/GameTableTest.kt @@ -16,14 +16,14 @@ import io.kotest.matchers.shouldBe class GameTableTest : DescribeSpec({ describe("dealToAll") { - context("카드가 모두 한 장도 없는 상태일 때 모두에게 2장씩 지급") { + context("카드가 모두 한 장도 없는 상태일 때 모두에게 2장씩 지급하면") { val dealer = Dealer() val players = players(player(hand = Hand()), player(hand = Hand())) val table = GameTable(players, dealer) table.dealToAll(2) - it("모두 두장씩 수령") { + it("모두 두장씩 수령한다") { table.dealer.hand.cards.size shouldBe 2 table.players.value.forEach { player -> player.hand.cards.size shouldBe 2 @@ -38,17 +38,17 @@ class GameTableTest : DescribeSpec({ val players = players(player(hand = Hand()), player()) val table = GameTable(players, dealer) - context("카드 1장 배분") { + context("현재 플레이어에게 카드 1장을 배분하면") { val playerInTurn = players.inTurn playerInTurn.hand.cards.size shouldBe 0 table.dealToPlayerInTurn(1) - it("차례인 플레이어는 카드 1장 수령") { + it("차례인 플레이어는 카드 1장을 수령한다") { playerInTurn.hand.cards.count() shouldBe 1 } - it("덱에서는 카드 1장 제거") { + it("덱에서는 카드 1장이 제거된다") { dealer.deck.cards.size shouldBe deckCount - 1 } } @@ -63,7 +63,7 @@ class GameTableTest : DescribeSpec({ table.passPlayerTurnIfNotLastTurn() - it("플레이어 2에게 차례가 넘어감") { + it("플레이어 2에게 차례가 넘어간다") { players.inTurn shouldBe players.value.last() } } @@ -89,16 +89,16 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players(), dealer) val count = 2 - context("딜러 카드가 한 장도 없을 때 카드 ${count}장 배분") { + context("딜러 카드가 한 장도 없을 때 카드 ${count}장씩 배분하면") { dealer.hand.cards.size shouldBe 0 table.dealToDealer(2) - it("딜러는 ${count}장 카드 수령") { + it("딜러는 ${count}장의 카드를 수령한다") { dealer.hand.cards.size shouldBe count } - it("덱에서는 카드 ${count}장 제거") { + it("덱에서는 카드 ${count}장이 제거된다") { dealer.deck.cards.size shouldBe deckCount - count } } @@ -107,18 +107,18 @@ class GameTableTest : DescribeSpec({ describe("isLastPlayerTurn") { val players = listOf(player("kim"), player("lee")) val table = GameTable(Players(players)) - context("첫 번째 플레이어 턴") { + context("첫 번째 플레이어 턴이라면") { table.players.inTurn shouldBe players.first() - it("false 반환") { + it("false가 반환된다") { table.isLastPlayerTurn shouldBe false } } - context("마지막 플레이어 턴") { + context("마지막 플레이어 턴이라면") { table.passPlayerTurnIfNotLastTurn() table.players.inTurn shouldBe players.last() - it("true 반환") { + it("true가 반환된다") { table.isLastPlayerTurn shouldBe true } } @@ -127,18 +127,18 @@ class GameTableTest : DescribeSpec({ describe("playerInTurn") { val players = listOf(player("kim"), player("lee")) val table = GameTable(Players(players)) - context("첫 번쨰 플레이어 턴") { + context("첫 번쨰 플레이어 턴이라면") { table.players.inTurn shouldBe players.first() - it("첫 번째 플레이어 반환") { + it("첫 번째 플레이어 반환된다") { table.playerInTurn shouldBe players.first() } } - context("마지막 플레이어 턴") { + context("마지막 플레이어 턴이라면") { table.passPlayerTurnIfNotLastTurn() table.players.inTurn shouldBe players.last() - it("두 번째 플레이어 반환") { + it("두 번째 플레이어 반환된다") { table.playerInTurn shouldBe players.last() } } @@ -151,7 +151,7 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players) table.playerInTurn shouldBe hitPlayer - it("HIT 반환") { + it("HIT가 반환된다") { val result = table.playerInTurnAction result shouldBe Action.HIT @@ -164,7 +164,7 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players) table.playerInTurn shouldBe standPlayer - it("STAND 반환") { + it("STAND가 반환된다") { val result = table.playerInTurnAction result shouldBe Action.STAND @@ -177,7 +177,7 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players) table.playerInTurn shouldBe standPlayer - it("STAND 반환") { + it("STAND가 반환된다") { val result = table.playerInTurnAction result shouldBe Action.STAND @@ -191,7 +191,7 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players(), dealer) dealer.hitOrStand() shouldBe Action.HIT - it("HIT 반환") { + it("HIT가 반환된다") { val result = table.dealerAction result shouldBe Action.HIT @@ -203,7 +203,7 @@ class GameTableTest : DescribeSpec({ val table = GameTable(players(), dealer) dealer.hitOrStand() shouldBe Action.STAND - it("STAND 반환") { + it("STAND가 반환된다") { val result = table.dealerAction result shouldBe Action.STAND diff --git a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt index 4ab0a8d69..d6bd412fa 100644 --- a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt @@ -22,7 +22,7 @@ import java.math.BigDecimal class BetBoardTest : DescribeSpec({ describe("BettingBoard") { - context("플레이어 이름으로 베팅 금액 등록 (kim: 3_000원, lee: 4_000원)") { + context("플레이어 이름으로 베팅 금액 (kim: 3_000원, lee: 4_000원)이 등록되면") { val players = players(player("kim"), player("lee")) val kimAmount = amount(3_000) val leeAmount = amount(4_000) @@ -33,7 +33,7 @@ class BetBoardTest : DescribeSpec({ val betBoard: BetBoard = BetBoard.of(players, betAmount) - it("플레이어 kim 의 베팅 금액은 3_000") { + it("플레이어 kim 의 베팅 금액은 3_000이 된다") { val name = PlayerName("kim") val playerBet = betBoard.playerBet(name) @@ -41,7 +41,7 @@ class BetBoardTest : DescribeSpec({ (playerBet as? PlayerBet.Placed)?.betAmount shouldBe kimAmount } - it("플레이어 lee 의 베팅 금액은 4_000") { + it("플레이어 lee 의 베팅 금액은 4_000이 된다") { val name = PlayerName("lee") val playerBet = betBoard.playerBet(name) @@ -52,7 +52,7 @@ class BetBoardTest : DescribeSpec({ } describe("playerProfit") { - context("베팅이 완료된 플레이어 (베팅: 1000, 받은돈: 0)") { + context("플레이어가 (베팅: 1000, 받은돈: 0) 으로 베팅을 완료했을 때") { val name = PlayerName("kim") val betAmount = amount(1000) val payoutAmount = amount(0) @@ -66,12 +66,12 @@ class BetBoardTest : DescribeSpec({ val result = betBord.playerProfit(name) - it("베팅 수익 조회(-1000)") { + it("베팅 수익은 -1000이 조회된다") { result.value shouldBe BigDecimal(-1000) } } - context("베팅이 완료되지 않은 플레이어") { + context("베팅이 완료되지 않은 플레이어의 수익을 조회하면") { val name = PlayerName("kim") val betAmount = amount(1000) val placedBet = PlayerBet.Placed(name, betAmount) @@ -82,7 +82,7 @@ class BetBoardTest : DescribeSpec({ ) ) - it("수익 조회 실패") { + it("수익 조회에 실패한다") { shouldThrowExactly { betBord.playerProfit(name) } @@ -90,7 +90,7 @@ class BetBoardTest : DescribeSpec({ } describe("dealerProfit") { - context("플레이어1의 수익: 2000, 플레이어2의 수익: -1000") { + context("플레이어1의 수익이 2000, 플레이어2의 수익이 -1000일 때") { val betBoard = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Finished(PlayerName("kim"), amount(2_000), amount(4_000)), @@ -101,7 +101,7 @@ class BetBoardTest : DescribeSpec({ betBoard.playerProfit(PlayerName("kim")) shouldBe profit(2_000) betBoard.playerProfit(PlayerName("lee")) shouldBe profit(-1000) - it("딜러의 수익 = -(두 플레이어 수익의 합) (-1000)") { + it("딜러의 수익은 두 플레이어 수익의 합인 (-1000)이 된다") { betBoard.dealerProfit() shouldBe profit(-1000) } } @@ -112,7 +112,7 @@ class BetBoardTest : DescribeSpec({ PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) ) ) - it("수익 조회 실패") { + it("수익 조회에 실패한다") { shouldThrowExactly { betBord.dealerProfit() } @@ -121,7 +121,7 @@ class BetBoardTest : DescribeSpec({ } describe("closeBetting") { - context("딜러를 블랙잭으로 이긴 플레이어") { + context("딜러를 블랙잭으로 이긴 플레이어라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -132,7 +132,7 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.ACE), card(Rank.TEN))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN)))) - it("베팅 종료시 수익이 1.5배") { + it("베팅 종료시 수익이 베팅 금액의 1.5배가 된다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) val expect = (5_000 * 1.5).toBigDecimal() @@ -140,7 +140,7 @@ class BetBoardTest : DescribeSpec({ } } - context("딜러를 일반 숫자로 이긴 플레이어") { + context("딜러를 일반 숫자로 이긴 플레이어라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -151,7 +151,7 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN)))) - it("베팅 종료시 수익이 1배") { + it("베팅 종료시 수익이 1배가 된다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) val expect = (5_000 * 1).toBigDecimal() @@ -159,7 +159,7 @@ class BetBoardTest : DescribeSpec({ } } - context("딜러와 둘다 블랙잭으로 무승부인 플레이어") { + context("딜러와 플레이어 둘 다 블랙잭으로 무승부라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -170,14 +170,14 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.ACE))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.ACE)))) - it("베팅 종료시 수익은 0") { + it("베팅 종료시 수익은 0이다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(0)) } } - context("딜러와 둘다 일반 점수로 무승부인 플레이어") { + context("딜러와 둘다 일반 점수로 무승부라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -188,14 +188,14 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.TWO), card(Rank.ACE))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TWO), card(Rank.ACE)))) - it("베팅 종료시 수익은 0") { + it("베팅 종료시 수익은 0이다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(0)) } } - context("버스트인 플레이어") { + context("플레이어의 카드가 버스트라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -206,14 +206,14 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TWO), card(Rank.ACE)))) - it("베팅 종료시 수익은 -베팅금액") { + it("베팅 종료시 수익은 -베팅금액 이다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(-5000)) } } - context("버스트인 플레이어와 딜러도 버스트") { + context("플레이어도 딜러도 모두 버스트라면") { val betBord = BetBoard( mutableMapOf( PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), @@ -224,7 +224,7 @@ class BetBoardTest : DescribeSpec({ val player = player("kim", hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN))) val dealer = Dealer(player = DealerPlayer(hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TEN)))) - it("베팅 종료시 수익은 -베팅금액") { + it("베팅 종료시 수익은 -베팅금액 이다") { betBord.closeBetting(DealEndResult(players(player, player("lee")), dealer)) betBord.playerProfit(PlayerName("kim")) shouldBe Profit(BigDecimal(-5000)) diff --git a/src/test/kotlin/blackjack/domain/card/CardTest.kt b/src/test/kotlin/blackjack/domain/card/CardTest.kt index 025f9ab88..53537279c 100644 --- a/src/test/kotlin/blackjack/domain/card/CardTest.kt +++ b/src/test/kotlin/blackjack/domain/card/CardTest.kt @@ -5,33 +5,33 @@ import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.shouldBe class CardTest : DescribeSpec({ - describe("카드 생성") { - context("카드 전체 요청") { + describe("카드 생성(allShuffled)") { + context("카드 전체를 요청하면") { val result = Card.allShuffled() - it("카드 전체는 52장") { + it("카드 전체는 52장이다") { result.cards.size shouldBe 52 } - it("스페이드 카드는 모든 랭크에 대한 13장") { + it("클럽 카드는 모든 랭크마다 있으며 총 13장이다") { val spadeCards = result.cards.filter { it.suit == Suit.CLUB } spadeCards.size shouldBe 13 spadeCards.map { it.rank } shouldContainAll Rank.entries } - it("다이아몬드 카드 13장") { + it("다이아몬드 카드는 모든 랭크마다 있으며 총 13장이다") { val diamondCards = result.cards.filter { it.suit == Suit.DIAMOND } diamondCards.size shouldBe 13 diamondCards.map { it.rank } shouldContainAll Rank.entries } - it("하트 카드 13장") { + it("하트 카드는 모든 랭크마다 있으며 총 13장이다") { val heartCard = result.cards.filter { it.suit == Suit.HEART } heartCard.size shouldBe 13 heartCard.map { it.rank } shouldContainAll Rank.entries } - it("스페이드 카드 13장") { + it("스페이드 카드는 모든 랭크마다 있으며 총 13장이다") { val spadeCard = result.cards.filter { it.suit == Suit.SPADE } spadeCard.size shouldBe 13 spadeCard.map { it.rank } shouldContainAll Rank.entries diff --git a/src/test/kotlin/blackjack/domain/card/DeckTest.kt b/src/test/kotlin/blackjack/domain/card/DeckTest.kt index 7264d4011..caef3991e 100644 --- a/src/test/kotlin/blackjack/domain/card/DeckTest.kt +++ b/src/test/kotlin/blackjack/domain/card/DeckTest.kt @@ -6,29 +6,29 @@ import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe class DeckTest : DescribeSpec({ - describe("카드 리스트로 덱 생성") { + describe("Deck.from()") { context("카드 리스트로") { val cards = listOf(Card(Suit.CLUB, Rank.SEVEN), Card(Suit.DIAMOND, Rank.FOUR)) - it("덱 생성") { + it("덱을 생성한다") { val result = Deck.from(cards) result.cards shouldBe cards } } } - describe("덱에서 카드 제거") { - context("덱에서 카드를 한 장 빼면") { + describe("draw()") { + context("덱에서 카드를 한 장을 빼면") { val cards = listOf(Card(Suit.HEART, Rank.FOUR), Card(Suit.CLUB, Rank.SEVEN)) val deck = deck(cards) val result = deck.draw() - it("맨 뒤의 카드 가져옴") { + it("맨 뒤의 카드 가져온다") { result shouldBe Card(Suit.CLUB, Rank.SEVEN) } - it("덱에서는 해당 카드 제거") { + it("덱에서는 해당 카드 제거한다") { deck.cards shouldBe listOf(Card(Suit.HEART, Rank.FOUR)) } } @@ -36,7 +36,7 @@ class DeckTest : DescribeSpec({ context("덱에 카드가 없으면") { val deck = Deck(ArrayDeque()) - it("카드 가져오기 실패") { + it("카드 한장 빼기에 실패한다") { shouldThrowExactly { deck.draw() } diff --git a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt index 5ce681b74..a81878998 100644 --- a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt @@ -31,7 +31,7 @@ class HandScoreTest : DescribeSpec({ context("Ace가 없는 카드에서 합이 11이하라면") { val hand = hand(card(Rank.TWO), card(Rank.THREE)) - it("모든 점수를 그대로 계산") { + it("모든 점수를 그대로 계산한다") { HandScore.from(hand).cardScore shouldBe 5 } } diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt index 11b130985..549b27d00 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealEndTest.kt @@ -11,7 +11,7 @@ import io.kotest.matchers.shouldBe class DealEndTest : DescribeSpec({ describe("deal") { - context("테이블의 플레이어와 딜러") { + context("DealEnd 의 카드 배분을 하면") { val players = players(player("currentPlayer", Action.HIT), player()) val dealer = Dealer() val table = table(dealer = dealer, players = players) @@ -19,25 +19,25 @@ class DealEndTest : DescribeSpec({ val result = dealEnd.deal() - it("결과의 플레이어는 테이블의 플레이어") { + it("결과에 반환된 플레이어들은 테이블의 모든 플레이어다") { result.players.value.forEachIndexed { index, player -> player shouldBe players.value[index] } } - it("결과의 딜러는 테이블의 딜러") { + it("결과의 반환된 딜러는 테이블의 딜러다") { result.dealer shouldBe dealer } } } describe("nextDistributor") { - context("dealEnd의 다음 배분자 호출") { + context("DealEnd의 다음 배분자 호출하면") { val players = players(player("currentPlayer", Action.HIT), player()) val dealer = Dealer() val table = table(dealer = dealer, players = players) val dealEnd = DealEnd(table) - it("호출 실패") { + it("다음 배분자가 없어 호출에 실패한다") { shouldThrowExactly { dealEnd.nextDistributor() } diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealInitialCardsTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealInitialCardsTest.kt index 73ca124dc..b080bae0b 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealInitialCardsTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealInitialCardsTest.kt @@ -7,22 +7,22 @@ import io.kotest.matchers.types.shouldBeTypeOf class DealInitialCardsTest : DescribeSpec({ describe("deal") { - context("첫 카드 배분 스테이지를 진행시키면") { + context("DealInitialCards 카드 배분을 하면") { val table = table() val dealInitialCards = DealInitialCards(table) dealInitialCards.deal() - it("플레이어마다 2장의 카드 수령") { + it("플레이어마다 2장의 카드를 수령한다") { table.players.value.forEach { player -> player.hand.cards.size shouldBe 2 } } - it("딜러도 2장의 카드 수령") { + it("딜러는 2장의 카드를 수령한다") { table.dealer.hand.cards.size shouldBe 2 } - it("다음 카드 배분은 플레이어에게 카드 배분") { + it("다음 카드 배분은 DealToPlayer이다") { dealInitialCards.nextDistributor.shouldBeTypeOf() } } diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealToDealerTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealToDealerTest.kt index 2f7660b8b..e8c22ffa2 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealToDealerTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealToDealerTest.kt @@ -26,11 +26,11 @@ class DealToDealerTest : DescribeSpec({ table.dealer.hand.cards.size shouldBe 3 } - it("배분 결과 참을 반환") { + it("배분이 진행되어 배분 결과는 참을 반환한다") { result.isHit shouldBe true } - it("게임의 다음 배분은 종료 상태") { + it("게임의 다음 배분은 DealEnd이다") { dealToDealer.nextDistributor.shouldBeTypeOf() } } @@ -48,11 +48,11 @@ class DealToDealerTest : DescribeSpec({ table.dealer.hand.cards.size shouldBe 2 } - it("배분 결과 거짓을 반환") { + it("배분이 진행되지 않아 배분 결과는 거짓을 반환한다") { result.isHit shouldBe false } - it("게임의 다음 배분은 종료 상태") { + it("게임의 다음 배분은 DealEnd 이다") { dealToDealer.nextDistributor.shouldBeTypeOf() } } diff --git a/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt b/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt index eae46b2b8..68e488728 100644 --- a/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/distirbution/DealToPlayerTest.kt @@ -24,24 +24,24 @@ class DealToPlayerTest : DescribeSpec({ val result = dealToPlayer.deal() - it("플레이어의 카드는 증가") { + it("플레이어의 카드 1장이 증가한다") { table.players.inTurn.hand.cards.size shouldBe handCount + 1 } - it("덱에서 카드는 제거") { + it("덱에서 카드 1장은 제거된다") { table.dealer.deck.cards.size shouldBe deckCount - 1 } - it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { + it("결과 값에 반환된 플레이어는 이번 차례 진행한 플레이어다") { result.player.name shouldBe PlayerName("currentPlayer") } - it("게임 룰에 의한 STAND인지 여부는 FALSE") { + it("플레이어가 HIT을 한 결과이므로 게임 룰에 의한 STAND인지 여부는 FALSE") { result.isSystemStand shouldBe false } } - context("플레이어가 STAND를 응답(player: STAND)할 때") { + context("플레이어가 STAND를 응답(player: STAND)했을 때") { val player1Cards = hand(card(Rank.TEN), card(Rank.TEN)) val players = players( player("currentPlayer", Action.STAND, player1Cards), @@ -49,100 +49,71 @@ class DealToPlayerTest : DescribeSpec({ ) val table = table(inputAction = Action.HIT, players = players) - context("첫번째 플레이어가 STAND을 하면") { + context("첫번째 플레이어의 STAND였다면") { val dealToPlayer = DealToPlayer(table) val result = dealToPlayer.deal() - it("게임의 다음 상태는 다음 플레이어 배분 차례") { + it("게임의 다음 배분은 DealToPlayer 다") { dealToPlayer.nextDistributor.shouldBeTypeOf() table.players.inTurn.name shouldBe PlayerName("nextPlayer") } - it("플레이어 카드는 변화 없음") { + it("플레이어 카드는 변화 없다") { table.players.value.first().hand shouldBe player1Cards } - it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { + it("결과 값에 반환된 플레이어는 이번 차례 진행한 플레이어다") { result.player.name shouldBe PlayerName("currentPlayer") } - it("게임 룰에 의한 STAND인지 여부는 FALSE") { + it("플레이어가 STAND를 했으므로 게임 룰에 의한 STAND인지 여부는 FALSE이다") { result.isSystemStand shouldBe false } } - context("카드 배분시 두 번째 플레이어가 STAND을 한 경우") { + context("두 번째 플레이어의 STAND였다면") { table.players.inTurn.name shouldBe PlayerName("nextPlayer") val dealToPlayer = DealToPlayer(table) val result = dealToPlayer.deal() - it("딜러 카드 배분 차례") { + it("게임의 다음 배분은 DealToDealer 다") { dealToPlayer.nextDistributor.shouldBeTypeOf() } - it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { + it("결과 값에 반환된 플레이어는 이번 차례 진행한 플레이어다") { result.player.name shouldBe PlayerName("nextPlayer") } - it("게임 룰에 의한 STAND인지 여부는 FALSE") { + it("플레이어가 STAND를 했으므로 게임 룰에 의한 STAND인지 여부는 FALSE다") { result.isSystemStand shouldBe false } } - - context("카드 배분시 게임 룰에 의한 (21점 이상) STAND을 한 경우") { - val cards = hand(card(Rank.TEN), card(Rank.ACE)) - val players = players(player("currentPlayer", hand = cards, action = Action.HIT), player("nextPlayer")) - - val table = table(inputAction = Action.HIT, players = players) - table.players.inTurn.name shouldBe PlayerName("currentPlayer") - val dealToPlayer = DealToPlayer(table) - - val result = dealToPlayer.deal() - - it("게임의 다음 상태는 다음 플레이어 배분 차례") { - dealToPlayer.nextDistributor.shouldBeTypeOf() - table.players.inTurn.name shouldBe PlayerName("nextPlayer") - } - - it("플레이어 카드는 변화 없음") { - table.players.value.first().hand shouldBe cards - } - - it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { - result.player.name shouldBe PlayerName("currentPlayer") - } - - it("게임 룰에 의한 STAND인지 여부는 TRUE") { - result.isSystemStand shouldBe true - } - } } - context("플레이어가 HIT를 했지만 STAND를 응답할 때(점수 21점 이상)") { - val player1Cards = hand(card(Rank.TEN), card(Rank.ACE)) - val players = players( - player("currentPlayer", Action.STAND, player1Cards), - player("nextPlayer", Action.STAND), - ) - val table = table(inputAction = Action.HIT, players = players) + context("첫번째 플레이어가 게임 룰에 의한 (21점 이상) STAND인 경우") { + val cards = hand(card(Rank.TEN), card(Rank.ACE)) + val players = players(player("currentPlayer", hand = cards, action = Action.HIT), player("nextPlayer")) + val table = table(inputAction = Action.HIT, players = players) + table.players.inTurn.name shouldBe PlayerName("currentPlayer") val dealToPlayer = DealToPlayer(table) + val result = dealToPlayer.deal() - it("게임의 다음 상태는 다음 플레이어 배분 차례") { + it("게임의 다음 배분은 DealToPlayer 다") { dealToPlayer.nextDistributor.shouldBeTypeOf() table.players.inTurn.name shouldBe PlayerName("nextPlayer") } - it("플레이어 카드는 변화 없음") { - table.players.value.first().hand shouldBe player1Cards + it("플레이어 카드는 변화 없다") { + table.players.value.first().hand shouldBe cards } - it("결과 값의 플레이어는 이번 차례 진행한 플레이어") { + it("결과 값에 반환된 플레이어는 이번 차례 진행한 플레이어다") { result.player.name shouldBe PlayerName("currentPlayer") } - it("게임 룰에 의한 STAND인지 여부는 TRUE") { + it("게임 룰에 의한 STAND인지 여부는 TRUE다") { result.isSystemStand shouldBe true } } diff --git a/src/test/kotlin/blackjack/domain/player/DealerPlayerTest.kt b/src/test/kotlin/blackjack/domain/player/DealerPlayerTest.kt index 024036001..5a5dc5263 100644 --- a/src/test/kotlin/blackjack/domain/player/DealerPlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/DealerPlayerTest.kt @@ -9,7 +9,6 @@ import io.kotest.matchers.shouldBe class DealerPlayerTest : DescribeSpec({ describe("hitOrStand") { - context("16점 이하라면") { val score16Cards = hand( card(Rank.EIGHT), @@ -17,7 +16,7 @@ class DealerPlayerTest : DescribeSpec({ ) val player = DealerPlayer(score16Cards) - it("HIT 반환") { + it("HIT가 반환된다") { val result = player.hitOrStand() result shouldBe Action.HIT @@ -31,7 +30,7 @@ class DealerPlayerTest : DescribeSpec({ ) val player = DealerPlayer(score20Cards) - it("STAND 반환") { + it("STAND가 반환된다") { val result = player.hitOrStand() result shouldBe Action.STAND diff --git a/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt index d978814f1..169cfc65a 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerNamesTest.kt @@ -5,13 +5,13 @@ import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe class PlayerNamesTest : DescribeSpec({ - describe("플레이어 이름 목록 생성") { + describe("PlayerNames.from()") { context("플레이어 이름이 문자열 리스트로 주어지면") { val names = listOf("홍길동", "백상어") val result = PlayerNames.from(names) - it("주어진 문자열로 이름 목록이 생성") { + it("주어진 문자열로 이름 목록이 생성된다") { result.value[0].value shouldBe "홍길동" result.value[1].value shouldBe "백상어" } @@ -20,7 +20,7 @@ class PlayerNamesTest : DescribeSpec({ context("두 플레이어 이름이 동일하면") { val names = listOf("홍길동", "홍길동") - it("플레이어 이름 생성 실패") { + it("플레이어 이름 생성에 실패한다") { shouldThrowExactly { PlayerNames.from(names) } diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index cd3dcb029..06d7270fc 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -19,7 +19,7 @@ class PlayerTest : DescribeSpec({ describe("Player()") { context("플레이어 이름이 주어지면") { val name = PlayerName("홍길동") - it("플레이어 생성") { + it("플레이어가 생성된다") { val result = Player(name, { Action.HIT }) result.name shouldBe name @@ -34,7 +34,7 @@ class PlayerTest : DescribeSpec({ player.addCard(card) - it("플레이가 소유한 카드에 카드가 추가") { + it("플레이가 소유한 카드에 해당 카드가 추가된다") { player.hand.cards shouldBe listOf(card) } } @@ -46,7 +46,7 @@ class PlayerTest : DescribeSpec({ player.addCard(newCard) - it("플레이가 소유한 카드에 카드가 추가") { + it("플레이가 소유한 카드에 해당 카드가 추가") { player.hand shouldBe Hand(mutableListOf(oldCard, newCard)) } } @@ -77,14 +77,14 @@ class PlayerTest : DescribeSpec({ describe("isGreaterOrEqualToMaxScore") { context("21을 넘었을 때") { val player = player(hand = hand(card(Rank.THREE), card(Rank.TEN), card(Rank.TEN))) - it("true 반환") { + it("true가 반환된다") { player.isGreaterOrEqualToMaxScore shouldBe true } } context("21점일 때") { val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN))) - it("true 반환") { + it("true가 반환된다") { player.isGreaterOrEqualToMaxScore shouldBe true } } @@ -92,18 +92,19 @@ class PlayerTest : DescribeSpec({ context("21을 넘지 않았을 때") { val player = player(hand = hand(card(Rank.ACE), card(Rank.ACE), card(Rank.ACE))) - it("false 반환") { + it("false가 반환된다") { player.isGreaterOrEqualToMaxScore shouldBe false } } } describe("score") { - val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN))) + context("플레이어가 21점의 카드를 갖고 있었다면") { + val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN))) - context("플레이어의 점수 조회") { val result = player.score - it("플레이어 점수 반환") { + + it("플레이어 점수는 21점이다") { result.cardScore shouldBe 21 } } @@ -112,14 +113,14 @@ class PlayerTest : DescribeSpec({ describe("hitOrStand") { context("이미 점수가 최대 점수 21을 넘었다면") { val score30Cards = hand(card(Rank.QUEEN), card(Rank.QUEEN), card(Rank.QUEEN)) - it("HIT을 받아도 STAND가 반환") { + it("HIT을 받아도 STAND가 반환된다") { val player = player(action = Action.HIT, hand = score30Cards) val result = player.hitOrStand() result shouldBe Action.STAND } - it("STAND를 받으면 STAND가 반환") { + it("STAND를 받으면 STAND가 반환된다") { val player = player(action = Action.STAND, hand = score30Cards) val result = player.hitOrStand() @@ -130,14 +131,14 @@ class PlayerTest : DescribeSpec({ context("이미 점수가 최대 점수 21점 이라면") { val score21Cards = hand(card(Rank.QUEEN), card(Rank.ACE)) - it("HIT을 받아도 STAND가 반환") { + it("HIT을 받아도 STAND가 반환된다") { val player = player(action = Action.HIT, hand = score21Cards) val result = player.hitOrStand() result shouldBe Action.STAND } - it("STAND를 받으면 STAND가 반환") { + it("STAND를 받으면 STAND가 반환된다") { val player = player(action = Action.STAND, hand = score21Cards) val result = player.hitOrStand() @@ -149,14 +150,14 @@ class PlayerTest : DescribeSpec({ context("최대 점수 21을 넘지 않았을 때") { val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) - it("HIT을 받으면 HIT이 반한") { + it("HIT을 받으면 HIT이 반한된다") { val player = player(action = Action.HIT, hand = score5Cards) val result = player.hitOrStand() result shouldBe Action.HIT } - it("STAND를 받으면 STAND가 반환") { + it("STAND를 받으면 STAND가 반환된다") { val player = player(action = Action.STAND, hand = score5Cards) val result = player.hitOrStand() @@ -174,7 +175,7 @@ class PlayerTest : DescribeSpec({ val player1 = Player(PlayerName("kim"), action, hand) val player2 = Player(PlayerName("lee"), action, hand) - it("다른 플레이어로 취급") { + it("다른 플레이어로 취급한다") { player1 shouldNotBe player2 } } @@ -186,7 +187,7 @@ class PlayerTest : DescribeSpec({ val player1 = Player(PlayerName("kim"), action, hand(card(Rank.ACE))) val player2 = Player(PlayerName("kim"), action, hand(card(Rank.TEN))) - it("같은 플레이어로 취급") { + it("같은 플레이어로 취급한다") { player1 shouldBe player2 } } @@ -194,21 +195,21 @@ class PlayerTest : DescribeSpec({ describe("isBlackJack") { context("플레이어가 블랙잭이면(카드가 2장이며, 해당 카드 합이 21)") { val player = player(hand = hand(card(Rank.TEN), card(Rank.ACE))) - it("true 반환") { + it("true가 반환된다") { player.isBlackJack shouldBe true } } context("플레이어 카드가 2장이지만 21점이 아니면") { val player = player(hand = hand(card(Rank.TEN), card(Rank.TWO))) - it("false 반환") { + it("false가 반환된다") { player.isBlackJack shouldBe false } } context("플레이어 카드 합이 21이지만 카드 수가 2장 초과면") { val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN), card(Rank.TEN))) - it("false 반환") { + it("false가 반환된다") { player.isBlackJack shouldBe false } } @@ -224,79 +225,79 @@ class PlayerTest : DescribeSpec({ val score10Cards = hand(card(Rank.FIVE), card(Rank.FIVE)) val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) - context("플레이어: 버스트") { + context("플레이어가 버스트라면") { val player = player(hand = score22Cards) val dealer = Dealer(player = DealerPlayer(score10Cards)) - it("LOSS") { + it("LOSS다") { player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS } } - context("플레이어: 버스트, 딜러: 버스트") { + context("플레이어: 버스트 이고 딜러도 버스트라면") { val player = player(hand = score22Cards) val dealer = Dealer(player = DealerPlayer(hand = score22Cards)) - it("LOSS") { + it("LOSS다") { player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS } } - context("플레이어(5) < 딜러(10)") { + context("플레이어(5) < 딜러(10) 로 플레이어가 딜러보다 점수가 낮다면") { val player = player(hand = score5Cards) val dealer = Dealer(player = DealerPlayer(score10Cards)) - it("LOSS") { + it("LOSS다") { player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS } } - context("플레이어(21, 블랙잭) > 딜러(5)") { + context("플레이어(21, 블랙잭) > 딜러(5) 로 플레이어가 블랙잭으로 딜러보다 점수가 높다면") { val player = player(hand = blackJackCards) val dealer = Dealer(player = DealerPlayer(score5Cards)) - it("WIN") { + it("WIN이다") { player.judgeVictory(dealer) shouldBe VictoryStatus.WIN } } - context("플레이어(10) > 딜러(5)") { + context("플레이어(10) > 딜러(5) 로 플레이어가 딜러보다 점수가 높다면") { val player = player(hand = score10Cards) val dealer = Dealer(player = DealerPlayer(score5Cards)) - it("WIN") { + it("WIN이다") { player.judgeVictory(dealer) shouldBe VictoryStatus.WIN } } - context("플레이어(10), 딜러: 버스트") { + context("플레이어(10)이고 딜러가 버스트라면") { val player = player(hand = score10Cards) val dealer = Dealer(player = DealerPlayer(score22Cards)) - it("WIN") { + it("WIN이다") { player.judgeVictory(dealer) shouldBe VictoryStatus.WIN } } - context("플레이어(10) == 딜러(10)") { + context("플레이어(10) == 딜러(10)로 점수가 같다면") { val player = player(hand = score10Cards) val dealer = Dealer(player = DealerPlayer(score10Cards)) - it("PUSH") { + it("PUSH다") { player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH } } - context("플레이어(블랙잭) == 딜러(블랙잭)") { + context("플레이어(블랙잭) == 딜러(블랙잭)로 점수가 같다면") { val player = player(hand = blackJackCards) val dealer = Dealer(player = DealerPlayer(blackJackCards)) - it("PUSH") { + it("PUSH다") { player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH } } - context("플레이어(21) == 딜러(블랙잭)") { + context("플레이어(21) == 딜러(블랙잭)로 점수가 같다면") { val player = player(hand = score21NotBlackJackCards) val dealer = Dealer(player = DealerPlayer(blackJackCards)) - it("PUSH") { + it("PUSH다") { player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH } } diff --git a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt index acf5047d6..d68806822 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt @@ -14,18 +14,18 @@ class PlayersTest : DescribeSpec({ val names = PlayerNames(listOf(name1, name2)) val result = Players.of(names) { Action.HIT } - it("주어진 이름 순서대로 플레이어들 생성") { + it("주어진 이름 순서대로 플레이어들이 생성된다") { result.value[0].name shouldBe name1 result.value[1].name shouldBe name2 } - it("첫 이름의 플레이어가 첫 순번") { + it("첫 이름의 플레이어가 첫 순번이다") { result.inTurn.name shouldBe name1 } } context("플레이어가 2명이 아닌 경우") { val playerNames = PlayerNames(listOf(PlayerName("홍길동"), PlayerName("베트맨"), PlayerName("아이언맨"))) - it("플레이어 생성 실패") { + it("플레이어 생성에 실패한다") { shouldThrowExactly { Players.of(playerNames) { Action.HIT } } @@ -33,14 +33,14 @@ class PlayersTest : DescribeSpec({ } } - describe("다음 플레이어에게 차례 넘김") { + describe("changePlayer()") { val playerList = listOf(player("kim"), player("lee")) val players = Players(playerList) context("플레이어 1이 차례인 경우") { players.inTurn shouldBe playerList.first() players.changePlayer() - it("플레이어 2에게 차례가 넘어감") { + it("플레이어 2에게 차례가 넘어간다") { players.inTurn shouldBe playerList.last() } } @@ -48,7 +48,7 @@ class PlayersTest : DescribeSpec({ context("플레이어 2가 차례인 경우") { players.inTurn shouldBe playerList.last() - it("턴이 끝났다는 에러") { + it("턴이 끝났다는 에러가 발생한다") { shouldThrowExactly { players.changePlayer() } @@ -56,13 +56,13 @@ class PlayersTest : DescribeSpec({ } } - describe("마지막 플레이어 차례인지 조회") { + describe("isLastTurn") { val playerList = listOf(player("kim"), player("lee")) val players = Players(playerList) context("플레이어 1이 차례인 경우") { players.inTurn shouldBe playerList.first() - it("false 반환") { + it("false가 반환된다") { players.isLastTurn shouldBe false } } @@ -71,7 +71,7 @@ class PlayersTest : DescribeSpec({ players.changePlayer() players.inTurn shouldBe playerList.last() - it("true 반환") { + it("true가 반환된다") { players.isLastTurn shouldBe true } } From 00f52f020c9319b517ec0a765f090e5bbeb63004 Mon Sep 17 00:00:00 2001 From: bongbong Date: Thu, 7 Dec 2023 00:09:22 +0900 Subject: [PATCH 13/19] =?UTF-8?q?[step4]=20feat:=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=8A=B9=ED=8C=A8=EB=8A=94=20BlackJackJudge=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=ED=8C=90=EB=8B=A8=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/domain/BlackJackJudge.kt | 24 ++++ .../blackjack/domain/batting/BetBoard.kt | 3 +- .../kotlin/blackjack/domain/card/HandScore.kt | 12 -- .../kotlin/blackjack/domain/player/Player.kt | 10 -- .../blackjack/domain/BlackJackJudgeTest.kt | 100 +++++++++++++++++ .../blackjack/domain/card/HandScoreTest.kt | 105 ------------------ .../blackjack/domain/player/PlayerTest.kt | 90 --------------- 7 files changed, 126 insertions(+), 218 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/BlackJackJudge.kt create mode 100644 src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt diff --git a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt new file mode 100644 index 000000000..84fee65c5 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt @@ -0,0 +1,24 @@ +package blackjack.domain + +import blackjack.domain.player.CardPlayer +import blackjack.domain.player.Player +import blackjack.domain.result.game.VictoryStatus + +object BlackJackJudge { + private const val BUST_THRESHOLD = 21 + private const val BUST_SCORE = 0 + + fun judgeVictory(player: Player, dealer: Dealer): VictoryStatus = + when { + player.isBust() -> VictoryStatus.LOSS + player.gameScore() > dealer.gameScore() -> VictoryStatus.WIN + player.gameScore() == dealer.gameScore() -> VictoryStatus.PUSH + else -> VictoryStatus.LOSS + } + + private fun CardPlayer.gameScore(): Int = + if (this.isBust()) BUST_SCORE + else this.score.cardScore + + private fun CardPlayer.isBust() = score.cardScore > BUST_THRESHOLD +} diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt index cce279fb9..2ce87d81f 100644 --- a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -1,5 +1,6 @@ package blackjack.domain.batting +import blackjack.domain.BlackJackJudge import blackjack.domain.Dealer import blackjack.domain.player.Player import blackjack.domain.player.PlayerName @@ -48,7 +49,7 @@ data class BetBoard( playerBets[name] ?: throw IllegalArgumentException("베팅에 참여한 플레이어가 아닙니다") private fun Player.payout(dealer: Dealer, betAmount: Amount): Amount = - when (this.judgeVictory(dealer)) { + when (BlackJackJudge.judgeVictory(this, dealer)) { VictoryStatus.WIN -> calculateWinAmount(this, betAmount) VictoryStatus.LOSS -> Amount.ZERO VictoryStatus.PUSH -> betAmount diff --git a/src/main/kotlin/blackjack/domain/card/HandScore.kt b/src/main/kotlin/blackjack/domain/card/HandScore.kt index d54bedbaf..362c49464 100644 --- a/src/main/kotlin/blackjack/domain/card/HandScore.kt +++ b/src/main/kotlin/blackjack/domain/card/HandScore.kt @@ -3,27 +3,15 @@ package blackjack.domain.card class HandScore( val cardScore: Int, ) { - val isBust: Boolean - get() = cardScore > BUST_THRESHOLD - val isGreaterOrEqualToMaxScore: Boolean = cardScore >= BUST_THRESHOLD val isBlackJackScore: Boolean = cardScore == BLACK_JACK_SCORE - val gameScore: Int - get() { - if (isBust) return BUST_SCORE - return cardScore - } - infix fun isGreaterCardScoreThan(score: Int) = cardScore > score - infix fun isGreaterGameScoreThan(other: HandScore) = gameScore > other.gameScore - infix fun isSameGameScoreTo(other: HandScore) = gameScore == other.gameScore companion object { private const val BUST_THRESHOLD = 21 private const val BLACK_JACK_SCORE = 21 - private const val BUST_SCORE = 0 private const val ACE_BONUS_SCORE = 10 fun from(hand: Hand): HandScore { diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index 7a1264864..00b714612 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -1,9 +1,7 @@ package blackjack.domain.player import blackjack.domain.Action -import blackjack.domain.Dealer import blackjack.domain.card.Hand -import blackjack.domain.result.game.VictoryStatus class Player( val name: PlayerName, @@ -11,14 +9,6 @@ class Player( override val hand: Hand = Hand(), ) : CardPlayer { - fun judgeVictory(dealer: Dealer): VictoryStatus = - when { - score.isBust -> VictoryStatus.LOSS - score isGreaterGameScoreThan dealer.score -> VictoryStatus.WIN - score isSameGameScoreTo dealer.score -> VictoryStatus.PUSH - else -> VictoryStatus.LOSS - } - override fun hitOrStand(): Action { if (isGreaterOrEqualToMaxScore) return Action.STAND return desiredAction(this) diff --git a/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt b/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt new file mode 100644 index 000000000..b9a437cfe --- /dev/null +++ b/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt @@ -0,0 +1,100 @@ +package blackjack.domain + +import blackjack.domain.card.Rank +import blackjack.domain.player.DealerPlayer +import blackjack.domain.result.game.VictoryStatus +import blackjack.mock.card +import blackjack.mock.hand +import blackjack.mock.player +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.shouldBe + +class BlackJackJudgeTest : DescribeSpec({ + describe("judgeVictory") { + val score22Cards = + hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TWO)) + val blackJackCards = + hand(card(Rank.TEN), card(Rank.ACE)) + val score21NotBlackJackCards = + hand(card(Rank.TEN), card(Rank.FIVE), card(Rank.SIX)) + val score10Cards = hand(card(Rank.FIVE), card(Rank.FIVE)) + val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) + + context("플레이어가 버스트라면") { + val player = player(hand = score22Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("LOSS다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어가 버스트 이고 딜러도 버스트라면") { + val player = player(hand = score22Cards) + val dealer = Dealer(player = DealerPlayer(hand = score22Cards)) + + it("LOSS다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어(5) < 딜러(10) 로 플레이어가 딜러보다 점수가 낮다면") { + val player = player(hand = score5Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("LOSS다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.LOSS + } + } + context("플레이어(21, 블랙잭) > 딜러(5) 로 플레이어가 블랙잭으로 딜러보다 점수가 높다면") { + val player = player(hand = blackJackCards) + val dealer = Dealer(player = DealerPlayer(score5Cards)) + + it("WIN이다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.WIN + } + } + context("플레이어(10) > 딜러(5) 로 플레이어가 딜러보다 점수가 높다면") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score5Cards)) + + it("WIN이다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.WIN + } + } + + context("플레이어(10)이고 딜러가 버스트라면") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score22Cards)) + + it("WIN이다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.WIN + } + } + + context("플레이어(10) == 딜러(10)로 점수가 같다면") { + val player = player(hand = score10Cards) + val dealer = Dealer(player = DealerPlayer(score10Cards)) + + it("PUSH다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.PUSH + } + } + + context("플레이어(블랙잭) == 딜러(블랙잭)로 점수가 같다면") { + val player = player(hand = blackJackCards) + val dealer = Dealer(player = DealerPlayer(blackJackCards)) + + it("PUSH다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.PUSH + } + } + + context("플레이어(21) == 딜러(블랙잭)로 점수가 같다면") { + val player = player(hand = score21NotBlackJackCards) + val dealer = Dealer(player = DealerPlayer(blackJackCards)) + + it("PUSH다") { + BlackJackJudge.judgeVictory(player, dealer) shouldBe VictoryStatus.PUSH + } + } + } +}) diff --git a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt index a81878998..a873003e1 100644 --- a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt @@ -37,42 +37,6 @@ class HandScoreTest : DescribeSpec({ } } - describe("gameScore") { - context("BUST 일 때") { - val hand = HandScore(22) - hand.isBust shouldBe true - - it("0점 반환") { - hand.gameScore shouldBe 0 - } - } - - context("BUST가 아닐 때") { - val hand = HandScore(21) - hand.isBust shouldBe false - - it("cardScore 반환") { - hand.gameScore shouldBe 21 - } - } - } - - describe("isBust") { - context("21을 넘었을 때") { - val handScore = HandScore(22) - it("true 반환") { - handScore.isBust shouldBe true - } - } - - context("21을 넘지 않았을 때") { - val handScore = HandScore(21) - it("false 반환") { - handScore.isBust shouldBe false - } - } - } - describe("isGreaterOrEqualToMaxScore") { context("21점 미만일 때") { val handScore = HandScore(20) @@ -142,75 +106,6 @@ class HandScoreTest : DescribeSpec({ result shouldBe false } } - - context("BUST VS other: 더 낮은 cardScore") { - val score = HandScore(22) - score.isBust shouldBe true - - val result = score isGreaterGameScoreThan HandScore(10) - - it("거짓 반환") { - result shouldBe false - } - } - - context("더 낮은 cardScore VS other: BUST") { - val score = HandScore(10) - score.isBust shouldBe false - - val other = HandScore(22) - other.isBust shouldBe true - - val result = score isGreaterGameScoreThan other - - it("참 반환") { - result shouldBe true - } - } - - context("BUST VS other: BUST") { - val score = HandScore(23) - score.isBust shouldBe true - - val other = HandScore(22) - other.isBust shouldBe true - - val result = score isGreaterGameScoreThan other - - it("거짓 반환") { - result shouldBe false - } - } - } - - describe("isSameGameScoreThan") { - context("같은 점수를 비교") { - val score = HandScore(16) - val result = score isSameGameScoreTo HandScore(16) - it("참을 반환") { - result shouldBe true - } - } - - context("다른 점수를 비교") { - val score = HandScore(16) - val result = score isSameGameScoreTo HandScore(20) - it("거짓 반환") { - result shouldBe false - } - } - - context("다른 cardScore이지만 둘 다 BUST일 때") { - val score = HandScore(22) - val other = HandScore(22) - score.isBust shouldBe true - other.isBust shouldBe true - - val result = score isSameGameScoreTo other - it("참 반환") { - result shouldBe true - } - } } describe("isBlackJackScore") { diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index 06d7270fc..b1af7d1b8 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -1,12 +1,10 @@ package blackjack.domain.player import blackjack.domain.Action -import blackjack.domain.Dealer import blackjack.domain.card.Card import blackjack.domain.card.Hand import blackjack.domain.card.Rank import blackjack.domain.card.Suit -import blackjack.domain.result.game.VictoryStatus import blackjack.mock.card import blackjack.mock.hand import blackjack.mock.player @@ -214,92 +212,4 @@ class PlayerTest : DescribeSpec({ } } } - - describe("judgeVictory") { - val score22Cards = - hand(card(Rank.TEN), card(Rank.TEN), card(Rank.TWO)) - val blackJackCards = - hand(card(Rank.TEN), card(Rank.ACE)) - val score21NotBlackJackCards = - hand(card(Rank.TEN), card(Rank.FIVE), card(Rank.SIX)) - val score10Cards = hand(card(Rank.FIVE), card(Rank.FIVE)) - val score5Cards = hand(card(Rank.TWO), card(Rank.THREE)) - - context("플레이어가 버스트라면") { - val player = player(hand = score22Cards) - val dealer = Dealer(player = DealerPlayer(score10Cards)) - - it("LOSS다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS - } - } - context("플레이어: 버스트 이고 딜러도 버스트라면") { - val player = player(hand = score22Cards) - val dealer = Dealer(player = DealerPlayer(hand = score22Cards)) - - it("LOSS다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS - } - } - context("플레이어(5) < 딜러(10) 로 플레이어가 딜러보다 점수가 낮다면") { - val player = player(hand = score5Cards) - val dealer = Dealer(player = DealerPlayer(score10Cards)) - - it("LOSS다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.LOSS - } - } - context("플레이어(21, 블랙잭) > 딜러(5) 로 플레이어가 블랙잭으로 딜러보다 점수가 높다면") { - val player = player(hand = blackJackCards) - val dealer = Dealer(player = DealerPlayer(score5Cards)) - - it("WIN이다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.WIN - } - } - context("플레이어(10) > 딜러(5) 로 플레이어가 딜러보다 점수가 높다면") { - val player = player(hand = score10Cards) - val dealer = Dealer(player = DealerPlayer(score5Cards)) - - it("WIN이다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.WIN - } - } - - context("플레이어(10)이고 딜러가 버스트라면") { - val player = player(hand = score10Cards) - val dealer = Dealer(player = DealerPlayer(score22Cards)) - - it("WIN이다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.WIN - } - } - - context("플레이어(10) == 딜러(10)로 점수가 같다면") { - val player = player(hand = score10Cards) - val dealer = Dealer(player = DealerPlayer(score10Cards)) - - it("PUSH다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH - } - } - - context("플레이어(블랙잭) == 딜러(블랙잭)로 점수가 같다면") { - val player = player(hand = blackJackCards) - val dealer = Dealer(player = DealerPlayer(blackJackCards)) - - it("PUSH다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH - } - } - - context("플레이어(21) == 딜러(블랙잭)로 점수가 같다면") { - val player = player(hand = score21NotBlackJackCards) - val dealer = Dealer(player = DealerPlayer(blackJackCards)) - - it("PUSH다") { - player.judgeVictory(dealer) shouldBe VictoryStatus.PUSH - } - } - } }) From 8bbefe0481bb9bd1fd6a67e75fdeca3758341e26 Mon Sep 17 00:00:00 2001 From: bongbong Date: Thu, 7 Dec 2023 00:34:13 +0900 Subject: [PATCH 14/19] =?UTF-8?q?[step4]=20feat:=20=EB=B8=94=EB=9E=99?= =?UTF-8?q?=EC=9E=AD=20=EC=97=AC=EB=B6=80=EB=8A=94=20BlackJackJudge?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=8C=90=EB=8B=A8=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ViewResultProcessor.kt | 4 +- .../kotlin/blackjack/domain/BlackJackJudge.kt | 9 ++- .../blackjack/domain/batting/BetBoard.kt | 2 +- src/main/kotlin/blackjack/domain/card/Hand.kt | 2 + .../kotlin/blackjack/domain/card/HandScore.kt | 15 ++-- .../blackjack/domain/player/CardPlayer.kt | 8 +-- .../blackjack/domain/player/DealerPlayer.kt | 2 +- .../view/dto/DealerCardsResultDto.kt | 2 +- .../view/dto/PlayerCardsResultDto.kt | 2 +- .../blackjack/view/output/OutputView.kt | 4 +- .../blackjack/domain/BlackJackJudgeTest.kt | 23 ++++++ .../kotlin/blackjack/domain/DealerTest.kt | 10 +-- .../blackjack/domain/card/HandScoreTest.kt | 71 ++----------------- .../kotlin/blackjack/domain/card/HandTest.kt | 17 ----- .../blackjack/domain/player/PlayerTest.kt | 25 +------ 15 files changed, 59 insertions(+), 137 deletions(-) diff --git a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt index b32c49391..68165a68d 100644 --- a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt @@ -36,9 +36,9 @@ object ViewResultProcessor { private fun drawCardResult(result: GameEndResult) { val dealerDto = - DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.cardScore) + DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value) val playersDto = result.playerResults.map { - PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.cardScore) + PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value) } OutputView.drawCardsResults(dealerDto, playersDto) } diff --git a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt index 84fee65c5..ee916c598 100644 --- a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt +++ b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt @@ -7,6 +7,8 @@ import blackjack.domain.result.game.VictoryStatus object BlackJackJudge { private const val BUST_THRESHOLD = 21 private const val BUST_SCORE = 0 + private const val BLACK_JACK_CARD_COUNT = 2 + private const val BLACK_JACK_SCORE = 21 fun judgeVictory(player: Player, dealer: Dealer): VictoryStatus = when { @@ -16,9 +18,12 @@ object BlackJackJudge { else -> VictoryStatus.LOSS } + fun isBlackJack(player: Player): Boolean = + (player isSameCardCount BLACK_JACK_CARD_COUNT) && (player isSameScore BLACK_JACK_SCORE) + private fun CardPlayer.gameScore(): Int = if (this.isBust()) BUST_SCORE - else this.score.cardScore + else this.score.value - private fun CardPlayer.isBust() = score.cardScore > BUST_THRESHOLD + private fun CardPlayer.isBust() = score.value > BUST_THRESHOLD } diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt index 2ce87d81f..a9faeadf9 100644 --- a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -56,7 +56,7 @@ data class BetBoard( } private fun calculateWinAmount(player: Player, betAmount: Amount): Amount = - if (player.isBlackJack) betAmount * BLACK_JACK_MULTIPLIER + betAmount + if (BlackJackJudge.isBlackJack(player)) betAmount * BLACK_JACK_MULTIPLIER + betAmount else betAmount * 2 companion object { diff --git a/src/main/kotlin/blackjack/domain/card/Hand.kt b/src/main/kotlin/blackjack/domain/card/Hand.kt index e79784e29..1f5540ea3 100644 --- a/src/main/kotlin/blackjack/domain/card/Hand.kt +++ b/src/main/kotlin/blackjack/domain/card/Hand.kt @@ -6,6 +6,8 @@ data class Hand( val cards: List get() = _cards.toList() + val cardsCount = _cards.size + val ranks: List get() = _cards.map { it.rank } diff --git a/src/main/kotlin/blackjack/domain/card/HandScore.kt b/src/main/kotlin/blackjack/domain/card/HandScore.kt index 362c49464..3a6c12278 100644 --- a/src/main/kotlin/blackjack/domain/card/HandScore.kt +++ b/src/main/kotlin/blackjack/domain/card/HandScore.kt @@ -1,17 +1,12 @@ package blackjack.domain.card -class HandScore( - val cardScore: Int, +data class HandScore( + val value: Int, ) { - val isGreaterOrEqualToMaxScore: Boolean = cardScore >= BUST_THRESHOLD - - val isBlackJackScore: Boolean = cardScore == BLACK_JACK_SCORE - - infix fun isGreaterCardScoreThan(score: Int) = cardScore > score + val isGreaterOrEqualToMaxScore: Boolean = value >= MAX_SCORE companion object { - private const val BUST_THRESHOLD = 21 - private const val BLACK_JACK_SCORE = 21 + private const val MAX_SCORE = 21 private const val ACE_BONUS_SCORE = 10 fun from(hand: Hand): HandScore { @@ -19,7 +14,7 @@ class HandScore( val score = ranks.sumOf { it.score() } return when { ranks.hasAce().not() -> score - score + ACE_BONUS_SCORE > BUST_THRESHOLD -> score + score + ACE_BONUS_SCORE > MAX_SCORE -> score else -> score + ACE_BONUS_SCORE }.let(::HandScore) } diff --git a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt index 27d521e66..307949330 100644 --- a/src/main/kotlin/blackjack/domain/player/CardPlayer.kt +++ b/src/main/kotlin/blackjack/domain/player/CardPlayer.kt @@ -14,11 +14,11 @@ interface CardPlayer { val isGreaterOrEqualToMaxScore: Boolean get() = score.isGreaterOrEqualToMaxScore - val isBlackJack: Boolean - get() = hand.isBlackJackCardSize && score.isBlackJackScore + infix fun isSameCardCount(count: Int): Boolean = hand.cardsCount == count + infix fun isSameScore(score: Int): Boolean = this.score.value == score - infix fun isGreaterCardScoreThan(other: Int): Boolean = - score isGreaterCardScoreThan other + infix fun isGreaterScoreThan(other: Int): Boolean = + score.value > other fun addCard(card: Card) { require(score.isGreaterOrEqualToMaxScore.not()) { "최대 점수 이상이라 카드를 더 받을 수 없습니다" } diff --git a/src/main/kotlin/blackjack/domain/player/DealerPlayer.kt b/src/main/kotlin/blackjack/domain/player/DealerPlayer.kt index 573ce15c9..294717d46 100644 --- a/src/main/kotlin/blackjack/domain/player/DealerPlayer.kt +++ b/src/main/kotlin/blackjack/domain/player/DealerPlayer.kt @@ -7,7 +7,7 @@ data class DealerPlayer( override val hand: Hand = Hand(), ) : CardPlayer { override fun hitOrStand(): Action { - if (this isGreaterCardScoreThan HIT_THRESHOLD_SCORE) return Action.STAND + if (this isGreaterScoreThan HIT_THRESHOLD_SCORE) return Action.STAND return Action.HIT } diff --git a/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt b/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt index b1e81f88f..8d7842ae1 100644 --- a/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt +++ b/src/main/kotlin/blackjack/view/dto/DealerCardsResultDto.kt @@ -4,5 +4,5 @@ import blackjack.domain.card.Card data class DealerCardsResultDto( val cards: List, - val cardScore: Int, + val score: Int, ) diff --git a/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt b/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt index 2cca62028..e7c4e6c16 100644 --- a/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt +++ b/src/main/kotlin/blackjack/view/dto/PlayerCardsResultDto.kt @@ -5,5 +5,5 @@ import blackjack.domain.card.Card data class PlayerCardsResultDto( val name: String, val cards: List, - val cardScore: Int, + val score: Int, ) diff --git a/src/main/kotlin/blackjack/view/output/OutputView.kt b/src/main/kotlin/blackjack/view/output/OutputView.kt index 22c184ad5..8588cdd3f 100644 --- a/src/main/kotlin/blackjack/view/output/OutputView.kt +++ b/src/main/kotlin/blackjack/view/output/OutputView.kt @@ -54,9 +54,9 @@ object OutputView { players: List, ) { println() - println(DEALER_FINAL_CARDS_MSG.format(extractCardsState(dealer.cards), dealer.cardScore)) + println(DEALER_FINAL_CARDS_MSG.format(extractCardsState(dealer.cards), dealer.score)) players.forEach { - println(PLAYER_FINAL_CARDS_MSG.format(it.name, extractCardsState(it.cards), it.cardScore)) + println(PLAYER_FINAL_CARDS_MSG.format(it.name, extractCardsState(it.cards), it.score)) } } diff --git a/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt b/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt index b9a437cfe..6e98948eb 100644 --- a/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt +++ b/src/test/kotlin/blackjack/domain/BlackJackJudgeTest.kt @@ -97,4 +97,27 @@ class BlackJackJudgeTest : DescribeSpec({ } } } + + describe("isBlackJack") { + context("플레이어가 블랙잭이면(카드가 2장이며, 해당 카드 합이 21)") { + val player = player(hand = hand(card(Rank.TEN), card(Rank.ACE))) + it("true가 반환된다") { + BlackJackJudge.isBlackJack(player) shouldBe true + } + } + + context("플레이어 카드가 2장이지만 21점이 아니면") { + val player = player(hand = hand(card(Rank.TEN), card(Rank.TWO))) + it("false가 반환된다") { + BlackJackJudge.isBlackJack(player) shouldBe false + } + } + + context("플레이어 카드 합이 21이지만 카드 수가 2장 초과면") { + val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN), card(Rank.TEN))) + it("false가 반환된다") { + BlackJackJudge.isBlackJack(player) shouldBe false + } + } + } }) diff --git a/src/test/kotlin/blackjack/domain/DealerTest.kt b/src/test/kotlin/blackjack/domain/DealerTest.kt index a55d3cc6e..72a882957 100644 --- a/src/test/kotlin/blackjack/domain/DealerTest.kt +++ b/src/test/kotlin/blackjack/domain/DealerTest.kt @@ -95,30 +95,30 @@ class DealerTest : DescribeSpec({ val result = dealer.score it("계산된 점수가 반환된다") { - result.cardScore shouldBe 21 + result.value shouldBe 21 } } } - describe("isGreaterCardScoreThan") { + describe("isGreaterScoreThan") { val score20cards = hand(card(Rank.QUEEN), card(Rank.QUEEN)) val dealer = DealerPlayer(score20cards) context("딜러보다 낮은 점수로 비교하면") { - val result = dealer isGreaterCardScoreThan 16 + val result = dealer isGreaterScoreThan 16 it("참을 반환한다") { result shouldBe true } } context("딜러보다 높은 점수로 비교하면") { - val result = dealer isGreaterCardScoreThan 21 + val result = dealer isGreaterScoreThan 21 it("거짓을 반환한다") { result shouldBe false } } context("딜러와 같은 점수로 비교하면") { - val result = dealer isGreaterCardScoreThan 20 + val result = dealer isGreaterScoreThan 20 it("거짓을 반환한다") { result shouldBe false diff --git a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt index a873003e1..fb54c665c 100644 --- a/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandScoreTest.kt @@ -8,14 +8,14 @@ import io.kotest.data.row import io.kotest.matchers.shouldBe class HandScoreTest : DescribeSpec({ - describe("cardScore") { + describe("HandScore.from(Hand)") { context("Ace를 1점으로 계산했을 때 합이 11이하라면") { forAll( row(hand(card(Rank.TWO), card(Rank.ACE)), 13), row(hand(card(Rank.TWO), card(Rank.THREE), card(Rank.FIVE), card(Rank.ACE)), 21), ) { hand, expect -> it("Ace 1개를 11점으로 계산한다") { - HandScore.from(hand).cardScore shouldBe expect + HandScore.from(hand).value shouldBe expect } } } @@ -24,7 +24,7 @@ class HandScoreTest : DescribeSpec({ val hand = hand(card(Rank.TEN), card(Rank.TEN), card(Rank.ACE)) it("Ace를 1점으로 계산한다") { - HandScore.from(hand).cardScore shouldBe 21 + HandScore.from(hand).value shouldBe 21 } } @@ -32,7 +32,7 @@ class HandScoreTest : DescribeSpec({ val hand = hand(card(Rank.TWO), card(Rank.THREE)) it("모든 점수를 그대로 계산한다") { - HandScore.from(hand).cardScore shouldBe 5 + HandScore.from(hand).value shouldBe 5 } } } @@ -60,67 +60,4 @@ class HandScoreTest : DescribeSpec({ } } } - - describe("isGreaterCardScoreThan") { - val score = HandScore(16) - context("보다 작은 점수를 비교") { - val result = score isGreaterCardScoreThan 15 - it("참을 반환") { - result shouldBe true - } - } - context("보다 큰 점수를 비교") { - val result = score isGreaterCardScoreThan 20 - it("거짓을 반환") { - result shouldBe false - } - } - context("같은 점수를 비교") { - val result = score isGreaterCardScoreThan 16 - it("거짓을 반환") { - result shouldBe false - } - } - } - - describe("isGreaterGameScoreThan") { - context("보다 작은 점수를 비교") { - val score = HandScore(16) - val result = score isGreaterCardScoreThan 15 - it("참을 반환") { - result shouldBe true - } - } - - context("보다 큰 점수를 비교") { - val score = HandScore(16) - val result = score isGreaterCardScoreThan 20 - it("거짓을 반환") { - result shouldBe false - } - } - context("같은 점수를 비교") { - val score = HandScore(16) - val result = score isGreaterCardScoreThan 16 - it("거짓을 반환") { - result shouldBe false - } - } - } - - describe("isBlackJackScore") { - context("블랙잭 점수인 21점이면") { - val handScore = HandScore(21) - it("true 반환") { - handScore.isBlackJackScore shouldBe true - } - } - - context("블랙잭 점수인 21점이 아니면") { - val handScore = HandScore(22) - it("false 반환") { - handScore.isBlackJackScore shouldBe false - } - } - } }) diff --git a/src/test/kotlin/blackjack/domain/card/HandTest.kt b/src/test/kotlin/blackjack/domain/card/HandTest.kt index e72520804..5f41bb38a 100644 --- a/src/test/kotlin/blackjack/domain/card/HandTest.kt +++ b/src/test/kotlin/blackjack/domain/card/HandTest.kt @@ -1,6 +1,5 @@ package blackjack.domain.card -import blackjack.mock.card import io.kotest.core.spec.style.DescribeSpec import io.kotest.matchers.shouldBe @@ -42,20 +41,4 @@ class HandTest : DescribeSpec({ } } } - - describe("isBlackJackCardSize") { - context("블랙잭 조건인 카드가 2장이라면") { - val hand = Hand(mutableListOf(card(), card())) - it("true 반환") { - hand.isBlackJackCardSize shouldBe true - } - } - - context("블랙잭 조건인 카드가 2장이 아니라면") { - val hand = Hand(mutableListOf(card(), card(), card())) - it("false 반환") { - hand.isBlackJackCardSize shouldBe false - } - } - } }) diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index b1af7d1b8..2c8fcbb06 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -103,7 +103,7 @@ class PlayerTest : DescribeSpec({ val result = player.score it("플레이어 점수는 21점이다") { - result.cardScore shouldBe 21 + result.value shouldBe 21 } } } @@ -189,27 +189,4 @@ class PlayerTest : DescribeSpec({ player1 shouldBe player2 } } - - describe("isBlackJack") { - context("플레이어가 블랙잭이면(카드가 2장이며, 해당 카드 합이 21)") { - val player = player(hand = hand(card(Rank.TEN), card(Rank.ACE))) - it("true가 반환된다") { - player.isBlackJack shouldBe true - } - } - - context("플레이어 카드가 2장이지만 21점이 아니면") { - val player = player(hand = hand(card(Rank.TEN), card(Rank.TWO))) - it("false가 반환된다") { - player.isBlackJack shouldBe false - } - } - - context("플레이어 카드 합이 21이지만 카드 수가 2장 초과면") { - val player = player(hand = hand(card(Rank.ACE), card(Rank.TEN), card(Rank.TEN))) - it("false가 반환된다") { - player.isBlackJack shouldBe false - } - } - } }) From f96543d707116a6eb778cf25942070876f35f5e8 Mon Sep 17 00:00:00 2001 From: bongbong Date: Thu, 7 Dec 2023 00:42:01 +0900 Subject: [PATCH 15/19] =?UTF-8?q?[step4]=20chore:=20BUST=5FTHRESHOLD=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20BLACK=5FJACK=5FSCORE=20=EC=82=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/domain/BlackJackJudge.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt index ee916c598..fa6bd0f15 100644 --- a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt +++ b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt @@ -5,7 +5,6 @@ import blackjack.domain.player.Player import blackjack.domain.result.game.VictoryStatus object BlackJackJudge { - private const val BUST_THRESHOLD = 21 private const val BUST_SCORE = 0 private const val BLACK_JACK_CARD_COUNT = 2 private const val BLACK_JACK_SCORE = 21 @@ -25,5 +24,5 @@ object BlackJackJudge { if (this.isBust()) BUST_SCORE else this.score.value - private fun CardPlayer.isBust() = score.value > BUST_THRESHOLD + private fun CardPlayer.isBust() = score.value > BLACK_JACK_SCORE } From ebae7ee523d5e15408ef80eba0aad8bf941ef09e Mon Sep 17 00:00:00 2001 From: bongbong Date: Sun, 31 Dec 2023 13:33:59 +0900 Subject: [PATCH 16/19] =?UTF-8?q?[step4]=20feat:=20BetAmount=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blackjack/controller/InputProcessor.kt | 4 +- .../controller/ViewInputProcessor.kt | 7 ++- .../kotlin/blackjack/domain/batting/Amount.kt | 8 ++-- .../blackjack/domain/batting/BetAmount.kt | 14 ++++++ .../blackjack/domain/batting/BetBoard.kt | 18 +++++--- .../blackjack/domain/batting/PlayerBet.kt | 6 +-- .../blackjack/domain/betting/AmountTest.kt | 14 +++--- .../blackjack/domain/betting/BetBoardTest.kt | 45 ++++++++++--------- .../blackjack/domain/betting/PlayerBetTest.kt | 5 ++- .../blackjack/mock/InputProcessorMock.kt | 6 +-- 10 files changed, 73 insertions(+), 54 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/batting/BetAmount.kt diff --git a/src/main/kotlin/blackjack/controller/InputProcessor.kt b/src/main/kotlin/blackjack/controller/InputProcessor.kt index 612bdcf92..93cc9e627 100644 --- a/src/main/kotlin/blackjack/controller/InputProcessor.kt +++ b/src/main/kotlin/blackjack/controller/InputProcessor.kt @@ -1,14 +1,14 @@ package blackjack.controller import blackjack.domain.Action -import blackjack.domain.batting.Amount +import blackjack.domain.batting.BetAmount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames interface InputProcessor { fun playerNames(): PlayerNames - fun playerBet(player: Player): Amount + fun playerBet(player: Player): BetAmount fun playerAction(player: Player): Action } diff --git a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt index 7bb9fd549..38cec3ba4 100644 --- a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt @@ -1,7 +1,7 @@ package blackjack.controller import blackjack.domain.Action -import blackjack.domain.batting.Amount +import blackjack.domain.batting.BetAmount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames import blackjack.view.dto.PlayerNameDto @@ -11,9 +11,8 @@ class ViewInputProcessor : InputProcessor { override fun playerNames(): PlayerNames = InputView.playerNames().let(PlayerNames::from) - override fun playerBet(player: Player): Amount = - InputView.playerBet(player.nameDto()) - .let { Amount.betAmount(it.toInt()) } + override fun playerBet(player: Player): BetAmount = + BetAmount(InputView.playerBet(player.nameDto()).toBigDecimal()) override fun playerAction(player: Player): Action { val action = InputView.playerAction(player.nameDto()) diff --git a/src/main/kotlin/blackjack/domain/batting/Amount.kt b/src/main/kotlin/blackjack/domain/batting/Amount.kt index d814a1279..887d622dd 100644 --- a/src/main/kotlin/blackjack/domain/batting/Amount.kt +++ b/src/main/kotlin/blackjack/domain/batting/Amount.kt @@ -6,6 +6,10 @@ data class Amount( val value: BigDecimal, ) : Comparable { + init { + require(value >= BigDecimal.ZERO) { "금액은 0이상이어야 합니다" } + } + operator fun plus(other: Amount): Amount = Amount(value.plus(other.value)) operator fun times(count: Int): Amount = Amount(value.times(count.toBigDecimal())) @@ -16,9 +20,5 @@ data class Amount( companion object { val ZERO = Amount(BigDecimal.ZERO) - fun betAmount(amount: Int): Amount { - require(amount > 0) { "베팅 금액은 0보다 커야 합니다" } - return Amount(amount.toBigDecimal()) - } } } diff --git a/src/main/kotlin/blackjack/domain/batting/BetAmount.kt b/src/main/kotlin/blackjack/domain/batting/BetAmount.kt new file mode 100644 index 000000000..0de4190de --- /dev/null +++ b/src/main/kotlin/blackjack/domain/batting/BetAmount.kt @@ -0,0 +1,14 @@ +package blackjack.domain.batting + +import java.math.BigDecimal + +@JvmInline +value class BetAmount( + val value: Amount, +) { + init { + require(value > Amount.ZERO) { "베팅 금액은 0보다 커야 합니다" } + } + + constructor(amount: BigDecimal) : this(Amount(amount)) +} diff --git a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt index a9faeadf9..5fa415ced 100644 --- a/src/main/kotlin/blackjack/domain/batting/BetBoard.kt +++ b/src/main/kotlin/blackjack/domain/batting/BetBoard.kt @@ -48,23 +48,27 @@ data class BetBoard( private fun findPlayerBet(name: PlayerName): PlayerBet = playerBets[name] ?: throw IllegalArgumentException("베팅에 참여한 플레이어가 아닙니다") - private fun Player.payout(dealer: Dealer, betAmount: Amount): Amount = + private fun Player.payout(dealer: Dealer, betAmount: BetAmount): Amount = when (BlackJackJudge.judgeVictory(this, dealer)) { VictoryStatus.WIN -> calculateWinAmount(this, betAmount) VictoryStatus.LOSS -> Amount.ZERO - VictoryStatus.PUSH -> betAmount + VictoryStatus.PUSH -> betAmount.value } - private fun calculateWinAmount(player: Player, betAmount: Amount): Amount = - if (BlackJackJudge.isBlackJack(player)) betAmount * BLACK_JACK_MULTIPLIER + betAmount - else betAmount * 2 + private fun calculateWinAmount(player: Player, betAmount: BetAmount): Amount { + val amount = betAmount.value + return when (BlackJackJudge.isBlackJack(player)) { + true -> amount * BLACK_JACK_MULTIPLIER + amount + false -> amount * 2 + } + } companion object { private val BLACK_JACK_MULTIPLIER = BigDecimal(1.5) - fun of(players: Players, betAmount: (player: Player) -> Amount): BetBoard = + fun of(players: Players, betAmount: (player: Player) -> BetAmount): BetBoard = BetBoard(players.value.associate { it.name to it.placeBet(betAmount) }.toMutableMap()) - private fun Player.placeBet(betAmount: (player: Player) -> Amount): PlayerBet = + private fun Player.placeBet(betAmount: (player: Player) -> BetAmount): PlayerBet = PlayerBet.Placed(this.name, betAmount(this)) } } diff --git a/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt index 2e40410e3..4cc3878eb 100644 --- a/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt +++ b/src/main/kotlin/blackjack/domain/batting/PlayerBet.kt @@ -8,16 +8,16 @@ sealed interface PlayerBet { data class Placed( override val playerName: PlayerName, - val betAmount: Amount, + val betAmount: BetAmount, ) : PlayerBet data class Finished( override val playerName: PlayerName, - val betAmount: Amount, + val betAmount: BetAmount, val payoutAmount: Amount, ) : PlayerBet { - val profit: Profit = Profit.of(betAmount, payoutAmount) + val profit: Profit = Profit.of(betAmount.value, payoutAmount) companion object { fun of(betPlaced: Placed, payoutAmount: Amount): Finished = Finished( playerName = betPlaced.playerName, diff --git a/src/test/kotlin/blackjack/domain/betting/AmountTest.kt b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt index b679689d4..c63291604 100644 --- a/src/test/kotlin/blackjack/domain/betting/AmountTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/AmountTest.kt @@ -8,19 +8,19 @@ import java.lang.IllegalArgumentException import java.math.BigDecimal class AmountTest : StringSpec({ - "0보다 큰 베팅 금액이 생성된다" { - val amount = 3_000 + "0이상의 금액이 생성된다" { + val amount = BigDecimal.valueOf(3_000) - val result = Amount.betAmount(amount) + val result = Amount(amount) - result.value shouldBe amount.toBigDecimal() + result.value shouldBe amount } - "0이하의 금액은 베팅 금액이 될 수 없다" { - val amount = 0 + "0미만의 숫자는 금액이 될 수 없다" { + val amount = -1.toBigDecimal() shouldThrowExactly { - Amount.betAmount(amount) + Amount(amount) } } diff --git a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt index d6bd412fa..794866699 100644 --- a/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/BetBoardTest.kt @@ -1,6 +1,7 @@ package blackjack.domain.betting import blackjack.domain.Dealer +import blackjack.domain.batting.BetAmount import blackjack.domain.batting.BetBoard import blackjack.domain.batting.PlayerBet import blackjack.domain.card.Rank @@ -24,8 +25,8 @@ class BetBoardTest : DescribeSpec({ describe("BettingBoard") { context("플레이어 이름으로 베팅 금액 (kim: 3_000원, lee: 4_000원)이 등록되면") { val players = players(player("kim"), player("lee")) - val kimAmount = amount(3_000) - val leeAmount = amount(4_000) + val kimAmount = BetAmount(amount(3_000)) + val leeAmount = BetAmount(amount(4_000)) val betAmount = { player: Player -> if (player.name.value == "kim") kimAmount else leeAmount @@ -54,13 +55,13 @@ class BetBoardTest : DescribeSpec({ describe("playerProfit") { context("플레이어가 (베팅: 1000, 받은돈: 0) 으로 베팅을 완료했을 때") { val name = PlayerName("kim") - val betAmount = amount(1000) + val betAmount = BetAmount(amount(1000)) val payoutAmount = amount(0) val finishedBet = PlayerBet.Finished(name, betAmount, payoutAmount) val betBord = BetBoard( mutableMapOf( name to finishedBet, - PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), amount(5_000), amount(5_000)) + PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), BetAmount(amount(5_000)), amount(5_000)) ) ) @@ -73,12 +74,12 @@ class BetBoardTest : DescribeSpec({ context("베팅이 완료되지 않은 플레이어의 수익을 조회하면") { val name = PlayerName("kim") - val betAmount = amount(1000) + val betAmount = BetAmount(amount(1000)) val placedBet = PlayerBet.Placed(name, betAmount) val betBord = BetBoard( mutableMapOf( name to placedBet, - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -93,8 +94,8 @@ class BetBoardTest : DescribeSpec({ context("플레이어1의 수익이 2000, 플레이어2의 수익이 -1000일 때") { val betBoard = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Finished(PlayerName("kim"), amount(2_000), amount(4_000)), - PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), amount(1_000), amount(0)), + PlayerName("kim") to PlayerBet.Finished(PlayerName("kim"), BetAmount(amount(2_000)), amount(4_000)), + PlayerName("lee") to PlayerBet.Finished(PlayerName("lee"), BetAmount(amount(1_000)), amount(0)), ) ) @@ -108,8 +109,8 @@ class BetBoardTest : DescribeSpec({ context("모든 플레이어 베팅이 끝나지 않았다면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) it("수익 조회에 실패한다") { @@ -124,8 +125,8 @@ class BetBoardTest : DescribeSpec({ context("딜러를 블랙잭으로 이긴 플레이어라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -143,8 +144,8 @@ class BetBoardTest : DescribeSpec({ context("딜러를 일반 숫자로 이긴 플레이어라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -162,8 +163,8 @@ class BetBoardTest : DescribeSpec({ context("딜러와 플레이어 둘 다 블랙잭으로 무승부라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -180,8 +181,8 @@ class BetBoardTest : DescribeSpec({ context("딜러와 둘다 일반 점수로 무승부라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -198,8 +199,8 @@ class BetBoardTest : DescribeSpec({ context("플레이어의 카드가 버스트라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) @@ -216,8 +217,8 @@ class BetBoardTest : DescribeSpec({ context("플레이어도 딜러도 모두 버스트라면") { val betBord = BetBoard( mutableMapOf( - PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), amount(5_000)), - PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), amount(5_000)) + PlayerName("kim") to PlayerBet.Placed(PlayerName("kim"), BetAmount(amount(5_000))), + PlayerName("lee") to PlayerBet.Placed(PlayerName("lee"), BetAmount(amount(5_000))) ) ) diff --git a/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt b/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt index 569326cf6..9a15ddae6 100644 --- a/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt +++ b/src/test/kotlin/blackjack/domain/betting/PlayerBetTest.kt @@ -1,5 +1,6 @@ package blackjack.domain.betting +import blackjack.domain.batting.BetAmount import blackjack.domain.batting.PlayerBet import blackjack.domain.player.PlayerName import blackjack.mock.amount @@ -11,7 +12,7 @@ class PlayerBetTest : StringSpec({ "BetPlaced와 수익 정보로 BetFinished 생성" { val placed = PlayerBet.Placed( playerName = PlayerName("kim"), - betAmount = amount(3_000) + betAmount = BetAmount(amount(3_000)) ) val payoutAmount = amount(0) @@ -23,7 +24,7 @@ class PlayerBetTest : StringSpec({ } "BetFinished의 수익 조회" { - val betAmount = amount(10000) + val betAmount = BetAmount(amount(10000)) val payoutAmount = amount(25000) val bet = PlayerBet.Finished(PlayerName("lee"), betAmount, payoutAmount) diff --git a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt index b56415c7a..ed9a31dec 100644 --- a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt +++ b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt @@ -2,18 +2,18 @@ package blackjack.mock import blackjack.controller.InputProcessor import blackjack.domain.Action -import blackjack.domain.batting.Amount +import blackjack.domain.batting.BetAmount import blackjack.domain.player.Player import blackjack.domain.player.PlayerNames class InputProcessorMock( private val playerNames: List = listOf("kim", "lee"), private val action: Action = Action.HIT, - private val betAmount: Amount = amount(3_000) + private val betAmount: BetAmount = BetAmount(amount(3_000)) ) : InputProcessor { override fun playerNames(): PlayerNames = playerNames.let(PlayerNames::from) - override fun playerBet(player: Player): Amount = betAmount + override fun playerBet(player: Player): BetAmount = betAmount override fun playerAction(player: Player): Action = action } From c66f8bf4410c2096ca3dd6505b71f8bbaa9d1d30 Mon Sep 17 00:00:00 2001 From: bongbong Date: Sun, 31 Dec 2023 13:37:38 +0900 Subject: [PATCH 17/19] =?UTF-8?q?[step4]=20chore:=20=EA=B0=80=EB=8F=85?= =?UTF-8?q?=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A4=84=EB=B0=94=EA=BF=88=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/blackjack/controller/ViewResultProcessor.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt index 68165a68d..9f80c9139 100644 --- a/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewResultProcessor.kt @@ -35,11 +35,9 @@ object ViewResultProcessor { } private fun drawCardResult(result: GameEndResult) { - val dealerDto = - DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value) - val playersDto = result.playerResults.map { - PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value) - } + val dealerDto = DealerCardsResultDto(result.dealerHand.cards, result.dealerScore.value) + val playersDto = result.playerResults + .map { PlayerCardsResultDto(it.name.value, it.hand.cards, it.score.value) } OutputView.drawCardsResults(dealerDto, playersDto) } From 02e8ce033b02dfd82f6359e87e4cb8831f246421 Mon Sep 17 00:00:00 2001 From: bongbong Date: Sun, 31 Dec 2023 13:54:14 +0900 Subject: [PATCH 18/19] =?UTF-8?q?[step4]=20chore:=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20let=20=EC=82=AC=EC=9A=A9=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/controller/ViewInputProcessor.kt | 4 ++-- src/test/kotlin/blackjack/mock/InputProcessorMock.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt index 38cec3ba4..92fa684e2 100644 --- a/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt +++ b/src/main/kotlin/blackjack/controller/ViewInputProcessor.kt @@ -9,7 +9,7 @@ import blackjack.view.input.InputView class ViewInputProcessor : InputProcessor { override fun playerNames(): PlayerNames = - InputView.playerNames().let(PlayerNames::from) + PlayerNames.from(InputView.playerNames()) override fun playerBet(player: Player): BetAmount = BetAmount(InputView.playerBet(player.nameDto()).toBigDecimal()) @@ -19,7 +19,7 @@ class ViewInputProcessor : InputProcessor { return toPlayerAction(action) } - private fun Player.nameDto(): PlayerNameDto = this.let(PlayerNameDto::from) + private fun Player.nameDto(): PlayerNameDto = PlayerNameDto.from(this) private fun toPlayerAction(action: String): Action = when (action) { "y" -> Action.HIT diff --git a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt index ed9a31dec..77cc29cc8 100644 --- a/src/test/kotlin/blackjack/mock/InputProcessorMock.kt +++ b/src/test/kotlin/blackjack/mock/InputProcessorMock.kt @@ -12,7 +12,7 @@ class InputProcessorMock( private val betAmount: BetAmount = BetAmount(amount(3_000)) ) : InputProcessor { - override fun playerNames(): PlayerNames = playerNames.let(PlayerNames::from) + override fun playerNames(): PlayerNames = PlayerNames.from(playerNames) override fun playerBet(player: Player): BetAmount = betAmount override fun playerAction(player: Player): Action = action From 25c8735e034298a120c477cd4551a20284fcaa52 Mon Sep 17 00:00:00 2001 From: bongbong Date: Sun, 31 Dec 2023 14:10:55 +0900 Subject: [PATCH 19/19] =?UTF-8?q?[step4]=20refactor:=20=EC=9D=98=EB=AF=B8?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20BUST=5FSCORE=20=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/blackjack/domain/BlackJackJudge.kt | 9 ++------- src/main/kotlin/blackjack/domain/batting/Amount.kt | 4 ++-- src/main/kotlin/blackjack/domain/card/HandScore.kt | 3 +++ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt index fa6bd0f15..b4fe695b8 100644 --- a/src/main/kotlin/blackjack/domain/BlackJackJudge.kt +++ b/src/main/kotlin/blackjack/domain/BlackJackJudge.kt @@ -5,24 +5,19 @@ import blackjack.domain.player.Player import blackjack.domain.result.game.VictoryStatus object BlackJackJudge { - private const val BUST_SCORE = 0 private const val BLACK_JACK_CARD_COUNT = 2 private const val BLACK_JACK_SCORE = 21 fun judgeVictory(player: Player, dealer: Dealer): VictoryStatus = when { player.isBust() -> VictoryStatus.LOSS - player.gameScore() > dealer.gameScore() -> VictoryStatus.WIN - player.gameScore() == dealer.gameScore() -> VictoryStatus.PUSH + dealer.isBust() || player.score > dealer.score -> VictoryStatus.WIN + player.score == dealer.score -> VictoryStatus.PUSH else -> VictoryStatus.LOSS } fun isBlackJack(player: Player): Boolean = (player isSameCardCount BLACK_JACK_CARD_COUNT) && (player isSameScore BLACK_JACK_SCORE) - private fun CardPlayer.gameScore(): Int = - if (this.isBust()) BUST_SCORE - else this.score.value - private fun CardPlayer.isBust() = score.value > BLACK_JACK_SCORE } diff --git a/src/main/kotlin/blackjack/domain/batting/Amount.kt b/src/main/kotlin/blackjack/domain/batting/Amount.kt index 887d622dd..fee55ec48 100644 --- a/src/main/kotlin/blackjack/domain/batting/Amount.kt +++ b/src/main/kotlin/blackjack/domain/batting/Amount.kt @@ -4,7 +4,7 @@ import java.math.BigDecimal data class Amount( val value: BigDecimal, -) : Comparable { +) { init { require(value >= BigDecimal.ZERO) { "금액은 0이상이어야 합니다" } @@ -15,7 +15,7 @@ data class Amount( operator fun times(count: BigDecimal): Amount = Amount(value * count) - override fun compareTo(other: Amount): Int = + operator fun compareTo(other: Amount): Int = this.value.compareTo(other.value) companion object { diff --git a/src/main/kotlin/blackjack/domain/card/HandScore.kt b/src/main/kotlin/blackjack/domain/card/HandScore.kt index 3a6c12278..bdcac177b 100644 --- a/src/main/kotlin/blackjack/domain/card/HandScore.kt +++ b/src/main/kotlin/blackjack/domain/card/HandScore.kt @@ -3,6 +3,9 @@ package blackjack.domain.card data class HandScore( val value: Int, ) { + operator fun compareTo(other: HandScore): Int = + this.value.compareTo(other.value) + val isGreaterOrEqualToMaxScore: Boolean = value >= MAX_SCORE companion object {