Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[step4] 블랙잭 (베팅) & step3 코드리뷰 반영 #812

Open
wants to merge 32 commits into
base: sarahan774
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dc0a080
[코드리뷰][step3] 현재 카드의 합이 21이 넘지 않는 경우 y 를 선택했을 때 카드를 뽑도록 로직 수정
SaraHan774 Dec 16, 2024
0f27917
[코드리뷰][step3] 매직넘버 const 로 대체
SaraHan774 Dec 16, 2024
daa723b
[코드리뷰][step3] PlayerResult -> GameResult 로 변수명 변경, GameResult 에 대한 카운…
SaraHan774 Dec 16, 2024
4fd6961
[코드리뷰][step3] PlayerResultCalculator class -> object class 로 변경, Blac…
SaraHan774 Dec 16, 2024
126be39
[코드리뷰][step3] 프로그래밍 제약 조건을 만족하기 위해 while 문의 if 제거
SaraHan774 Dec 16, 2024
ce186a3
[코드리뷰][step3] 테스트 오류 수정, Player 클래스 버그 수정
SaraHan774 Dec 16, 2024
8b26a92
[코드리뷰][step3] 테스트 코드 수정: DisplayName 사용 해보기, Parameterized 테스트 적용 가능한…
SaraHan774 Dec 16, 2024
ec35146
[코드리뷰][step3] ktlintformat
SaraHan774 Dec 16, 2024
38c0b9d
[step4] 플레이어는 게임을 시작할 때 베팅 금액을 입력받는다
SaraHan774 Dec 16, 2024
995344b
[step4] 베팅 관련 규칙을 추가
SaraHan774 Dec 16, 2024
3e26696
[step4] Dealer 의 베팅 금액 설정 로직 수정
SaraHan774 Dec 16, 2024
9944f63
[step4] 로그 삭제
SaraHan774 Dec 16, 2024
17e1d6b
[step4] 계산로직 버그 수정
SaraHan774 Dec 16, 2024
bf2dcc2
[step4] 테스트 수정
SaraHan774 Dec 16, 2024
ae042c1
[step4] ktlintformat
SaraHan774 Dec 16, 2024
1ab98c9
[step4] 테스트 보강
SaraHan774 Dec 16, 2024
6861336
[step4] 결과 추출하는 부분 리팩토링
SaraHan774 Dec 16, 2024
d6ac32a
[step4] 주석 보강
SaraHan774 Dec 16, 2024
204492f
[step4] ktlintformat
SaraHan774 Dec 16, 2024
c2de14a
[step4] 딜러 수익 계산 로직 버그 수정
SaraHan774 Dec 16, 2024
7f3f1ce
[step4] 딜러 수익 계산 로직 버그 수정 - 불필요한 필드 제거
SaraHan774 Dec 16, 2024
4ab56d9
[step4] GameResult 파일 분리
SaraHan774 Dec 16, 2024
4d5b3ca
[step4] 확장함수 제거, 중복 제거
SaraHan774 Dec 16, 2024
8876e4b
[step4] Participant 책임 변경
SaraHan774 Dec 16, 2024
e77f4f5
[step4] 암시적으로 Dealer 의 수익을 세팅하는 부분을 명시적으로 변경
SaraHan774 Dec 16, 2024
28a20fa
[코드리뷰][step4] Deprecated 클래스 및 함수 제거
SaraHan774 Dec 16, 2024
334f5d8
[코드리뷰][step4] GameResult 로부터 베팅금액 반환하는 로직을 GameResult의 책임으로 이동시킴
SaraHan774 Dec 16, 2024
4a2a268
[코드리뷰][step4] ResultManager 생성자 주입에서 메서드주입으로 변경
SaraHan774 Dec 17, 2024
7c81404
[코드리뷰][step4] 일부 구조 변경
SaraHan774 Dec 19, 2024
3c5be6d
[코드리뷰][step4] profitMoney 는 Player 가 관리하도록 함
SaraHan774 Dec 19, 2024
223331d
[코드리뷰][step4] lint
SaraHan774 Dec 19, 2024
fde1866
[코드리뷰][step4] test 오류 수정
SaraHan774 Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,21 @@
- 기능 요구사항 바탕으로 테스트코드 작성
- 테스트코드 컴파일 되도록 코드 수정
- 게임의 주체를 나타내는 Player 객체 정의
- 블랙잭 카드를 표현할 수 있는 객체 정의
- 블랙잭 카드를 표현할 수 있는 객체 정의


- 3단계 목록이 날라갔네 ..

### 4단계 구현 기능 목록

