-
Notifications
You must be signed in to change notification settings - Fork 59
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단계 블랙잭 제출합니다. #29
Changes from all commits
6d56055
62534e6
0ef6f34
574a268
732ffb2
17dd825
e485c71
12c2936
4ecb57d
665cd3f
ebb267e
873652f
c34208b
c000e8a
164a338
f07fa50
462fe5b
6f81c4d
4e25946
dc551ca
b8593a6
6cc7c41
d81e48a
2642d6f
f9ea704
8758b12
8136214
97827b6
7f13916
36f007c
b9370d2
4554fca
a422885
2a7727c
d484e96
ab844ca
505e1eb
3d245ae
9538695
6ac1cdd
01f139f
e464505
9832ec0
584e709
d5772ca
25916d4
b1a5d65
319818b
cfc3417
0858ccf
4790920
1bff0ae
a990b7c
08c3ab7
4d3b86a
ffd6f14
d6fe092
837a6fe
5ab0252
ab2b1d8
22bb2a4
2a3853d
08ccdc3
1052618
1a52be5
bb1bebd
3844089
2fd6f72
f7fafc1
b856fda
e98eb4d
fea1fe5
b5d1be7
cf4acd2
9c69293
99e833a
2ca1467
800dee5
24518fa
94fbd5a
bd49760
0fa56ff
6d09998
c6d0c08
eb4d450
52b79d8
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 |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# 기능 요구 사항 | ||
- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. | ||
- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다. 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다. | ||
- 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다. | ||
- 게임을 완료한 후 각 플레이어별로 승패를 출력한다. | ||
|
||
# 기능 목록 | ||
- 카드 모양 | ||
- [x] 카드 모양에는 스페이드, 다이아몬드, 하트, 클로버가 있다. | ||
|
||
- 카드 숫자 | ||
- [x] 카드 숫자의 범위는 1부터 13이다. | ||
- [x] 카드 숫자를 문자로 반환한다. | ||
- [x] 1, 11, 12, 13은 각각 A, J, Q, K로 반환한다. | ||
- [x] 2 ~ 10은 그대로 반환한다. | ||
- [x] 카드 숫자를 정수형으로 반환한다. | ||
|
||
- 카드 | ||
- [x] 카드는 각 모양별로 2부터 10, A, J, Q, K가 존재한다. | ||
|
||
- 플레이어의 카드 목록 | ||
- [x] 카드 목록에 카드를 추가한다. | ||
- [x] 총 점수를 반환한다. | ||
- [x] 단, A는 기존 총 점수에 11점을 더한 값이 21점 이하이면 11점으로 계산하고, 21점을 초과하면 1점으로 계산한다. | ||
- [x] 단, J, Q, K는 모두 10점으로 계산한다. | ||
- [x] 2 ~ 10은 그대로 계산한다. | ||
|
||
- 카드덱 | ||
- [x] 카드 하나를 뽑는다. | ||
- [x] 카드덱은 아직 뽑히지 않은 카드를 갖는다. | ||
- [x] 뽑은 카드는 아직 뽑히지 않은 카드 중 하나여야 한다. | ||
|
||
- 플레이어 | ||
- [x] 플레이어는 이름을 갖는다. | ||
- [x] 플레이어는 카드 목록에 카드를 추가한다. | ||
- [x] 플레이어는 자신이 뽑은 카드 목록을 갖는다. | ||
- [x] 플레이어는 카드의 합이 21을 초과하지 않는지 확인한다. | ||
- [x] 자신의 점수를 반환한다. | ||
- [x] 자신이 가진 카드를 반환한다. | ||
|
||
- 플레이어들 | ||
- [x] 모든 플레이어의 카드를 2장씩 뽑는다. | ||
|
||
- 딜러 | ||
- [x] 딜러는 스테이인지 확인한다. | ||
- [x] 카드의 합이 17점 이상이면 스테이이다. | ||
- [x] 카드의 합이 16점 이하이면 스테이가 아니다. | ||
- [x] 딜러는 자신이 보유한 첫번째 카드를 반환한다. | ||
|
||
- 승부 결과 | ||
- [x] 플레이어의 승패를 결정한다. | ||
- [x] 플레이어가 21점을 초과하면 패배한다. | ||
- [x] 딜러만 21점을 초과하면 플레이어가 승리한다. | ||
- [x] 딜러와 플레이어 모두 21점을 초과하지 않고 플레이어가 딜러보다 점수가 높으면 플레이어가 승리한다. | ||
- [x] 딜러와 플레이어 모두 21점을 초과하지 않고 플레이어와 딜러의 점수가 같으면 무승부이다. | ||
- [x] 딜러와 플레이어 모두 21점을 초과하지 않고 딜러가 플레이어보다 점수가 높으면 플레이어가 패배한다. | ||
|
||
- 명령어 | ||
- [x] 명령어는 y 또는 n이다. | ||
- [x] 단, 대소문자를 구분하지 않는다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package blackjack | ||
|
||
import blackjack.controller.BlackjackController | ||
|
||
fun main() { | ||
BlackjackController().start() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.domain.Blackjack | ||
import blackjack.domain.CardDeck | ||
import blackjack.domain.Player | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
class BlackjackController { | ||
fun start() { | ||
with(initBlackjack()) { | ||
setUpCard(this) | ||
|
||
val result = start(onDrawn = OutputView::printDrawn) | ||
OutputView.printBlackjackResult(result) | ||
} | ||
} | ||
|
||
private fun initBlackjack(): Blackjack = Blackjack(CardDeck(), enrollPlayers()) | ||
|
||
private fun enrollPlayers(): List<Player> = InputView.inputNames().map { name -> | ||
Player(name, needToDraw = { | ||
InputView.inputDrawCommand(name) | ||
}) | ||
} | ||
|
||
private fun setUpCard(blackJack: Blackjack) { | ||
drawInitialCards(blackJack) | ||
OutputView.printFirstOpenCards(blackJack.getFirstOpenCards()) | ||
OutputView.printInterval() | ||
} | ||
|
||
private fun drawInitialCards(blackJack: Blackjack) { | ||
blackJack.readyToStart() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackjack.domain | ||
|
||
class Blackjack(private val deck: CardDeck, private val participants: Participants) { | ||
constructor(deck: CardDeck, players: List<Player>) : this(deck, Participants(listOf(Dealer()) + players)) | ||
|
||
fun readyToStart() { | ||
participants.drawFirst(deck) | ||
} | ||
|
||
fun start(onDrawn: (Participant) -> Unit): BlackjackResult { | ||
participants.takePlayerTurns(deck, onDrawn) | ||
participants.takeDealerTurns(deck, onDrawn) | ||
|
||
return BlackjackResult( | ||
participants.getCardResults(), | ||
participants.getMatchResults(), | ||
) | ||
} | ||
|
||
fun getFirstOpenCards(): Map<String, List<Card>> = participants.getFirstOpenCards() | ||
Comment on lines
+6
to
+20
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 start(onDrawn: (Participant) -> Unit): Result {
participants.drawFirst(deck, onDrawn)
participants.draw(deck, onDrawn)
return ...
} |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package blackjack.domain | ||
|
||
data class BlackjackResult( | ||
val cardResults: List<CardResult>, | ||
val matchResults: List<MatchResult>, | ||
Comment on lines
+4
to
+5
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. CardResult랑 MatchResult를 나눌 필요가 있을까요? |
||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package blackjack.domain | ||
|
||
data class Card(val number: CardNumber, val suit: Suit) { | ||
fun getScore(): Int = number.score | ||
|
||
fun isAce(): Boolean = number == CardNumber.ACE | ||
|
||
companion object { | ||
private val CARDS = Suit.values().flatMap { suit -> | ||
CardNumber.values().map { cardNumber -> | ||
Card(cardNumber, suit) | ||
} | ||
} | ||
|
||
fun all(): List<Card> = CARDS.toList() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackjack.domain | ||
|
||
class CardDeck(deck: List<Card> = Card.all().shuffled()) { | ||
constructor(vararg cards: Card) : this(cards.toList()) | ||
|
||
private val deck: MutableList<Card> = deck.toMutableList() | ||
|
||
fun draw(): Card = deck.removeFirst() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package blackjack.domain | ||
|
||
enum class CardNumber(val score: Int) { | ||
ACE(1), | ||
TWO(2), | ||
THREE(3), | ||
FOUR(4), | ||
FIVE(5), | ||
SIX(6), | ||
SEVEN(7), | ||
EIGHT(8), | ||
NINE(9), | ||
TEN(10), | ||
JACK(10), | ||
QUEEN(10), | ||
KING(10); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package blackjack.domain | ||
|
||
data class CardResult( | ||
val participant: Participant, | ||
val cards: List<Card>, | ||
val scoreSum: Int, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package blackjack.domain | ||
|
||
class Cards(vararg cards: Card) { | ||
private val _items: MutableList<Card> by lazy { mutableListOf() } | ||
val items: List<Card> | ||
get() = _items.toList() | ||
|
||
init { | ||
_items.addAll(cards) | ||
} | ||
Comment on lines
+8
to
+10
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. _items 프로퍼티를 만들면서 생성자로 받은 cards를 초기화 할 수 있어요 |
||
|
||
fun add(card: Card) { | ||
_items.add(card) | ||
} | ||
|
||
fun getFirstCard(): Card = _items.first() | ||
|
||
fun calculateTotalScore(): Int { | ||
val score = _items.sumOf(Card::getScore) | ||
return calculateAceScore(score) | ||
} | ||
|
||
private fun calculateAceScore(score: Int): Int = | ||
if (hasAce() && !isOverBlackjack(score + BONUS_SCORE)) score + BONUS_SCORE else score | ||
|
||
fun isOverBlackjack(): Boolean = calculateTotalScore() > BLACKJACK_SCORE | ||
|
||
private fun isOverBlackjack(score: Int): Boolean = score > BLACKJACK_SCORE | ||
|
||
fun isStay(): Boolean = calculateTotalScore() >= STAY_SCORE | ||
|
||
private fun hasAce(): Boolean = _items.any(Card::isAce) | ||
|
||
companion object { | ||
private const val BONUS_SCORE = 10 | ||
private const val BLACKJACK_SCORE = 21 | ||
private const val STAY_SCORE = 17 | ||
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. 블랙잭 게임에서 스테이라는 의미는 어떠한 점수를 나타내는 상태가 아닙니다.
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단계와 피드백을 보면서 깨달았습니다.. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package blackjack.domain | ||
|
||
class Dealer : Participant(DEALER_NAME) { | ||
override fun getFirstOpenCards(): List<Card> = listOf(getFirstCard()) | ||
|
||
override fun canDraw(): Boolean = !isStay() | ||
|
||
companion object { | ||
private const val DEALER_NAME = "딜러" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package blackjack.domain | ||
|
||
enum class GameResult { | ||
WIN, DRAW, LOSE; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package blackjack.domain | ||
|
||
data class MatchResult( | ||
val participant: Participant, | ||
val winCount: Int, | ||
val loseCount: Int, | ||
val drawCount: Int | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package blackjack.domain | ||
|
||
abstract class Participant(val name: String) { | ||
private val cards = Cards() | ||
|
||
abstract fun getFirstOpenCards(): List<Card> | ||
|
||
abstract fun canDraw(): Boolean | ||
|
||
fun getTotalScore(): Int = cards.calculateTotalScore() | ||
|
||
fun isBust(): Boolean = cards.isOverBlackjack() | ||
|
||
fun isStay(): Boolean = cards.isStay() | ||
|
||
infix fun judge(other: Participant): GameResult = when { | ||
isBust() && other.isBust() -> GameResult.DRAW | ||
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. 블랙잭 규칙에서는 상대의 점수가 무엇이든지 상관없이 자신이 버스트라면 패배입니다. 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. 그렇군요..! 제가 규칙을 잘못 이해하고 있었던 것 같습니다! |
||
isBust() -> GameResult.LOSE | ||
other.isBust() -> GameResult.WIN | ||
getTotalScore() == other.getTotalScore() -> GameResult.DRAW | ||
getTotalScore() > other.getTotalScore() -> GameResult.WIN | ||
Comment on lines
+20
to
+21
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. 이 함수를 끝내기까지 getTotalScore()가 몇번 호출이될까요? |
||
else -> GameResult.LOSE | ||
} | ||
|
||
fun addCard(card: Card) { | ||
cards.add(card) | ||
} | ||
|
||
fun getCards(): List<Card> = cards.items | ||
|
||
fun getFirstCard(): Card = cards.getFirstCard() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package blackjack.domain | ||
|
||
class Participants(private val participants: List<Participant>) { | ||
init { | ||
require(participants.size in MINIMUM_PARTICIPANTS..MAXIMUM_PARTICIPANTS) { | ||
"블랙잭은 딜러를 포함하여 최소 ${MINIMUM_PARTICIPANTS}명에서 최대 ${MAXIMUM_PARTICIPANTS}명의 플레이어가 참여 가능합니다. (현재 플레이어수 : ${participants.size}명)" | ||
} | ||
} | ||
|
||
fun drawFirst(deck: CardDeck) { | ||
participants.forEach { participant -> | ||
participant.addCard(deck.draw()) | ||
participant.addCard(deck.draw()) | ||
} | ||
} | ||
|
||
fun takePlayerTurns(deck: CardDeck, onDrawn: (Participant) -> Unit) { | ||
getPlayers().forEach { participant -> | ||
drawUntilCanDraw(participant, deck, onDrawn) | ||
} | ||
} | ||
|
||
fun takeDealerTurns(deck: CardDeck, onDrawn: (Participant) -> Unit) { | ||
drawUntilCanDraw(getDealer(), deck, onDrawn) | ||
} | ||
|
||
private fun drawUntilCanDraw( | ||
participant: Participant, | ||
deck: CardDeck, | ||
onDrawn: (Participant) -> Unit | ||
) { | ||
while (participant.canDraw()) { | ||
draw(participant, deck) | ||
onDrawn(participant) | ||
} | ||
} | ||
|
||
fun getMatchResults(): List<MatchResult> = listOf(getDealerMatchResult()) + getPlayerMatchResults() | ||
|
||
private fun getDealerMatchResult(): MatchResult { | ||
var (win, lose, draw) = Triple(0, 0, 0) | ||
getPlayers().forEach { player -> | ||
when (getDealer() judge player) { | ||
GameResult.WIN -> win++ | ||
GameResult.LOSE -> lose++ | ||
GameResult.DRAW -> draw++ | ||
} | ||
} | ||
return MatchResult(getDealer(), win, lose, draw) | ||
} | ||
|
||
private fun getPlayerMatchResults(): List<MatchResult> = getPlayers().map { player -> | ||
var (win, lose, draw) = Triple(0, 0, 0) | ||
when (player judge getDealer()) { | ||
GameResult.WIN -> win++ | ||
GameResult.LOSE -> lose++ | ||
GameResult.DRAW -> draw++ | ||
} | ||
MatchResult(player, win, lose, draw) | ||
} | ||
|
||
fun getCardResults(): List<CardResult> = participants.map { participant -> | ||
CardResult( | ||
participant, | ||
participant.getCards(), | ||
participant.getTotalScore() | ||
) | ||
} | ||
|
||
fun getFirstOpenCards(): Map<String, List<Card>> = participants.associate { it.name to it.getFirstOpenCards() } | ||
|
||
private fun getPlayers(): List<Participant> = participants.filterIsInstance<Player>() | ||
|
||
private fun getDealer(): Participant = participants.first { it is Dealer } | ||
|
||
private fun draw(participant: Participant, deck: CardDeck) { | ||
participant.addCard(deck.draw()) | ||
} | ||
|
||
companion object { | ||
private const val MINIMUM_PARTICIPANTS = 2 | ||
private const val MAXIMUM_PARTICIPANTS = 8 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package blackjack.domain | ||
|
||
class Player(name: String, private val needToDraw: () -> Boolean = { true }) : Participant(name) { | ||
override fun getFirstOpenCards(): List<Card> = getCards() | ||
|
||
override fun canDraw(): Boolean = !isBust() && needToDraw() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package blackjack.domain | ||
|
||
enum class Suit { | ||
SPADE, HEART, DIAMOND, CLOVER; | ||
} |
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.
함수를 분리하지 않아도? 괜찮아 보여요!
setupCard()와 drawInitialCards()가 어떤 차이인지 헷갈림!
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.
앗 그렇군요..!
어차피 한 줄이고 굳이 나누지 않는 것이 가독성이 좋아보입니다!