-
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단계] 미플 미션 제출합니다 #119
base: hambeomjoon
Are you sure you want to change the base?
Changes from all commits
fc8cbdf
431eb2e
b754bb1
55fd723
22d77d5
bcd629f
267d955
650d0ef
97a422c
a705b7d
928c125
eeb753a
b9db320
998000a
e917698
4170ccf
1115799
6b12423
2cfd6d6
cfeb476
029d16c
f1358cd
9f64b83
b014f88
ec35943
1f3b5bc
4997796
6e5f3fe
cee98c3
6f8f9a5
7c7d831
39b4776
abf3e76
bc8428c
3b58f99
7b887d1
04dbc61
cc38e15
6210d0e
9a2f041
768234c
735c332
8c30b4b
ec97c61
ea4638f
ab04116
e0d3c23
d81f839
0dd2764
b329a2e
1cae40b
8791b2b
fd6fbd7
c2fa06f
c2f1755
3c29778
210ada3
81bd9c0
7db355e
51f458c
9ad6e31
997703a
bad91a6
dfe6b08
7411599
d735d41
8567600
7e71ac4
d4322bf
3b7f68a
fb9202d
cfd8383
dbe5336
f27972c
19ee934
88ef305
55affb2
d78a3a2
0235265
cf606b5
ade3e61
fb412a6
e14e037
c06faff
003634a
2364de1
d5c30ca
c8f1d51
fa526f6
2e35bfc
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,26 @@ | ||
# kotlin-blackjack | ||
# 블랙잭 | ||
|
||
## 기능 목록 | ||
|
||
- [x] 카드 Shape에는 스페이드, 다이아몬드, 하트, 클로버가 있다. | ||
- [x] 카드 Number에는 1~10, J, Q, K가 있다. | ||
- [x] 카드는 Number와 Shape의 조합을 가진다. | ||
|
||
- 덱 | ||
- [x] 덱은 카드 리스트를 생성한다. | ||
- [x] 덱에서 카드 한 장을 꺼낼 수 있다. | ||
- [x] 덱에 카드가 존재하지 않은 경우 예외가 발생한다. | ||
|
||
- 입력 | ||
- [x] 사용자 이름을 입력받는다. | ||
- [x] 사용자 이름이 중복될 경우 재입력 받는다. | ||
-[x] 플레이어에게 한 장 더 받을지 입력받는다. | ||
- [x] 입력값이 y, n이 아니면 재입력 받는다. | ||
|
||
- 출력 | ||
- [x] 플레이어 이름과 함께 초기 카드 안내 메시지를 출력한다. | ||
- [x] 딜러와 플레이어 초기 카드 상태를 출력한다. | ||
- [x] 각 플레이어마다 현재 카드 상태를 출력한다. | ||
- [x] 딜러의 카드 점수에 따라 안내 문구를 출력한다. | ||
- [x] 딜러와 모든 플레이어의 최종 카드 상태와 점수를 출력한다. | ||
- [x] 최종 승패를 출력한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package blackjack | ||
|
||
import blackjack.controller.BlackjackController | ||
import blackjack.model.Dealer | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
fun main() { | ||
val blackjackController = BlackjackController(InputView(), OutputView()) | ||
blackjackController.play(dealer = Dealer()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.model.Dealer | ||
import blackjack.model.Deck.INITIAL_HAND_OUT_CARD_COUNT | ||
import blackjack.model.DrawChoice | ||
import blackjack.model.GameManager | ||
import blackjack.model.Player | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
class BlackjackController( | ||
private val inputView: InputView, | ||
private val outputView: OutputView, | ||
) { | ||
private lateinit var gameManager: GameManager | ||
|
||
fun play(dealer: Dealer) { | ||
val players = playerSetting() | ||
|
||
outputView.printInitialHandOutCardMessage(players) | ||
gameManager = GameManager(dealer, players) | ||
gameManager.dealInitialCardWithCount(INITIAL_HAND_OUT_CARD_COUNT) | ||
outputView.printAllPlayerHands(dealer, players) | ||
|
||
playersDrawCards(players) | ||
|
||
dealerDrawCards(dealer) | ||
|
||
outputView.printFinalHandStatus(dealer, players) | ||
|
||
resultSummary(gameManager) | ||
} | ||
|
||
private fun playerSetting(): List<Player> { | ||
var playerNames: List<String>? = null | ||
while (playerNames == null) { | ||
playerNames = inputView.readPlayerNames() | ||
} | ||
return playerNames.map { Player(it) } | ||
} | ||
|
||
private fun playersDrawCards(players: List<Player>) { | ||
players.forEach { player -> playerDrawOrStay(player) } | ||
} | ||
|
||
private fun playerDrawOrStay(player: Player) { | ||
var condition: String? = null | ||
while (condition == null) { | ||
condition = inputView.readMoreCardCondition(player) | ||
} | ||
val playerCondition = DrawChoice.from(condition) | ||
if (playerCondition!!.isStay()) { | ||
outputView.printPlayerHands(player) | ||
return | ||
} | ||
gameManager.drawCard(player) | ||
outputView.printPlayerHands(player) | ||
if (player.isBust()) return | ||
playerDrawOrStay(player) | ||
} | ||
|
||
private fun dealerDrawCards(dealer: Dealer) { | ||
val moreCard = dealer.isMoreCard() | ||
if (moreCard) { | ||
gameManager.drawCard(dealer) | ||
} | ||
outputView.printDealerHandStatus(moreCard) | ||
} | ||
|
||
private fun resultSummary(gameManager: GameManager) { | ||
val result = gameManager.calculateResultMap() | ||
val dealerResult = gameManager.calculateDealerResult(result) | ||
outputView.printFinalResult(result, dealerResult) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package blackjack.model | ||
|
||
data class Card(val shape: Shape, val number: Number) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package blackjack.model | ||
|
||
import blackjack.model.ResultType.Companion.BUST_NUMBER | ||
|
||
class Dealer : Person() { | ||
val name = DEALER_NAME | ||
|
||
fun isMoreCard() = calculateTotalScore() < DEALER_MORE_CARD_MINIMUM | ||
|
||
override fun isBust(): Boolean = super.calculateTotalScore() > BUST_NUMBER | ||
|
||
companion object { | ||
private const val DEALER_NAME = "딜러" | ||
private const val DEALER_MORE_CARD_MINIMUM = 17 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package blackjack.model | ||
|
||
object Deck { | ||
const val INITIAL_HAND_OUT_CARD_COUNT = 2 | ||
private const val ERROR_NO_MORE_CARD_MESSAGE = "카드가 더 없습니다." | ||
private val CARDS = generateCards() | ||
|
||
fun draw(): Card { | ||
return CARDS.removeFirstOrNull() ?: throw IllegalStateException(ERROR_NO_MORE_CARD_MESSAGE) | ||
} | ||
|
||
fun drawWithCount(count: Int): List<Card> { | ||
return List(count) { draw() } | ||
} | ||
|
||
private fun generateCards(): MutableList<Card> = | ||
Shape.entries.flatMap { shape -> | ||
Number.entries.map { number -> | ||
Card(shape, number) | ||
} | ||
}.shuffled().toMutableList() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package blackjack.model | ||
|
||
enum class DrawChoice(val answer: String) { | ||
YES("y"), | ||
NO("n"), | ||
; | ||
|
||
fun isStay(): Boolean { | ||
return this == NO | ||
} | ||
|
||
companion object { | ||
fun from(answer: String): DrawChoice? { | ||
return entries.find { choice -> choice.answer == answer } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package blackjack.model | ||
|
||
class GameManager( | ||
private val dealer: Dealer, | ||
private val players: List<Player>, | ||
) { | ||
fun dealInitialCardWithCount(count: Int) { | ||
dealer.addCards(Deck.drawWithCount(count)) | ||
players.forEach { player -> player.addCards(Deck.drawWithCount(count)) } | ||
} | ||
|
||
fun calculateResultMap(): Map<Player, ResultType> { | ||
val playersStatus = | ||
players.associateBy( | ||
{ player -> player }, | ||
{ player -> ResultType.judgeScore(dealer, player) }, | ||
) | ||
return playersStatus | ||
} | ||
|
||
fun calculateDealerResult(resultMap: Map<Player, ResultType>): Map<ResultType, Int> { | ||
val result = mutableMapOf<ResultType, Int>() | ||
|
||
resultMap.forEach { | ||
when (it.value) { | ||
ResultType.WIN -> result[ResultType.LOSS] = result.getOrDefault(ResultType.LOSS, 0) + 1 | ||
ResultType.TIE -> result[ResultType.TIE] = result.getOrDefault(ResultType.TIE, 0) + 1 | ||
ResultType.LOSS -> result[ResultType.WIN] = result.getOrDefault(ResultType.WIN, 0) + 1 | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
fun drawCard(person: Person) { | ||
person.addCard(Deck.draw()) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package blackjack.model | ||
|
||
enum class Number(val score: Int) { | ||
ACE(11), | ||
TWO(2), | ||
THREE(3), | ||
FOUR(4), | ||
FIVE(5), | ||
SIX(6), | ||
SEVEN(7), | ||
EIGHT(8), | ||
NINE(9), | ||
TEN(10), | ||
JACK(10), | ||
QUEEN(10), | ||
KING(10), | ||
; | ||
|
||
override fun toString(): String { | ||
return when (this) { | ||
ACE -> "A" | ||
JACK -> "J" | ||
QUEEN -> "Q" | ||
KING -> "K" | ||
else -> (ordinal + 1).toString() | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package blackjack.model | ||
|
||
abstract class Person { | ||
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. 사람으로 따지면 "이름" 도 공통이 될 수 있지 않을까요? |
||
private val _cards: MutableList<Card> = mutableListOf() | ||
open val cards get() = _cards.toList() | ||
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. 이 값이 open 되어야 할 필요가 있을까요? 그리고 외부로 공개 되어 있는 이유는 지금 이 객체에게 메세지를 던지지 않고 있는 상황이 아닌지 생각해 봅시다. |
||
|
||
fun addCard(card: Card) = _cards.add(card) | ||
|
||
fun addCards(cards: List<Card>) = _cards.addAll(cards) | ||
|
||
Comment on lines
+7
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.
|
||
fun calculateTotalScore() = cards.sumOf { card -> card.number.score } | ||
|
||
abstract fun isBust(): Boolean | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package blackjack.model | ||
|
||
import blackjack.model.ResultType.Companion.BUST_NUMBER | ||
|
||
class Player( | ||
val name: String, | ||
) : Person() { | ||
fun adjustScore(): Int { | ||
var sumScore = calculateTotalScore() | ||
var countAce = countAce() | ||
while (countAce-- > 0) { | ||
if (sumScore > BUST_NUMBER) { | ||
sumScore -= ADJUST_ACE_NUMBER | ||
} | ||
} | ||
return sumScore | ||
} | ||
|
||
private fun countAce() = super.cards.count { it.number == Number.ACE } | ||
|
||
override fun isBust() = adjustScore() > BUST_NUMBER | ||
|
||
companion object { | ||
private const val ADJUST_ACE_NUMBER = 10 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package blackjack.model | ||
|
||
enum class ResultType(val value: Char) { | ||
WIN('승'), | ||
TIE('무'), | ||
LOSS('패'), ; | ||
Comment on lines
+3
to
+6
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도 view에만 표시하기 위한 선언부인데 만약 |
||
|
||
companion object { | ||
fun judgeScore( | ||
dealer: Dealer, | ||
player: Player, | ||
): ResultType { | ||
val dealerFinalScore = if (dealer.isBust()) 0 else dealer.calculateTotalScore() | ||
val playerFinalScore = if (player.isBust()) 0 else player.calculateTotalScore() | ||
if (dealerFinalScore < playerFinalScore) return WIN | ||
if (dealerFinalScore == playerFinalScore) return TIE | ||
return LOSS | ||
} | ||
|
||
const val BUST_NUMBER = 21 | ||
} | ||
Comment on lines
+8
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.
블랙잭이라는 도메인으로 생각했을 때, 블랙잭은 플레이어와 딜러간의 대결일텐데요. 이 부분에 대해서 천천히 고민해봅시다. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package blackjack.model | ||
|
||
enum class Shape(val type: 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. 여기도 |
||
SPADE("스페이드"), | ||
DIAMOND("다이아몬드"), | ||
HEART("하트"), | ||
CLOVER("클로버"), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package blackjack.view | ||
|
||
import blackjack.model.DrawChoice | ||
import blackjack.model.Player | ||
|
||
class InputView { | ||
fun readPlayerNames(): List<String>? { | ||
println(PLAYER_NAME_MESSAGE_GUIDE) | ||
val playerNames: List<String> = readln().split(PLAYER_NAME_DELIMITER).map { name -> name.trim() } | ||
|
||
if (playerNames.any { it.isEmpty() }) { | ||
println(ERROR_PLAYER_NAME_EMPTY) | ||
return null | ||
} | ||
|
||
if (playerNames.size != playerNames.toSet().size) { | ||
println(ERROR_INVALID_PLAYER_NAMES) | ||
return null | ||
} | ||
|
||
return playerNames | ||
} | ||
|
||
fun readMoreCardCondition(player: Player): String? { | ||
println(PLAYER_MORE_CARD_MESSAGE_GUIDE.format(player.name)) | ||
val condition: String = readln().trim() | ||
if (DrawChoice.from(condition) == null) { | ||
println(ERROR_INVALID_CARD_CONDITION) | ||
return null | ||
} | ||
|
||
return condition | ||
} | ||
|
||
companion object { | ||
private const val PLAYER_NAME_MESSAGE_GUIDE = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)" | ||
private const val ERROR_PLAYER_NAME_EMPTY = "플레이어 이름은 비어있으면 안 됩니다. 다시 입력해주세요." | ||
private const val ERROR_INVALID_PLAYER_NAMES = "중복된 이름이 있습니다. 다시 입력해주세요." | ||
private const val PLAYER_MORE_CARD_MESSAGE_GUIDE = "%s은(는) 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)" | ||
private const val ERROR_INVALID_CARD_CONDITION = "y나 n을 입력해야 합니다. 다시 입력해주세요." | ||
private const val PLAYER_NAME_DELIMITER = "," | ||
} | ||
} |
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.
companion object
는 어디에 위치를 해야 하는가. 를 확인해보시면 좋을 것 같네요.https://kotlinlang.org/docs/coding-conventions.html#class-layout