diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..c95608ed1 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +## 기능 구현 목록 +- [X] 카드들을 구현한다. + - [X] 문양을 구현한다. + - [X] 숫자를 구현한다. +- [X] 카드덱을 만든다. +- [X] 딜러랑 유저를 만든다. + - [X] 유저 각자의 카드덱을 만든다. +- [X] 카드의 합을 구한다. +- [X] 게임 초기 세팅을 한다. +- [X] 중간결과를 반환한다. +- [X] 최종결과를 반환한다. + +### 입력 구현 목록 +- [X] 사람들의 이름을 입력받는다. +- [X] 카드를 더 받을지 입력 받는다. +### 출력 구현 목록 +- [X] 나눠준 카드를 출력한다. +- [X] 현재 카드 상태를 출력한다. +- [X] 카드의 결과를 반환한다. \ No newline at end of file diff --git a/src/main/kotlin/blackjack/Application.kt b/src/main/kotlin/blackjack/Application.kt new file mode 100644 index 000000000..fca60eb48 --- /dev/null +++ b/src/main/kotlin/blackjack/Application.kt @@ -0,0 +1,10 @@ +package blackjack + +import blackjack.controller.BlackJackController +import blackjack.view.InputView +import blackjack.view.OutputView + +fun main() { + val blackJackController = BlackJackController(InputView(), OutputView()) + blackJackController.run() +} diff --git a/src/main/kotlin/blackjack/controller/BlackJackController.kt b/src/main/kotlin/blackjack/controller/BlackJackController.kt new file mode 100644 index 000000000..7ed7bad13 --- /dev/null +++ b/src/main/kotlin/blackjack/controller/BlackJackController.kt @@ -0,0 +1,36 @@ +package blackjack.controller + +import blackjack.domain.blackjack.BlackJack +import blackjack.domain.blackjack.BlackJackGame +import blackjack.domain.blackjack.blackJack +import blackjack.domain.card.Cards +import blackjack.view.InputView +import blackjack.view.OutputView + +class BlackJackController( + private val inputView: InputView, + private val outputView: OutputView, +) { + fun run() { + val blackJack = setUpBlackJack() + outputView.outputInitState(blackJack) + startBlackJack(blackJack) + outputView.outputResult(blackJack) + } + + private fun setUpBlackJack(): BlackJack = blackJack { + cardDeck(Cards.all()) + participants { + dealer() + guests(inputView.inputParticipants()) + } + draw() + } + + private fun startBlackJack(blackJack: BlackJack) = + BlackJackGame().apply { + getCommand = inputView::inputDrawMore + guestsTurn(blackJack.guests, blackJack.cardDeck, outputView::outputCard) + dealerTurn(blackJack.dealer, blackJack.cardDeck, outputView::outputDealerDraw) + } +} diff --git a/src/main/kotlin/blackjack/domain/blackjack/BlackJack.kt b/src/main/kotlin/blackjack/domain/blackjack/BlackJack.kt new file mode 100644 index 000000000..d8714de17 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/blackjack/BlackJack.kt @@ -0,0 +1,21 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.CardDeck +import blackjack.domain.participants.Dealer +import blackjack.domain.participants.Guest +import blackjack.domain.participants.Participants +import blackjack.domain.result.Outcome +import blackjack.domain.result.Outcome.Companion.winTo + +data class BlackJack( + val cardDeck: CardDeck, + val participants: Participants, +) { + val dealer: Dealer + get() = participants.dealer + + val guests: List + get() = participants.guests + + fun getResult(): List = participants.guests.map { guest -> guest.winTo(participants.dealer) } +} diff --git a/src/main/kotlin/blackjack/domain/blackjack/BlackJackBuilder.kt b/src/main/kotlin/blackjack/domain/blackjack/BlackJackBuilder.kt new file mode 100644 index 000000000..40e039f99 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/blackjack/BlackJackBuilder.kt @@ -0,0 +1,27 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.Card +import blackjack.domain.card.CardDeck +import blackjack.domain.participants.Participants +import blackjack.domain.participants.ParticipantsBuilder + +fun blackJack(block: BlackJackBuilder.() -> Unit): BlackJack { + return BlackJackBuilder().apply(block).build() +} + +class BlackJackBuilder { + private lateinit var cardDeck: CardDeck + private lateinit var participants: Participants + fun cardDeck(cards: List) { cardDeck = CardDeck(cards.shuffled()) } + + fun participants(block: ParticipantsBuilder.() -> Unit) { + participants = ParticipantsBuilder().apply(block).build() + } + + fun draw() = participants.all().forEach { + it.draw(cardDeck.nextCard()) + it.draw(cardDeck.nextCard()) + } + + fun build(): BlackJack = BlackJack(cardDeck, participants) +} diff --git a/src/main/kotlin/blackjack/domain/blackjack/BlackJackGame.kt b/src/main/kotlin/blackjack/domain/blackjack/BlackJackGame.kt new file mode 100644 index 000000000..21acd1dd1 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/blackjack/BlackJackGame.kt @@ -0,0 +1,43 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.CardDeck +import blackjack.domain.participants.Dealer +import blackjack.domain.participants.Guest +import blackjack.domain.participants.User + +class BlackJackGame { + lateinit var getCommand: (String) -> String + + fun guestsTurn(guests: List, cardDeck: CardDeck, output: (User) -> Unit) = + guests.forEach { guest -> guestTurn(guest, cardDeck, output) } + + fun dealerTurn(dealer: Dealer, cardDeck: CardDeck, output: () -> Unit) { + if (dealer.isBlackJack) return + if (dealer.isContinue) { + dealer.draw(cardDeck.nextCard()) + output() + } + } + + private fun guestTurn(guest: Guest, cardDeck: CardDeck, output: (User) -> Unit) { + if (guest.isBlackJack) return + when (getCommand(guest.name.toString())) { + in DRAW_COMMANDS -> draw(guest, cardDeck, output) + in END_TURN_COMMANDS -> output(guest) + else -> this.guestTurn(guest, cardDeck, output) + } + } + + private fun draw(guest: Guest, cardDeck: CardDeck, output: (User) -> Unit) { + guest.draw(cardDeck.nextCard()) + output(guest) + if (guest.isContinue) { + guestTurn(guest, cardDeck, output) + } + } + + companion object { + private val DRAW_COMMANDS = listOf("Y", "y") + private val END_TURN_COMMANDS = listOf("N", "n") + } +} diff --git a/src/main/kotlin/blackjack/domain/card/Card.kt b/src/main/kotlin/blackjack/domain/card/Card.kt new file mode 100644 index 000000000..506959af0 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/Card.kt @@ -0,0 +1,3 @@ +package blackjack.domain.card + +data class Card(val mark: CardMark, val value: CardValue) diff --git a/src/main/kotlin/blackjack/domain/card/CardDeck.kt b/src/main/kotlin/blackjack/domain/card/CardDeck.kt new file mode 100644 index 000000000..7b8cce676 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/CardDeck.kt @@ -0,0 +1,20 @@ +package blackjack.domain.card + +class CardDeck(cards: List) { + private val cards: MutableList = cards.toMutableList() + + init { + require(cards.toSet().size == cards.size) { ERROR_EXIST_DUPLICATE_CARDS } + require(cards.size == CARDS_SIZE) { ERROR_INVALID_CARDS_SIZE } + } + + val size: Int + get() = cards.size + + fun nextCard(): Card = cards.removeFirst() + companion object { + private const val CARDS_SIZE = 52 + private const val ERROR_INVALID_CARDS_SIZE = "카드덱 초기 사이즈는 ${CARDS_SIZE}장이어야 합니다." + private const val ERROR_EXIST_DUPLICATE_CARDS = "카드덱에는 중복이 없어야 합니다." + } +} diff --git a/src/main/kotlin/blackjack/domain/card/CardMark.kt b/src/main/kotlin/blackjack/domain/card/CardMark.kt new file mode 100644 index 000000000..6a3d1453c --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/CardMark.kt @@ -0,0 +1,8 @@ +package blackjack.domain.card + +enum class CardMark { + CLOVER, + HEART, + SPADE, + DIA, +} diff --git a/src/main/kotlin/blackjack/domain/card/CardValue.kt b/src/main/kotlin/blackjack/domain/card/CardValue.kt new file mode 100644 index 000000000..a647a7a05 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/CardValue.kt @@ -0,0 +1,17 @@ +package blackjack.domain.card + +enum class CardValue(val value: Int) { + ACE(1), + KING(10), + QUEEN(10), + JACK(10), + TEN(10), + NINE(9), + EIGHT(8), + SEVEN(7), + SIX(6), + FIVE(5), + FOUR(4), + THREE(3), + TWO(2), +} diff --git a/src/main/kotlin/blackjack/domain/card/Cards.kt b/src/main/kotlin/blackjack/domain/card/Cards.kt new file mode 100644 index 000000000..1c25a6dbf --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/Cards.kt @@ -0,0 +1,24 @@ +package blackjack.domain.card + +import blackjack.domain.participants.Score + +class Cards(private val cards: Set = setOf()) { + val result = Score(this) + + val size: Int + get() = cards.size + + fun toList() = cards.toList() + + operator fun plus(card: Card): Cards = Cards(cards.plus(card)) + + fun containsACE() = cards.map { it.value }.contains(CardValue.ACE) + + companion object { + private val CARDS: List = CardMark.values().flatMap { mark -> + CardValue.values().map { value -> Card(mark, value) } + } + + fun all(): List = CARDS + } +} diff --git a/src/main/kotlin/blackjack/domain/participants/Dealer.kt b/src/main/kotlin/blackjack/domain/participants/Dealer.kt new file mode 100644 index 000000000..e16be08be --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/Dealer.kt @@ -0,0 +1,10 @@ +package blackjack.domain.participants + +class Dealer(name: String = "딜러") : User(name) { + override val isContinue: Boolean + get() = cards.result.maxScore < DEALER_MIN_NUMBER + + companion object { + private const val DEALER_MIN_NUMBER = 17 + } +} diff --git a/src/main/kotlin/blackjack/domain/participants/Guest.kt b/src/main/kotlin/blackjack/domain/participants/Guest.kt new file mode 100644 index 000000000..1fb7222b0 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/Guest.kt @@ -0,0 +1,13 @@ +package blackjack.domain.participants + +class Guest(name: String) : User(name) { + override val isContinue: Boolean + get() = isNotBust && isBlackJack.not() + + private val isNotBust: Boolean + get() = cards.result.minScore <= BLACKJACK_NUMBER + + companion object { + private const val BLACKJACK_NUMBER = 21 + } +} diff --git a/src/main/kotlin/blackjack/domain/participants/Name.kt b/src/main/kotlin/blackjack/domain/participants/Name.kt new file mode 100644 index 000000000..e4c8330f1 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/Name.kt @@ -0,0 +1,16 @@ +package blackjack.domain.participants + +@JvmInline +value class Name(private val value: String) { + init { + require(value.length in NAME_LENGTH_MIN..NAME_LENGTH_MAX) { ERROR_NAME_LENGTH_MAX } + } + + override fun toString(): String = value + + companion object { + private const val NAME_LENGTH_MIN = 1 + private const val NAME_LENGTH_MAX = 20 + private const val ERROR_NAME_LENGTH_MAX = "이름의 글자수는 $NAME_LENGTH_MIN~${NAME_LENGTH_MAX}사이 입니다." + } +} diff --git a/src/main/kotlin/blackjack/domain/participants/Participants.kt b/src/main/kotlin/blackjack/domain/participants/Participants.kt new file mode 100644 index 000000000..0de611a59 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/Participants.kt @@ -0,0 +1,8 @@ +package blackjack.domain.participants + +data class Participants( + val dealer: Dealer, + val guests: List, +) { + fun all(): List = listOf(dealer) + guests +} diff --git a/src/main/kotlin/blackjack/domain/participants/ParticipantsBuilder.kt b/src/main/kotlin/blackjack/domain/participants/ParticipantsBuilder.kt new file mode 100644 index 000000000..5aaf59517 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/ParticipantsBuilder.kt @@ -0,0 +1,12 @@ +package blackjack.domain.participants + +class ParticipantsBuilder { + private lateinit var dealer: Dealer + private lateinit var guests: List + + fun dealer() { dealer = Dealer() } + + fun guests(names: List) { guests = names.map { Guest(it) } } + + fun build(): Participants = Participants(dealer, guests) +} diff --git a/src/main/kotlin/blackjack/domain/participants/Score.kt b/src/main/kotlin/blackjack/domain/participants/Score.kt new file mode 100644 index 000000000..456f682d7 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/Score.kt @@ -0,0 +1,24 @@ +package blackjack.domain.participants + +import blackjack.domain.card.Cards + +class Score(cards: Cards) { + + val minScore: Int = cards.toList().sumOf { it.value.value } + + val maxScore: Int = + minScore + if (cards.containsACE() && validateAceCondition) ACE_OTHER_NUMBER_DIFF else 0 + + fun score(): Int = if (isMaxScoreInRange) maxScore else minScore + + private val validateAceCondition: Boolean + get() = minScore + ACE_OTHER_NUMBER_DIFF <= BLACKJACK_NUMBER + + private val isMaxScoreInRange: Boolean + get() = maxScore <= BLACKJACK_NUMBER + + companion object { + private const val ACE_OTHER_NUMBER_DIFF = 10 + private const val BLACKJACK_NUMBER = 21 + } +} diff --git a/src/main/kotlin/blackjack/domain/participants/User.kt b/src/main/kotlin/blackjack/domain/participants/User.kt new file mode 100644 index 000000000..a5a932d94 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/participants/User.kt @@ -0,0 +1,23 @@ +package blackjack.domain.participants + +import blackjack.domain.card.Card +import blackjack.domain.card.Cards + +abstract class User(name: String) { + val name = Name(name) + var cards = Cards() + + val score: Int + get() = cards.result.score() + + val isBlackJack: Boolean + get() = cards.result.score() == BLACKJACK_NUMBER + + abstract val isContinue: Boolean + + fun draw(card: Card) { cards += card } + + companion object { + private const val BLACKJACK_NUMBER = 21 + } +} diff --git a/src/main/kotlin/blackjack/domain/result/Outcome.kt b/src/main/kotlin/blackjack/domain/result/Outcome.kt new file mode 100644 index 000000000..2a91eecc9 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/result/Outcome.kt @@ -0,0 +1,24 @@ +package blackjack.domain.result +import blackjack.domain.participants.User +import java.lang.IllegalStateException + +enum class Outcome { + WIN, DRAW, LOSE; + + companion object { + private const val BLACKJACK_NUMBER = 21 + + fun User.winTo(other: User): Outcome = + when { + other.score > BLACKJACK_NUMBER && this.score > BLACKJACK_NUMBER -> DRAW + other.score > BLACKJACK_NUMBER -> WIN + this.score > BLACKJACK_NUMBER -> LOSE + + other.score == this.score -> DRAW + this.score > other.score -> WIN + other.score > this.score -> LOSE + + else -> throw IllegalStateException() + } + } +} diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt new file mode 100644 index 000000000..4509efabf --- /dev/null +++ b/src/main/kotlin/blackjack/view/InputView.kt @@ -0,0 +1,13 @@ +package blackjack.view + +class InputView { + fun inputParticipants(): List { + println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)") + return readln().split(",").map { it.trim() } + } + + fun inputDrawMore(name: String): String { + println("\n${name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)") + return readln() + } +} diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt new file mode 100644 index 000000000..da55b3f69 --- /dev/null +++ b/src/main/kotlin/blackjack/view/OutputView.kt @@ -0,0 +1,77 @@ +package blackjack.view + +import blackjack.domain.blackjack.BlackJack +import blackjack.domain.card.CardMark +import blackjack.domain.card.CardValue +import blackjack.domain.participants.User +import blackjack.domain.result.Outcome + +class OutputView { + fun outputInitState(blackJack: BlackJack) { + print("\n딜러와 ${blackJack.guests.map{it.name}.joinToString(", ")} 에게 2장의 나누었습니다.") + outputCardForDealer(blackJack.dealer) + blackJack.guests.forEach { user -> + outputCard(user) + } + } + + fun outputResult(blackJack: BlackJack) { + blackJack.participants.all().forEach { user -> + outputCard(user) + outputScore(user) + } + println("") + outputOutcomes(blackJack) + } + + private fun outputCardForDealer(user: User) { + val cardText = user.cards.toList()[0].let { it.value.pattern() + it.mark.name() } + print("\n${user.name}카드: $cardText") + } + + fun outputCard(user: User) { + val cardText = user.cards.toList().joinToString(", ") { it.value.pattern() + it.mark.name() } + print("\n${user.name}카드: $cardText") + } + + fun outputDealerDraw() { + println("\n\n딜러는 16이하라 한장의 카드를 더 받았습니다.") + } + + private fun outputScore(user: User) { + print(" - 결과: ${user.score}") + } + + private fun outputOutcomes(blackJack: BlackJack) { + blackJack.run { + println("\n## 최종 승패") + println("${dealer.name}: ${getResult().count { it == Outcome.LOSE }}승 ${getResult().count { it == Outcome.WIN }}패") + guests.forEachIndexed { index, user -> outputOutcome(user, getResult()[index]) } + } + } + + private fun outputOutcome(user: User, outcome: Outcome) { + when (outcome) { + Outcome.WIN -> "승" + Outcome.DRAW -> "무" + Outcome.LOSE -> "패" + }.let { println("${user.name}: $it") } + } + + private fun CardMark.name(): String = + when (this) { + CardMark.CLOVER -> "클로버" + CardMark.DIA -> "다이아몬드" + CardMark.HEART -> "하트" + CardMark.SPADE -> "스페이드" + } + + private fun CardValue.pattern(): String = + when (this) { + CardValue.ACE -> "A" + CardValue.KING -> "K" + CardValue.QUEEN -> "Q" + CardValue.JACK -> "J" + else -> this.value.toString() + } +} diff --git a/src/test/kotlin/blackjack/domain/blackjack/BlackJackBuilderTest.kt b/src/test/kotlin/blackjack/domain/blackjack/BlackJackBuilderTest.kt new file mode 100644 index 000000000..156eaffa9 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/blackjack/BlackJackBuilderTest.kt @@ -0,0 +1,29 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.Cards +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll + +class BlackJackBuilderTest { + @Test + fun `세팅이 된다`() { + val blackJack = blackJack { + cardDeck(Cards.all()) + participants { + dealer() + guests(listOf("아크", "로피")) + } + draw() + } + assertAll( + { assertThat(blackJack.guests[0].name.toString()).isEqualTo("아크") }, + { assertThat(blackJack.guests[0].cards.size).isEqualTo(2) }, + { assertThat(blackJack.guests[1].name.toString()).isEqualTo("로피") }, + { assertThat(blackJack.guests[1].cards.size).isEqualTo(2) }, + { assertThat(blackJack.dealer.name.toString()).isEqualTo("딜러") }, + { assertThat(blackJack.dealer.cards.size).isEqualTo(2) }, + { assertThat(blackJack.cardDeck.size).isEqualTo(46) }, + ) + } +} diff --git a/src/test/kotlin/blackjack/domain/blackjack/BlackJackGameTest.kt b/src/test/kotlin/blackjack/domain/blackjack/BlackJackGameTest.kt new file mode 100644 index 000000000..5540b1da0 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/blackjack/BlackJackGameTest.kt @@ -0,0 +1,36 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.Cards +import blackjack.domain.participants.User +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow + +class BlackJackGameTest { + @Test + fun `게임을 실행한다`() { + val blackJack = blackJack { + cardDeck(Cards.all()) + participants { + dealer() + guests(listOf("아크", "로피")) + } + draw() + } + + assertDoesNotThrow { + BlackJackGame().apply { + getCommand = ::inputDrawMore + guestsTurn(blackJack.guests, blackJack.cardDeck, ::outputCard) + dealerTurn(blackJack.dealer, blackJack.cardDeck, ::outputDealer) + } + } + } + + private fun inputDrawMore(string: String): String { + return "y" + } + + private fun outputCard(user: User) = null + + private fun outputDealer() = null +} diff --git a/src/test/kotlin/blackjack/domain/blackjack/BlackJackTest.kt b/src/test/kotlin/blackjack/domain/blackjack/BlackJackTest.kt new file mode 100644 index 000000000..153ab61d3 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/blackjack/BlackJackTest.kt @@ -0,0 +1,31 @@ +package blackjack.domain.blackjack + +import blackjack.domain.card.Card +import blackjack.domain.card.CardDeck +import blackjack.domain.card.CardMark +import blackjack.domain.card.CardValue +import blackjack.domain.card.Cards +import blackjack.domain.participants.Dealer +import blackjack.domain.participants.Guest +import blackjack.domain.participants.Participants +import blackjack.domain.result.Outcome +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class BlackJackTest { + @Test + fun `게임 결과를 반환한다`() { + val blackJack = BlackJack( + participants = Participants(Dealer(), listOf(Guest("아크"), Guest("로피"))), + cardDeck = CardDeck(Cards.all()), + ).apply { + dealer.draw(Card(CardMark.CLOVER, CardValue.QUEEN)) + dealer.draw(Card(CardMark.CLOVER, CardValue.QUEEN)) + guests[0].draw(Card(CardMark.CLOVER, CardValue.NINE)) + guests[0].draw(Card(CardMark.CLOVER, CardValue.NINE)) + guests[1].draw(Card(CardMark.CLOVER, CardValue.ACE)) + guests[1].draw(Card(CardMark.CLOVER, CardValue.QUEEN)) + } + assertThat(blackJack.getResult()).isEqualTo(listOf(Outcome.LOSE, Outcome.WIN)) + } +} diff --git a/src/test/kotlin/blackjack/domain/card/CardDeckTest.kt b/src/test/kotlin/blackjack/domain/card/CardDeckTest.kt new file mode 100644 index 000000000..d29c727fe --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardDeckTest.kt @@ -0,0 +1,34 @@ +package blackjack.domain.card + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class CardDeckTest { + @Test + fun `초기 카드 덱은 52장이다`() { + val cardDeck = CardDeck(Cards.all()) + assertThat(cardDeck.size).isEqualTo(52) + } + + @Test + fun `카드를 뽑으면 숫자가 줄어든다`() { + val cardDeck = CardDeck(Cards.all()) + cardDeck.nextCard() + assertThat(cardDeck.size).isEqualTo(51) + } + + @Test + fun `초기 카드가 52장이 아니면 에러난다`() { + assertThrows { + CardDeck(Cards.all().take(51)) + } + } + + @Test + fun `초기 카드에 중복이 있으면 에러난다`() { + assertThrows { + CardDeck(Cards.all().take(26) + Cards.all().take(26)) + } + } +} diff --git a/src/test/kotlin/blackjack/domain/card/CardTest.kt b/src/test/kotlin/blackjack/domain/card/CardTest.kt new file mode 100644 index 000000000..2cb04a5a4 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardTest.kt @@ -0,0 +1,15 @@ +package blackjack.domain.card + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource + +class CardTest { + @ParameterizedTest + @CsvSource(value = ["CLOVER, ACE", "HEART, TEN", "SPADE, TWO", "DIA, JACK"]) + fun `카드의 문양과 숫자를 가져올 수 있다`(mark: CardMark, value: CardValue) { + val card = Card(mark, value) + assertThat(card.mark).isEqualTo(mark) + assertThat(card.value).isEqualTo(value) + } +} diff --git a/src/test/kotlin/blackjack/domain/card/CardValueTest.kt b/src/test/kotlin/blackjack/domain/card/CardValueTest.kt new file mode 100644 index 000000000..7379f0e10 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardValueTest.kt @@ -0,0 +1,13 @@ +package blackjack.domain.card + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource + +class CardValueTest { + @ParameterizedTest + @CsvSource(value = ["ACE,1", "KING,10", "QUEEN,10", "JACK,10", "TEN,10", "NINE,9", "EIGHT,8", "SEVEN,7", "SIX,6", "FIVE,5", "FOUR,4", "THREE,3", "TWO,2"]) + fun `카드의 값들이 나온다`(cardValue: CardValue, value: Int) { + assertThat(cardValue.value).isEqualTo(value) + } +} diff --git a/src/test/kotlin/blackjack/domain/card/CardsTest.kt b/src/test/kotlin/blackjack/domain/card/CardsTest.kt new file mode 100644 index 000000000..c8b918f76 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardsTest.kt @@ -0,0 +1,34 @@ +package blackjack.domain.card + +import blackjack.domain.card.CardMark.CLOVER +import blackjack.domain.card.CardValue.ACE +import blackjack.domain.card.CardValue.EIGHT +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class CardsTest { + @Test + fun `카드를 추가할 수 있다`() { + var cards = Cards() + cards += Card(CLOVER, EIGHT) + assertThat(cards.size).isEqualTo(1) + assertThat(cards.toList()[0]).isEqualTo(Card(CLOVER, EIGHT)) + } + + @Test + fun `ACE 카드가 있는지 확인 할 수 있다`() { + var cards = Cards() + cards += Card(CLOVER, EIGHT) + assertThat(cards.containsACE()).isFalse + cards += Card(CLOVER, ACE) + assertThat(cards.containsACE()).isTrue + } + + @Test + fun `점수의 합을 반환한다`() { + var cards = Cards() + cards += Card(CLOVER, EIGHT) + cards += Card(CardMark.SPADE, CardValue.EIGHT) + assertThat(cards.result.score()).isEqualTo(16) + } +} diff --git a/src/test/kotlin/blackjack/domain/participants/NameTest.kt b/src/test/kotlin/blackjack/domain/participants/NameTest.kt new file mode 100644 index 000000000..862a3fc9f --- /dev/null +++ b/src/test/kotlin/blackjack/domain/participants/NameTest.kt @@ -0,0 +1,24 @@ +package blackjack.domain.participants + +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +class NameTest { + @ParameterizedTest + @ValueSource(strings = ["아", "01234567890123456789"]) + fun `이름은 1~20자 이하다`(name: String) { + assertDoesNotThrow { + Name(name) + } + } + + @ParameterizedTest + @ValueSource(strings = ["", "012345678901234567890"]) + fun `이름은 1~20자 이외면 에러난다`(name: String) { + assertThrows { + Name(name) + } + } +} diff --git a/src/test/kotlin/blackjack/domain/participants/UserTest.kt b/src/test/kotlin/blackjack/domain/participants/UserTest.kt new file mode 100644 index 000000000..44ed52a89 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/participants/UserTest.kt @@ -0,0 +1,24 @@ +package blackjack.domain.participants + +import blackjack.domain.card.Card +import blackjack.domain.card.CardMark +import blackjack.domain.card.CardValue +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class UserTest { + @Test + fun `카드를 뽑을 수 있다`() { + val user = Guest("아크") + user.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + assertThat(user.cards.size).isEqualTo(1) + } + + @Test + fun `점수의 합을 반환한다`() { + val user = Guest("아크") + user.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + user.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + assertThat(user.score).isEqualTo(16) + } +} diff --git a/src/test/kotlin/blackjack/domain/result/OutcomeTest.kt b/src/test/kotlin/blackjack/domain/result/OutcomeTest.kt new file mode 100644 index 000000000..5c9141ce5 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/result/OutcomeTest.kt @@ -0,0 +1,100 @@ +package blackjack.domain.result + +import blackjack.domain.card.Card +import blackjack.domain.card.CardMark +import blackjack.domain.card.CardValue +import blackjack.domain.participants.Dealer +import blackjack.domain.participants.Guest +import blackjack.domain.result.Outcome.Companion.winTo +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class OutcomeTest { + @Test + fun `딜러와 유저의 점수가 같으면 비긴다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.DRAW) + } + + @Test + fun `딜러와 유저의 점수가 둘 다 21점 이상이면 비긴다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + dealer.draw(Card(CardMark.HEART, CardValue.NINE)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + guest.draw(Card(CardMark.CLOVER, CardValue.QUEEN)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.DRAW) + } + + @Test + fun `딜러 점수가 유저 점수보다 크면 유저가 진다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + dealer.draw(Card(CardMark.HEART, CardValue.THREE)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + guest.draw(Card(CardMark.CLOVER, CardValue.TWO)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.LOSE) + } + + @Test + fun `유저 점수가 21을 넘으면 유저가 진다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + dealer.draw(Card(CardMark.HEART, CardValue.THREE)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + guest.draw(Card(CardMark.CLOVER, CardValue.KING)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.LOSE) + } + + @Test + fun `유저 점수가 딜러 점수보다 크면 유저가 이긴다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + dealer.draw(Card(CardMark.HEART, CardValue.TWO)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + guest.draw(Card(CardMark.CLOVER, CardValue.THREE)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.WIN) + } + + @Test + fun `딜러 점수가 21을 넘으면 유저가 이긴다`() { + val dealer = Dealer() + val guest = Guest("아크") + dealer.draw(Card(CardMark.CLOVER, CardValue.EIGHT)) + dealer.draw(Card(CardMark.DIA, CardValue.SEVEN)) + dealer.draw(Card(CardMark.HEART, CardValue.KING)) + + guest.draw(Card(CardMark.SPADE, CardValue.EIGHT)) + guest.draw(Card(CardMark.HEART, CardValue.SEVEN)) + guest.draw(Card(CardMark.CLOVER, CardValue.THREE)) + + assertThat(guest.winTo(dealer)).isEqualTo(Outcome.WIN) + } +} diff --git a/src/test/kotlin/study/DSLTest.kt b/src/test/kotlin/study/DSLTest.kt new file mode 100644 index 000000000..ec9380348 --- /dev/null +++ b/src/test/kotlin/study/DSLTest.kt @@ -0,0 +1,97 @@ +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertAll + +class PersonBuilder { + private lateinit var name: String + private var company: String? = null + private lateinit var skills: Skills + private lateinit var languages: Languages + + fun name(block: () -> String) { name = block() } + + fun company(block: () -> String) { company = block() } + + fun skills(block: SkillsBuilder.() -> Unit) { + skills = SkillsBuilder().apply(block).build() + } + + fun languages(block: LanguagesBuilder.() -> Unit) { + languages = LanguagesBuilder().apply(block).build() + } + + fun build(): Person = Person(name, company, skills, languages) +} + +class SkillsBuilder { + private val soft = MutableList(0) { "" } + private val hard = MutableList(0) { "" } + fun soft(block: () -> String) = soft.add(block()) + + fun hard(block: () -> String) = hard.add(block()) + + fun build(): Skills = Skills(soft, hard) +} + +class LanguagesBuilder { + private val languages: MutableMap = mutableMapOf() + + fun build(): Languages = Languages(languages.toMap()) + + infix fun String.level(grade: Int) { + languages[this] = grade + } +} + +fun person(block: PersonBuilder.() -> Unit): Person { + val personBuilder = PersonBuilder().apply(block) + return personBuilder.build() +} + +data class Person( + var name: String, + var company: String?, + var skills: Skills, + var languages: Languages, +) + +data class Skills( + val soft: List, + val hard: List, +) + +data class Languages( + private val languages: Map, +) { + operator fun get(key: String) = languages[key] + fun size() = languages.size +} + +class DSLTest { + @Test + fun company() { + val person = person { + name { "아크" } + company { "우아한형제들" } + skills { + soft { "A passion for problem solving" } + soft { "Good communication skills" } + hard { "Kotlin" } + } + languages { + "Korean" level 5 + "English" level 3 + } + } + assertAll( + { assertThat(person.name).isEqualTo("아크") }, + { assertThat(person.company).isEqualTo("우아한형제들") }, + { assertThat(person.skills.soft[0]).isEqualTo("A passion for problem solving") }, + { assertThat(person.skills.soft[1]).isEqualTo("Good communication skills") }, + { assertThat(person.skills.hard[0]).isEqualTo("Kotlin") }, + { assertThat(person.languages["Korean"]).isEqualTo(5) }, + { assertThat(person.languages["English"]).isEqualTo(3) }, + { assertThat(person.languages.size()).isEqualTo(2) }, + ) + } +} diff --git a/src/test/kotlin/study/LambdaTest1.kt b/src/test/kotlin/study/LambdaTest1.kt new file mode 100644 index 000000000..e5eb0d286 --- /dev/null +++ b/src/test/kotlin/study/LambdaTest1.kt @@ -0,0 +1,31 @@ +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class LambdaTest1 { + @Test + fun `이동`() { + val car = Car("아크", 0) + val actual: Car = car.move { true } + assertThat(actual).isEqualTo(Car("아크", 1)) + } + + @Test + fun `정지`() { + val car = Car("아크", 0) + val actual: Car = car.move { false } + assertThat(actual).isEqualTo(Car("아크", 0)) + } +} + +data class Car(val name: String, val position: Int) { + fun move(moveStrategy: MoveStrategy): Car { + if (moveStrategy.isMovable()) { + return copy(position = position + 1) + } + return this + } +} + +fun interface MoveStrategy { + fun isMovable(): Boolean +} diff --git a/src/test/kotlin/study/LambdaTest2.kt b/src/test/kotlin/study/LambdaTest2.kt new file mode 100644 index 000000000..4cf615836 --- /dev/null +++ b/src/test/kotlin/study/LambdaTest2.kt @@ -0,0 +1,37 @@ +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +val numbers: List = listOf(1, 2, 3, 4, 5, 6) + +class LambdaTest2 { + @Test + fun sumAll() { + assertThat(sumWith(numbers) { true }) + .isEqualTo(21) + } + + @Test + fun sumAllEven() { + assertThat(sumWith(numbers) { it % 2 == 0 }) + .isEqualTo(12) + } + + @Test + fun sumAllOverThree() { + assertThat(sumWith(numbers) { it > 3 }) + .isEqualTo(15) + } +} + +fun interface Condition { + operator fun invoke(int: Int): Boolean +} +fun sumWith(numbers: List, condition: Condition): Int { + var total = 0 + for (number in numbers) { + if (condition(number)) { + total += number + } + } + return total +}