-
Notifications
You must be signed in to change notification settings - Fork 82
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
[블랙잭 1단계] 모찌 미션 제출합니다. #124
base: wondroid-world
Are you sure you want to change the base?
Changes from all commits
d9f4ea4
c9e3b8a
2df5152
7ceb108
d3d99c2
5d8b925
6d8e4d1
e902fdf
7fe01d4
61ed0d5
9c24b94
90a4195
5b79da7
47dc2ec
3557ebc
48d8577
48e2386
ea6300e
e55f6e2
a4a29b4
a0ccfcc
8147bce
bbce347
fd9af80
827acb6
16be104
e44a767
9100d77
fa8bedd
1a91b51
7088c4e
8a55fdc
520e9b9
1325edb
a90b02a
43bca8f
aeebccf
6ff398c
929540a
9b3d0e5
fa7d4c4
d2bb180
eb8b486
d395b56
bb1979e
701086a
18f3c58
5aa7ecd
feda5e3
5390252
14d5ccb
dd2cd76
60ff224
b4a8fbc
2cce28d
a60f022
3ff8699
196df71
05980db
9024c63
09916bb
69e1097
e1779e9
87b91af
08cba24
70c7164
cbebd8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,63 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
|
||
- 카드들 상태 | ||
- [X] 게임 진행 중 카드들의 상태는 Blackjack, Bust, None이 있다. | ||
- [X] 카드들의 점수와 처음 턴 여부를 받아서 카드들의 상태를 반환한다. | ||
- Blackjack는 처음 받은 카드의 합이 21이다. | ||
- Bust는 카드의 합이 21초과이다. | ||
- None은 카드의 합이 21미만이다. | ||
- 게임 결과 | ||
- [X] 게임 결과는 Push, Win, Lose가 있고, 한국어 게임 결과를 가진다. | ||
- [X] Push는 기준 점수가 비교 점수와 같다. | ||
- [X] Win는 기준 점수가 비교 점수보다 높다. | ||
- [X] Lose는 기준 점수가 비교 점수보다 낮다. | ||
- 플레이어 | ||
- [X] 플레이어는 이름과 카드 리스트를 가진다. | ||
- [X] 플레이어는 카드를 추가로 받을 수 있다. | ||
- [X] 플레이어가 이름만 가질 경우, 가진 카드 리스트는 비어있다. | ||
- 플레이어 이름 검증 로직 | ||
- [X] 플레이어의 이름은 1자 이상 5자 이내이다. | ||
- [X] 플레이어 이름은 공백일 수 없다. | ||
- [X] 플레이어 이름은 딜러가 될 수 없다. | ||
- [X] 플레이어 이름은 중복될 수 없다. | ||
- 카드 모양 | ||
- [X] 카드 모양은 클로버, 하트, 다이아몬드, 스페이드를 가진다. | ||
- [X] 각 모양에 대해 한글 이름을 가진다. | ||
- 카드 | ||
- [X] Card는 모양과 denomination(끗수)를 가진다. | ||
- [X] Card의 끗수가 J, Q, K이면 숫자는 10이다. | ||
- [X] Card의 끗수가 2~10 사이의 숫자이면 숫자 그대로 가진다. | ||
- [X] 위의 끗수가 아닐 경우엔 숫자는 0이다. | ||
- 카드들 | ||
- [X] 카드들은 생성자로 카드 리스트를 가진다. | ||
- [X] Ace 카드가 0개 일때, 카드들의 총합을 반환한다. | ||
- [X] Ace 카드가 1개이고 카드의 합이 11미만 일때, Ace카드를 11로 판단한다. | ||
- [X] Ace 카드가 1개이고 카드의 합이 11이상 일때, Ace카드를 1로 판단한다. | ||
- [X] Ace 카드가 2개이고 카드의 합이 10미만 일때, Ace카드의 합을 12로 판단한다. | ||
- Ace의 카드 2장 중 1장은 11, 1장은 1로 판단한다. | ||
- [X] Ace 카드가 2개이고 카드의 합이 10이상 일때, Ace카드의 합을 2로 판단한다. | ||
- Ace의 카드 2장 다 1로 판단한다. | ||
- [X] Ace 카드가 3개이고 카드의 합이 9미만 일때, Ace카드의 합을 13로 판단한다. | ||
- Ace의 카드 3장 중 1장은 11, 2장은 1로 판단한다. | ||
- [X] Ace 카드가 3개이고 카드의 합이 9이상 일때, Ace카드의 합을 3으로 판단한다. | ||
- Ace의 카드 3장 다 1로 판단한다. | ||
- [X] Ace 카드가 4개이고 카드의 합이 8미만 일때, Ace카드의 합을 14로 판단한다. | ||
- Ace의 카드 4장 중 1장은 11, 3장은 1로 판단한다. | ||
- [X] Ace 카드가 4개이고 카드의 합이 8이상 일때, Ace카드를 합을 4으로 판단한다. | ||
- Ace의 카드 4장 다 1로 판단한다. | ||
- [X] 카드들에 카드를 추가할 수 있다. | ||
- 플레이어 행동 | ||
- [X] 응답은 Y, N로만 받을 수 있다. | ||
- [X] 플레이 행동은 hit과 stay로 나뉜다. | ||
- [X] 딜러 카드의 합이 16 이하일 경우 hit을 반환하고, 아닐 경우엔 stay를 반환한다. | ||
- 딜러 | ||
- [X] 딜러는 이름과 카드들을 가진다. | ||
- [X] 딜러는 이름이 없을 경우, 딜러라는 이름을 가진다. | ||
- [X] 딜러의 초기 카드 리스트는 비어있다. | ||
- [X] 딜러는 카드를 추가로 받을 수 있다. | ||
- 카드 생성기 | ||
- [X] 무작위로 섞은 카드 번들을 생성한다. | ||
- 카드 덱 | ||
- [X] 카드 덱은 첫번째 순서의 카드부터 차례대로 카드를 반환한다. | ||
- 끗수 | ||
- [X] 끗수는 제목과 숫자를 가진다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack | ||
|
||
import blackjack.controller.BlackjackController | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
fun main() { | ||
val blackjackController = BlackjackController(InputView(), OutputView()) | ||
blackjackController.run() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.model.CardDeck | ||
import blackjack.model.CardsMaker | ||
import blackjack.model.CardsStatus | ||
import blackjack.model.Dealer | ||
import blackjack.model.GameResult | ||
import blackjack.model.Player | ||
import blackjack.model.PlayerBehavior | ||
import blackjack.model.Players | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
class BlackjackController( | ||
private val inputView: InputView, | ||
private val outputView: OutputView, | ||
) { | ||
private val cardDeck = CardDeck(CardsMaker.CARDS) | ||
|
||
fun run() { | ||
outputView.printStartMessage() | ||
val players: Players = inputView.readPlayers() | ||
val dealer = Dealer() | ||
|
||
getCards(players, dealer) | ||
playGames(players, dealer) | ||
} | ||
|
||
private fun getCards( | ||
players: Players, | ||
dealer: Dealer, | ||
) { | ||
repeat(2) { | ||
getCardsToPlayer(players.value) | ||
getCardsToDealer(dealer) | ||
} | ||
outputView.printPlayersCards(dealer, players.value) | ||
} | ||
|
||
private fun getCardsToPlayer(players: List<Player>) { | ||
players.forEach { player -> | ||
val card = cardDeck.pickCard() | ||
player.appendCard(card) | ||
} | ||
} | ||
|
||
private fun getCardsToDealer(dealer: Dealer) { | ||
val card = cardDeck.pickCard() | ||
dealer.appendCard(card) | ||
} | ||
|
||
private fun playGames( | ||
players: Players, | ||
dealer: Dealer, | ||
) { | ||
if (dealer.isBlackjack(true)) { | ||
val blackjackPlayers: List<Player> = players.findBlackjackPlayer() | ||
updateGameResult(players, dealer) | ||
outputView.printDealerBlackjackMessage(dealer, blackjackPlayers) | ||
displayResult(players, dealer) | ||
return | ||
} | ||
players.value.forEach { player -> | ||
playGame(player, dealer) | ||
} | ||
displayResult(players, dealer) | ||
} | ||
|
||
private fun updateGameResult( | ||
players: Players, | ||
dealer: Dealer, | ||
) { | ||
players.value.forEach { player -> | ||
if (isAllPlayerBlackjack(player, dealer)) return@forEach | ||
player.updateResult(GameResult.WIN) | ||
dealer.updateResult(player.cards.calculateScore()) | ||
} | ||
} | ||
|
||
private fun isAllPlayerBlackjack( | ||
player: Player, | ||
dealer: Dealer, | ||
): Boolean { | ||
if (player.isBlackjack(true)) { | ||
player.updateResult(GameResult.PUSH) | ||
dealer.updateResult(player.cards.calculateScore()) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
private fun playGame( | ||
player: Player, | ||
dealer: Dealer, | ||
) { | ||
if (isPlayerBlackjack(player, dealer)) return | ||
executePlayerGameLogic(player) | ||
executeDealerGameLogic(dealer) | ||
val dealerResult: GameResult = dealer.updateResult(dealer.cards.calculateScore()) | ||
player.updateResult(dealerResult) | ||
} | ||
|
||
private fun isPlayerBlackjack( | ||
player: Player, | ||
dealer: Dealer, | ||
): Boolean { | ||
if (player.isBlackjack(true)) { | ||
val dealerResult: GameResult = dealer.updateResult(CardsStatus.BLACKJACK_SCORE) | ||
player.updateResult(dealerResult) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
private fun executePlayerGameLogic(player: Player) { | ||
while (!player.isBust()) { | ||
outputView.printPlayerBehaviorGuide(player) | ||
val playerBehavior: PlayerBehavior = inputView.readPlayerBehavior() | ||
|
||
if (executePlayerBehavior(playerBehavior, player)) break | ||
} | ||
} | ||
|
||
private fun executePlayerBehavior( | ||
playerBehavior: PlayerBehavior, | ||
player: Player, | ||
): Boolean { | ||
when (playerBehavior) { | ||
PlayerBehavior.HIT -> { | ||
player.appendCard(cardDeck.pickCard()) | ||
outputView.printPlayerCard(player) | ||
if (isPlayerBust(player)) return true | ||
} | ||
|
||
PlayerBehavior.STAY -> return true | ||
} | ||
return false | ||
} | ||
|
||
private fun isPlayerBust(player: Player): Boolean { | ||
if (player.isBust()) { | ||
outputView.printBust(player) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
private fun executeDealerGameLogic(dealer: Dealer) { | ||
while (dealer.isHit()) { | ||
dealer.appendCard(cardDeck.pickCard()) | ||
outputView.printDealerGettingCard() | ||
if (isDealerBust(dealer)) break | ||
} | ||
} | ||
|
||
private fun isDealerBust(dealer: Dealer): Boolean { | ||
if (dealer.isBust()) { | ||
outputView.printBust(dealer) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
private fun displayResult( | ||
players: Players, | ||
dealer: Dealer, | ||
) { | ||
outputView.printResult(dealer, players) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack.model | ||
|
||
data class Card( | ||
val shape: CardShape, | ||
val denomination: Denomination, | ||
) { | ||
fun isDenominationAce(card: Card): Boolean = card.denomination == Denomination.ACE | ||
|
||
fun combine(): String = denomination.title + shape.koreanName | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
기본적으로 객체가 꽁꽁 숨겨져있고, 객체로 하여금 필요한 행동을 메시징 이라는 형태로 요청한다고 봐주시면 됩니다.
다만 위 예시는 메시징을 통해 가져오는 것과 어울리지 않습니다. 가령 메시징을 던지기 이전의 관심사 분리가 선행되어야 할 것으로 보입니다! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackjack.model | ||
|
||
class CardDeck( | ||
private val cards: List<Card>, | ||
) { | ||
private var index = 0 | ||
|
||
fun pickCard(): Card = cards[index++] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack.model | ||
|
||
enum class CardShape( | ||
val koreanName: String, | ||
) { | ||
DIAMOND("다이아몬드"), | ||
SPADE("스페이드"), | ||
HEART("하트"), | ||
CLOVER("클로버"), | ||
Comment on lines
+6
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 마찬가지로 카드 모형이 위와 같이 출력될 것이라는 것을 결정하는 것은 도메인이 아닌 UI 의 관심사입니다. 😓 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package blackjack.model | ||
|
||
class Cards( | ||
value: List<Card>, | ||
) { | ||
private val _value: MutableList<Card> = value.toMutableList() | ||
val value: List<Card> get() = _value.map { card -> card.copy() } | ||
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
var size: Int = this.value.size | ||
private set | ||
|
||
fun add(card: Card) { | ||
_value.add(card) | ||
} | ||
|
||
fun isBlackjack(firstTurn: Boolean): Boolean = CardsStatus.from(calculateScore(), firstTurn) == CardsStatus.BLACKJACK | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 블랙잭 상태는, 첫번째 턴이라기 보다는 "카드가 2장일 때 합계가 21점이면" 으로 접근을 하시는 방향이 좋을거에요! |
||
|
||
fun isBust(): Boolean = CardsStatus.from(calculateScore()) == CardsStatus.BUST | ||
|
||
fun calculateScore(): Int { | ||
val aceCount: Int = value.count { card -> card.isDenominationAce(card) } | ||
val score: Int = value.sumOf { card -> card.denomination.number } | ||
return when (aceCount) { | ||
1 -> if (score < 11) score + 11 else score + 1 | ||
2 -> if (score < 10) score + 11 + 1 else score + aceCount * 1 | ||
3 -> if (score < 9) score + 11 + 1 + 1 else score + aceCount * 1 | ||
4 -> if (score < 8) score + 11 + 1 + 1 + 1 else score + aceCount * 1 | ||
else -> score | ||
} | ||
} | ||
Comment on lines
+20
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 알고리즘적이 조금 복잡하게 느껴지는데 쉽게 생각해보세요!
|
||
|
||
fun getCardsInfomation(): List<String> = value.map { it.combine() } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package blackjack.model | ||
|
||
object CardsMaker { | ||
private val DENOMINATIONS: List<Denomination> = Denomination.entries | ||
private val SHAPES: List<CardShape> = CardShape.entries | ||
|
||
val CARDS: List<Card> = | ||
DENOMINATIONS | ||
.flatMap { denomination -> | ||
SHAPES.map { shape -> | ||
Card(shape, denomination) | ||
} | ||
}.shuffled() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackjack.model | ||
|
||
enum class CardsStatus { | ||
BLACKJACK, | ||
BUST, | ||
NONE, | ||
; | ||
|
||
companion object { | ||
const val BLACKJACK_SCORE = 21 | ||
|
||
fun from( | ||
cardsScore: Int, | ||
firstTurn: Boolean = false, | ||
): CardsStatus { | ||
if (firstTurn && cardsScore == 21) return BLACKJACK | ||
if (cardsScore > 21) return BUST | ||
return NONE | ||
} | ||
Comment on lines
+12
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 카드의 상태를 결정하는 것은 카드가 스스로 해야 하지 않을까요? 아래 시그니처와 비교하면 어떤가요? class Cards {
...
fun getStatus(): CardsStatus { ... }
} |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package blackjack.model | ||
|
||
class Dealer( | ||
private val dealerName: String = "딜러", | ||
override val cards: Cards = Cards(mutableListOf()), | ||
) : Player(dealerName) { | ||
private var _results: MutableMap<GameResult, Int> = mutableMapOf() | ||
val results: Map<GameResult, Int> get() = _results.toMap() | ||
|
||
override fun appendCard(card: Card) { | ||
cards.add(card) | ||
} | ||
|
||
fun isHit(): Boolean { | ||
val dealerScore = cards.calculateScore() | ||
return PlayerBehavior.from(dealerScore) == PlayerBehavior.HIT | ||
} | ||
|
||
fun updateResult(playerScore: Int): GameResult { | ||
val dealerScore: Int = cards.calculateScore() | ||
val result: GameResult = GameResult.of(dealerScore, playerScore) | ||
_results[result] = _results.getOrDefault(result, 0) + 1 | ||
return result | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package blackjack.model | ||
|
||
enum class Denomination( | ||
val title: String, | ||
val number: Int, | ||
) { | ||
ACE("A", 0), | ||
TWO("2", 2), | ||
THREE("3", 3), | ||
FOUR("4", 4), | ||
FIVE("5", 5), | ||
SIX("6", 6), | ||
SEVEN("7", 7), | ||
EIGHT("8", 8), | ||
NINE("9", 9), | ||
TEN("10", 10), | ||
JACK("J", 10), | ||
QUEEN("Q", 10), | ||
KING("K", 10), | ||
Comment on lines
+7
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
이미 enum에 점수를 넣은 것 부터 점수를 결정하는 규칙을 도메인에서 결정하였다고 보입니다. 다만 지금 블랙잭 규칙에서는 그러한 요구사항이 없기 때문에 enum에 포함되어도 괜찮다고 봅니다. |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자기 스스로가 Card인데 파라미터로 Card를 받는건 무엇인가요?