-
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단계 - 블랙잭 #682
base: hyotaek-jang
Are you sure you want to change the base?
2단계 - 블랙잭 #682
Changes from all commits
2ec4008
571bc6b
935d909
8e35001
36eee72
7e8a0a7
0464053
57cc773
670cef8
0f6ee4a
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 |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package blackjack | ||
|
||
import blackjack.BlackJackController.initPlayer | ||
import blackjack.BlackJackController.initSetting | ||
import blackjack.BlackJackController.progressGame | ||
import blackjack.domain.Deck | ||
import blackjack.domain.Player | ||
import blackjack.view.InputView | ||
import blackjack.view.OutputView | ||
|
||
object BlackJackController { | ||
fun initPlayer(names: List<String>): List<Player> { | ||
return names.map { Player(it) } | ||
} | ||
|
||
fun initSetting(players: List<Player>, deck: Deck) { | ||
players.forEach { | ||
it.addCard(deck.draw(2)) | ||
} | ||
} | ||
|
||
fun progressGame(player: Player, deck: Deck) { | ||
while (player.canDraw() && InputView.inputHitOrStand(player.name)) { | ||
player.addCard(deck.draw()) | ||
OutputView.printCards(player.name, player.cards) | ||
} | ||
} | ||
} | ||
|
||
fun main() { | ||
val deck = Deck.create() | ||
val players = initPlayer(InputView.inputNames()) | ||
|
||
initSetting(players, deck) | ||
OutputView.printDrawTwoCards(players) | ||
|
||
players.forEach { progressGame(it, deck) } | ||
OutputView.printPlayersScore(players) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# kotlin-blackjack | ||
|
||
## 기능 요구사항 | ||
|
||
- [x] 카드의 종류는 스페이드, 다이아몬드, 하트, 클로버의 4가지로 한다. | ||
- [x] 숫자는 각각 1에서 10까지의 점수를 갖는다. | ||
- [x] A는 1 또는 11로 계산할 수 있다. | ||
- [x] A를 11로 계산했을 때 21을 초과하면 1로 계산한다. | ||
- [x] J, Q, K는 각각 10으로 계산한다. | ||
- [x] 게임에 참여할 사람의 이름은 쉼표(,)를 기준으로 구분한다. | ||
- [x] 게임을 시작하면 각 플레이어는 각자 두 장의 카드를 지급 받는다. | ||
- [x] 받은 카드를 출력한다. | ||
- [x] 카드 숫자를 모두 합쳐 21을 초과하지 않으면 카드를 더 받을 수 있다. | ||
- [x] 한 사용자의 받기가 끝나면 다음 사용자로 넘어간다. | ||
- [x] 예는 y, 아니오는 n을 입력한다. | ||
- [x] 카드를 받을 때마다 현재 갖고 있는 카드를 출력한다. | ||
- [x] 더이상 카드를 받지 않는다면 각 플레이어는 각자가 가진 카드의 합을 계산한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package blackjack.domain | ||
|
||
object BlackJackGame { | ||
const val MAX_SCORE = 21 | ||
private const val ADDITIONAL_ACE_SCORE = 10 | ||
|
||
fun score(cards: CardList): Int { | ||
var score = cards.cards.sumOf { it.score() } | ||
if (cards.hasAce() && score + ADDITIONAL_ACE_SCORE <= MAX_SCORE) { | ||
score += ADDITIONAL_ACE_SCORE | ||
} | ||
return score | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package blackjack.domain | ||
|
||
class Card(private val suit: Suit, private val rank: Rank) { | ||
fun score(): Int { | ||
return rank.score | ||
} | ||
|
||
fun isAce(): Boolean { | ||
return rank == Rank.ACE | ||
} | ||
|
||
override fun toString(): String { | ||
return "${rank.outputName}${suit.value}" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package blackjack.domain | ||
|
||
class CardList(private val _cards: MutableList<Card> = mutableListOf()) { | ||
val cards: List<Card> | ||
get() = _cards | ||
|
||
fun addCard(newCard: Card) { | ||
_cards.add(newCard) | ||
} | ||
|
||
fun addCard(newCards: List<Card>) { | ||
_cards.addAll(newCards) | ||
} | ||
|
||
fun hasAce(): Boolean { | ||
return _cards.any(Card::isAce) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package blackjack.domain | ||
|
||
import java.util.Stack | ||
|
||
class Deck(private val cards: Stack<Card>) { | ||
fun draw(): Card { | ||
require(cards.isNotEmpty()) { "카드가 없습니다." } | ||
return cards.pop() | ||
} | ||
|
||
fun draw(count: Int): List<Card> { | ||
require(cards.size >= count) { "카드가 없습니다." } | ||
return (1..count).map { cards.pop() } | ||
} | ||
|
||
companion object { | ||
fun create(): Deck { | ||
val cards: List<Card> = Rank.getRankSet().values | ||
.flatMap { rank -> Suit.getSuitSet().values.map { suit -> Card(suit, rank) } } | ||
.shuffled() | ||
|
||
return Deck( | ||
Stack<Card>().apply { | ||
addAll(cards) | ||
} | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package blackjack.domain | ||
|
||
import blackjack.domain.BlackJackGame.MAX_SCORE | ||
import blackjack.domain.BlackJackGame.score | ||
|
||
class Player(val name: String, val cards: CardList = 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 addCard(newCard: Card) { | ||
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. 참가자가 카드를 추가하는것도 좋지만 카드를 뽑는 행위를 제공하는것도 좋을것 같아요 😄 |
||
cards.addCard(newCard) | ||
} | ||
|
||
fun addCard(newCards: List<Card>) { | ||
cards.addCard(newCards) | ||
} | ||
Comment on lines
+11
to
+13
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 canDraw(): Boolean { | ||
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. 플레이어가 카드를 뽑지 못하는 상황은 뽑은 카드의 합이 21이 넘었을 경우와 스스로 카드를 그만 받는다고 선언한 상태가 존재합니다. |
||
return score(cards) < MAX_SCORE | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package blackjack.domain | ||
|
||
enum class Rank(val outputName: String, val score: Int) { | ||
ACE("A", 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), | ||
TEN("10", 10), | ||
J("J", 10), | ||
Q("Q", 10), | ||
K("K", 10); | ||
|
||
companion object { | ||
private val RANK_SET = values().associateBy { it.outputName } | ||
|
||
fun getRankSet(): Map<String, Rank> { | ||
return RANK_SET | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package blackjack.domain | ||
|
||
enum class Suit(val value: String) { | ||
SPADE("스페이드"), DIAMOND("다이아몬드"), HEART("하트"), CLUB("클로버"); | ||
|
||
companion object { | ||
private val SUIT_SET = values().associateBy { it.value } | ||
|
||
fun getSuitSet(): Map<String, Suit> { | ||
return SUIT_SET | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package blackjack.view | ||
|
||
object InputView { | ||
private const val DELIMITER = "," | ||
private val RESPONSE_MAP = mapOf("y" to true, "n" to false) | ||
|
||
fun inputNames(): List<String> { | ||
println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") | ||
return readln().split(DELIMITER) | ||
} | ||
|
||
fun inputHitOrStand(name: String): Boolean { | ||
println("\n${name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)") | ||
return RESPONSE_MAP[readln()] ?: throw IllegalArgumentException("잘못된 입력입니다.") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package blackjack.view | ||
|
||
import blackjack.domain.BlackJackGame | ||
import blackjack.domain.CardList | ||
import blackjack.domain.Player | ||
|
||
object OutputView { | ||
private const val DELIMITER = ", " | ||
|
||
fun printCards(name: String, cards: CardList) { | ||
println("${name}카드: ${cards.cards.joinToString(DELIMITER)}") | ||
} | ||
|
||
fun printDrawTwoCards(players: List<Player>) { | ||
println("\n${players.joinToString(DELIMITER) { it.name }}에게 2장의 카드를 나누었습니다.") | ||
players.forEach { printCards(it.name, it.cards) } | ||
} | ||
|
||
fun printPlayersScore(players: List<Player>) { | ||
println() | ||
players.forEach { printPlayerScore(it, it.cards) } | ||
} | ||
|
||
private fun printPlayerScore(player: Player, cards: CardList) { | ||
println("${player.name}카드 : ${cards.cards.joinToString(DELIMITER)} - 결과: ${BlackJackGame.score(cards)}") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,4 +26,3 @@ class PersonBuilder { | |
return Person(name, company, skills, languages) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
package dsl.domain | ||
|
||
class Skills(val soft: List<String>, val hard: List<String>) { | ||
} | ||
class Skills(val soft: List<String>, val hard: List<String>) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package blackjack.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class BlackJackGameTest { | ||
@Test | ||
fun `카드 점수 계산 - 에이스를 제외하고 카드 점수의 총합이 11점 이상이면 에이스는 1점이 된다`() { | ||
// given | ||
val cardList = CardList() | ||
val cards = listOf( | ||
Card(Suit.SPADE, Rank.ACE), | ||
Card(Suit.DIAMOND, Rank.NINE), | ||
Card(Suit.HEART, Rank.TWO) | ||
) | ||
cardList.addCard(cards) | ||
|
||
// when | ||
val score = BlackJackGame.score(cardList) | ||
|
||
// then | ||
assertThat(score).isEqualTo(12) | ||
} | ||
|
||
@Test | ||
fun `카드 점수 계산 - 에이스를 제외하고 카드 점수의 총합이 10점 이하면 에이스는 11점이 된다`() { | ||
// given | ||
val cardList = CardList() | ||
val cards = listOf( | ||
Card(Suit.SPADE, Rank.ACE), | ||
Card(Suit.DIAMOND, Rank.SIX), | ||
Card(Suit.HEART, Rank.FOUR) | ||
) | ||
cardList.addCard(cards) | ||
|
||
// when | ||
val score = BlackJackGame.score(cardList) | ||
|
||
// then | ||
assertThat(score).isEqualTo(21) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package blackjack.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class CardListTest { | ||
@Test | ||
fun `단건 카드 추가`() { | ||
// given | ||
val cardList = CardList() | ||
val cards = listOf(Card(Suit.SPADE, Rank.ACE)) | ||
|
||
// when | ||
cardList.addCard(cards) | ||
|
||
// then | ||
assertThat(cardList.cards).containsAll(cards) | ||
} | ||
|
||
@Test | ||
fun `여러 카드 추가`() { | ||
// given | ||
val cardList = CardList() | ||
val cards = listOf( | ||
Card(Suit.SPADE, Rank.ACE), | ||
Card(Suit.DIAMOND, Rank.NINE), | ||
Card(Suit.HEART, Rank.TWO) | ||
) | ||
|
||
// when | ||
cardList.addCard(cards) | ||
|
||
// then | ||
assertThat(cardList.cards).containsAll(cards) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package blackjack.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertThrows | ||
import java.lang.IllegalArgumentException | ||
|
||
internal class DeckTest { | ||
@Test | ||
fun `Deck은 52장의 카드를 갖는다 따라서 그 이상의 카드를 draw하면 오류가 발생한다`() { | ||
// given, when | ||
val deck = Deck.create() | ||
val deckSize = 52 | ||
|
||
// then | ||
assertThrows<IllegalArgumentException> { deck.draw(deckSize + 1) } | ||
} | ||
|
||
@Test | ||
fun `Deck은 52장의 카드를 갖는다`() { | ||
// given, when | ||
val deck = Deck.create() | ||
val deckSize = 52 | ||
|
||
// then | ||
assertThat(deck.draw(deckSize).size).isEqualTo(deckSize) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package blackjack.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class PlayerTest { | ||
@Test | ||
fun `21점을 넘지 않으면 플레이어는 게임을 계속할 수 있다`() { | ||
// given | ||
val player = Player("test") | ||
val cardList = CardList() | ||
val cards = listOf( | ||
Card(Suit.SPADE, Rank.ACE), | ||
Card(Suit.DIAMOND, Rank.NINE), | ||
Card(Suit.HEART, Rank.TWO) | ||
) | ||
cardList.addCard(cards) | ||
|
||
// when | ||
val isContinue = player.canDraw() | ||
|
||
// then | ||
assertThat(isContinue).isTrue() | ||
} | ||
} |
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.
코멘트 남겨주신것 처럼 BlackJackGame의 책임이 모호한거 같아요 😄
플레이어가 뽑은 카드의 점수를 계산하는 책임을 부여 해주셨는데 개인적으로는 CardList 일급 컬렉션이 해당 기능을 제공하는것이 자연스러운 모델링이 될수 있을것 같은데 검토 부탁드려요 😄