- [x] 플레이어는 게임을 시작할 때 베팅 금액을 정해야 한다.
- 베팅 관련 규칙 추가
- [x] 카드를 추가로 뽑아 21을 초과할 경우 베팅 금액을 모두 잃게 된다.
- [x] 처음 두 장의 카드 합이 21일 경우 블랙잭이 되면 베팅 금액의 1.5 배를 딜러에게 받는다.
- [x] 딜러와 플레이어가 모두 동시에 블랙잭인 경우 플레이어는 베팅한 금액을 돌려받는다.
- [x] 딜러가 21을 초과하면 그 시점까지 남아 있던 플레이어들은 가지고 있는 패에 상관 없이 승리해 베팅 금액을 받는다.

### 4단계 프로그래밍 요구사항
- 모든 엔티티를 작게 유지한다.
- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
- 딜러와 플레이어에서 발생하는 중복 코드를 제거해야 한다.
6 changes: 3 additions & 3 deletions src/main/kotlin/blackjack/controller/BlackJackController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ object BlackJackController {
val players =
dealer.initPlayers(
fetchPlayerNames = { InputView.readPlayerNames() },
getBettingAmount = { name -> InputView.readBettingAmount(name) },
onPlayerInit = { names ->
ResultView.printPlayerInitMessage(names)
ResultView.printDealerWithCard(dealer.getCardForInitialDisplay())
Expand Down Expand Up @@ -59,8 +60,7 @@ object BlackJackController {
players.onEach { player ->
ResultView.printFinalScoresForPlayer(player)
}

val result = BlackJackResultManager(dealer, players).getResult()
ResultView.printFinalWinLose(result)
val result = BlackJackResultManager.getResult(dealer, players)
ResultView.printFinalProfit(result)
}
}
34 changes: 34 additions & 0 deletions src/main/kotlin/blackjack/domain/BetMoney.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package blackjack.domain

import blackjack.domain.GameResult.*
import java.math.BigDecimal
import java.math.RoundingMode

@JvmInline
value class BetMoney(private val amount: BigDecimal) {
fun getAmount(gameResult: GameResult): BigDecimal {
return when (gameResult) {
BLACK_JACK -> getAmountOnBlackJack()
WIN -> getOriginalBetAmount()
PUSH -> getOriginalBetAmount()
LOSE -> getAmountOnLose()
BUST -> getAmountOnBust()
Comment on lines +11 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이러한 연관관계를 GameResult의 멤버변수로 표현해보는 방향은 어떻게 생각하시나요?

GameResult(val amountStrategy: (BetMoney -> BigDecimal))

}
}

private fun getOriginalBetAmount(): BigDecimal {
return amount
}

private fun getAmountOnBlackJack(): BigDecimal {
return amount.multiply((1.5).toBigDecimal()).setScale(0, RoundingMode.DOWN)
}

private fun getAmountOnBust(): BigDecimal {
return -(amount)
}

private fun getAmountOnLose(): BigDecimal {
return -(amount)
}
}
42 changes: 18 additions & 24 deletions src/main/kotlin/blackjack/domain/BlackJackResultManager.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
package blackjack.domain

class BlackJackResultManager(
private val dealer: Dealer,
private val players: Players,
private val playerResultCalculator: PlayerResultCalculator = PlayerResultCalculator(),
) {
fun getResult(): BlackJackResult {
val dealerScore = dealer.cardsSum
val playersWinLose =
players.value.associateWith { player ->
playerResultCalculator.calculate(dealerScore, player.cardsSum)
}
import java.math.BigDecimal

val dealerWinCount = playersWinLose.count { it.value == PlayerResult.LOSE }
val dealerLoseCount = playersWinLose.count { it.value == PlayerResult.WIN }
return BlackJackResult(dealerWinCount, dealerLoseCount, PlayerToResultMap(playersWinLose))
object BlackJackResultManager {
fun getResult(
dealer: Dealer,
players: Players,
): BlackJackResult {
val playersProfits =
players.getPlayersToProfitMoney(
dealer.isBlackJackInitially,
dealer.cardsSum,
)
return BlackJackResult(playersProfits)
}
}

data class BlackJackResult(
val dealerWinCount: Int,
val dealerLoseCount: Int,
val playerToResultMap: PlayerToResultMap,
)

@JvmInline
value class PlayerToResultMap(val value: Map<Player, PlayerResult>)
val playerToProfit: PlayerToProfitMoney,
) {
val dealerProfitMoney: ProfitMoney get() = ProfitMoney().apply { set(-playerToProfit.getAllProfitSum) }
}

enum class PlayerResult {
WIN,
LOSE,
data class PlayerToProfitMoney(val value: Map<Player, ProfitMoney>) {
val getAllProfitSum: BigDecimal get() = value.values.sumOf { it.getCurrentProfit() }
}
8 changes: 0 additions & 8 deletions src/main/kotlin/blackjack/domain/Card.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ package blackjack.domain
data class Card(val rank: Rank, val suit: Suit) {
private fun isAce() = rank == Rank.ACE

fun isOverMaxSum(currentCardSum: Int): Boolean {
return if (isAce()) {
currentCardSum + ACE_VALUE_ONE > MAX_SUM && currentCardSum + this.rank.value > MAX_SUM
} else {
currentCardSum + this.rank.value > MAX_SUM
}
}

companion object {
const val ACE_VALUE_ONE = 1
const val MAX_SUM = 21
Expand Down
22 changes: 18 additions & 4 deletions src/main/kotlin/blackjack/domain/Dealer.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
package blackjack.domain

import java.math.BigDecimal

class Dealer(
private val drawCard: () -> Card,
) : Participant(drawCard = drawCard) {
fun initPlayers(
fetchPlayerNames: () -> List<String>,
getBettingAmount: (String) -> BigDecimal,
onPlayerInit: (List<String>) -> Unit,
): Players {
val names = fetchPlayerNames()
val players = names.map { name -> Player(name = name, drawCard = drawCard) }
val nameAndBets = names.associateWith(getBettingAmount)
val players =
Players(
nameAndBets.map { (name, bet) ->
Player(
name = name,
betMoney = BetMoney(bet),
drawCard = drawCard,
)
},
)
onPlayerInit(names)
return Players(value = players)
return players
}

fun drawOneMoreCardIfNeeded(onDrawCard: () -> Unit) {
addCardIfAvailable(requireCard = { drawCard() }, onDrawCard = onDrawCard)
addCardIfAvailable(requireCard = drawCard, onDrawCard = onDrawCard)
}

override fun isAddCardEnabled(): Boolean {
return cardsSum <= 16
return cardsSum <= DEALER_DRAW_ONE_MORE_CARD_THRESHOLD
}

fun getCardForInitialDisplay(): Card {
Expand All @@ -28,5 +41,6 @@ class Dealer(

companion object {
private const val DEALER_CARD_COUNT = 2
private const val DEALER_DRAW_ONE_MORE_CARD_THRESHOLD = 16
}
}
38 changes: 38 additions & 0 deletions src/main/kotlin/blackjack/domain/GameResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package blackjack.domain

enum class GameResult {
WIN,
BUST,
LOSE,
PUSH,
BLACK_JACK,
;

companion object {
fun getGameResultsWith(
isPlayerBlackJackInitially: Boolean,
isDealerBlackJackInitially: Boolean,
dealerCardSum: Int,
playerCardSum: Int,
Comment on lines +13 to +16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4개의 파라미터를 다루어야 하는 부분은 부담으로 다가올 수 있을것 같습니다.

4개의 파라미터가 타입도 2개씩 동일하여 순서를 잘못 지켜 주입한다면 의도치 않게 동작할 수도 있을것 같네요!

이러한 값을 추상화하여 표현해볼 수 없을지 한번 고민해보시면 좋을것 같습니다.

): GameResult {
return when {
isPlayerBlackJackInitially && isDealerBlackJackInitially.not() -> BLACK_JACK
isPlayerBlackJackInitially && isDealerBlackJackInitially -> PUSH
else -> fromScores(dealerCardSum, playerCardSum)
}
}

private fun fromScores(
dealerScore: Int,
playerScore: Int,
): GameResult {
return when {
dealerScore > Card.MAX_SUM -> WIN // Dealer bust
playerScore > Card.MAX_SUM -> BUST // Player bust
dealerScore > playerScore -> LOSE
playerScore > dealerScore -> WIN
else -> PUSH
}
}
}
}
9 changes: 7 additions & 2 deletions src/main/kotlin/blackjack/domain/Participant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package blackjack.domain

abstract class Participant(
private val drawCard: () -> Card,
initialCardCount: Int = 2,
) {
private val _cards = mutableListOf<Card>()
val cards: Cards = Cards(_cards)
val cardsSum: Int get() = cards.sumValues()
val isBlackJackInitially: Boolean

init {
repeat(initialCardCount) { addCard(drawCard()) }
repeat(INITIAL_CARD_COUNT) { addCard(drawCard()) }
isBlackJackInitially = cardsSum == Card.MAX_SUM
Comment on lines +9 to +13
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여러가지 상태를 관리하는것 같습니다.

Cards를 가지고 점수를 계산하고 이러한 상태가 관리되는것 같은데요

디자인 패턴중 상태패턴을 이용한다면 이러한 부분을 개선해볼 수 있을것 같습니다.

반드시 개선을 바라는 부분은 아니니 참고만 해주셔도 좋습니다. 😄

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pci2676 요거 혹시 간단하게 예시를 주실 수 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

블랙잭 피드백에 나와있는 내용이군요!
이거 근데 현재 구조에서 적용하려면 코드를 아예 새로 짜야 할 것 같아서
별도로 한번 적용 해보는 연습을 해봐야 할 것 같습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 드리는 리뷰를 반드시 반영할 필요는 없으니 참고만 해주셔도 됩니다~

}

private fun addCard(card: Card) {
Expand All @@ -29,4 +30,8 @@ abstract class Participant(
}

abstract fun isAddCardEnabled(): Boolean

companion object {
private const val INITIAL_CARD_COUNT = 2
}
}
23 changes: 17 additions & 6 deletions src/main/kotlin/blackjack/domain/Player.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ package blackjack.domain

class Player(
val name: String,
private val betMoney: BetMoney,
private val drawCard: () -> Card,
) : Participant(drawCard = drawCard) {
private lateinit var currentCard: Card
private val profitMoney: ProfitMoney = ProfitMoney()

fun play(
isDrawCard: (String) -> Boolean,
onDrawCard: () -> Unit,
onExitPlay: () -> Unit,
) {
while (isDrawCard(name)) {
currentCard = drawCard()
val isCardAdded = addCardIfAvailable(requireCard = { currentCard }, onDrawCard = onDrawCard)
if (isCardAdded.not()) break
var shouldContinue = shouldContinueDrawing(isDrawCard)
while (shouldContinue) {
val isCardAdded = addCardIfAvailable(requireCard = drawCard, onDrawCard = onDrawCard)
shouldContinue = isCardAdded && shouldContinueDrawing(isDrawCard)
}
onExitPlay()
}

fun getProfitMoney(gameResult: GameResult): ProfitMoney {
val betMoneyAmount = betMoney.getAmount(gameResult)
profitMoney.set(betMoneyAmount)
return profitMoney
}

private fun shouldContinueDrawing(isDrawCard: (String) -> Boolean): Boolean {
return isDrawCard(name)
}

override fun isAddCardEnabled(): Boolean {
return currentCard.isOverMaxSum(cardsSum).not()
return cardsSum < Card.MAX_SUM
}
}
15 changes: 0 additions & 15 deletions src/main/kotlin/blackjack/domain/PlayerResultCalculator.kt

This file was deleted.

22 changes: 20 additions & 2 deletions src/main/kotlin/blackjack/domain/Players.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@ package blackjack.domain

data class Players(val value: List<Player>) {
fun onEachPreparePlay(action: (Player) -> Unit): Players {
value.forEach { action(it) }
onEach(action)
return this
}

fun onEachStartPlay(action: (Player) -> Unit): Players {
value.forEach { action(it) }
onEach(action)
return this
}

fun onEach(action: (Player) -> Unit): Players {
value.forEach { action(it) }
return this
}

fun getPlayersToProfitMoney(
isDealerBlackJack: Boolean,
dealerCardSum: Int,
): PlayerToProfitMoney {
return PlayerToProfitMoney(
value.associateWith { player ->
val gameResult =
GameResult.getGameResultsWith(
isPlayerBlackJackInitially = player.isBlackJackInitially,
isDealerBlackJackInitially = isDealerBlackJack,
dealerCardSum = dealerCardSum,
playerCardSum = player.cardsSum,
)
player.getProfitMoney(gameResult)
},
)
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/blackjack/domain/ProfitMoney.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package blackjack.domain

import java.math.BigDecimal

class ProfitMoney {
private var current: BigDecimal = BigDecimal.ZERO

fun getCurrentProfit(): BigDecimal {
return current
}

fun set(amount: BigDecimal) {
current = amount
}
}
Comment on lines +5 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kotlin을 사용하신다면 가능한 불변한 객체를 설계하여 사용하시는것을 권장드립니다~

https://kotlinlang.org/docs/coding-conventions.html#idiomatic-use-of-language-features

9 changes: 9 additions & 0 deletions src/main/kotlin/blackjack/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package blackjack.view

import java.math.BigDecimal

object InputView {
fun readPlayerNames(): List<String> {
println("\n* 게임에 참여할 사람의 이름을 임력하세요 (쉼표 기준으로 분리)")
Expand All @@ -10,6 +12,13 @@ object InputView {
return names
}

fun readBettingAmount(name: String): BigDecimal {
println("\n* $name 의 베팅 금액은?")
val amount =
readlnOrNull()?.trim()?.toBigDecimalOrNull() ?: throw IllegalStateException("Invalid Betting amount")
return amount
}

fun readIsDrawMore(name: String): Boolean {
println("\n* $name 는 한 장의 카드를 더 받겠습니까? (예는 y, 아니오는 n)")
val yesOrNo = readlnOrNull()?.trim() ?: "n"
Expand Down
Loading