-
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 all 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,54 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
|
||
## 기능 요구 사항 | ||
|
||
블랙잭 게임을 변형한 프로그램을 구현한다. 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다. | ||
|
||
- 카드의 숫자 계산은 카드 숫자를 기본으로 하며, 예외로 Ace는 1 또는 11로 계산할 수 있으며, King, Queen, Jack은 각각 10으로 계산한다. | ||
- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받으며, 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다. 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다. | ||
|
||
## 기능 목록 | ||
|
||
### 플레이어 | ||
|
||
- [O] 플레이어의 초기 상태는 START이다. | ||
- [O] 플레이어가 카드 받는 것을 끝내면 상태는 STAND가 된다. | ||
- [O] 플레이어는 받은 카드를 저장한다. | ||
- [O] 플레이어는 상태를 업데이트 한다. | ||
- [O] 카드의 합이 20 이하 라면 PLAYING | ||
- [O] 카드의 합이 21을 이상 이라면 BUST | ||
- [O] 카드를 받지 않는다 하면 STAND | ||
- [O] 첫 2장의 카드 합이 21이라면 BLACK_JACK | ||
|
||
### 플레이어 그룹 | ||
|
||
- [O] 플레이어는 최소 2명 이상 26명 이하여야 한다. | ||
|
||
### 플레이어 펙토리 | ||
- [O] 문자열을 받아서 플레이어 리스트 생성. | ||
- [O] 공백은 플레어어로 생성하지 않는다. | ||
|
||
### 트럼프 카드(플레잉 카드) | ||
|
||
- [O] 카드의 포인트를 반환한다. | ||
- [O] 카드의 무늬 명을 반환한다. | ||
- [O] 카드의 번호를 반환한다. | ||
|
||
### 게임 덱 | ||
- [O] 카드를 순차적으로 반환한다. | ||
|
||
### 플레이어 덱 | ||
- [O] 카드를 추가할 수 있다. | ||
- [O] 점수를 반환한다. | ||
- [O] A는 블랙잭에 유리하게 점수를 반영한다. | ||
|
||
### inputView | ||
|
||
- [O] 플레이어를 입력받는다. | ||
- [O] 플레이어가 입력되지 않으면 예외를 던진다. | ||
|
||
### output | ||
|
||
- [O] 게임 시작 시 초기 카드 출력 | ||
- [O] 플레이어가 가지고 있는 카드를 출력 | ||
- [O] 플레이어 카드 계산값을 출력 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import card.deck.CardDeck | ||
import player.Player | ||
import player.PlayerGroup | ||
import player.Status | ||
import view.InputViewInterface | ||
import view.OutputViewInterface | ||
|
||
class BlackjackGame( | ||
private val cardDeck: CardDeck, | ||
val playerGroup: PlayerGroup, | ||
private val inputView: InputViewInterface, | ||
private val outputView: OutputViewInterface, | ||
) { | ||
|
||
init { | ||
for (player in playerGroup.playerList) { | ||
settingCard(player) | ||
} | ||
} | ||
|
||
fun start() { | ||
for (player in playerGroup.playerList) { | ||
gamePlay(player) | ||
} | ||
} | ||
|
||
private fun gamePlay(player: Player) { | ||
while (player.status == Status.PLAYING) { | ||
val response = inputView.askForHit(player.name) | ||
handleForResponse(response, player) | ||
outputView.showPlayingCard(player) | ||
} | ||
} | ||
|
||
private fun handleForResponse(response: String, player: Player) { | ||
when (response.uppercase()) { | ||
TEXT_ANSWER_YES -> { | ||
player.saveCard(cardDeck.getCardWithIncrease()) | ||
} | ||
TEXT_ANSWER_NO -> { | ||
player.playDone() | ||
} | ||
else -> { | ||
println(TEXT_RETRY_INPUT) | ||
} | ||
Comment on lines
+43
to
+45
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. 이 부분도 여전히 Console View의 의존관계를 가지고있습니다 :) |
||
} | ||
} | ||
|
||
private fun settingCard(player: Player) { | ||
repeat(2) { | ||
player.saveCard(cardDeck.getCardWithIncrease()) | ||
} | ||
} | ||
|
||
companion object { | ||
private const val TEXT_RETRY_INPUT = "Y 혹은 N만 입력 가능합니다." | ||
private const val TEXT_ANSWER_YES = "Y" | ||
private const val TEXT_ANSWER_NO = "N" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import card.CardPack | ||
import card.deck.CardDeck | ||
import player.PlayerFactory | ||
import player.PlayerGroup | ||
import view.InputView | ||
import view.OutputView | ||
|
||
fun main() { | ||
|
||
val playerNames = InputView.inputPlayerName() | ||
val playerGroup = PlayerGroup(PlayerFactory.createPlayerList(playerNames)) | ||
val cardDeck = CardDeck(CardPack.cards.toMutableList()) | ||
|
||
val game = BlackjackGame(cardDeck = cardDeck, playerGroup = playerGroup, InputView, OutputView) | ||
|
||
OutputView.showGameStart(game.playerGroup) | ||
|
||
game.start() | ||
|
||
OutputView.showGameEnd(game.playerGroup) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package card | ||
|
||
object CardPack { | ||
val cards: List<PlayingCard> = CardRank.values().flatMap { rank -> | ||
Suit.values().map { suit -> | ||
PlayingCard.of(suit = suit, cardRank = rank) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package card | ||
|
||
enum class CardRank(val point: Int, val symbol: 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"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package card | ||
|
||
data class PlayingCard(val suit: Suit, val cardRank: CardRank) { | ||
fun getPoint(): Int { | ||
return cardRank.point | ||
} | ||
|
||
fun getSuitName(): String { | ||
return suit.koName | ||
} | ||
|
||
override fun toString(): String { | ||
return "${cardRank.symbol}${suit.koName}" | ||
} | ||
|
||
companion object { | ||
private val CARDS: MutableMap<String, PlayingCard> = mutableMapOf() | ||
|
||
fun of(suit: Suit, cardRank: CardRank): PlayingCard { | ||
return CARDS[toKey(suit, cardRank)] ?: throw NoSuchElementException() | ||
} | ||
|
||
private fun toKey(suit: Suit, cardRank: CardRank): String { | ||
return suit.name + cardRank.name | ||
} | ||
|
||
init { | ||
for (suit in Suit.values()) { | ||
for (cardRank in CardRank.values()) { | ||
CARDS[toKey(suit, cardRank)] = PlayingCard(suit, cardRank) | ||
} | ||
} | ||
} | ||
Comment on lines
+27
to
+33
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,7 @@ | ||
package card | ||
enum class Suit(val koName: String) { | ||
SPADE("스페이드"), | ||
HEART("하트"), | ||
DIAMOND("다이아몬드"), | ||
CLUB("클러버"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package card.deck | ||
|
||
import card.PlayingCard | ||
|
||
class CardDeck(private val cardList: MutableList<PlayingCard>) { | ||
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. 생성자 파라미터로 Mutable Collection을 받는 것은 지양하는 것이 좋습니다 :) val mutableList = mutableListOf()
val cardDeck = CardDeck(mutableList)
mutableList.clear() |
||
|
||
init { | ||
cardList.shuffle() | ||
} | ||
|
||
private var index = 0 | ||
|
||
Comment on lines
+10
to
+12
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 getCardWithIncrease(): PlayingCard { | ||
if (isMaxIndexOfCard()) resetCard() | ||
return cardList[index++] | ||
} | ||
|
||
private fun isMaxIndexOfCard(): Boolean { | ||
return index == cardList.size | ||
} | ||
|
||
private fun resetCard() { | ||
cardList.shuffle() | ||
index = 0 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package card.deck | ||
|
||
import card.CardRank | ||
import card.PlayingCard | ||
|
||
class Hands(val cardList: MutableList<PlayingCard> = mutableListOf()) { | ||
|
||
fun addCard(playingCard: PlayingCard) { | ||
cardList.add(playingCard) | ||
} | ||
|
||
fun getResultPoint(): Int { | ||
var point = cardList.sumOf { it.getPoint() } | ||
|
||
if (isContainAce()) { | ||
point += addAcePoint(point) | ||
} | ||
|
||
return point | ||
} | ||
|
||
fun cardDeckSize() = cardList.size | ||
|
||
private fun isContainAce(): Boolean { | ||
return cardList.any { it.cardRank == CardRank.ACE } | ||
} | ||
|
||
private fun addAcePoint(point: Int): Int { | ||
return if (point <= 11) { | ||
ADD_ACE_POINT | ||
} else { | ||
ADD_ACE_POINT_NONE | ||
} | ||
} | ||
|
||
companion object { | ||
fun create() = Hands(mutableListOf()) | ||
private const val ADD_ACE_POINT = 10 | ||
private const val ADD_ACE_POINT_NONE = 0 | ||
} | ||
|
||
override fun toString(): String { | ||
val sb = StringBuilder() | ||
for (card in cardList) { | ||
sb.append("$card, ") | ||
} | ||
sb.deleteCharAt(sb.lastIndex - 1) | ||
return sb.toString() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package player | ||
|
||
import card.PlayingCard | ||
import card.deck.Hands | ||
|
||
class Player( | ||
val name: String, | ||
status: Status = Status.START, | ||
hands: Hands = Hands(), | ||
) { | ||
|
||
var status = status | ||
private set | ||
|
||
var playerDeck = hands | ||
private set | ||
|
||
fun playDone() { | ||
this.status = Status.STAND | ||
} | ||
|
||
fun saveCard(card: PlayingCard) { | ||
playerDeck.addCard(card) | ||
updateStatus() | ||
} | ||
|
||
fun updateStatus() { | ||
val totalPoint = playerDeck.getResultPoint() | ||
|
||
if (totalPoint > BLACKJACK_NUMBER) { | ||
this.status = Status.BUST | ||
} else if (isBlackJack()) { | ||
this.status = Status.BLACK_JACK | ||
} else { | ||
this.status = Status.PLAYING | ||
} | ||
} | ||
|
||
fun getResultPoint(): Int { | ||
return playerDeck.getResultPoint() | ||
} | ||
|
||
private fun isBlackJack(): Boolean { | ||
val totalPoint = playerDeck.getResultPoint() | ||
return playerDeck.cardDeckSize() == BLACKJACK_CARD_COUNT && totalPoint == BLACKJACK_NUMBER | ||
} | ||
|
||
companion object { | ||
private const val BLACKJACK_CARD_COUNT = 2 | ||
private const val BLACKJACK_NUMBER = 21 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package player | ||
|
||
object PlayerFactory { | ||
|
||
private const val DELIMITERS = ',' | ||
|
||
fun createPlayerList(playerNames: String): List<Player> { | ||
val playerNameList = splitInputData(playerNames) | ||
val playerList = mutableListOf<Player>() | ||
playerNameList.forEach { name -> | ||
playerList.add(Player(name)) | ||
} | ||
return playerList | ||
} | ||
|
||
private fun splitInputData(inputData: String): List<String> { | ||
return inputData.split(DELIMITERS).map { it.trim() }.filter { it.isNotBlank() } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package player | ||
|
||
class PlayerGroup(val playerList: List<Player>) { | ||
|
||
init { | ||
validatePlayer() | ||
} | ||
|
||
private fun validatePlayer() { | ||
require(playerList.size in MIN_PLAYER_CNT..MAM_PLAYER_CNT) { "참여 가능한 플레이어의 범위를 넘어섰습니다." } | ||
} | ||
|
||
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 { | ||
START, PLAYING, STAND, BLACK_JACK, BUST | ||
} |
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.
response가 String으로 들어오는 것은 Console View의 의존성이 여전히 묻어나보여요.
입력 뷰가 조금 바뀌어서 y, n가 아닌, YES, NO 혹은 예,아니오로 변경된다면, 이 모든 String에서 검증해주어야할까요?
view에서는 예 혹은 아니오에 대한 도메인 객체를 반환하도록 만들어보는 건 어떨까요?