-
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 #673
base: yw9594
Are you sure you want to change the base?
Step2 #673
Changes from all commits
f2b4dec
56de002
f646c64
72f3621
4b96d12
3a9f31b
80baf83
89e84c0
945a368
966b657
3a40362
754caf0
8f43bdb
30f68ab
55f4706
bde2e2f
f725eef
032ec32
fd2665e
dd2dba9
3cd31d9
0c52d71
15c1f8e
d4ebf16
9d94bbf
9443997
491eb72
4b711ee
0a64aae
2ab809c
07ce271
95e5420
72819b0
7ad08bc
c226eb1
0fa1cf3
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,25 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
# Step2 | ||
- 기능 목록 | ||
- 도메인 | ||
- 카드 | ||
- 숫자와 문양을 결합한 문자열을 반환 | ||
- 숫자에 맞는 점수 리스트를 반환 | ||
- 카드 A의 경우 1, 11의 점수를 가짐 | ||
- 카드 K, Q, J의 경우 10의 점수를 가짐 | ||
- 카드 풀 | ||
- 랜덤한 카드 한 장을 반환 | ||
- 카드 풀에서 반환한 카드 제거 | ||
- 플레이어 | ||
- 보유한 카드 점수 합 리스트를 반환 | ||
- 추가 카드를 뽑을 수 있는 점수 합인지 검증 | ||
- 블랙잭 게임 | ||
- 플레이어들을 전달 받고 블랙잭 게임 반환 | ||
- 각 개별 플레이어들 블랙젝 게임 실행 | ||
- 입력 | ||
- 참가하는 플레이어의 이름을 입력받음 | ||
- 플레이어들의 이름 문자열이 콤마(,)로 구분되는지 검사 | ||
- 플레이어가 카드를 더 뽑을지 말지를 Y/N으로 입력 | ||
- 입력받는 값이 Y/N인지 검증 | ||
- 출력 | ||
- 플레이어의 이름, 보유 카드 및 플레이어의 점수 출력 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package blackjack | ||
|
||
import blackjack.contoller.BlackjackController | ||
import blackjack.domain.component.BlackjackGameProxy | ||
import blackjack.domain.component.BlackjackInputValidator | ||
import blackjack.domain.model.PlayerName | ||
import blackjack.view.BlackjackInputView | ||
import blackjack.view.BlackjackResultView | ||
|
||
fun main() { | ||
val controller = init() | ||
|
||
process(controller) | ||
} | ||
|
||
private fun init(): BlackjackController { | ||
return BlackjackController( | ||
BlackjackInputView(), | ||
BlackjackInputValidator(), | ||
BlackjackResultView(), | ||
BlackjackGameProxy() | ||
) | ||
} | ||
|
||
private fun process(blackjackController: BlackjackController) { | ||
val playerNames: List<PlayerName> = blackjackController.getPlayerNames() | ||
|
||
blackjackController.initGame(playerNames) | ||
blackjackController.printInitialGameStatus() | ||
|
||
playerNames.forEach { | ||
it.play(blackjackController) | ||
} | ||
|
||
blackjackController.printGameResultStatus() | ||
} | ||
|
||
private fun PlayerName.play(blackjackController: BlackjackController) { | ||
while (blackjackController.getHitPossible(this)) { | ||
if (blackjackController.getHit(this).isNo()) { | ||
return | ||
} | ||
|
||
val player = blackjackController.hit(this) | ||
|
||
blackjackController.printPlayerInfo(player) | ||
} | ||
} | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package blackjack.contoller | ||
|
||
import blackjack.domain.component.BlackjackGameProxy | ||
import blackjack.domain.component.BlackjackInputValidator | ||
import blackjack.domain.model.Player | ||
import blackjack.domain.model.PlayerInfo | ||
import blackjack.domain.model.PlayerName | ||
import blackjack.domain.model.YesNo | ||
import blackjack.view.BlackjackInputView | ||
import blackjack.view.BlackjackResultView | ||
|
||
class BlackjackController( | ||
private val blackJackInputView: BlackjackInputView, | ||
private val blackjackInputValidator: BlackjackInputValidator, | ||
private val blackjackResultView: BlackjackResultView, | ||
private val blackjackGameProxy: BlackjackGameProxy | ||
) { | ||
fun getPlayerNames(): List<PlayerName> { | ||
val playerNamesString: String? = blackJackInputView.getPlayerNames() | ||
|
||
return convertPlayerNamesStringToList(playerNamesString) | ||
} | ||
|
||
fun getHitPossible(playerName: PlayerName): Boolean { | ||
return blackjackGameProxy.isHitPossible(playerName) | ||
} | ||
|
||
fun getHit(playerName: PlayerName): YesNo { | ||
return blackJackInputView | ||
.getHit(playerName.name) | ||
.run { convertYesNo(this) } | ||
} | ||
|
||
fun initGame(playerNames: List<PlayerName>) { | ||
blackjackGameProxy.init(playerNames) | ||
} | ||
|
||
fun hit(playerName: PlayerName): Player { | ||
return blackjackGameProxy.hit(playerName) | ||
} | ||
|
||
fun printInitialGameStatus() { | ||
blackjackGameProxy | ||
.fetchPlayerInfos() | ||
.run { blackjackResultView.printGameInitialStatus(this) } | ||
} | ||
|
||
fun printGameResultStatus() { | ||
blackjackGameProxy | ||
.fetchPlayerInfos() | ||
.run { blackjackResultView.printGameResultStatus(this) } | ||
} | ||
|
||
fun printPlayerInfos() { | ||
blackjackGameProxy | ||
.fetchPlayerInfos() | ||
.run { blackjackResultView.printPlayerInfos(this) } | ||
} | ||
|
||
fun printPlayerInfo(player: Player) { | ||
val playerInfo = PlayerInfo.from(player) | ||
|
||
blackjackResultView.printPlayerInfo(playerInfo, false) | ||
} | ||
|
||
private fun convertPlayerNamesStringToList(playerNames: String?): List<PlayerName> { | ||
return playerNames | ||
.run { blackjackInputValidator.validatePlayerNamesString(this) } | ||
.split(PLAYER_NAME_SEPARATOR) | ||
.run { blackjackInputValidator.validatePlayerNamesSize(this) } | ||
.map { PlayerName(it) } | ||
} | ||
|
||
private fun convertYesNo(yesNo: String?): YesNo { | ||
return yesNo | ||
.run { blackjackInputValidator.validateYesNoString(this) } | ||
.run { YesNo.from(this) ?: YesNo.N } | ||
} | ||
|
||
private fun BlackjackGameProxy.fetchPlayerInfos(): List<PlayerInfo> { | ||
return this | ||
.getPlayers() | ||
.players | ||
.map { PlayerInfo.from(it) } | ||
} | ||
|
||
companion object { | ||
private const val PLAYER_NAME_SEPARATOR = "," | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package blackjack.domain.component | ||
|
||
import blackjack.domain.model.* | ||
|
||
class BlackjackGame private constructor( | ||
val players: Players, | ||
private val cardPool: CardPool | ||
) { | ||
fun isHitPossible(playerName: PlayerName): Boolean { | ||
return players | ||
.findByName(playerName) | ||
.checkNull() | ||
.isPossibleToHit() | ||
} | ||
|
||
fun hit(playerName: PlayerName): Player { | ||
val player = players.findByName(playerName) | ||
val card = cardPool.pickAndRemove() | ||
|
||
return player | ||
.checkNull() | ||
.append(card) | ||
} | ||
|
||
private fun Player?.checkNull(): Player { | ||
check(this != null) { "해당 플레이어는 존재하지 않습니다." } | ||
|
||
return this | ||
} | ||
|
||
companion object { | ||
const val INITIAL_CARD_COUNT = 2 | ||
|
||
fun create(playerNames: List<PlayerName>): BlackjackGame { | ||
val cardPool = CardPool.create() | ||
val players = playerNames | ||
.map { Player(it, fetchInitialCards(cardPool)) } | ||
.run { Players(this) } | ||
|
||
return BlackjackGame(players, cardPool) | ||
} | ||
|
||
private fun fetchInitialCards(cardPool: CardPool): Cards { | ||
return cardPool | ||
.pickAndRemove(INITIAL_CARD_COUNT) | ||
.run { Cards.of(this) } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package blackjack.domain.component | ||
|
||
import blackjack.domain.model.Player | ||
import blackjack.domain.model.PlayerName | ||
import blackjack.domain.model.Players | ||
|
||
class BlackjackGameProxy( | ||
private var blackjackGame: BlackjackGame? = null | ||
Comment on lines
+6
to
+8
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 init(playerNames: List<PlayerName>) { | ||
blackjackGame = BlackjackGame.create(playerNames) | ||
} | ||
|
||
fun isHitPossible(playerName: PlayerName): Boolean { | ||
return blackjackGame | ||
.checkGameExist() | ||
.isHitPossible(playerName) | ||
} | ||
|
||
fun hit(playerName: PlayerName): Player { | ||
return blackjackGame | ||
.checkGameExist() | ||
.hit(playerName) | ||
} | ||
|
||
fun getPlayers(): Players { | ||
return blackjackGame | ||
.checkGameExist() | ||
.players | ||
} | ||
|
||
private fun BlackjackGame?.checkGameExist(): BlackjackGame { | ||
check(this != null) { "게임이 존재하지 않습니다." } | ||
|
||
return this | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackjack.domain.component | ||
|
||
class BlackjackInputValidator { | ||
fun validatePlayerNamesString(playerNames: String?): String { | ||
require(!playerNames.isNullOrBlank()) { "플레이어 이름은 null 또는 공백일 수 없습니다." } | ||
|
||
return playerNames | ||
} | ||
|
||
fun validatePlayerNamesSize(playerNames: List<String>): List<String> { | ||
require(playerNames.isNotEmpty()) { "게임에는 1명 이상의 플레이어가 참가해야합니다." } | ||
|
||
return playerNames | ||
} | ||
|
||
fun validateYesNoString(yesNo: String?): String { | ||
require(!yesNo.isNullOrBlank()) { "힛은 null 또는 공백일 수 없습니다." } | ||
|
||
return yesNo | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package blackjack.domain.model | ||
|
||
class Card private constructor( | ||
Comment on lines
+2
to
+3
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 number: CardNumber, | ||
val shape: CardShape, | ||
val scores: List<Score> | ||
) { | ||
companion object { | ||
private val CARD_ACE_SCORES = listOf(Score(1), Score(11)) | ||
private val CARD_KQJ_SCORES = listOf(Score(10)) | ||
Comment on lines
+9
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. CardNumber enum class가 카드의 점수를 갖게 해보는 건 어떨까요? 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. 또, Ace의 점수는 기본적으로 1점입니다. |
||
|
||
fun of(number: CardNumber, shape: CardShape): Card { | ||
if (number.isAce()) { | ||
return Card(number, shape, CARD_ACE_SCORES) | ||
} | ||
|
||
if (number.isOneOfKQJ()) { | ||
return Card(number, shape, CARD_KQJ_SCORES) | ||
} | ||
|
||
return Card(number, shape, listOf(Score(number.number))) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package blackjack.domain.model | ||
|
||
enum class CardNumber(val number: Int, val displayName: String) { | ||
ACE(1, "A"), | ||
TWO(2, "2"), | ||
THREE(3, "3"), | ||
FOUR(4, "4"), | ||
FIVE(5, "5"), | ||
SIX(6, "6"), | ||
SEVEN(7, "7"), | ||
EIGHT(8, "8"), | ||
NINE(9, "9"), | ||
TEN(10, "10"), | ||
JACK(11, "J"), | ||
QUEEN(12, "Q"), | ||
KING(13, "K"), | ||
; | ||
|
||
fun isAce(): Boolean { | ||
return number == 1 | ||
} | ||
|
||
fun isOneOfKQJ(): Boolean { | ||
return number >= 11 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package blackjack.domain.model | ||
|
||
class CardPool private constructor(private var cards: MutableList<Card>) { | ||
Comment on lines
+2
to
+3
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 pool = CardPool(mutableList)
mutableList.clear() 방어적 복사를 활용해보는 건 어떨까요? |
||
fun pickAndRemove(): Card { | ||
check(cards.size > 0) { "카드 풀의 카드가 존재하지 않습니다." } | ||
|
||
Comment on lines
+4
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. 52장의 카드를 모두 사용하면 Exception이 발생하고 게임이 비정상 종료될 것으로 보여요. 카드를 다 사용하더라도 게임을 계속 진행할 수 있게 구성해보는 건 어떨까요? |
||
val target = (0 until cards.size).random() | ||
|
||
return cards.removeAt(target) | ||
} | ||
|
||
fun pickAndRemove(count: Int): List<Card> { | ||
if (count < 0) { | ||
return listOf() | ||
} | ||
Comment on lines
+14
to
+15
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. 빈 list는 emptyList() 함수를 활용해볼 수 있어요 :) |
||
|
||
return (0 until count).map { pickAndRemove() } | ||
} | ||
|
||
companion object { | ||
fun create(): CardPool { | ||
return CardPool(Cards.create().cards.toMutableList()) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package blackjack.domain.model | ||
|
||
enum class CardShape(val shape: String) { | ||
Spade("스페이드"), | ||
Clover("클로버"), | ||
Heart("하트"), | ||
Diamond("다이아몬드"), | ||
} |
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.
플레이어가 카드를 받는 것은 도메인 객체로 표현해보는 건 어떨까요?