-
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
블랙잭 2단계 구현 #615
base: bsgreentea
Are you sure you want to change the base?
블랙잭 2단계 구현 #615
Changes from all commits
b2e1f65
6bbe688
8278288
2a5331c
376b5bb
2059ffd
59c6b76
0d038e4
89cd455
8e76b66
0856693
a6f7065
a297099
b6f555a
f4cb19f
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,17 @@ | ||
# kotlin-blackjack | ||
# kotlin-blackjack | ||
|
||
게임 관련 기능 | ||
|
||
- [x] 카드 정보 클래스 구성 | ||
- [x] 참여자 클래스 구성 | ||
- [x] 기본 카드 나눠주기(게임 시작과 함께 2장씩 나눠준다.) | ||
- [x] 카드를 나눠줄 수 있는 상태 확인(나눠줄 카드가 남아있는지, 현재 점수가 21점보다 작은지 판단) | ||
- [x] y/n 응답 별 카드 배분 | ||
- [x] 최적의 점수 구하기 | ||
|
||
입출력 기능 | ||
|
||
- [x] 참여자 입력 구현 | ||
- [x] 현재 카드 상태 출력 | ||
- [x] y/n 응답 묻기 | ||
- [x] 결과 출력 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package blackjack.controller | ||
|
||
import blackjack.model.Card | ||
import blackjack.model.CardInfo | ||
import blackjack.model.CardType | ||
import blackjack.model.Participant | ||
import blackjack.ui.InputView | ||
import blackjack.ui.ResultView | ||
|
||
class BlackJackGame( | ||
val participants: List<Participant>, | ||
) { | ||
|
||
private val cardsPool = mutableSetOf<Card>() | ||
|
||
init { | ||
makeCardsPool() | ||
allocateDefaultCards() | ||
} | ||
|
||
fun allocateCards() { | ||
participants.forEach { participant -> | ||
while (participant.isPossibleToTakeMoreCard()) { | ||
if (InputView.askCardPicking(participant.name)) { | ||
allocateOneCard(participant) | ||
ResultView.showStatusOfParticipant(participant) | ||
} else { | ||
ResultView.showStatusOfParticipant(participant) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
fun isPossibleToAllocation() = cardsPool.isNotEmpty() | ||
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. 테스트에서만 활용되는 함수네요. 테스트에서만 사용되는 로직을 위해 비즈니스 로직을 public으로 변경하는것은 좋지 않습니다. 가독성의 이유만으로 분리한 private 함수의 경우 public으로도 검증 가능하다고 여길 수 있으나 가독성 이상의 역할을 하는 경우 testable하게 구현하기 위해서는 클래스 분리를 할 시점은 아닐지 고민해볼 수 있습니다. 🙂 |
||
|
||
private fun makeCardsPool() { | ||
CardType.values().forEach { type -> | ||
CardInfo.values().forEach { cardInfo -> | ||
cardsPool.add(Card(type, cardInfo)) | ||
} | ||
} | ||
} | ||
|
||
private fun allocateDefaultCards() { | ||
participants.forEach { | ||
it.cards.addAll(pickRandomCards(DEFAULT_CARD_COUNTS)) | ||
} | ||
} | ||
|
||
fun allocateOneCard(participant: Participant) { | ||
participant.cards.addAll(pickRandomCards(count = 1)) | ||
} | ||
|
||
private fun pickRandomCards(count: Int): List<Card> { | ||
val pickedCards = cardsPool.shuffled().take(count) | ||
pickedCards.forEach { pickedCard -> | ||
cardsPool.remove(pickedCard) | ||
} | ||
return pickedCards | ||
} | ||
Comment on lines
+55
to
+61
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 { | ||
const val DEFAULT_CARD_COUNTS = 2 | ||
const val BEST_SCORE = 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. 이 상수값을 들고 있는 책임이 Controller인게 적절할까요? |
||
} | ||
} | ||
|
||
fun main() { | ||
val participants = InputView.registerParticipants() | ||
|
||
val blackJackGame = BlackJackGame(participants) | ||
|
||
ResultView.showInitialStatusOfParticipants(participants) | ||
|
||
blackJackGame.allocateCards() | ||
|
||
ResultView.showGameResult(blackJackGame.participants) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package blackjack.model | ||
|
||
data class Card( | ||
val type: CardType, | ||
val info: CardInfo, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package blackjack.model | ||
|
||
enum class CardInfo( | ||
val displayName: String, | ||
val value1: Int, | ||
val value2: Int = value1, | ||
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. A를 1로 처리할지, 11로 처리할지 구분하는 로직 작성이 이번 미션의 핵심 중 하나입니다! |
||
) { | ||
Ace("A", 1, 11), | ||
One("1", 1), | ||
Two("2", 2), | ||
Three("3", 3), | ||
Four("4", 4), | ||
Five("5", 5), | ||
Six("6", 6), | ||
Seven("7", 7), | ||
Eight("8", 8), | ||
Nine("9", 9), | ||
King("K", 10), | ||
Queen("Q", 10), | ||
Jack("J", 10), | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package blackjack.model | ||
|
||
enum class CardType( | ||
val displayName: String, | ||
) { | ||
Diamond("다이아몬드"), | ||
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. 만약 뷰 요구사항이 수정되어 "Diamond"로 출력해야 한다고 가정해봅시다! |
||
Spade("스페이드"), | ||
Heart("하트"), | ||
Clover("클로버"), | ||
; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package blackjack.model | ||
|
||
import blackjack.controller.BlackJackGame.Companion.BEST_SCORE | ||
import kotlin.math.max | ||
import kotlin.math.min | ||
|
||
data class Participant( | ||
val name: String, | ||
val cards: MutableList<Card> = mutableListOf(), | ||
) { | ||
fun isPossibleToTakeMoreCard(): Boolean { | ||
return checkCurrentScore() < BEST_SCORE | ||
} | ||
|
||
private fun checkCurrentScore(): Int { | ||
return cards.sumOf { it.info.value1 } | ||
} | ||
|
||
fun takeBestScore(): Int { | ||
val scoreWithoutAce = cards.filter { it.info != CardInfo.Ace }.sumOf { it.info.value1 } | ||
val countOfAce = cards.count { it.info == CardInfo.Ace } | ||
var bestScore = scoreWithoutAce + countOfAce * CardInfo.Ace.value1 | ||
(0 until countOfAce).forEach { countOfScoreOneAce -> | ||
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. repeat을 사용해보면 어떨까요? |
||
val scoreOfAces = | ||
countOfScoreOneAce * CardInfo.Ace.value1 + (countOfAce - countOfScoreOneAce) * CardInfo.Ace.value2 | ||
val totalScore = scoreWithoutAce + scoreOfAces | ||
bestScore = if (totalScore <= BEST_SCORE) { | ||
max(bestScore, totalScore) | ||
} else { | ||
min(bestScore, totalScore) | ||
} | ||
} | ||
return bestScore | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package blackjack.ui | ||
|
||
import blackjack.model.Participant | ||
|
||
object InputView { | ||
|
||
fun registerParticipants(): List<Participant> { | ||
println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") | ||
return readlnOrNull() | ||
?.split(',') | ||
?.map { name -> | ||
Participant(name) | ||
} | ||
?.toList() | ||
?: throw IllegalArgumentException("참여자의 이름은 null을 허용하지 않습니다.") | ||
} | ||
|
||
fun askCardPicking(name: String): Boolean { | ||
println("${name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)") | ||
return readlnOrNull()?.let { | ||
when (it) { | ||
"y" -> true | ||
"n" -> false | ||
else -> throw IllegalArgumentException("카드 받기 여부는 y 또는 n만 입력 가능합니다.") | ||
} | ||
} ?: throw IllegalArgumentException("카드 받기 여부는 null을 허용하지 않습니다.") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package blackjack.ui | ||
|
||
import blackjack.controller.BlackJackGame | ||
import blackjack.model.Participant | ||
|
||
object ResultView { | ||
|
||
fun showInitialStatusOfParticipants(participants: List<Participant>) { | ||
participants.forEach { participant -> | ||
val separator = if (participant == participants.first()) "" else ", " | ||
print("$separator${participant.name}") | ||
} | ||
println("에게 ${BlackJackGame.DEFAULT_CARD_COUNTS}장의 카드를 나눠주었습니다.") | ||
|
||
participants.forEach { participant -> | ||
print("${participant.name}카드 : ") | ||
showCards(participant) | ||
println() | ||
} | ||
} | ||
|
||
fun showStatusOfParticipant(participant: Participant, useNewLine: Boolean = true) { | ||
print("${participant.name}카드 : ") | ||
showCards(participant) | ||
if (useNewLine) println() | ||
} | ||
|
||
private fun showCards(participant: Participant) { | ||
participant.cards.forEach { | ||
val postfix = if (it == participant.cards.last()) "" else ", " | ||
print("${it.info.displayName}${it.type.displayName}$postfix") | ||
} | ||
} | ||
|
||
fun showGameResult(participants: List<Participant>) { | ||
println() | ||
participants.forEach { participant -> | ||
showStatusOfParticipant(participant, useNewLine = false) | ||
println(" - 결과 : ${participant.takeBestScore()}") | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,17 +24,16 @@ class DslTest { | |
name shouldBe "greentea.latte" | ||
company shouldBe "kakao" | ||
softSkills shouldContainInOrder listOf( | ||
"A passion for problem solving", | ||
"Good communication skills" | ||
Skill.SoftSkill("A passion for problem solving"), | ||
Skill.SoftSkill("Good communication skills"), | ||
) | ||
hardSkills shouldContain "Kotlin" | ||
hardSkills shouldContain Skill.HardSkill("Kotlin") | ||
languageLevels shouldContainInOrder listOf( | ||
"Korean" to 5, | ||
"English" to 3, | ||
) | ||
} | ||
} | ||
|
||
} | ||
|
||
fun introduce(block: PersonBuilder.() -> Unit): Person { | ||
|
@@ -45,8 +44,8 @@ class PersonBuilder { | |
private lateinit var name: String | ||
private lateinit var company: String | ||
|
||
private val softSkills = mutableListOf<String>() | ||
private val hardSkills = mutableListOf<String>() | ||
private val softSkills = mutableListOf<Skill.SoftSkill>() | ||
private val hardSkills = mutableListOf<Skill.HardSkill>() | ||
|
||
private val languageLevels = mutableListOf<Pair<String, Int>>() | ||
|
||
|
@@ -63,11 +62,11 @@ class PersonBuilder { | |
} | ||
|
||
fun soft(value: String) { | ||
softSkills.add(value) | ||
softSkills.add(Skill.SoftSkill(value)) | ||
} | ||
|
||
fun hard(value: String) { | ||
hardSkills.add(value) | ||
hardSkills.add(Skill.HardSkill(value)) | ||
} | ||
|
||
fun languages(block: PersonBuilder.() -> Unit): PersonBuilder { | ||
|
@@ -92,7 +91,12 @@ class PersonBuilder { | |
data class Person( | ||
val name: String, | ||
val company: String?, | ||
val softSkills: List<String>, | ||
val hardSkills: List<String>, | ||
val softSkills: List<Skill.SoftSkill>, | ||
val hardSkills: List<Skill.HardSkill>, | ||
val languageLevels: List<Pair<String, Int>>, | ||
) | ||
) | ||
|
||
sealed interface Skill { | ||
data class SoftSkill(val name: String) : Skill | ||
data class HardSkill(val name: String) : Skill | ||
} | ||
Comment on lines
+99
to
+102
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. sealed interface를 잘 사용해주셨습니다! enum과 어떤 점이 다른지 눈치채셨나요? 앞으로 블랙잭 미션을 진행하면서 활용해볼 곳이 있는지 고민해보시면 좋을 것 같아요! |
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.
Controller 역할을 하고 있는 BlackJackGame 내에서 분리해볼 수 있는 역할/책임이 있을까요?
다시 말해서, Controller를 만약 테스트하지 않는다고 하면(관련 코멘트는 따로 드릴게요) 테스트 범위에서 벗어나는 게임 요구사항이 있을까요?