-
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단계 블랙잭 제출합니다. #8
Changes from 65 commits
9a30725
e3a31d0
7bebf1e
5e0266b
de3e7b2
ebb3b1b
99211ec
3505d6f
7240f04
a5e59e7
3890452
018f574
59766ea
b5a2618
a3a1cf8
de0c72f
466c804
1bc13ec
4764415
30cae0f
17b90cb
f5c7282
591d1db
2b0181e
dd9b03f
3e47095
60a6d56
6f7795c
5b8a7b6
85a8445
9e96394
528dd60
0f707d5
a1e4845
c3e8861
89ee94e
5549988
b58161f
aecfaa0
d497eb0
5ac413c
4ac7f57
aa8ee2f
7300126
dbc1728
f4066c2
71711db
cd1824f
8f8cbab
ff971be
c410c87
bf0c973
8c750d9
60ed19d
c96c354
da1b57d
b1dfd38
9cb6249
1e8a542
fefb027
8da9717
93d72d0
564f955
f374a5f
95b5e7a
e485867
e3844a1
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,19 @@ | ||
## 기능 구현 목록 | ||
- [X] 카드들을 구현한다. | ||
- [X] 문양을 구현한다. | ||
- [X] 숫자를 구현한다. | ||
- [X] 카드덱을 만든다. | ||
- [X] 딜러랑 유저를 만든다. | ||
- [X] 유저 각자의 카드덱을 만든다. | ||
- [X] 카드의 합을 구한다. | ||
- [X] 게임 초기 세팅을 한다. | ||
- [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,36 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.domain.BlackJack | ||
import blackjack.domain.BlackJackGame | ||
import blackjack.domain.Cards | ||
import blackjack.domain.blackJack | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
class BlackJackController( | ||
private val inputView: InputView, | ||
private val outputView: OutputView, | ||
) { | ||
fun run() { | ||
val blackJack = setUpBlackJack() | ||
outputView.outputInitState(blackJack) | ||
startBlackJack(blackJack) | ||
outputView.outputResult(blackJack) | ||
} | ||
|
||
private fun setUpBlackJack(): BlackJack = blackJack { | ||
cardDeck(Cards.all()) | ||
participants { | ||
dealer() | ||
guests(inputView.inputParticipants()) | ||
} | ||
draw() | ||
} | ||
|
||
private fun startBlackJack(blackJack: BlackJack) = | ||
BlackJackGame().apply { | ||
getCommand = inputView::inputDrawMore | ||
guestsTurn(blackJack.guests, blackJack.cardDeck, outputView::outputCard) | ||
dealerTurn(blackJack.dealer, blackJack.cardDeck, outputView::outputDealerDraw) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.Outcome.Companion.winTo | ||
|
||
data class 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.
위 요구 사항을 지켜보면 어떨까요? |
||
val cardDeck: CardDeck, | ||
val participants: Participants, | ||
) { | ||
val dealer: Dealer | ||
get() = participants.dealer | ||
|
||
val guests: List<Guest> | ||
get() = participants.guests | ||
|
||
fun getResult(): List<Outcome> = participants.guests.map { guest -> guest.winTo(participants.dealer) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package blackjack.domain | ||
|
||
fun blackJack(block: BlackJackBuilder.() -> Unit): BlackJack { | ||
return BlackJackBuilder().apply(block).build() | ||
} | ||
|
||
class BlackJackBuilder { | ||
private lateinit var cardDeck: CardDeck | ||
private lateinit var participants: Participants | ||
fun cardDeck(cards: List<Card>) { cardDeck = CardDeck(cards.shuffled()) } | ||
|
||
fun participants(block: ParticipantsBuilder.() -> Unit) { | ||
participants = ParticipantsBuilder().apply(block).build() | ||
} | ||
|
||
fun draw() = participants.all().forEach { | ||
it.draw(cardDeck.nextCard()) | ||
it.draw(cardDeck.nextCard()) | ||
} | ||
|
||
fun build(): BlackJack { | ||
return BlackJack(cardDeck, participants) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package blackjack.domain | ||
|
||
class BlackJackGame { | ||
lateinit var getCommand: (String) -> String | ||
|
||
fun guestsTurn(guests: List<Guest>, cardDeck: CardDeck, output: (User) -> Unit) = | ||
guests.forEach { guest -> guestTurn(guest, cardDeck, output) } | ||
|
||
fun dealerTurn(dealer: Dealer, cardDeck: CardDeck, output: () -> Unit) { | ||
if (dealer.isBlackJack) return | ||
if (dealer.isContinue) { | ||
dealer.draw(cardDeck.nextCard()) | ||
output() | ||
} | ||
} | ||
|
||
private fun guestTurn(guest: Guest, cardDeck: CardDeck, output: (User) -> Unit) { | ||
if (guest.isBlackJack) return | ||
when (getCommand(guest.name.toString())) { | ||
in DRAW_COMMANDS -> draw(guest, cardDeck, output) | ||
in END_TURN_COMMANDS -> output(guest) | ||
else -> this.guestTurn(guest, cardDeck, output) | ||
} | ||
} | ||
|
||
private fun draw(guest: Guest, cardDeck: CardDeck, output: (User) -> Unit) { | ||
guest.draw(cardDeck.nextCard()) | ||
output(guest) | ||
if (guest.isContinue) { | ||
guestTurn(guest, cardDeck, output) | ||
} | ||
} | ||
|
||
companion object { | ||
private val DRAW_COMMANDS = listOf("Y", "y") | ||
private val END_TURN_COMMANDS = listOf("N", "n") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package blackjack.domain | ||
|
||
data class Card(val mark: CardMark, val value: CardValue) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package blackjack.domain | ||
|
||
class CardDeck(cards: List<Card>) { | ||
private val cards: MutableList<Card> = cards.toMutableList() | ||
|
||
init { | ||
require(cards.toSet().size == cards.size) { ERROR_EXIST_DUPLICATE_CARDS } | ||
require(cards.size == CARDS_SIZE) { ERROR_INVALID_CARDS_SIZE } | ||
} | ||
|
||
val size: Int | ||
get() = cards.size | ||
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 nextCard(): Card = cards.removeFirst() | ||
companion object { | ||
private const val CARDS_SIZE = 52 | ||
private const val ERROR_INVALID_CARDS_SIZE = "카드덱 초기 사이즈는 ${CARDS_SIZE}장이어야 합니다." | ||
private const val ERROR_EXIST_DUPLICATE_CARDS = "카드덱에는 중복이 없어야 합니다." | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package blackjack.domain | ||
|
||
enum class CardMark { | ||
CLOVER, | ||
HEART, | ||
SPADE, | ||
DIA, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package blackjack.domain | ||
|
||
enum class CardValue(val value: Int) { | ||
ACE(1), | ||
KING(10), | ||
QUEEN(10), | ||
JACK(10), | ||
TEN(10), | ||
NINE(9), | ||
EIGHT(8), | ||
SEVEN(7), | ||
SIX(6), | ||
FIVE(5), | ||
FOUR(4), | ||
THREE(3), | ||
TWO(2), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package blackjack.domain | ||
|
||
class Cards(private val cards: Set<Card> = setOf()) { | ||
val size: Int | ||
get() = cards.size | ||
|
||
fun toList() = cards.toList() | ||
|
||
operator fun plus(card: Card): Cards = Cards(cards.plus(card)) | ||
|
||
fun containsACE() = cards.map { it.value }.contains(CardValue.ACE) | ||
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. isContainsAce()라는 네이밍이 좀 더 어울리지 않을까요? |
||
|
||
companion object { | ||
private val CARDS: List<Card> = CardMark.values().flatMap { mark -> | ||
CardValue.values().map { value -> Card(mark, value) } | ||
} | ||
|
||
fun all(): List<Card> = CARDS | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package blackjack.domain | ||
|
||
class Dealer(name: String = "딜러") : User(name) { | ||
override val isContinue: Boolean | ||
get() = Score(cards).maxScore < DEALER_MIN_NUMBER | ||
|
||
companion object { | ||
private const val DEALER_MIN_NUMBER = 17 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package blackjack.domain | ||
|
||
class Guest(name: String) : User(name) { | ||
override val isContinue: Boolean | ||
get() = isNotBust && isBlackJack.not() | ||
|
||
private val isNotBust: Boolean | ||
get() = Score(cards).minScore <= BLACKJACK_NUMBER | ||
|
||
companion object { | ||
private const val BLACKJACK_NUMBER = 21 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package blackjack.domain | ||
|
||
@JvmInline | ||
value class Name(private val value: String) { | ||
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. value class 사용 굿 👍 |
||
init { | ||
require(value.length in NAME_LENGTH_MIN..NAME_LENGTH_MAX) { ERROR_NAME_LENGTH_MAX } | ||
} | ||
|
||
override fun toString(): String = value | ||
|
||
companion object { | ||
private const val NAME_LENGTH_MIN = 1 | ||
private const val NAME_LENGTH_MAX = 20 | ||
private const val ERROR_NAME_LENGTH_MAX = "이름의 글자수는 $NAME_LENGTH_MIN~${NAME_LENGTH_MAX}사이 입니다." | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package blackjack.domain | ||
import java.lang.IllegalStateException | ||
|
||
enum class Outcome { | ||
WIN, DRAW, LOSE; | ||
|
||
companion object { | ||
private const val BLACKJACK_NUMBER = 21 | ||
|
||
fun User.winTo(other: User): Outcome = | ||
when { | ||
other.score > BLACKJACK_NUMBER && this.score > BLACKJACK_NUMBER -> DRAW | ||
other.score > BLACKJACK_NUMBER -> WIN | ||
this.score > BLACKJACK_NUMBER -> LOSE | ||
|
||
other.score == this.score -> DRAW | ||
this.score > other.score -> WIN | ||
other.score > this.score -> LOSE | ||
|
||
else -> throw IllegalStateException() | ||
} | ||
} | ||
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,8 @@ | ||
package blackjack.domain | ||
|
||
data class Participants( | ||
val dealer: Dealer, | ||
val guests: List<Guest>, | ||
) { | ||
fun all(): List<User> = listOf(dealer) + guests | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package blackjack.domain | ||
|
||
class ParticipantsBuilder { | ||
private lateinit var dealer: Dealer | ||
private lateinit var guests: List<Guest> | ||
|
||
fun dealer() { dealer = Dealer() } | ||
|
||
fun guests(names: List<String>) { guests = names.map { Guest(it) } } | ||
|
||
fun build(): Participants = Participants(dealer, guests) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package blackjack.domain | ||
|
||
class Score(val cards: Cards) { | ||
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. Score에 대한 테스트 코드도 작성해보시죠! |
||
val score: Int | ||
get() = if (isMaxScoreInRange) maxScore else minScore | ||
|
||
val minScore: Int | ||
get() = cards.toList().sumOf { it.value.value } | ||
|
||
val maxScore: Int | ||
get() = minScore + if (cards.containsACE() && validateAceCondition) ACE_OTHER_NUMBER_DIFF else 0 | ||
|
||
private val validateAceCondition: Boolean | ||
get() = minScore + ACE_OTHER_NUMBER_DIFF <= BLACKJACK_NUMBER | ||
|
||
private val isMaxScoreInRange: Boolean | ||
get() = maxScore <= BLACKJACK_NUMBER | ||
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. 보통 Boolean 타입은 is로 시작하죠. 네이밍에 일관성을 맞추면 더 좋겠어요. |
||
|
||
companion object { | ||
private const val ACE_OTHER_NUMBER_DIFF = 10 | ||
private const val BLACKJACK_NUMBER = 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.
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package blackjack.domain | ||
|
||
abstract class User(name: String) { | ||
val name = Name(name) | ||
var cards = Cards() | ||
val score: Int | ||
get() = Score(cards).score | ||
|
||
val isBlackJack: Boolean | ||
get() = Score(cards).score == BLACKJACK_NUMBER | ||
|
||
abstract val isContinue: Boolean | ||
|
||
fun draw(card: Card) { cards += card } | ||
|
||
companion object { | ||
private const val BLACKJACK_NUMBER = 21 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package blackjack.view | ||
|
||
class InputView { | ||
fun inputParticipants(): List<String> { | ||
println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") | ||
return readln().split(",").map { it.trim() } | ||
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 inputDrawMore(name: String): String { | ||
println("\n${name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)") | ||
return readln() | ||
} | ||
} |
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.
다음 사항에 관한 구현 여부를 확인해보세요. 기본적인 기능 요구 사항을 만족하지 않고 있습니다.