-
Notifications
You must be signed in to change notification settings - Fork 313
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
Step2 - 블랙젝 #672
Step2 - 블랙젝 #672
Changes from 32 commits
aabf9e4
67bf9ca
58a82f8
15c50de
6b33e03
b88d964
0d06426
c1a5504
257482f
e384c31
683ccfd
d261000
8aca69f
e897c27
18317d6
88e3b66
c5b5adf
aae5ec2
84b7ea9
f4fef00
231ba04
07f695a
5b83654
00d42dd
18c0aea
eccd3a8
05dbca5
9280506
3cf1551
d056703
70a6c84
a8acb07
9c0d5e9
cfe4fde
63171c3
23fd469
5854bb5
335541a
a041fe2
2b1857d
ac1c262
c70c516
30d7837
01d4259
e8c4212
9dd1499
07ed6eb
3bd337a
fa7ef73
31afda1
1c2fd96
4396f52
aacff6c
ff53d99
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,68 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
|
||
## 기능 요구 사항 | ||
|
||
블랙잭 게임을 변형한 프로그램을 구현한다. 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다. | ||
|
||
- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. | ||
- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다. 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다. | ||
|
||
## 기능 목록 | ||
|
||
### 플레이어 | ||
|
||
- [O] 플레이어의 초기 상태는 PLAYING이다. | ||
- [O] 플레이어가 카드 받는 것을 끝내면 상태는 STAND가 된다. | ||
- [O] 플레이어는 받은 카드를 저장한다. | ||
- [O] 플레이어는 상태를 업데이트 한다. | ||
- [O] 카드의 합이 20 이하 라면 PLAYING | ||
- [O] 카드의 합이 21을 이상 이라면 STAND | ||
- [O] 카드를 받지 않는다 하면 STAND | ||
|
||
### 플레이어 그룹 | ||
- [O] 플레이어는 최소 2명 이상 26명 이하여야 한다. | ||
- [O] 현재 플레이 중인 플레이어를 반환한다. | ||
- [O] 플레이어 턴을 넘긴다. | ||
|
||
### 블랙젝 게임 | ||
|
||
- [O] 플레이어는 게임 시작시 2장의 카드를 받는다. | ||
- [O] 배포한 카드를 플레이어에게 전달한다. | ||
- [O] 유저를 반환한다. (TestCode X) | ||
- [O] 유저 턴을 넘긴다. (TestCode X) | ||
- [O] 유저 턴을 종료한다. (TestCode X) | ||
|
||
### 블랙젝 계산기 | ||
- [O] 결과를 계산한다. | ||
|
||
### 트럼프 카드(플레잉 카드) | ||
|
||
- [O] 카드의 포인트를 반환한다. | ||
- [O] 카드의 무늬 명을 반환한다. | ||
- [O] 카드의 번호를 반환한다. | ||
|
||
### 카드팩 | ||
|
||
- [O] 카드를 셔플한다. (TestCode X) | ||
- [O] 중복되지 않고 순차적으로 카드를 반환한다. | ||
- [O] 카드 팩을 검증한다. | ||
- [O] 중복이 없다. | ||
- [O] 52장의 카드로 구성된다. | ||
|
||
### 게임 인덱스 | ||
|
||
- [O] 현재 카드 인덱스를 반환한다. | ||
- [O] 카드 인덱스를 1 증가시킨다. | ||
- [O] 카드 인덱스가 카드 수량을 넘지 않는다. | ||
- [O] 플레이어 인덱스를 1 증가시킨다. | ||
|
||
### inputView | ||
|
||
- [] 플레이어를 입력받는다. | ||
- [] 플레이어가 입력되지 않으면 예외를 던진다. | ||
- [] 카드 수집 여부를 받는다. | ||
|
||
### output | ||
|
||
- [] 플레이어가 가지고 있는 카드를 보여준다. | ||
- [] 플레이어 카드 계산값을 보여준다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import card.CardNumber | ||
import card.PlayingCard | ||
|
||
object BlackJackCalculator { | ||
|
||
private const val ADDITIONAL_SCORE = 10 | ||
private const val NO_ADDITIONAL_SCORE = 0 | ||
private const val ACE_ADDITIONAL_SCORE_THRESHOLD = 12 | ||
|
||
fun calculate(cardList: List<PlayingCard>): Int { | ||
var result = cardList.sumOf { it.getPoint() } | ||
cardList.filter { it.getCardNumber() == CardNumber.ACE } | ||
.forEach { _ -> result += addCalculateAce(result) } | ||
|
||
return result | ||
} | ||
|
||
private fun addCalculateAce(sumValue: Int): Int { | ||
return if (sumValue < ACE_ADDITIONAL_SCORE_THRESHOLD) { | ||
ADDITIONAL_SCORE | ||
} else { | ||
NO_ADDITIONAL_SCORE | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import card.CardPack | ||
import player.Player | ||
import player.PlayerGroup | ||
import player.Status | ||
|
||
class BlackjackGame(private val cardPack: CardPack, private val playerGroup: PlayerGroup) { | ||
|
||
init { | ||
playerGroup.playerList.forEach { | ||
distributeTwoCards(it) | ||
} | ||
} | ||
|
||
fun hitCard() { | ||
val player = playerGroup.getCurrentPlayer() | ||
player.saveCard(cardPack.hit()) | ||
player.updateStatus() | ||
} | ||
|
||
fun getCurrentPlayer(): Player { | ||
return playerGroup.getCurrentPlayer() | ||
} | ||
|
||
// 턴오버. | ||
fun playerTurnOver() { | ||
val player = playerGroup.getCurrentPlayer() | ||
if (player.status == Status.STAND) { | ||
playerGroup.turnOverPlayer() | ||
} | ||
} | ||
|
||
fun hitDone() { | ||
val player = playerGroup.getCurrentPlayer() | ||
player.playDone() | ||
} | ||
|
||
private fun distributeTwoCards(player: Player) { | ||
repeat(2) { | ||
player.saveCard(cardPack.hit()) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
class GameIndex(private val maxCardIndex: Int) { | ||
|
||
private var _cardIndex: Int = 0 | ||
val cardIndex: Int | ||
get() = _cardIndex | ||
|
||
private var _playerIndex: Int = 0 | ||
val playerIndex: Int | ||
get() = _playerIndex | ||
|
||
fun increaseCardIndex() { | ||
_cardIndex++ | ||
validateIndex() | ||
} | ||
|
||
fun increasePlayerIndex() { | ||
_playerIndex++ | ||
} | ||
|
||
private fun validateIndex() { | ||
require(_cardIndex < maxCardIndex) { ERR_MSG_EMPTY_CARD } | ||
} | ||
|
||
companion object { | ||
private const val ERR_MSG_EMPTY_CARD = "카드가 모두 소진되었습니다." | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package card | ||
|
||
enum class CardNumber(val point: Int, val cardName: String) { | ||
ACE(1, "A"), | ||
TWO(2, "2"), | ||
TREE(3, "3"), | ||
FOUR(4, "4"), | ||
FIVE(5, "5"), | ||
SIX(6, "6"), | ||
SEVEN(7, "7"), | ||
EIGHT(8, "8"), | ||
NINE(9, "9"), | ||
TEN(10, "10"), | ||
JACK(10, "J"), | ||
QUEEN(10, "Q"), | ||
KING(10, "K"); | ||
|
||
companion object { | ||
|
||
private val cardNumberList = listOf( | ||
ACE, TWO, TREE, FOUR, FIVE, SIX, SEVEN, | ||
EIGHT, NINE, TEN, JACK, QUEEN, KING, | ||
) | ||
|
||
fun getCardNumberList() = cardNumberList | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package card | ||
|
||
class CardPack(val cardList: List<PlayingCard>) { | ||
|
||
private var cardIndex = 0 | ||
|
||
fun hit(): PlayingCard { | ||
return cardPack.cardList[cardIndex++] | ||
} | ||
|
||
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. 어떤 의도를 담은 객체인지 알기가 어려웠어요 😥 companion object에 대부분의 메서드가 분리되어있는 건 어떤 이유이셨나요? 또, 52번 카드를 뽑은 경우, out of index exception이 발생할 것으로 예상되는데요, 또, 블랙잭 게임 룰 중에는 한 번에 여러 벌의 카드를 섞어 진행하기도 합니다. |
||
companion object { | ||
private const val CARD_PACK_SIZE = 52 | ||
private const val ERR_MSG_CARD_PACK_SIZE = "카드는 52개로 구성되어야 합나다." | ||
private const val ERR_MSG_DUPLICATE_CARD_PACK = "중복되는 카드는 없어야 합니다." | ||
|
||
private val cardPack = createCard() | ||
|
||
private fun createCard(): CardPack { | ||
val suitList = Suit.getSuitList() | ||
val playingCardList = mutableListOf<PlayingCard>() | ||
|
||
for (suit in suitList) { | ||
playingCardList.addAll(createCardWithSuit(suit)) | ||
} | ||
cardShuffle(playingCardList) | ||
|
||
val cardPack = CardPack(playingCardList) | ||
validateCardPack(cardPack) | ||
|
||
return cardPack | ||
} | ||
|
||
private fun cardShuffle(playingCardList: MutableList<PlayingCard>) { | ||
playingCardList.shuffle() | ||
} | ||
|
||
private fun createCardWithSuit(suit: Suit): List<PlayingCard> { | ||
val playingCardList = mutableListOf<PlayingCard>() | ||
val cardNumberList = CardNumber.getCardNumberList() | ||
|
||
for (cardNumber in cardNumberList) { | ||
playingCardList.add(PlayingCard(suit, cardNumber)) | ||
} | ||
return playingCardList | ||
} | ||
|
||
private fun validateCardPack(cardPack: CardPack) { | ||
validateCardPackSize(cardPack) | ||
validateDuplicateCardPack(cardPack) | ||
} | ||
|
||
private fun validateCardPackSize(cardPack: CardPack) { | ||
require(cardPack.cardList.size == CARD_PACK_SIZE) { ERR_MSG_CARD_PACK_SIZE } | ||
} | ||
|
||
private fun validateDuplicateCardPack(cardPack: CardPack) { | ||
require(cardPack.cardList.size == cardPack.cardList.distinct().size) { ERR_MSG_DUPLICATE_CARD_PACK } | ||
} | ||
|
||
fun getCardPack(): CardPack { | ||
return cardPack | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package card | ||
|
||
import card.Suit.Companion.getName | ||
|
||
class PlayingCard(private val suit: Suit, private val cardNumber: CardNumber) { | ||
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. 52장의 각 카드의 인스턴스를 재활용해보는 건 어떨까요? |
||
fun getPoint(): Int { | ||
return cardNumber.point | ||
} | ||
|
||
fun getSuitName(): String { | ||
return suit.getName() | ||
} | ||
|
||
fun getCardNumber(): CardNumber { | ||
return cardNumber | ||
} | ||
|
||
override fun toString(): String { | ||
return "${cardNumber.cardName}${suit.koName}" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package card | ||
enum class Suit(val koName: String) { | ||
SPADE("스페이드"), | ||
HEART("하트"), | ||
DIAMOND("다이아몬드"), | ||
CLUB("클러버"); | ||
|
||
companion object { | ||
|
||
private val suitList = listOf(SPADE, HEART, DIAMOND, CLUB) | ||
fun Suit.getName(): String { | ||
return this.koName | ||
} | ||
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. getName 확장함수는 어떨 때 필요한걸까요? |
||
|
||
fun getSuitList() = suitList | ||
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의 값을 모두 가져오기 위해서는 values() 메서드를 사용하시면 됩니다 :) val allSuits = Suit.values() |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package player | ||
|
||
import BlackJackCalculator | ||
import card.PlayingCard | ||
|
||
class Player(private val name: String) { | ||
|
||
private var _status = Status.PLAYING | ||
val status: Status | ||
get() = _status | ||
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 status: Status
private set koltin property 공식 문서를 꼭 읽어보세요! |
||
|
||
private var _cardList = mutableListOf<PlayingCard>() | ||
val cardList: List<PlayingCard> | ||
get() = _cardList | ||
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 playDone() { | ||
updatePlayerStatus(Status.STAND) | ||
} | ||
|
||
fun saveCard(card: PlayingCard) { | ||
_cardList.add(card) | ||
} | ||
|
||
fun getName() = name | ||
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. 코틀린에서는 프로퍼티를 활용해서, 따로 getter를 만드는 경우가 드물어요 :) 아래 kotlin property에 대한 공식 문서를 참고해주세요! |
||
fun updateStatus() { | ||
val newStatus = determineStatus() | ||
updatePlayerStatus(newStatus) | ||
} | ||
|
||
private fun determineStatus(): Status { | ||
val totalPoint = BlackJackCalculator.calculate(_cardList) | ||
return when { | ||
(totalPoint <= 20) -> Status.PLAYING | ||
else -> Status.STAND | ||
} | ||
} | ||
|
||
private fun updatePlayerStatus(status: Status) { | ||
_status = status | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package player | ||
|
||
class PlayerGroup(val playerList: List<Player>) { | ||
private var playerIndex = 0 | ||
|
||
init { | ||
validatePlayer() | ||
} | ||
|
||
private fun validatePlayer() { | ||
require(playerList.size in MIN_PLAYER_CNT..MAM_PLAYER_CNT) { "참여 가능한 플레이어의 범위를 넘어섰습니다." } | ||
} | ||
|
||
fun getCurrentPlayer(): Player { | ||
return playerList[playerIndex] | ||
} | ||
|
||
fun turnOverPlayer() { | ||
playerIndex++ | ||
} | ||
|
||
companion object { | ||
private const val MIN_PLAYER_CNT = 2 | ||
private const val MAM_PLAYER_CNT = 26 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package player | ||
|
||
enum class Status { | ||
PLAYING, STAND | ||
} |
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.
카드의 점수는 카드들에게 메시지를 던져 물어보는 것은 어떨까요?