diff --git a/src/main/kotlin/blackjack/BlackJackController.kt b/src/main/kotlin/blackjack/BlackJackController.kt new file mode 100644 index 0000000000..f6ad51076c --- /dev/null +++ b/src/main/kotlin/blackjack/BlackJackController.kt @@ -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): List { + return names.map { Player(it) } + } + + fun initSetting(players: List, 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) +} diff --git a/src/main/kotlin/blackjack/README.md b/src/main/kotlin/blackjack/README.md new file mode 100644 index 0000000000..edbded6b63 --- /dev/null +++ b/src/main/kotlin/blackjack/README.md @@ -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] 더이상 카드를 받지 않는다면 각 플레이어는 각자가 가진 카드의 합을 계산한다. diff --git a/src/main/kotlin/blackjack/domain/BlackJackGame.kt b/src/main/kotlin/blackjack/domain/BlackJackGame.kt new file mode 100644 index 0000000000..a83576cb85 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/BlackJackGame.kt @@ -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 + } +} diff --git a/src/main/kotlin/blackjack/domain/Card.kt b/src/main/kotlin/blackjack/domain/Card.kt new file mode 100644 index 0000000000..c18a3a595c --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Card.kt @@ -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}" + } +} diff --git a/src/main/kotlin/blackjack/domain/CardList.kt b/src/main/kotlin/blackjack/domain/CardList.kt new file mode 100644 index 0000000000..680a919f13 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/CardList.kt @@ -0,0 +1,18 @@ +package blackjack.domain + +class CardList(private val _cards: MutableList = mutableListOf()) { + val cards: List + get() = _cards + + fun addCard(newCard: Card) { + _cards.add(newCard) + } + + fun addCard(newCards: List) { + _cards.addAll(newCards) + } + + fun hasAce(): Boolean { + return _cards.any(Card::isAce) + } +} diff --git a/src/main/kotlin/blackjack/domain/Deck.kt b/src/main/kotlin/blackjack/domain/Deck.kt new file mode 100644 index 0000000000..1aaa65fd9a --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Deck.kt @@ -0,0 +1,29 @@ +package blackjack.domain + +import java.util.Stack + +class Deck(private val cards: Stack) { + fun draw(): Card { + require(cards.isNotEmpty()) { "카드가 없습니다." } + return cards.pop() + } + + fun draw(count: Int): List { + require(cards.size >= count) { "카드가 없습니다." } + return (1..count).map { cards.pop() } + } + + companion object { + fun create(): Deck { + val cards: List = Rank.getRankSet().values + .flatMap { rank -> Suit.getSuitSet().values.map { suit -> Card(suit, rank) } } + .shuffled() + + return Deck( + Stack().apply { + addAll(cards) + } + ) + } + } +} diff --git a/src/main/kotlin/blackjack/domain/Player.kt b/src/main/kotlin/blackjack/domain/Player.kt new file mode 100644 index 0000000000..e2c1b2b36c --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Player.kt @@ -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()) { + fun addCard(newCard: Card) { + cards.addCard(newCard) + } + + fun addCard(newCards: List) { + cards.addCard(newCards) + } + + fun canDraw(): Boolean { + return score(cards) < MAX_SCORE + } +} diff --git a/src/main/kotlin/blackjack/domain/Rank.kt b/src/main/kotlin/blackjack/domain/Rank.kt new file mode 100644 index 0000000000..a06e783ff4 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Rank.kt @@ -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 { + return RANK_SET + } + } +} diff --git a/src/main/kotlin/blackjack/domain/Suit.kt b/src/main/kotlin/blackjack/domain/Suit.kt new file mode 100644 index 0000000000..6c9062af2d --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Suit.kt @@ -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 { + return SUIT_SET + } + } +} diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt new file mode 100644 index 0000000000..5b45f136ac --- /dev/null +++ b/src/main/kotlin/blackjack/view/InputView.kt @@ -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 { + println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") + return readln().split(DELIMITER) + } + + fun inputHitOrStand(name: String): Boolean { + println("\n${name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)") + return RESPONSE_MAP[readln()] ?: throw IllegalArgumentException("잘못된 입력입니다.") + } +} diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt new file mode 100644 index 0000000000..95474e1c47 --- /dev/null +++ b/src/main/kotlin/blackjack/view/OutputView.kt @@ -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) { + println("\n${players.joinToString(DELIMITER) { it.name }}에게 2장의 카드를 나누었습니다.") + players.forEach { printCards(it.name, it.cards) } + } + + fun printPlayersScore(players: List) { + println() + players.forEach { printPlayerScore(it, it.cards) } + } + + private fun printPlayerScore(player: Player, cards: CardList) { + println("${player.name}카드 : ${cards.cards.joinToString(DELIMITER)} - 결과: ${BlackJackGame.score(cards)}") + } +} diff --git a/src/main/kotlin/dsl/domain/PersonBuilder.kt b/src/main/kotlin/dsl/domain/PersonBuilder.kt index 6c58394f54..55e4271f26 100644 --- a/src/main/kotlin/dsl/domain/PersonBuilder.kt +++ b/src/main/kotlin/dsl/domain/PersonBuilder.kt @@ -26,4 +26,3 @@ class PersonBuilder { return Person(name, company, skills, languages) } } - diff --git a/src/main/kotlin/dsl/domain/Skills.kt b/src/main/kotlin/dsl/domain/Skills.kt index 0d2a5b0edc..142e88b80c 100644 --- a/src/main/kotlin/dsl/domain/Skills.kt +++ b/src/main/kotlin/dsl/domain/Skills.kt @@ -1,4 +1,3 @@ package dsl.domain -class Skills(val soft: List, val hard: List) { -} +class Skills(val soft: List, val hard: List) diff --git a/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt new file mode 100644 index 0000000000..2f49c0a818 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/BlackJackGameTest.kt @@ -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) + } +} diff --git a/src/test/kotlin/blackjack/domain/CardListTest.kt b/src/test/kotlin/blackjack/domain/CardListTest.kt new file mode 100644 index 0000000000..8cb76e7f8a --- /dev/null +++ b/src/test/kotlin/blackjack/domain/CardListTest.kt @@ -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) + } +} diff --git a/src/test/kotlin/blackjack/domain/DeckTest.kt b/src/test/kotlin/blackjack/domain/DeckTest.kt new file mode 100644 index 0000000000..01fd4707e5 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/DeckTest.kt @@ -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 { 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) + } +} diff --git a/src/test/kotlin/blackjack/domain/PlayerTest.kt b/src/test/kotlin/blackjack/domain/PlayerTest.kt new file mode 100644 index 0000000000..b5465482a4 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/PlayerTest.kt @@ -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() + } +} diff --git a/src/test/kotlin/blackjack/domain/RankTest.kt b/src/test/kotlin/blackjack/domain/RankTest.kt new file mode 100644 index 0000000000..8edf07ceb6 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/RankTest.kt @@ -0,0 +1,17 @@ +package blackjack.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class RankTest { + + @Test + fun `Rank는 1부터 10까지의 점수를 갖는다`() { + // given, when + val rankList = Rank.values().map { it.score }.sorted() + + // then + assertThat(rankList.first()).isEqualTo(1) + assertThat(rankList.last()).isEqualTo(10) + } +} diff --git a/src/test/kotlin/blackjack/domain/SuitTest.kt b/src/test/kotlin/blackjack/domain/SuitTest.kt new file mode 100644 index 0000000000..0d3174252a --- /dev/null +++ b/src/test/kotlin/blackjack/domain/SuitTest.kt @@ -0,0 +1,19 @@ +package blackjack.domain + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class SuitTest { + @Test + fun `Suit는 4가지 종류의 무늬(스페이드, 다이아몬드, 하트, 클로버)를 갖는다`() { + // given, when + val suitSet = Suit.getSuitSet() + + // then + assertThat(suitSet.size).isEqualTo(4) + assertThat(suitSet["스페이드"]).isEqualTo(Suit.SPADE) + assertThat(suitSet["다이아몬드"]).isEqualTo(Suit.DIAMOND) + assertThat(suitSet["하트"]).isEqualTo(Suit.HEART) + assertThat(suitSet["클로버"]).isEqualTo(Suit.CLUB) + } +}