From 06e589f363f95899ae8e277b6653223c32aaf715 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 13 Apr 2025 16:10:34 +0200 Subject: [PATCH 01/28] refactor: try using sealed class --- src/main/kotlin/dsl/Skills.kt | 15 +++++++++++---- src/test/kotlin/dsl/DslTest.kt | 10 ++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/dsl/Skills.kt b/src/main/kotlin/dsl/Skills.kt index b288a23ec..98ada0228 100644 --- a/src/main/kotlin/dsl/Skills.kt +++ b/src/main/kotlin/dsl/Skills.kt @@ -1,14 +1,21 @@ package dsl data class Skills( - val softSkills: MutableList = mutableListOf(), - val hardSkills: MutableList = mutableListOf(), + val values: MutableList = mutableListOf(), ) { fun soft(skill: String) { - this.softSkills.add(skill) + this.values.add(Skill.Soft(skill)) } fun hard(skill: String) { - this.hardSkills.add(skill) + this.values.add(Skill.Hard(skill)) } } + +sealed class Skill( + val value: String, +) { + data class Soft(val skill: String) : Skill(skill) + + data class Hard(val skill: String) : Skill(skill) +} diff --git a/src/test/kotlin/dsl/DslTest.kt b/src/test/kotlin/dsl/DslTest.kt index 906b5a08b..1440bc1ca 100644 --- a/src/test/kotlin/dsl/DslTest.kt +++ b/src/test/kotlin/dsl/DslTest.kt @@ -40,8 +40,9 @@ class DslTest : FunSpec({ } assertSoftly(person.skills) { - softSkills.size shouldBe 2 - hardSkills.size shouldBe 1 + values.size shouldBe 3 + values.filterIsInstance().size shouldBe 2 + values.filterIsInstance().size shouldBe 1 } } @@ -80,8 +81,9 @@ class DslTest : FunSpec({ assertSoftly(person) { name shouldBe "Sun" company shouldBe "Delivery Hero" - skills.softSkills.size shouldBe 2 - skills.hardSkills.size shouldBe 1 + skills.values.size shouldBe 3 + skills.values.filterIsInstance().size shouldBe 2 + skills.values.filterIsInstance().size shouldBe 1 languages.values.size shouldBe 2 } } From 51e50180d2995f53e3bb98bbfd17c71e86b7cb37 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 13 Apr 2025 16:25:22 +0200 Subject: [PATCH 02/28] docs: update README.md --- README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e1c7c927d..ba5aa1d14 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# kotlin-blackjack \ No newline at end of file +# kotlin-blackjack + +## Lotto (Auto) + +- [ ] At the start of the game, each player receives two cards. +- [ ] Players can choose to draw additional cards as long as their total does not exceed 21. + +Card + +- [ ] Number cards are counted by their face value. +- [ ] Aces can count as either 1 or 11. + +Card Number + +- [ ] Number or symbol (J, Q, K, A). +- [ ] Face cards (King, Queen, Jack) are each worth 10. + +Card Suit + +- [ ] Contains four card suits (Spade, Heart, Diamond, Club). + +Deck + +- [ ] Consists of 52 cards with 13 numbers and 4 suits. + +Player + +- [ ] Receives two cards at start of the game. +- [ ] Players can choose to draw additional cards as long as their total does not exceed 21. From 32fa875c4c15bde2b2e153b0fefce0a5786d9a62 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 13 Apr 2025 17:02:16 +0200 Subject: [PATCH 03/28] feat: add card domain --- README.md | 6 +-- src/main/kotlin/blackjack/domain/card/Card.kt | 8 ++++ .../blackjack/domain/card/CardNumber.kt | 24 ++++++++++ src/main/kotlin/blackjack/domain/card/Suit.kt | 15 ++++++ .../kotlin/blackjack/domain/card/CardTest.kt | 46 +++++++++++++++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/card/Card.kt create mode 100644 src/main/kotlin/blackjack/domain/card/CardNumber.kt create mode 100644 src/main/kotlin/blackjack/domain/card/Suit.kt create mode 100644 src/test/kotlin/blackjack/domain/card/CardTest.kt diff --git a/README.md b/README.md index ba5aa1d14..2476b52cf 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Card Card Number -- [ ] Number or symbol (J, Q, K, A). -- [ ] Face cards (King, Queen, Jack) are each worth 10. +- [x] Number or symbol (J, Q, K, A). +- [x] Face cards (King, Queen, Jack) are each worth 10. Card Suit -- [ ] Contains four card suits (Spade, Heart, Diamond, Club). +- [x] Contains four card suits (Spades, Hearts, Diamonds, Clubs). Deck 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..0976a9048 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/Card.kt @@ -0,0 +1,8 @@ +package blackjack.domain.card + +data class Card( + private val number: CardNumber, + private val suit: Suit, +) { + constructor(rawNumber: String, rawSuit: String) : this(CardNumber.fromName(rawNumber), Suit.fromName(rawSuit)) +} diff --git a/src/main/kotlin/blackjack/domain/card/CardNumber.kt b/src/main/kotlin/blackjack/domain/card/CardNumber.kt new file mode 100644 index 000000000..9ac2901b2 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/CardNumber.kt @@ -0,0 +1,24 @@ +package blackjack.domain.card + +enum class CardNumber(val value: Int) { + ACE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8), + NINE(9), + TEN(10), + JACK(10), + QUEEN(10), + KING(10), + ; + + companion object { + fun fromName(name: String) = + entries.find { it.name == name.uppercase() } + ?: throw IllegalArgumentException("Card number does not exist for $name") + } +} diff --git a/src/main/kotlin/blackjack/domain/card/Suit.kt b/src/main/kotlin/blackjack/domain/card/Suit.kt new file mode 100644 index 000000000..cc300d3dc --- /dev/null +++ b/src/main/kotlin/blackjack/domain/card/Suit.kt @@ -0,0 +1,15 @@ +package blackjack.domain.card + +enum class Suit { + SPADES, + HEARTS, + DIAMONDS, + CLUBS, + ; + + companion object { + fun fromName(name: String) = + entries.find { it.name == name.uppercase() } + ?: throw IllegalArgumentException("Card suit does not exist for $name") + } +} 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..51c8f93ed --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardTest.kt @@ -0,0 +1,46 @@ +package blackjack.domain.card + +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData + +class CardTest : FunSpec({ + context("card creation") { + test("should create with valid enum values") { + this@context.withData( + CardNumber.ACE to Suit.SPADES, + CardNumber.QUEEN to Suit.HEARTS, + ) { (number, suit) -> + shouldNotThrowAny { + Card(number, suit) + } + } + } + + test("should create with raw values") { + this@context.withData( + "ACE" to "CLUBS", + "QUEEN" to "HEARTS", + "KING" to "DIAMONDS", + ) { (number, suit) -> + shouldNotThrowAny { + Card(number, suit) + } + } + } + + test("should throw exception with invalid values") { + this@context.withData( + "TRIPLE" to "CLUBS", + "TWO" to "CLOVER", + "ACE" to "DIAMOND", + "ACE" to "SPACEX", + ) { + shouldThrow { + Card(it.first, it.second) + } + } + } + } +}) From 2f146f884905dbf069233629bd9ad5c21251966c Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 13 Apr 2025 17:27:08 +0200 Subject: [PATCH 04/28] feat: add deck domain --- README.md | 2 +- src/main/kotlin/blackjack/domain/deck/Deck.kt | 17 +++++++++++ .../blackjack/domain/deck/DeckGenerator.kt | 18 ++++++++++++ .../kotlin/blackjack/domain/deck/DeckTest.kt | 29 +++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/blackjack/domain/deck/Deck.kt create mode 100644 src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt create mode 100644 src/test/kotlin/blackjack/domain/deck/DeckTest.kt diff --git a/README.md b/README.md index 2476b52cf..74ac7f61a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Card Suit Deck -- [ ] Consists of 52 cards with 13 numbers and 4 suits. +- [x] Deck must not contain duplicated cards. Player diff --git a/src/main/kotlin/blackjack/domain/deck/Deck.kt b/src/main/kotlin/blackjack/domain/deck/Deck.kt new file mode 100644 index 000000000..2c5a68959 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/deck/Deck.kt @@ -0,0 +1,17 @@ +package blackjack.domain.deck + +import blackjack.domain.card.Card + +class Deck private constructor( + private val cards: List, +) { + init { + check(cards.size == cards.distinct().size) { + "Deck must not contain duplicated cards." + } + } + + companion object { + fun create(generator: () -> List = RandomDeckGenerator::generate) = Deck(generator()) + } +} diff --git a/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt new file mode 100644 index 000000000..4fb68211a --- /dev/null +++ b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt @@ -0,0 +1,18 @@ +package blackjack.domain.deck + +import blackjack.domain.card.Card +import blackjack.domain.card.CardNumber +import blackjack.domain.card.Suit + +interface DeckGenerator { + fun generate(): List +} + +object RandomDeckGenerator : DeckGenerator { + override fun generate() = + CardNumber.entries.flatMap { number -> + Suit.entries.map { suit -> + Card(number, suit) + } + } +} diff --git a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt new file mode 100644 index 000000000..ff4623773 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt @@ -0,0 +1,29 @@ +package blackjack.domain.deck + +import blackjack.domain.card.Card +import blackjack.domain.card.CardNumber.ACE +import blackjack.domain.card.Suit.SPADES +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec + +class DeckTest : FunSpec({ + context("create") { + test("should create a Deck") { + shouldNotThrowAny { + Deck.create() + } + } + + test("should throw exception if duplicated cards exist") { + shouldThrow { + Deck.create { + listOf( + Card(ACE, SPADES), + Card(ACE, SPADES), + ) + } + } + } + } +}) From daec608874235fea7ef2189a132f2d643392e996 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Tue, 15 Apr 2025 10:00:22 +0200 Subject: [PATCH 05/28] feat: add cache to reusable cards --- src/main/kotlin/blackjack/domain/card/Card.kt | 23 +++++++++++-- .../kotlin/blackjack/domain/card/CardTest.kt | 33 ++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/card/Card.kt b/src/main/kotlin/blackjack/domain/card/Card.kt index 0976a9048..1ff9cdf36 100644 --- a/src/main/kotlin/blackjack/domain/card/Card.kt +++ b/src/main/kotlin/blackjack/domain/card/Card.kt @@ -1,8 +1,27 @@ package blackjack.domain.card -data class Card( +class Card private constructor( private val number: CardNumber, private val suit: Suit, ) { - constructor(rawNumber: String, rawSuit: String) : this(CardNumber.fromName(rawNumber), Suit.fromName(rawSuit)) + companion object { + private val cache: Map, Card> = + CardNumber.entries.flatMap { number -> + Suit.entries.map { suit -> + Pair(Pair(number, suit), Card(number, suit)) + } + }.toMap() + + val cached: List = cache.values.toList() + + fun of( + number: CardNumber, + suit: Suit, + ) = cache[Pair(number, suit)] ?: throw IllegalStateException("Card does not exist in cache.") + + fun of( + rawNumber: String, + rawSuit: String, + ) = of(CardNumber.fromName(rawNumber), Suit.fromName(rawSuit)) + } } diff --git a/src/test/kotlin/blackjack/domain/card/CardTest.kt b/src/test/kotlin/blackjack/domain/card/CardTest.kt index 51c8f93ed..4aaa0e7d2 100644 --- a/src/test/kotlin/blackjack/domain/card/CardTest.kt +++ b/src/test/kotlin/blackjack/domain/card/CardTest.kt @@ -1,19 +1,28 @@ package blackjack.domain.card +import blackjack.domain.card.CardNumber.ACE +import blackjack.domain.card.CardNumber.NINE +import blackjack.domain.card.CardNumber.QUEEN +import blackjack.domain.card.CardNumber.TWO +import blackjack.domain.card.Suit.CLUBS +import blackjack.domain.card.Suit.DIAMONDS +import blackjack.domain.card.Suit.HEARTS +import blackjack.domain.card.Suit.SPADES import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.datatest.withData +import io.kotest.matchers.shouldBe class CardTest : FunSpec({ context("card creation") { test("should create with valid enum values") { this@context.withData( - CardNumber.ACE to Suit.SPADES, - CardNumber.QUEEN to Suit.HEARTS, + ACE to SPADES, + QUEEN to HEARTS, ) { (number, suit) -> shouldNotThrowAny { - Card(number, suit) + Card.of(number, suit) } } } @@ -25,7 +34,7 @@ class CardTest : FunSpec({ "KING" to "DIAMONDS", ) { (number, suit) -> shouldNotThrowAny { - Card(number, suit) + Card.of(number, suit) } } } @@ -38,9 +47,23 @@ class CardTest : FunSpec({ "ACE" to "SPACEX", ) { shouldThrow { - Card(it.first, it.second) + Card.of(it.first, it.second) } } } + + test("same number and suit should have save reference") { + this@context.withData( + ACE to SPADES, + QUEEN to HEARTS, + TWO to DIAMONDS, + NINE to CLUBS, + ) { (number, suit) -> + val card1 = Card.of(number, suit) + val card2 = Card.of(number, suit) + + (card1 === card2) shouldBe true + } + } } }) From 8ddf6edb33e0af550c4daa539dd7b987d02b2a9a Mon Sep 17 00:00:00 2001 From: syoun602 Date: Tue, 15 Apr 2025 22:13:42 +0200 Subject: [PATCH 06/28] feat: add deck generator --- README.md | 2 +- src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt | 9 +-------- src/test/kotlin/blackjack/domain/deck/DeckTest.kt | 4 ++-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 74ac7f61a..0db4a7e1d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # kotlin-blackjack -## Lotto (Auto) +## Blackjack - [ ] At the start of the game, each player receives two cards. - [ ] Players can choose to draw additional cards as long as their total does not exceed 21. diff --git a/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt index 4fb68211a..14b81c90c 100644 --- a/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt +++ b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt @@ -1,18 +1,11 @@ package blackjack.domain.deck import blackjack.domain.card.Card -import blackjack.domain.card.CardNumber -import blackjack.domain.card.Suit interface DeckGenerator { fun generate(): List } object RandomDeckGenerator : DeckGenerator { - override fun generate() = - CardNumber.entries.flatMap { number -> - Suit.entries.map { suit -> - Card(number, suit) - } - } + override fun generate() = Card.cached.shuffled() } diff --git a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt index ff4623773..4897d1d12 100644 --- a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt +++ b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt @@ -19,8 +19,8 @@ class DeckTest : FunSpec({ shouldThrow { Deck.create { listOf( - Card(ACE, SPADES), - Card(ACE, SPADES), + Card.of(ACE, SPADES), + Card.of(ACE, SPADES), ) } } From 3042beea3e8cda7669f7a7a21856bfd7068375a8 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 00:34:34 +0200 Subject: [PATCH 07/28] feat: add Hands --- README.md | 4 ++ src/main/kotlin/blackjack/domain/Hands.kt | 20 +++++++ src/main/kotlin/blackjack/domain/card/Card.kt | 2 +- src/test/kotlin/blackjack/domain/HandsTest.kt | 58 +++++++++++++++++++ .../blackjack/domain/card/CardFixture.kt | 9 +++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/blackjack/domain/Hands.kt create mode 100644 src/test/kotlin/blackjack/domain/HandsTest.kt create mode 100644 src/test/kotlin/blackjack/domain/card/CardFixture.kt diff --git a/README.md b/README.md index 0db4a7e1d..31cdbe642 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ Deck - [x] Deck must not contain duplicated cards. +Hands +- [x] Must have two cards to be initialized. +- [x] Should be able to add card to hands. + Player - [ ] Receives two cards at start of the game. diff --git a/src/main/kotlin/blackjack/domain/Hands.kt b/src/main/kotlin/blackjack/domain/Hands.kt new file mode 100644 index 000000000..d0a67903b --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Hands.kt @@ -0,0 +1,20 @@ +package blackjack.domain + +import blackjack.domain.card.Card + +class Hands( + private val cards: List = emptyList(), +) { + val initialized = cards.size == 2 + + val size: Int + get() = cards.size + + infix operator fun plus(card: Card) = Hands(cards + card) + + fun isBust(): Boolean = cards.sumOf { it.number.value } > BLACKJACK_SCORE + + companion object { + private const val BLACKJACK_SCORE = 21 + } +} diff --git a/src/main/kotlin/blackjack/domain/card/Card.kt b/src/main/kotlin/blackjack/domain/card/Card.kt index 1ff9cdf36..378358b24 100644 --- a/src/main/kotlin/blackjack/domain/card/Card.kt +++ b/src/main/kotlin/blackjack/domain/card/Card.kt @@ -1,7 +1,7 @@ package blackjack.domain.card class Card private constructor( - private val number: CardNumber, + val number: CardNumber, private val suit: Suit, ) { companion object { diff --git a/src/test/kotlin/blackjack/domain/HandsTest.kt b/src/test/kotlin/blackjack/domain/HandsTest.kt new file mode 100644 index 000000000..c34815066 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/HandsTest.kt @@ -0,0 +1,58 @@ +package blackjack.domain + +import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_JACK +import blackjack.domain.card.CardFixture.SPADES_QUEEN +import blackjack.domain.card.CardFixture.SPADES_SIX +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class HandsTest : FunSpec({ + context("initialized") { + test("if there are two cards should return true") { + val hands = + Hands( + listOf( + SPADES_JACK, + SPADES_SIX, + ), + ) + + hands.initialized shouldBe true + } + + test("if there are less than two cards should return false") { + val hands = + Hands( + listOf(SPADES_ACE), + ) + + hands.initialized shouldBe false + } + } + + test("size") { + val hands = Hands() + + hands.size shouldBe 0 + } + + test("addCard") { + val hands = Hands() + val actual = hands + SPADES_JACK + + actual.size shouldBe 1 + } + + test("bust") { + val hands = Hands( + listOf( + SPADES_JACK, + SPADES_SIX, + SPADES_QUEEN, + ) + ) + + hands.isBust() shouldBe true + } +}) diff --git a/src/test/kotlin/blackjack/domain/card/CardFixture.kt b/src/test/kotlin/blackjack/domain/card/CardFixture.kt new file mode 100644 index 000000000..a3b94c712 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/card/CardFixture.kt @@ -0,0 +1,9 @@ +package blackjack.domain.card + +object CardFixture { + val SPADES_SIX = Card.of(CardNumber.SIX, Suit.SPADES) + val SPADES_ACE = Card.of(CardNumber.ACE, Suit.SPADES) + val SPADES_JACK = Card.of(CardNumber.JACK, Suit.SPADES) + val SPADES_QUEEN = Card.of(CardNumber.QUEEN, Suit.SPADES) + val SPADES_TEN = Card.of(CardNumber.TEN, Suit.SPADES) +} From 52001fef81b6e8f845515696de0cee169f686dc4 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 00:48:13 +0200 Subject: [PATCH 08/28] feat: add states for initial turn and hit --- README.md | 14 ++++++ src/main/kotlin/blackjack/domain/Hands.kt | 3 +- src/main/kotlin/blackjack/domain/state/Hit.kt | 14 ++++++ .../blackjack/domain/state/InitialTurn.kt | 15 +++++++ .../kotlin/blackjack/domain/state/State.kt | 10 +++++ src/test/kotlin/blackjack/domain/HandsTest.kt | 13 +++--- .../blackjack/domain/card/CardFixture.kt | 4 +- .../kotlin/blackjack/domain/state/HitTest.kt | 27 ++++++++++++ .../blackjack/domain/state/InitialTurnTest.kt | 44 +++++++++++++++++++ 9 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/state/Hit.kt create mode 100644 src/main/kotlin/blackjack/domain/state/InitialTurn.kt create mode 100644 src/main/kotlin/blackjack/domain/state/State.kt create mode 100644 src/test/kotlin/blackjack/domain/state/HitTest.kt create mode 100644 src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt diff --git a/README.md b/README.md index 31cdbe642..b4eaba4eb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ## Blackjack +### Key objects - [ ] At the start of the game, each player receives two cards. - [ ] Players can choose to draw additional cards as long as their total does not exceed 21. @@ -31,3 +32,16 @@ Player - [ ] Receives two cards at start of the game. - [ ] Players can choose to draw additional cards as long as their total does not exceed 21. + +### States + +State +- [x] Interface for all game states + +InitialTurn +- [x] Should remain initial until card size is less than or equal to 2 +- [x] If hands has more than 3 cards after draw, should move state to Hit. + +Hit +- [x] Can add cards until bust +- diff --git a/src/main/kotlin/blackjack/domain/Hands.kt b/src/main/kotlin/blackjack/domain/Hands.kt index d0a67903b..67c5487b0 100644 --- a/src/main/kotlin/blackjack/domain/Hands.kt +++ b/src/main/kotlin/blackjack/domain/Hands.kt @@ -5,7 +5,8 @@ import blackjack.domain.card.Card class Hands( private val cards: List = emptyList(), ) { - val initialized = cards.size == 2 + val initialized + get() = cards.size >= 2 val size: Int get() = cards.size diff --git a/src/main/kotlin/blackjack/domain/state/Hit.kt b/src/main/kotlin/blackjack/domain/state/Hit.kt new file mode 100644 index 000000000..b502570d3 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/state/Hit.kt @@ -0,0 +1,14 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.Card + +class Hit(override val hands: Hands) : State { + override fun addCard(card: Card): State { + val hands = hands + card + if (hands.isBust()) { + TODO() + } + return Hit(hands) + } +} diff --git a/src/main/kotlin/blackjack/domain/state/InitialTurn.kt b/src/main/kotlin/blackjack/domain/state/InitialTurn.kt new file mode 100644 index 000000000..57036e718 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/state/InitialTurn.kt @@ -0,0 +1,15 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.Card + +class InitialTurn(override val hands: Hands = Hands()) : State { + override fun addCard(card: Card): State { + val hands = hands + card + if (!hands.initialized) { + return InitialTurn(hands) + } + + return Hit(hands) + } +} diff --git a/src/main/kotlin/blackjack/domain/state/State.kt b/src/main/kotlin/blackjack/domain/state/State.kt new file mode 100644 index 000000000..55b2e0404 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/state/State.kt @@ -0,0 +1,10 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.Card + +interface State { + val hands: Hands + + fun addCard(card: Card): State +} diff --git a/src/test/kotlin/blackjack/domain/HandsTest.kt b/src/test/kotlin/blackjack/domain/HandsTest.kt index c34815066..a8ddc5e3f 100644 --- a/src/test/kotlin/blackjack/domain/HandsTest.kt +++ b/src/test/kotlin/blackjack/domain/HandsTest.kt @@ -45,13 +45,14 @@ class HandsTest : FunSpec({ } test("bust") { - val hands = Hands( - listOf( - SPADES_JACK, - SPADES_SIX, - SPADES_QUEEN, + val hands = + Hands( + listOf( + SPADES_JACK, + SPADES_SIX, + SPADES_QUEEN, + ), ) - ) hands.isBust() shouldBe true } diff --git a/src/test/kotlin/blackjack/domain/card/CardFixture.kt b/src/test/kotlin/blackjack/domain/card/CardFixture.kt index a3b94c712..15b4d1617 100644 --- a/src/test/kotlin/blackjack/domain/card/CardFixture.kt +++ b/src/test/kotlin/blackjack/domain/card/CardFixture.kt @@ -1,8 +1,10 @@ package blackjack.domain.card object CardFixture { - val SPADES_SIX = Card.of(CardNumber.SIX, Suit.SPADES) val SPADES_ACE = Card.of(CardNumber.ACE, Suit.SPADES) + val SPADES_TWO = Card.of(CardNumber.TWO, Suit.SPADES) + val SPADES_SIX = Card.of(CardNumber.SIX, Suit.SPADES) + val SPADES_SEVEN = Card.of(CardNumber.SEVEN, Suit.SPADES) val SPADES_JACK = Card.of(CardNumber.JACK, Suit.SPADES) val SPADES_QUEEN = Card.of(CardNumber.QUEEN, Suit.SPADES) val SPADES_TEN = Card.of(CardNumber.TEN, Suit.SPADES) diff --git a/src/test/kotlin/blackjack/domain/state/HitTest.kt b/src/test/kotlin/blackjack/domain/state/HitTest.kt new file mode 100644 index 000000000..7d3a9c910 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/state/HitTest.kt @@ -0,0 +1,27 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.CardFixture.SPADES_SEVEN +import blackjack.domain.card.CardFixture.SPADES_SIX +import blackjack.domain.card.CardFixture.SPADES_TWO +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class HitTest : FunSpec({ + context("addCard") { + test("should remain Hit until bust") { + val hit = + Hit( + Hands( + listOf( + SPADES_SIX, + SPADES_SEVEN, + ), + ), + ) + val state = hit.addCard(SPADES_TWO) + + state::class shouldBe Hit::class + } + } +}) diff --git a/src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt b/src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt new file mode 100644 index 000000000..bd6dbc180 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt @@ -0,0 +1,44 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.Card +import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_SIX +import blackjack.domain.card.CardNumber +import blackjack.domain.card.Suit +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class InitialTurnTest : FunSpec({ + context("addCard") { + test("if not initialized, should return initial turn state") { + val initialTurn = InitialTurn() + val newCard = Card.of(CardNumber.ACE, Suit.HEARTS) + val state = initialTurn.addCard(newCard) + + assertSoftly { + state.hands.size shouldBe 1 + state::class shouldBe InitialTurn::class + } + } + + test("if initialized, should return hit state") { + val initialTurn = + InitialTurn( + Hands( + listOf( + SPADES_ACE, + SPADES_SIX, + ), + ), + ) + val state = initialTurn.addCard(SPADES_ACE) + + assertSoftly { + state.hands.size shouldBe 3 + state::class shouldBe Hit::class + } + } + } +}) From d0612db2603eabb710fc90f8fb9197eb79773e67 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 00:50:23 +0200 Subject: [PATCH 09/28] feat: add Bust state --- src/main/kotlin/blackjack/domain/state/Bust.kt | 10 ++++++++++ src/main/kotlin/blackjack/domain/state/Hit.kt | 3 ++- src/test/kotlin/blackjack/domain/state/BustTest.kt | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/blackjack/domain/state/Bust.kt create mode 100644 src/test/kotlin/blackjack/domain/state/BustTest.kt diff --git a/src/main/kotlin/blackjack/domain/state/Bust.kt b/src/main/kotlin/blackjack/domain/state/Bust.kt new file mode 100644 index 000000000..23b750f02 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/state/Bust.kt @@ -0,0 +1,10 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.Card + +class Bust(override val hands: Hands) : State { + override fun addCard(card: Card): State { + throw IllegalStateException("Can't add card after bust.") + } +} diff --git a/src/main/kotlin/blackjack/domain/state/Hit.kt b/src/main/kotlin/blackjack/domain/state/Hit.kt index b502570d3..f48dd5607 100644 --- a/src/main/kotlin/blackjack/domain/state/Hit.kt +++ b/src/main/kotlin/blackjack/domain/state/Hit.kt @@ -7,8 +7,9 @@ class Hit(override val hands: Hands) : State { override fun addCard(card: Card): State { val hands = hands + card if (hands.isBust()) { - TODO() + Bust(hands) } + return Hit(hands) } } diff --git a/src/test/kotlin/blackjack/domain/state/BustTest.kt b/src/test/kotlin/blackjack/domain/state/BustTest.kt new file mode 100644 index 000000000..3d5cf13c5 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/state/BustTest.kt @@ -0,0 +1,14 @@ +package blackjack.domain.state + +import blackjack.domain.Hands +import blackjack.domain.card.CardFixture.SPADES_ACE +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec + +class BustTest : FunSpec({ + test("addCard throws exception if busted") { + shouldThrow { + Bust(Hands()).addCard(SPADES_ACE) + } + } +}) From 00fdb88fd8d34a4be1d4360a97b4a1491f50d69c Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 00:58:39 +0200 Subject: [PATCH 10/28] feat: add input view --- src/main/kotlin/blackjack/view/InputView.kt | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/kotlin/blackjack/view/InputView.kt diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt new file mode 100644 index 000000000..251722873 --- /dev/null +++ b/src/main/kotlin/blackjack/view/InputView.kt @@ -0,0 +1,33 @@ +package blackjack.view + +object InputView { + fun getPlayerNames(): List { + println("Enter the names of the players (comma-separated):") + + return readln() + .split(",") + .filter { it.isNotBlank() } + .map { it.trim() } + } + + fun getUserChoice(): UserChoice { + println("Would pobi like to draw another card? (y for yes, n for no)") + val input = readln() + + return UserChoice.from(input) + } +} + +enum class UserChoice { + Y, + N, + ; + + companion object { + fun from(input: String): UserChoice { + return entries.find { + it.name == input.uppercase() + } ?: throw IllegalArgumentException("Invalid input: $input") + } + } +} From ef119dd7eb37d4df15bf116dc03b493441de915b Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 01:03:51 +0200 Subject: [PATCH 11/28] feat: add name domain --- src/main/kotlin/blackjack/BlackjackApplication.kt | 5 +++++ src/main/kotlin/blackjack/domain/Name.kt | 7 +++++++ src/test/kotlin/blackjack/domain/NameTest.kt | 15 +++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 src/main/kotlin/blackjack/BlackjackApplication.kt create mode 100644 src/main/kotlin/blackjack/domain/Name.kt create mode 100644 src/test/kotlin/blackjack/domain/NameTest.kt diff --git a/src/main/kotlin/blackjack/BlackjackApplication.kt b/src/main/kotlin/blackjack/BlackjackApplication.kt new file mode 100644 index 000000000..058ab5baa --- /dev/null +++ b/src/main/kotlin/blackjack/BlackjackApplication.kt @@ -0,0 +1,5 @@ +package blackjack + +fun main() { +} + diff --git a/src/main/kotlin/blackjack/domain/Name.kt b/src/main/kotlin/blackjack/domain/Name.kt new file mode 100644 index 000000000..9b480b056 --- /dev/null +++ b/src/main/kotlin/blackjack/domain/Name.kt @@ -0,0 +1,7 @@ +package blackjack.domain + +class Name(private val value: String) { + init { + require(value.trim().isNotEmpty()) { "Name can't be empty." } + } +} diff --git a/src/test/kotlin/blackjack/domain/NameTest.kt b/src/test/kotlin/blackjack/domain/NameTest.kt new file mode 100644 index 000000000..57ff94a09 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/NameTest.kt @@ -0,0 +1,15 @@ +package blackjack.domain + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FunSpec +import io.kotest.inspectors.forAll + +class NameTest : FunSpec({ + test("should not be empty") { + listOf("", " ", " ").forAll { + shouldThrow { + Name(it) + } + } + } +}) From bc0f2bf1d884fe693cf7c9b2159515e50abe19e9 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 01:18:09 +0200 Subject: [PATCH 12/28] refactor: rename state --- .../state/{InitialTurn.kt => InitialState.kt} | 4 ++-- .../{InitialTurnTest.kt => InitialStateTest.kt} | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) rename src/main/kotlin/blackjack/domain/state/{InitialTurn.kt => InitialState.kt} (70%) rename src/test/kotlin/blackjack/domain/state/{InitialTurnTest.kt => InitialStateTest.kt} (77%) diff --git a/src/main/kotlin/blackjack/domain/state/InitialTurn.kt b/src/main/kotlin/blackjack/domain/state/InitialState.kt similarity index 70% rename from src/main/kotlin/blackjack/domain/state/InitialTurn.kt rename to src/main/kotlin/blackjack/domain/state/InitialState.kt index 57036e718..3563ee2d4 100644 --- a/src/main/kotlin/blackjack/domain/state/InitialTurn.kt +++ b/src/main/kotlin/blackjack/domain/state/InitialState.kt @@ -3,11 +3,11 @@ package blackjack.domain.state import blackjack.domain.Hands import blackjack.domain.card.Card -class InitialTurn(override val hands: Hands = Hands()) : State { +class InitialState(override val hands: Hands = Hands()) : State { override fun addCard(card: Card): State { val hands = hands + card if (!hands.initialized) { - return InitialTurn(hands) + return InitialState(hands) } return Hit(hands) diff --git a/src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt similarity index 77% rename from src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt rename to src/test/kotlin/blackjack/domain/state/InitialStateTest.kt index bd6dbc180..864c9e7b1 100644 --- a/src/test/kotlin/blackjack/domain/state/InitialTurnTest.kt +++ b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt @@ -10,22 +10,22 @@ import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe -class InitialTurnTest : FunSpec({ +class InitialStateTest : FunSpec({ context("addCard") { test("if not initialized, should return initial turn state") { - val initialTurn = InitialTurn() + val initialState = InitialState() val newCard = Card.of(CardNumber.ACE, Suit.HEARTS) - val state = initialTurn.addCard(newCard) + val state = initialState.addCard(newCard) assertSoftly { state.hands.size shouldBe 1 - state::class shouldBe InitialTurn::class + state::class shouldBe InitialState::class } } test("if initialized, should return hit state") { - val initialTurn = - InitialTurn( + val initialState = + InitialState( Hands( listOf( SPADES_ACE, @@ -33,7 +33,7 @@ class InitialTurnTest : FunSpec({ ), ), ) - val state = initialTurn.addCard(SPADES_ACE) + val state = initialState.addCard(SPADES_ACE) assertSoftly { state.hands.size shouldBe 3 From 7975485c680284e65aea3f0917e9b923e985eaf1 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 01:18:48 +0200 Subject: [PATCH 13/28] feat: add Name domain --- src/main/kotlin/blackjack/domain/Name.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/blackjack/domain/Name.kt b/src/main/kotlin/blackjack/domain/Name.kt index 9b480b056..c679b36f0 100644 --- a/src/main/kotlin/blackjack/domain/Name.kt +++ b/src/main/kotlin/blackjack/domain/Name.kt @@ -1,6 +1,6 @@ package blackjack.domain -class Name(private val value: String) { +class Name(val value: String) { init { require(value.trim().isNotEmpty()) { "Name can't be empty." } } From 25f34ef7f6c78c197646263705a895654fb8d3c3 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 01:20:17 +0200 Subject: [PATCH 14/28] refactor: change package structure --- src/main/kotlin/blackjack/domain/{ => player}/Hands.kt | 4 ++-- src/main/kotlin/blackjack/domain/{ => player}/Name.kt | 2 +- src/main/kotlin/blackjack/domain/state/Bust.kt | 2 +- src/main/kotlin/blackjack/domain/state/Hit.kt | 2 +- src/main/kotlin/blackjack/domain/state/InitialState.kt | 2 +- src/main/kotlin/blackjack/domain/state/State.kt | 2 +- src/main/kotlin/blackjack/view/InputView.kt | 2 +- src/test/kotlin/blackjack/domain/deck/DeckTest.kt | 2 +- src/test/kotlin/blackjack/domain/{ => player}/HandsTest.kt | 2 +- src/test/kotlin/blackjack/domain/{ => player}/NameTest.kt | 2 +- src/test/kotlin/blackjack/domain/state/BustTest.kt | 2 +- src/test/kotlin/blackjack/domain/state/HitTest.kt | 2 +- src/test/kotlin/blackjack/domain/state/InitialStateTest.kt | 2 +- 13 files changed, 14 insertions(+), 14 deletions(-) rename src/main/kotlin/blackjack/domain/{ => player}/Hands.kt (83%) rename src/main/kotlin/blackjack/domain/{ => player}/Name.kt (79%) rename src/test/kotlin/blackjack/domain/{ => player}/HandsTest.kt (97%) rename src/test/kotlin/blackjack/domain/{ => player}/NameTest.kt (91%) diff --git a/src/main/kotlin/blackjack/domain/Hands.kt b/src/main/kotlin/blackjack/domain/player/Hands.kt similarity index 83% rename from src/main/kotlin/blackjack/domain/Hands.kt rename to src/main/kotlin/blackjack/domain/player/Hands.kt index 67c5487b0..b1f818790 100644 --- a/src/main/kotlin/blackjack/domain/Hands.kt +++ b/src/main/kotlin/blackjack/domain/player/Hands.kt @@ -1,9 +1,9 @@ -package blackjack.domain +package blackjack.domain.player import blackjack.domain.card.Card class Hands( - private val cards: List = emptyList(), + val cards: List = emptyList(), ) { val initialized get() = cards.size >= 2 diff --git a/src/main/kotlin/blackjack/domain/Name.kt b/src/main/kotlin/blackjack/domain/player/Name.kt similarity index 79% rename from src/main/kotlin/blackjack/domain/Name.kt rename to src/main/kotlin/blackjack/domain/player/Name.kt index c679b36f0..9bbdf99f0 100644 --- a/src/main/kotlin/blackjack/domain/Name.kt +++ b/src/main/kotlin/blackjack/domain/player/Name.kt @@ -1,4 +1,4 @@ -package blackjack.domain +package blackjack.domain.player class Name(val value: String) { init { diff --git a/src/main/kotlin/blackjack/domain/state/Bust.kt b/src/main/kotlin/blackjack/domain/state/Bust.kt index 23b750f02..9a23bfcff 100644 --- a/src/main/kotlin/blackjack/domain/state/Bust.kt +++ b/src/main/kotlin/blackjack/domain/state/Bust.kt @@ -1,7 +1,7 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.Card +import blackjack.domain.player.Hands class Bust(override val hands: Hands) : State { override fun addCard(card: Card): State { diff --git a/src/main/kotlin/blackjack/domain/state/Hit.kt b/src/main/kotlin/blackjack/domain/state/Hit.kt index f48dd5607..775927877 100644 --- a/src/main/kotlin/blackjack/domain/state/Hit.kt +++ b/src/main/kotlin/blackjack/domain/state/Hit.kt @@ -1,7 +1,7 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.Card +import blackjack.domain.player.Hands class Hit(override val hands: Hands) : State { override fun addCard(card: Card): State { diff --git a/src/main/kotlin/blackjack/domain/state/InitialState.kt b/src/main/kotlin/blackjack/domain/state/InitialState.kt index 3563ee2d4..15d3329ef 100644 --- a/src/main/kotlin/blackjack/domain/state/InitialState.kt +++ b/src/main/kotlin/blackjack/domain/state/InitialState.kt @@ -1,7 +1,7 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.Card +import blackjack.domain.player.Hands class InitialState(override val hands: Hands = Hands()) : State { override fun addCard(card: Card): State { diff --git a/src/main/kotlin/blackjack/domain/state/State.kt b/src/main/kotlin/blackjack/domain/state/State.kt index 55b2e0404..f479c2998 100644 --- a/src/main/kotlin/blackjack/domain/state/State.kt +++ b/src/main/kotlin/blackjack/domain/state/State.kt @@ -1,7 +1,7 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.Card +import blackjack.domain.player.Hands interface State { val hands: Hands diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt index 251722873..9ec021e68 100644 --- a/src/main/kotlin/blackjack/view/InputView.kt +++ b/src/main/kotlin/blackjack/view/InputView.kt @@ -6,8 +6,8 @@ object InputView { return readln() .split(",") - .filter { it.isNotBlank() } .map { it.trim() } + .filter { it.isNotBlank() } } fun getUserChoice(): UserChoice { diff --git a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt index 4897d1d12..cb18d7709 100644 --- a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt +++ b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt @@ -18,7 +18,7 @@ class DeckTest : FunSpec({ test("should throw exception if duplicated cards exist") { shouldThrow { Deck.create { - listOf( + mutableListOf( Card.of(ACE, SPADES), Card.of(ACE, SPADES), ) diff --git a/src/test/kotlin/blackjack/domain/HandsTest.kt b/src/test/kotlin/blackjack/domain/player/HandsTest.kt similarity index 97% rename from src/test/kotlin/blackjack/domain/HandsTest.kt rename to src/test/kotlin/blackjack/domain/player/HandsTest.kt index a8ddc5e3f..451005a98 100644 --- a/src/test/kotlin/blackjack/domain/HandsTest.kt +++ b/src/test/kotlin/blackjack/domain/player/HandsTest.kt @@ -1,4 +1,4 @@ -package blackjack.domain +package blackjack.domain.player import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.card.CardFixture.SPADES_JACK diff --git a/src/test/kotlin/blackjack/domain/NameTest.kt b/src/test/kotlin/blackjack/domain/player/NameTest.kt similarity index 91% rename from src/test/kotlin/blackjack/domain/NameTest.kt rename to src/test/kotlin/blackjack/domain/player/NameTest.kt index 57ff94a09..1c93eb991 100644 --- a/src/test/kotlin/blackjack/domain/NameTest.kt +++ b/src/test/kotlin/blackjack/domain/player/NameTest.kt @@ -1,4 +1,4 @@ -package blackjack.domain +package blackjack.domain.player import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec diff --git a/src/test/kotlin/blackjack/domain/state/BustTest.kt b/src/test/kotlin/blackjack/domain/state/BustTest.kt index 3d5cf13c5..9cce18103 100644 --- a/src/test/kotlin/blackjack/domain/state/BustTest.kt +++ b/src/test/kotlin/blackjack/domain/state/BustTest.kt @@ -1,7 +1,7 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.player.Hands import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec diff --git a/src/test/kotlin/blackjack/domain/state/HitTest.kt b/src/test/kotlin/blackjack/domain/state/HitTest.kt index 7d3a9c910..556341b41 100644 --- a/src/test/kotlin/blackjack/domain/state/HitTest.kt +++ b/src/test/kotlin/blackjack/domain/state/HitTest.kt @@ -1,9 +1,9 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.CardFixture.SPADES_SEVEN import blackjack.domain.card.CardFixture.SPADES_SIX import blackjack.domain.card.CardFixture.SPADES_TWO +import blackjack.domain.player.Hands import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe diff --git a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt index 864c9e7b1..b2f17d0c9 100644 --- a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt +++ b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt @@ -1,11 +1,11 @@ package blackjack.domain.state -import blackjack.domain.Hands import blackjack.domain.card.Card import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.card.CardFixture.SPADES_SIX import blackjack.domain.card.CardNumber import blackjack.domain.card.Suit +import blackjack.domain.player.Hands import io.kotest.assertions.assertSoftly import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe From cbb5c80f6ec2dc1cade9fc187a6cd656ff080b46 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 01:55:29 +0200 Subject: [PATCH 15/28] feat: add draw card logic for deck --- src/main/kotlin/blackjack/domain/deck/Deck.kt | 9 +++++++-- .../blackjack/domain/deck/DeckGenerator.kt | 4 ++-- .../kotlin/blackjack/domain/deck/DeckTest.kt | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/blackjack/domain/deck/Deck.kt b/src/main/kotlin/blackjack/domain/deck/Deck.kt index 2c5a68959..e88af802c 100644 --- a/src/main/kotlin/blackjack/domain/deck/Deck.kt +++ b/src/main/kotlin/blackjack/domain/deck/Deck.kt @@ -3,7 +3,7 @@ package blackjack.domain.deck import blackjack.domain.card.Card class Deck private constructor( - private val cards: List, + private val cards: MutableList, ) { init { check(cards.size == cards.distinct().size) { @@ -11,7 +11,12 @@ class Deck private constructor( } } + fun drawCard(): Card { + check(cards.isNotEmpty()) { "There are no cards left in deck." } + return cards.removeFirst() + } + companion object { - fun create(generator: () -> List = RandomDeckGenerator::generate) = Deck(generator()) + fun create(generator: () -> MutableList = RandomDeckGenerator::generate) = Deck(generator()) } } diff --git a/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt index 14b81c90c..ca4f1397f 100644 --- a/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt +++ b/src/main/kotlin/blackjack/domain/deck/DeckGenerator.kt @@ -3,9 +3,9 @@ package blackjack.domain.deck import blackjack.domain.card.Card interface DeckGenerator { - fun generate(): List + fun generate(): MutableList } object RandomDeckGenerator : DeckGenerator { - override fun generate() = Card.cached.shuffled() + override fun generate() = Card.cached.shuffled().toMutableList() } diff --git a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt index cb18d7709..9f04b7fa9 100644 --- a/src/test/kotlin/blackjack/domain/deck/DeckTest.kt +++ b/src/test/kotlin/blackjack/domain/deck/DeckTest.kt @@ -1,11 +1,13 @@ package blackjack.domain.deck import blackjack.domain.card.Card +import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.card.CardNumber.ACE import blackjack.domain.card.Suit.SPADES import io.kotest.assertions.throwables.shouldNotThrowAny import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe class DeckTest : FunSpec({ context("create") { @@ -26,4 +28,21 @@ class DeckTest : FunSpec({ } } } + + context("drawCard") { + test("should draw a card and remove from deck") { + val deck = Deck.create { mutableListOf(SPADES_ACE) } + val card = deck.drawCard() + + card shouldBe SPADES_ACE + } + + test("should throw exception if no card to draw") { + val deck = Deck.create { mutableListOf() } + + shouldThrow { + deck.drawCard() + } + } + } }) From aa5caee452a4f0da7eeeeb5dcf5c0bc9cb57261b Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 02:01:22 +0200 Subject: [PATCH 16/28] feat: add player domain --- .../kotlin/blackjack/domain/player/Player.kt | 22 +++++++++++ .../blackjack/domain/player/PlayerTest.kt | 38 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 src/main/kotlin/blackjack/domain/player/Player.kt create mode 100644 src/test/kotlin/blackjack/domain/player/PlayerTest.kt diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt new file mode 100644 index 000000000..9ac831f3d --- /dev/null +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -0,0 +1,22 @@ +package blackjack.domain.player + +import blackjack.domain.card.Card +import blackjack.domain.state.InitialState +import blackjack.domain.state.State + +class Player( + val name: Name, + _state: State = InitialState(), +) { + var state = _state + private set + + val cards: List + get() = state.hands.cards + + constructor(rawName: String) : this(Name(rawName)) + + fun draw(card: Card) { + state = state.addCard(card) + } +} diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt new file mode 100644 index 000000000..450851524 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -0,0 +1,38 @@ +package blackjack.domain.player + +import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_SEVEN +import blackjack.domain.card.CardFixture.SPADES_SIX +import blackjack.domain.card.CardFixture.SPADES_TWO +import blackjack.domain.state.Hit +import blackjack.domain.state.InitialState +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class PlayerTest : FunSpec({ + test("when created, state should be initial state") { + val player = Player("me") + + player.state::class shouldBe InitialState::class + } + + test("can draw a card") { + val player = Player("me") + player.draw(SPADES_ACE) + + player.state.hands.size shouldBe 1 + } + + test("state should change") { + val player = Player("me") + player.draw(SPADES_SIX) + player.draw(SPADES_SEVEN) + player.draw(SPADES_TWO) + + assertSoftly { + player.state.hands.size shouldBe 3 + player.state::class shouldBe Hit::class + } + } +}) From 0e54eb49596b75db96ac3d75bbfda138c85f5f4f Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 17 Apr 2025 13:35:53 +0200 Subject: [PATCH 17/28] chore: for save --- .../kotlin/blackjack/BlackjackApplication.kt | 16 ++++++- src/main/kotlin/blackjack/domain/card/Card.kt | 2 +- .../kotlin/blackjack/domain/player/Players.kt | 19 ++++++++ src/main/kotlin/blackjack/view/OutputView.kt | 44 +++++++++++++++++++ .../blackjack/domain/player/PlayersTest.kt | 7 +++ 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/blackjack/domain/player/Players.kt create mode 100644 src/main/kotlin/blackjack/view/OutputView.kt create mode 100644 src/test/kotlin/blackjack/domain/player/PlayersTest.kt diff --git a/src/main/kotlin/blackjack/BlackjackApplication.kt b/src/main/kotlin/blackjack/BlackjackApplication.kt index 058ab5baa..24c9ab342 100644 --- a/src/main/kotlin/blackjack/BlackjackApplication.kt +++ b/src/main/kotlin/blackjack/BlackjackApplication.kt @@ -1,5 +1,19 @@ package blackjack +import blackjack.domain.deck.Deck +import blackjack.domain.player.Name +import blackjack.domain.player.Player +import blackjack.domain.player.Players +import blackjack.view.InputView +import blackjack.view.OutputView + fun main() { -} + val rawNames = InputView.getPlayerNames() + val players = Players(rawNames.map(::Name).map { Player(it) }) + val deck = Deck.create() + players.initializeState { deck.drawCard() } + println(deck) + + OutputView.printInitialCards(players) +} diff --git a/src/main/kotlin/blackjack/domain/card/Card.kt b/src/main/kotlin/blackjack/domain/card/Card.kt index 378358b24..4d57c2e3d 100644 --- a/src/main/kotlin/blackjack/domain/card/Card.kt +++ b/src/main/kotlin/blackjack/domain/card/Card.kt @@ -2,7 +2,7 @@ package blackjack.domain.card class Card private constructor( val number: CardNumber, - private val suit: Suit, + val suit: Suit, ) { companion object { private val cache: Map, Card> = diff --git a/src/main/kotlin/blackjack/domain/player/Players.kt b/src/main/kotlin/blackjack/domain/player/Players.kt new file mode 100644 index 000000000..76ebc61ba --- /dev/null +++ b/src/main/kotlin/blackjack/domain/player/Players.kt @@ -0,0 +1,19 @@ +package blackjack.domain.player + +import blackjack.domain.card.Card + +class Players( + val values: List, +) { + val names = values.map { it.name.value } + + fun initializeState(block: () -> Card) { + repeat(FIRST_TURN_REPETITIONS) { + values.forEach { it.draw(block()) } + } + } + + companion object { + private const val FIRST_TURN_REPETITIONS = 2 + } +} diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt new file mode 100644 index 000000000..051048443 --- /dev/null +++ b/src/main/kotlin/blackjack/view/OutputView.kt @@ -0,0 +1,44 @@ +package blackjack.view + +import blackjack.domain.card.Card +import blackjack.domain.card.CardNumber +import blackjack.domain.card.Suit +import blackjack.domain.player.Players + +object OutputView { + fun printInitialCards(players: Players) { + println("Dealing two cards to ${players.names.joinToString()}.") + players.values.forEach { + println("${it.name.value}'s cards: ${it.cards.toView()}.") + } + } + + private fun List.toView() = joinToString(" ") { card -> card.toView() } + + private fun Card.toView() = "${this.number.toView()}${this.suit.toView()}" + + private fun CardNumber.toView() = + when (this) { + CardNumber.ACE -> "A" + CardNumber.TWO -> "2" + CardNumber.THREE -> "3" + CardNumber.FOUR -> "4" + CardNumber.FIVE -> "5" + CardNumber.SIX -> "6" + CardNumber.SEVEN -> "7" + CardNumber.EIGHT -> "8" + CardNumber.NINE -> "9" + CardNumber.TEN -> "10" + CardNumber.JACK -> "J" + CardNumber.QUEEN -> "Q" + CardNumber.KING -> "K" + } + + private fun Suit.toView() = + when (this) { + Suit.SPADES -> "♠️" + Suit.DIAMONDS -> "♦️" + Suit.HEARTS -> "❤️" + Suit.CLUBS -> "♣️️" + } +} diff --git a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt new file mode 100644 index 000000000..4325abdb4 --- /dev/null +++ b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt @@ -0,0 +1,7 @@ +package blackjack.domain.player + +import io.kotest.core.spec.style.FunSpec + +class PlayersTest : FunSpec({ + +}) From 86af5df3825c9835af3c1741caadfaeb14e7dca0 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:03:29 +0200 Subject: [PATCH 18/28] feat: add view logics --- src/main/kotlin/blackjack/view/InputView.kt | 16 +++++++++------- src/main/kotlin/blackjack/view/OutputView.kt | 7 ++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt index 9ec021e68..f2ba279fd 100644 --- a/src/main/kotlin/blackjack/view/InputView.kt +++ b/src/main/kotlin/blackjack/view/InputView.kt @@ -1,5 +1,7 @@ package blackjack.view +import blackjack.domain.player.Player + object InputView { fun getPlayerNames(): List { println("Enter the names of the players (comma-separated):") @@ -10,24 +12,24 @@ object InputView { .filter { it.isNotBlank() } } - fun getUserChoice(): UserChoice { - println("Would pobi like to draw another card? (y for yes, n for no)") + fun getUserChoice(player: Player): Boolean { + println("Would ${player.name.value} like to draw another card? (y for yes, n for no)") val input = readln() - return UserChoice.from(input) + return UserChoice.from(input).value } } -enum class UserChoice { - Y, - N, +enum class UserChoice(val value: Boolean) { + Y(true), + N(false), ; companion object { fun from(input: String): UserChoice { return entries.find { it.name == input.uppercase() - } ?: throw IllegalArgumentException("Invalid input: $input") + } ?: throw IllegalArgumentException("Invalid user choice: $input") } } } diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt index 051048443..5db06b199 100644 --- a/src/main/kotlin/blackjack/view/OutputView.kt +++ b/src/main/kotlin/blackjack/view/OutputView.kt @@ -3,16 +3,17 @@ package blackjack.view import blackjack.domain.card.Card import blackjack.domain.card.CardNumber import blackjack.domain.card.Suit +import blackjack.domain.player.Player import blackjack.domain.player.Players object OutputView { fun printInitialCards(players: Players) { println("Dealing two cards to ${players.names.joinToString()}.") - players.values.forEach { - println("${it.name.value}'s cards: ${it.cards.toView()}.") - } + players.values.forEach { printPlayerCards(it) } } + fun printPlayerCards(player: Player) = println("${player.name.value}'s cards: ${player.cards.toView()}") + private fun List.toView() = joinToString(" ") { card -> card.toView() } private fun Card.toView() = "${this.number.toView()}${this.suit.toView()}" From d89e9e3c71448641a1e4d3ba88ce090c38e54e95 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:03:58 +0200 Subject: [PATCH 19/28] feat: add continuable condition for state --- src/main/kotlin/blackjack/domain/state/Bust.kt | 2 ++ src/main/kotlin/blackjack/domain/state/Hit.kt | 2 +- src/main/kotlin/blackjack/domain/state/State.kt | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/blackjack/domain/state/Bust.kt b/src/main/kotlin/blackjack/domain/state/Bust.kt index 9a23bfcff..b5d2107f6 100644 --- a/src/main/kotlin/blackjack/domain/state/Bust.kt +++ b/src/main/kotlin/blackjack/domain/state/Bust.kt @@ -4,6 +4,8 @@ import blackjack.domain.card.Card import blackjack.domain.player.Hands class Bust(override val hands: Hands) : State { + override val canContinue = false + override fun addCard(card: Card): State { throw IllegalStateException("Can't add card after bust.") } diff --git a/src/main/kotlin/blackjack/domain/state/Hit.kt b/src/main/kotlin/blackjack/domain/state/Hit.kt index 775927877..88e4e2484 100644 --- a/src/main/kotlin/blackjack/domain/state/Hit.kt +++ b/src/main/kotlin/blackjack/domain/state/Hit.kt @@ -7,7 +7,7 @@ class Hit(override val hands: Hands) : State { override fun addCard(card: Card): State { val hands = hands + card if (hands.isBust()) { - Bust(hands) + return Bust(hands) } return Hit(hands) diff --git a/src/main/kotlin/blackjack/domain/state/State.kt b/src/main/kotlin/blackjack/domain/state/State.kt index f479c2998..b4e1783d4 100644 --- a/src/main/kotlin/blackjack/domain/state/State.kt +++ b/src/main/kotlin/blackjack/domain/state/State.kt @@ -6,5 +6,8 @@ import blackjack.domain.player.Hands interface State { val hands: Hands + val canContinue: Boolean + get() = true + fun addCard(card: Card): State } From c728cd171667a2a49356e0b08276c76eeca62eb9 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:15:39 +0200 Subject: [PATCH 20/28] test: add test for players --- .../kotlin/blackjack/domain/player/Players.kt | 2 ++ .../blackjack/domain/player/PlayersTest.kt | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/main/kotlin/blackjack/domain/player/Players.kt b/src/main/kotlin/blackjack/domain/player/Players.kt index 76ebc61ba..67bea3d41 100644 --- a/src/main/kotlin/blackjack/domain/player/Players.kt +++ b/src/main/kotlin/blackjack/domain/player/Players.kt @@ -7,6 +7,8 @@ class Players( ) { val names = values.map { it.name.value } + constructor(vararg rawNames: String) : this(rawNames.map { Player(Name(it)) }) + fun initializeState(block: () -> Card) { repeat(FIRST_TURN_REPETITIONS) { values.forEach { it.draw(block()) } diff --git a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt index 4325abdb4..30cffe075 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayersTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayersTest.kt @@ -1,7 +1,42 @@ package blackjack.domain.player +import blackjack.domain.card.CardFixture.SPADES_ACE +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldHaveSize class PlayersTest : FunSpec({ + context("create") { + test("from raw names") { + shouldNotThrowAny { + Players("sun", "justin", "jason") + } + } + test("from players") { + shouldNotThrowAny { + Players( + listOf( + Player("sun"), + Player("justin"), + Player("jason"), + ), + ) + } + } + } + + test("initializeState should give exactly two cards to each player") { + val playerNames = listOf("sun", "justin", "jason") + val players = Players(*playerNames.toTypedArray()) + + players.initializeState { SPADES_ACE } + + players.values.zip(playerNames).forEach { (player, name) -> + withClue("Player '$name' should have exactly 2 cards") { + player.cards shouldHaveSize 2 + } + } + } }) From a8a6cf6e013ddaa601f11e4f1abd350d60f82c84 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:21:23 +0200 Subject: [PATCH 21/28] feat: add property for player on can draw card --- .../kotlin/blackjack/domain/player/Player.kt | 4 ++++ .../blackjack/domain/player/PlayerTest.kt | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index 9ac831f3d..c179e0617 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -14,7 +14,11 @@ class Player( val cards: List get() = state.hands.cards + val canDraw: Boolean + get() = state.canContinue + constructor(rawName: String) : this(Name(rawName)) + constructor(rawName: String, state: State) : this(Name(rawName), state) fun draw(card: Card) { state = state.addCard(card) diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index 450851524..dd29d5b76 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -4,6 +4,7 @@ import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.card.CardFixture.SPADES_SEVEN import blackjack.domain.card.CardFixture.SPADES_SIX import blackjack.domain.card.CardFixture.SPADES_TWO +import blackjack.domain.state.Bust import blackjack.domain.state.Hit import blackjack.domain.state.InitialState import io.kotest.assertions.assertSoftly @@ -17,6 +18,23 @@ class PlayerTest : FunSpec({ player.state::class shouldBe InitialState::class } + context("canDraw") { + test("return true on initial state") { + val player = Player("sun") + player.canDraw shouldBe true + } + + test("return true on hit state") { + val player = Player("sun", Hit(Hands())) + player.canDraw shouldBe true + } + + test("return false on bust state") { + val player = Player("sun", Bust(Hands())) + player.canDraw shouldBe false + } + } + test("can draw a card") { val player = Player("me") player.draw(SPADES_ACE) From ae0df5b7f4289e27dc54189b78e80456c84b09d3 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:24:17 +0200 Subject: [PATCH 22/28] test: add tests for states canContinue --- src/test/kotlin/blackjack/domain/state/BustTest.kt | 5 +++++ src/test/kotlin/blackjack/domain/state/HitTest.kt | 4 ++++ src/test/kotlin/blackjack/domain/state/InitialStateTest.kt | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/test/kotlin/blackjack/domain/state/BustTest.kt b/src/test/kotlin/blackjack/domain/state/BustTest.kt index 9cce18103..d88340e9e 100644 --- a/src/test/kotlin/blackjack/domain/state/BustTest.kt +++ b/src/test/kotlin/blackjack/domain/state/BustTest.kt @@ -4,6 +4,7 @@ import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.player.Hands import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe class BustTest : FunSpec({ test("addCard throws exception if busted") { @@ -11,4 +12,8 @@ class BustTest : FunSpec({ Bust(Hands()).addCard(SPADES_ACE) } } + + test("canContinue returns false on bust") { + Bust(Hands()).canContinue shouldBe false + } }) diff --git a/src/test/kotlin/blackjack/domain/state/HitTest.kt b/src/test/kotlin/blackjack/domain/state/HitTest.kt index 556341b41..4e3d68a7e 100644 --- a/src/test/kotlin/blackjack/domain/state/HitTest.kt +++ b/src/test/kotlin/blackjack/domain/state/HitTest.kt @@ -24,4 +24,8 @@ class HitTest : FunSpec({ state::class shouldBe Hit::class } } + + test("canContinue returns true on hit") { + Hit(Hands()).canContinue shouldBe true + } }) diff --git a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt index b2f17d0c9..3a7381f95 100644 --- a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt +++ b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt @@ -41,4 +41,8 @@ class InitialStateTest : FunSpec({ } } } + + test("canContinue returns true on initialState") { + InitialState().canContinue shouldBe true + } }) From 5166805c568c0398b46ff4a0f0f3636ef94a4e2f Mon Sep 17 00:00:00 2001 From: syoun602 Date: Thu, 1 May 2025 13:45:24 +0200 Subject: [PATCH 23/28] feat: add calculating score for ace values --- .../kotlin/blackjack/domain/player/Hands.kt | 16 +++++++++- .../blackjack/domain/player/HandsTest.kt | 30 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/blackjack/domain/player/Hands.kt b/src/main/kotlin/blackjack/domain/player/Hands.kt index b1f818790..ebd1f2caf 100644 --- a/src/main/kotlin/blackjack/domain/player/Hands.kt +++ b/src/main/kotlin/blackjack/domain/player/Hands.kt @@ -1,6 +1,7 @@ package blackjack.domain.player import blackjack.domain.card.Card +import blackjack.domain.card.CardNumber class Hands( val cards: List = emptyList(), @@ -13,9 +14,22 @@ class Hands( infix operator fun plus(card: Card) = Hands(cards + card) - fun isBust(): Boolean = cards.sumOf { it.number.value } > BLACKJACK_SCORE + fun isBust(): Boolean = calculateScore() > BLACKJACK_SCORE + + fun calculateScore(): Int { + val sum = cards.sumOf { it.number.value } + + return when { + !hasAce() -> sum + sum + ACE_ADD_SCORE > BLACKJACK_SCORE -> sum + else -> sum + ACE_ADD_SCORE + } + } + + fun hasAce() = cards.map { it.number }.contains(CardNumber.ACE) companion object { private const val BLACKJACK_SCORE = 21 + private const val ACE_ADD_SCORE = 10 } } diff --git a/src/test/kotlin/blackjack/domain/player/HandsTest.kt b/src/test/kotlin/blackjack/domain/player/HandsTest.kt index 451005a98..543f9f559 100644 --- a/src/test/kotlin/blackjack/domain/player/HandsTest.kt +++ b/src/test/kotlin/blackjack/domain/player/HandsTest.kt @@ -3,8 +3,10 @@ package blackjack.domain.player import blackjack.domain.card.CardFixture.SPADES_ACE import blackjack.domain.card.CardFixture.SPADES_JACK import blackjack.domain.card.CardFixture.SPADES_QUEEN +import blackjack.domain.card.CardFixture.SPADES_SEVEN import blackjack.domain.card.CardFixture.SPADES_SIX import io.kotest.core.spec.style.FunSpec +import io.kotest.datatest.withData import io.kotest.matchers.shouldBe class HandsTest : FunSpec({ @@ -56,4 +58,32 @@ class HandsTest : FunSpec({ hands.isBust() shouldBe true } + + context("score") { + test("score is calculated correctly") { + this@context.withData( + listOf(SPADES_JACK, SPADES_SIX) to 16, + listOf(SPADES_SIX, SPADES_SEVEN) to 13, + listOf(SPADES_QUEEN, SPADES_JACK) to 20, + ) { (cards, expected) -> + val hands = Hands(cards) + + hands.calculateScore() shouldBe expected + } + } + + test("ace is counted as 1 if cards + ACE(11) is over than 21") { + val cards = listOf(SPADES_QUEEN, SPADES_JACK, SPADES_ACE) + val hands = Hands(cards) + + hands.calculateScore() shouldBe 21 + } + + test("ace is counted as 11 if cards + ACE(11) is below or equal to 21") { + val cards = listOf(SPADES_QUEEN, SPADES_ACE) + val hands = Hands(cards) + + hands.calculateScore() shouldBe 21 + } + } }) From aaf4172ed128de6d5deec56e811748f046788d91 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 4 May 2025 14:48:47 +0200 Subject: [PATCH 24/28] feat: add score for all states and player --- src/main/kotlin/blackjack/domain/player/Player.kt | 3 +++ src/main/kotlin/blackjack/domain/state/Bust.kt | 3 +++ src/main/kotlin/blackjack/domain/state/Hit.kt | 3 +++ src/main/kotlin/blackjack/domain/state/InitialState.kt | 3 +++ src/main/kotlin/blackjack/domain/state/State.kt | 2 ++ src/test/kotlin/blackjack/domain/player/PlayerTest.kt | 7 +++++++ src/test/kotlin/blackjack/domain/state/BustTest.kt | 9 +++++++++ src/test/kotlin/blackjack/domain/state/HitTest.kt | 8 ++++++++ .../kotlin/blackjack/domain/state/InitialStateTest.kt | 8 ++++++++ 9 files changed, 46 insertions(+) diff --git a/src/main/kotlin/blackjack/domain/player/Player.kt b/src/main/kotlin/blackjack/domain/player/Player.kt index c179e0617..7de5fb06d 100644 --- a/src/main/kotlin/blackjack/domain/player/Player.kt +++ b/src/main/kotlin/blackjack/domain/player/Player.kt @@ -17,6 +17,9 @@ class Player( val canDraw: Boolean get() = state.canContinue + val score: Int? + get() = state.score + constructor(rawName: String) : this(Name(rawName)) constructor(rawName: String, state: State) : this(Name(rawName), state) diff --git a/src/main/kotlin/blackjack/domain/state/Bust.kt b/src/main/kotlin/blackjack/domain/state/Bust.kt index b5d2107f6..364f5dae1 100644 --- a/src/main/kotlin/blackjack/domain/state/Bust.kt +++ b/src/main/kotlin/blackjack/domain/state/Bust.kt @@ -6,6 +6,9 @@ import blackjack.domain.player.Hands class Bust(override val hands: Hands) : State { override val canContinue = false + override val score: Int? + get() = null + override fun addCard(card: Card): State { throw IllegalStateException("Can't add card after bust.") } diff --git a/src/main/kotlin/blackjack/domain/state/Hit.kt b/src/main/kotlin/blackjack/domain/state/Hit.kt index 88e4e2484..2fc8edf8a 100644 --- a/src/main/kotlin/blackjack/domain/state/Hit.kt +++ b/src/main/kotlin/blackjack/domain/state/Hit.kt @@ -4,6 +4,9 @@ import blackjack.domain.card.Card import blackjack.domain.player.Hands class Hit(override val hands: Hands) : State { + override val score: Int? + get() = hands.calculateScore() + override fun addCard(card: Card): State { val hands = hands + card if (hands.isBust()) { diff --git a/src/main/kotlin/blackjack/domain/state/InitialState.kt b/src/main/kotlin/blackjack/domain/state/InitialState.kt index 15d3329ef..0a858fe2e 100644 --- a/src/main/kotlin/blackjack/domain/state/InitialState.kt +++ b/src/main/kotlin/blackjack/domain/state/InitialState.kt @@ -4,6 +4,9 @@ import blackjack.domain.card.Card import blackjack.domain.player.Hands class InitialState(override val hands: Hands = Hands()) : State { + override val score: Int? + get() = hands.calculateScore() + override fun addCard(card: Card): State { val hands = hands + card if (!hands.initialized) { diff --git a/src/main/kotlin/blackjack/domain/state/State.kt b/src/main/kotlin/blackjack/domain/state/State.kt index b4e1783d4..0eef79839 100644 --- a/src/main/kotlin/blackjack/domain/state/State.kt +++ b/src/main/kotlin/blackjack/domain/state/State.kt @@ -9,5 +9,7 @@ interface State { val canContinue: Boolean get() = true + val score: Int? + fun addCard(card: Card): State } diff --git a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt index dd29d5b76..ad4a15f24 100644 --- a/src/test/kotlin/blackjack/domain/player/PlayerTest.kt +++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt @@ -53,4 +53,11 @@ class PlayerTest : FunSpec({ player.state::class shouldBe Hit::class } } + + test("score") { + val player = Player("me") + player.draw(SPADES_SIX) + + player.score shouldBe 6 + } }) diff --git a/src/test/kotlin/blackjack/domain/state/BustTest.kt b/src/test/kotlin/blackjack/domain/state/BustTest.kt index d88340e9e..6c66e2225 100644 --- a/src/test/kotlin/blackjack/domain/state/BustTest.kt +++ b/src/test/kotlin/blackjack/domain/state/BustTest.kt @@ -1,6 +1,9 @@ package blackjack.domain.state import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_QUEEN +import blackjack.domain.card.CardFixture.SPADES_TEN +import blackjack.domain.card.CardFixture.SPADES_TWO import blackjack.domain.player.Hands import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec @@ -16,4 +19,10 @@ class BustTest : FunSpec({ test("canContinue returns false on bust") { Bust(Hands()).canContinue shouldBe false } + + test("hands score must be null on bust") { + val hands = Hands(listOf(SPADES_TEN, SPADES_QUEEN, SPADES_TWO)) + + Bust(hands).score shouldBe null + } }) diff --git a/src/test/kotlin/blackjack/domain/state/HitTest.kt b/src/test/kotlin/blackjack/domain/state/HitTest.kt index 4e3d68a7e..bb042b38a 100644 --- a/src/test/kotlin/blackjack/domain/state/HitTest.kt +++ b/src/test/kotlin/blackjack/domain/state/HitTest.kt @@ -1,5 +1,7 @@ package blackjack.domain.state +import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_QUEEN import blackjack.domain.card.CardFixture.SPADES_SEVEN import blackjack.domain.card.CardFixture.SPADES_SIX import blackjack.domain.card.CardFixture.SPADES_TWO @@ -28,4 +30,10 @@ class HitTest : FunSpec({ test("canContinue returns true on hit") { Hit(Hands()).canContinue shouldBe true } + + test("should return hands score") { + val hands = Hands(listOf(SPADES_ACE, SPADES_QUEEN)) + + Hit(hands).score shouldBe 21 + } }) diff --git a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt index 3a7381f95..72fb0e1c2 100644 --- a/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt +++ b/src/test/kotlin/blackjack/domain/state/InitialStateTest.kt @@ -2,7 +2,9 @@ package blackjack.domain.state import blackjack.domain.card.Card import blackjack.domain.card.CardFixture.SPADES_ACE +import blackjack.domain.card.CardFixture.SPADES_QUEEN import blackjack.domain.card.CardFixture.SPADES_SIX +import blackjack.domain.card.CardFixture.SPADES_TEN import blackjack.domain.card.CardNumber import blackjack.domain.card.Suit import blackjack.domain.player.Hands @@ -45,4 +47,10 @@ class InitialStateTest : FunSpec({ test("canContinue returns true on initialState") { InitialState().canContinue shouldBe true } + + test("should return hands score") { + val hands = Hands(listOf(SPADES_TEN, SPADES_QUEEN)) + + InitialState(hands).score shouldBe 20 + } }) From 9e6b39bb7259b9a0ff94c588a00de4791b258445 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 4 May 2025 14:54:31 +0200 Subject: [PATCH 25/28] feat: add blackjack game plays --- .../kotlin/blackjack/BlackjackApplication.kt | 16 ++++++++++++++-- src/main/kotlin/blackjack/view/OutputView.kt | 16 ++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/blackjack/BlackjackApplication.kt b/src/main/kotlin/blackjack/BlackjackApplication.kt index 24c9ab342..a41f48dd4 100644 --- a/src/main/kotlin/blackjack/BlackjackApplication.kt +++ b/src/main/kotlin/blackjack/BlackjackApplication.kt @@ -13,7 +13,19 @@ fun main() { val deck = Deck.create() players.initializeState { deck.drawCard() } - println(deck) - OutputView.printInitialCards(players) + + players.values.forEach { player -> player.takeTurn(deck) } + OutputView.printBlackjackResult(players) +} + +private fun Player.takeTurn(deck: Deck) { + while (this.canDraw && InputView.getUserChoice(this)) { + this.draw(deck.drawCard()) + OutputView.printPlayerCards(this) + + if (!this.canDraw) { + return OutputView.announceBust(this) + } + } } diff --git a/src/main/kotlin/blackjack/view/OutputView.kt b/src/main/kotlin/blackjack/view/OutputView.kt index 5db06b199..f5731ca43 100644 --- a/src/main/kotlin/blackjack/view/OutputView.kt +++ b/src/main/kotlin/blackjack/view/OutputView.kt @@ -9,10 +9,22 @@ import blackjack.domain.player.Players object OutputView { fun printInitialCards(players: Players) { println("Dealing two cards to ${players.names.joinToString()}.") - players.values.forEach { printPlayerCards(it) } + players.values.forEach { println(it.toView()) } } - fun printPlayerCards(player: Player) = println("${player.name.value}'s cards: ${player.cards.toView()}") + fun printPlayerCards(player: Player) = println(player.toView()) + + fun printBlackjackResult(players: Players) { + players.values.forEach { + println(it.toView() + " - Total: ${it.score ?: "BUST"}") + } + } + + fun announceBust(player: Player) { + println("${player.name.value} busts!") + } + + private fun Player.toView() = "${this.name.value}'s cards: ${this.cards.toView()}" private fun List.toView() = joinToString(" ") { card -> card.toView() } From 9eaf730a66a6bb97148e666844424b5833128cf4 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 4 May 2025 14:57:18 +0200 Subject: [PATCH 26/28] docs: update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b4eaba4eb..c9d001dcf 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Card -- [ ] Number cards are counted by their face value. -- [ ] Aces can count as either 1 or 11. +- [x] Number cards are counted by their face value. +- [x] Aces can count as either 1 or 11. Card Number @@ -30,8 +30,8 @@ Hands Player -- [ ] Receives two cards at start of the game. -- [ ] Players can choose to draw additional cards as long as their total does not exceed 21. +- [x] Receives two cards at start of the game. +- [x] Players can choose to draw additional cards as long as their total does not exceed 21. ### States @@ -44,4 +44,3 @@ InitialTurn Hit - [x] Can add cards until bust -- From 74c14d04b8b9d3801cb134995b60f3d7edeea48a Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 4 May 2025 15:02:10 +0200 Subject: [PATCH 27/28] feat: use kotlin dsl marker --- src/main/kotlin/dsl/Languages.kt | 1 + src/main/kotlin/dsl/Person.kt | 1 + src/main/kotlin/dsl/PersonDsl.kt | 4 ++++ src/main/kotlin/dsl/Skills.kt | 1 + 4 files changed, 7 insertions(+) create mode 100644 src/main/kotlin/dsl/PersonDsl.kt diff --git a/src/main/kotlin/dsl/Languages.kt b/src/main/kotlin/dsl/Languages.kt index 172bfb185..e357eea8b 100644 --- a/src/main/kotlin/dsl/Languages.kt +++ b/src/main/kotlin/dsl/Languages.kt @@ -1,5 +1,6 @@ package dsl +@PersonDsl data class Languages( val values: MutableList = mutableListOf(), ) { diff --git a/src/main/kotlin/dsl/Person.kt b/src/main/kotlin/dsl/Person.kt index 874bce4e4..cfefcc1f1 100644 --- a/src/main/kotlin/dsl/Person.kt +++ b/src/main/kotlin/dsl/Person.kt @@ -7,6 +7,7 @@ data class Person( val languages: Languages = Languages(), ) +@PersonDsl class PersonBuilder { private lateinit var name: String private var company: String? = null diff --git a/src/main/kotlin/dsl/PersonDsl.kt b/src/main/kotlin/dsl/PersonDsl.kt new file mode 100644 index 000000000..968915c25 --- /dev/null +++ b/src/main/kotlin/dsl/PersonDsl.kt @@ -0,0 +1,4 @@ +package dsl + +@DslMarker +annotation class PersonDsl diff --git a/src/main/kotlin/dsl/Skills.kt b/src/main/kotlin/dsl/Skills.kt index 98ada0228..c2972e9cd 100644 --- a/src/main/kotlin/dsl/Skills.kt +++ b/src/main/kotlin/dsl/Skills.kt @@ -1,5 +1,6 @@ package dsl +@PersonDsl data class Skills( val values: MutableList = mutableListOf(), ) { From 0670f0aa0a26ae470aa2967dc8cebdd846bcd7b3 Mon Sep 17 00:00:00 2001 From: syoun602 Date: Sun, 4 May 2025 15:12:05 +0200 Subject: [PATCH 28/28] docs: update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index c9d001dcf..ae584b3c9 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,6 @@ ## Blackjack ### Key objects -- [ ] At the start of the game, each player receives two cards. -- [ ] Players can choose to draw additional cards as long as their total does not exceed 21. - Card - [x] Number cards are counted by their face value.