-
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 #645
base: kakao-moses-lee
Are you sure you want to change the base?
Step2 #645
Changes from all commits
4d8a92c
8cc8728
cc9a256
77f999a
3efda6a
562450a
6f49311
e241a39
118996e
0a5687d
5f843a9
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,20 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
|
||
## step2 - 블랙잭 | ||
|
||
기능 요구사항 | ||
- 딜러 | ||
- [x] : 게임 시작 시에, Player 에게 카드를 2장씩 나눠준다. | ||
- [x] : Player 에게 카드를 나눠준다. | ||
- [x] : Player 에게 카드를 나눠줄 수 있는지 확인한다. | ||
- 플레이어 | ||
- [x] : 딜러에게 카드를 달라고 한다. | ||
- [x] : 받은 카드의 총합을 계산한다. | ||
- 게임 | ||
- [x] : N 명의 플레이어가 play 할 수 있다. | ||
|
||
## 용어 정리 | ||
- Hand : 플레이어가 들고 있는 패 | ||
- Card : 카드 (단수) | ||
- Rank : 무늬별로 A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K의 13 가지 끗수(rank) | ||
- Suit : 스페이드, 하트, 다이아몬드, 클럽의 4 가지 무늬(suit) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackJack | ||
|
||
import blackJack.controller.BlackJackController | ||
|
||
class BlackJackRunner | ||
|
||
fun main() { | ||
BlackJackController().play() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package blackJack.controller | ||
|
||
import blackJack.model.Dealer | ||
import blackJack.model.Player | ||
import blackJack.model.askMoreCard | ||
import blackJack.model.checkDrawCardIsAllowedFor | ||
import blackJack.view.InputView | ||
import blackJack.view.OutputView | ||
|
||
class BlackJackController { | ||
fun play() { | ||
val req = InputView.getNames() | ||
|
||
val candidates = req.map { Player(it) } | ||
val dealer = Dealer("dealer") | ||
|
||
val players = dealer.startGame(candidates) | ||
OutputView.printPlayersState(players) | ||
|
||
players.forEach { player -> | ||
shouldContinue(player, dealer) | ||
} | ||
|
||
OutputView.printFinalState(players) | ||
} | ||
|
||
private fun shouldContinue(player: Player, dealer: Dealer) { | ||
while (true) { | ||
val req = InputView.getPlayerInput(player.name) | ||
if (req == "n") { | ||
break | ||
} | ||
player askMoreCard dealer | ||
|
||
if ((dealer checkDrawCardIsAllowedFor player).not()) { | ||
break | ||
} | ||
OutputView.printPlayerState(player) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package blackJack.model | ||
|
||
import blackJack.model.enums.Rank | ||
import blackJack.model.enums.Suit | ||
|
||
data class Card( | ||
val suit: Suit, | ||
val rank: Rank | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package blackJack.model | ||
|
||
import blackJack.model.enums.Rank | ||
import blackJack.model.enums.Suit | ||
|
||
class CardDeck(val cards: List<Card>) { | ||
companion object { | ||
fun of(): CardDeck { | ||
val cards = generateAllCards() | ||
return CardDeck(cards) | ||
} | ||
Comment on lines
+8
to
+11
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. 플레잉 카드는 작성해주신 대로 생성할 때 중복되지 않은 13개의 끗수와 4개의 모양, 총 52개의 카드를 가지고 있습니다. |
||
|
||
private fun generateAllCards(): List<Card> { | ||
return Suit.values().flatMap { suit -> | ||
generateCardsForSuit(suit) | ||
} | ||
} | ||
|
||
private fun generateCardsForSuit(suit: Suit): List<Card> { | ||
return Rank.values().map { rank -> | ||
Card(suit, rank) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package blackJack.model | ||
|
||
class Dealer(val name: String) { | ||
private var cardDeck = CardDeck.of() | ||
Comment on lines
+3
to
+4
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. 카지노에 Dealer 마다 CarDeck 이 여러개 인것처럼, Dealer 에게 할당되는 CarDeck 이 있다고 생각했습니다. 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 MAXIMUM_SCORE = 21 | ||
|
||
fun countCard(): Int { | ||
return cardDeck.cards.size | ||
} | ||
|
||
fun drawCard(): Card { | ||
val currentCard = cardDeck.cards | ||
.shuffled() | ||
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. 카지노라고 가정하더라도 카드를 뽑을 때마다 매번 섞지는 않지 않을까요!?? |
||
.first() | ||
|
||
cardDeck = cardDeck.cards | ||
.filter { it != currentCard } | ||
.let(::CardDeck) | ||
Comment on lines
+16
to
+18
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.
불변 객체 관리 좋습니다. 👍🏻 cardDeck = cardDeck.drawFirst() |
||
|
||
return currentCard | ||
} | ||
Comment on lines
+11
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. CarDeck 의 cards 를 매번 새로 생성해서, 불변 객체로 관리하고자 했습니다. |
||
|
||
fun startGame(players: List<Player>): List<Player> { | ||
return initializePlayerHands(players) | ||
} | ||
|
||
private fun initializePlayerHands(players: List<Player>): List<Player> { | ||
players.forEach { player -> | ||
player requestCardToDealer drawCard() | ||
player requestCardToDealer drawCard() | ||
} | ||
|
||
return players | ||
} | ||
Comment on lines
+23
to
+34
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 dealer: Dealer
val players1: List<Player>
val players2: List<Player>
...
dealer.startGame(players1)
dealer.startGame(players2)
dealer.startGame(players1)
... 제가 생각한 바로는 딜러도 결국 하나의 블랙잭 게임에 참여하는 구성원으로 볼 수 있지는 않을까요? class BlackJackGame(딜러, 플레이어들, 덱) |
||
} | ||
|
||
infix fun Dealer.checkDrawCardIsAllowedFor(player: Player): Boolean { | ||
return player.calculateScore() < MAXIMUM_SCORE | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package blackJack.model | ||
|
||
class Player( | ||
val name: String, | ||
var hand: List<Card> = listOf() | ||
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. 중위함수를 객체 외부에 작성하면서 외부에서 값을 수정할 수 있도록 불변 객체를 유지하는 것을 목표로 한다면, 어떠한 동작이 새로운 Player를 반환하도록 만들 수 있지 않을까요? |
||
) { | ||
fun calculateScore(): Int { | ||
return hand.sumOf { it.rank.score } | ||
} | ||
} | ||
|
||
infix fun Player.askMoreCard(dealer: Dealer) { | ||
if (dealer checkDrawCardIsAllowedFor this) { | ||
this requestCardToDealer dealer.drawCard() | ||
} | ||
} | ||
|
||
infix fun Player.requestCardToDealer(card: Card) { | ||
hand += card | ||
} | ||
Comment on lines
+12
to
+20
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. 중위함수도 결국 함수이기 때문에 프로젝트의 규모와 관계없다고 생각을 합니다.
코틀린에서 사용되는 대표적인 중위 함수로는 val contains = player in players |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackJack.model.enums | ||
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. enums 를 별도의 패키지로 관리하는 것은 어떤 이점이 있나요? 만약 더이상 enum class가 아니게 된다면 패키지도 바꿔야 하는 문제가 되지는 않을까요? |
||
|
||
enum class Rank( | ||
val symbol: String, | ||
val score: Int, | ||
val isAce: Boolean = false | ||
) { | ||
ACE("A", 1, true), | ||
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("J", 10), | ||
QUEEN("Q", 10), | ||
KING("K", 10), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package blackJack.model.enums | ||
|
||
enum class Suit(val symbol: String) { | ||
CLUBS("클로버"), | ||
DIAMONDS("다이아몬드"), | ||
HEARTS("하트"), | ||
SPADES("스페이드") | ||
} | ||
Comment on lines
+3
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. symbol의 경우 어떻게 출력할지는 UI의 관심사로 볼 수 있지 않을까요? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package blackJack.view | ||
|
||
object InputView { | ||
private const val PLAYER_QUERY_FORMAT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)" | ||
|
||
fun getNames(): List<String> { | ||
println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") | ||
val input = readlnOrNull() ?: throw IllegalArgumentException("콘솔 입력을 확인해 주세요.") | ||
|
||
return input.replace(" ", "") | ||
.split(",") | ||
Comment on lines
+10
to
+11
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. 순서의 차이같지만, trim() 을 사용하도록 만들 수도 있을 것 같네요. input.split(",")
.map { it.trim() } |
||
} | ||
|
||
fun getPlayerInput(playerName: String): 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. getPlayerInput()이 반환하는 String이 무엇인지 예측하기 어려워 보입니다. |
||
println(PLAYER_QUERY_FORMAT.format(playerName)) | ||
|
||
val input = readlnOrNull() ?: throw IllegalArgumentException("콘솔 입력을 확인해 주세요.") | ||
require(input == "y" || input == "n") ?: throw IllegalArgumentException("y 또는 n을 입력해 주세요.") | ||
|
||
return input | ||
} | ||
} | ||
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,28 @@ | ||
package blackJack.view | ||
|
||
import blackJack.model.Player | ||
|
||
object OutputView { | ||
private const val PLAYER_STATE_FORMAT = "%s카드 : %s" | ||
private const val PLAYER__FINAL_STATE_FORMAT = "%s카드 : %s - 결과: %s" | ||
|
||
fun printPlayersState(players: List<Player>) { | ||
players.forEach { printPlayerState(it) } | ||
} | ||
|
||
fun printFinalState(players: List<Player>) { | ||
players.forEach { printPlayerFinalState(it) } | ||
} | ||
|
||
fun printPlayerState(player: Player) { | ||
player.hand | ||
.joinToString { it.rank.symbol + it.suit.symbol } | ||
.let { println(PLAYER_STATE_FORMAT.format(player.name, it)) } | ||
} | ||
|
||
private fun printPlayerFinalState(player: Player) { | ||
player.hand | ||
.joinToString { it.rank.symbol + it.suit.symbol } | ||
.let { println(PLAYER__FINAL_STATE_FORMAT.format(player.name, it, player.calculateScore())) } | ||
} | ||
Comment on lines
+9
to
+27
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. MVC 패턴의 정의에 따라서 View 를 작성했습니다.
Q. view 가 model 을 알게됨으로 발생하는 비용은 없나요? 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.
질문에서 이미 답을 어느정도 가지고 계신 것 같습니다. DTO를 이용해서 출력을 하는 것 자체는 관심사 분리라는 이점을 가지지만 충분히 이해하고 있다면 미션에서 이를 적용하시지는 않아도 괜찮습니다. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
package study.dto | ||
package study.domain | ||
|
||
data class Language(val name: String, val level: Int) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
package study.dto | ||
package study.domain | ||
|
||
data class Person(val name: String, val company: String, val skills: List<Skill>, val languages: List<Language>) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
package study.dto | ||
package study.domain | ||
|
||
data class Skill(val description: String) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package blackjack.domain | ||
|
||
import blackJack.model.Dealer | ||
import blackJack.model.Player | ||
import io.kotest.core.spec.style.BehaviorSpec | ||
import io.kotest.matchers.shouldBe | ||
|
||
class DealerSpec : BehaviorSpec({ | ||
given("딜러와 플레이어 2명이 있을떄") { | ||
val dealer = Dealer("dealer") | ||
val player1 = Player("player1") | ||
val player2 = Player("player2") | ||
val players = listOf(player1, player2) | ||
|
||
`when`("게임을 시작했을때") { | ||
dealer.startGame(players) | ||
|
||
then("플레이어는 딜러가 나눠준 카드를 두장씩 갖고 있다.") { | ||
players[0].hand.size shouldBe 2 | ||
players[1].hand.size shouldBe 2 | ||
} | ||
} | ||
} | ||
|
||
given("딜러에게 카드덱이 주어지고") { | ||
val dealer = Dealer("dealer") | ||
|
||
`when`("딜러가 게임을 시작했을때") { | ||
val players = listOf(Player("player1"), Player("player2")) | ||
dealer.startGame(players) | ||
|
||
then("플레이어는 딜러가 나눠준 카드를 두장씩 갖고 있다.") { | ||
players[0].hand.size shouldBe 2 | ||
players[1].hand.size shouldBe 2 | ||
} | ||
} | ||
|
||
`when`("딜러에게 카드를 한장 나눠줬을때") { | ||
val prevCount = dealer.countCard() | ||
dealer.drawCard() | ||
val currentCount = dealer.countCard() | ||
|
||
then("카드덱에는 한장의 카드가 사라졌다.") { | ||
currentCount shouldBe prevCount - 1 | ||
} | ||
} | ||
Comment on lines
+38
to
+46
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 comment
The reason will be displayed to describe this comment to others. Learn more.
req
라는 이름 대신 명확하게 변수 이름을 정해주면 좋을 것 같아요!이하 동일합니다.