From bca0a3068252003f17f9f649ea59b71af6562781 Mon Sep 17 00:00:00 2001
From: Duhan Mo <duhandv@gmail.com>
Date: Wed, 11 Dec 2024 18:51:34 +0900
Subject: [PATCH] =?UTF-8?q?fix:=20=EC=B6=9C=EB=A0=A5=EA=B5=AC=EB=AC=B8=20?=
 =?UTF-8?q?=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 README.md                                     |   6 +-
 src/main/kotlin/blackjack/Main.kt             |   4 +-
 .../blackjack/controller/BlackjackGame.kt     |  92 +++---------
 .../kotlin/blackjack/controller/GameTable.kt  |  46 ++++++
 src/main/kotlin/blackjack/domain/Cards.kt     |  46 ------
 src/main/kotlin/blackjack/domain/Deck.kt      |  14 --
 .../kotlin/blackjack/domain/GameResult.kt     |  27 ----
 src/main/kotlin/blackjack/domain/GameTable.kt |  21 ---
 .../kotlin/blackjack/domain/Participant.kt    |  63 --------
 .../blackjack/domain/{ => card}/Card.kt       |   4 +-
 src/main/kotlin/blackjack/domain/card/Deck.kt |   8 +
 src/main/kotlin/blackjack/domain/card/Hand.kt |  30 ++++
 .../blackjack/domain/{ => card}/Rank.kt       |   7 +-
 .../blackjack/domain/{ => card}/Suit.kt       |   2 +-
 .../blackjack/domain/dto/PlayerGameResult.kt  |   9 --
 .../blackjack/domain/game/GameResult.kt       |  31 ++++
 .../domain/{ => game}/MatchResult.kt          |   4 +-
 .../domain/{ => game}/dto/DealerGameResult.kt |   4 +-
 .../domain/game/dto/PlayerGameResult.kt       |   9 ++
 .../kotlin/blackjack/domain/player/Dealer.kt  |  16 ++
 .../kotlin/blackjack/domain/player/Player.kt  |  42 ++++++
 src/main/kotlin/blackjack/view/InputView.kt   |  11 +-
 src/main/kotlin/blackjack/view/ResultView.kt  |  63 ++++----
 src/test/kotlin/blackjack/domain/CardTest.kt  |  21 ---
 .../kotlin/blackjack/domain/DealerTest.kt     |  19 ---
 src/test/kotlin/blackjack/domain/DeckTest.kt  |  21 ---
 .../kotlin/blackjack/domain/GameTableTest.kt  |  15 --
 .../kotlin/blackjack/domain/PlayerTest.kt     | 132 -----------------
 .../kotlin/blackjack/domain/card/DeckTest.kt  |  30 ++++
 .../domain/{CardsTest.kt => card/HandTest.kt} |  16 +-
 .../blackjack/domain/{ => card}/RankTest.kt   |   2 +-
 .../blackjack/domain/game/GameResultTest.kt   |  66 +++++++++
 .../blackjack/domain/player/DealerTest.kt     |  42 ++++++
 .../blackjack/domain/player/PlayerTest.kt     | 140 ++++++++++++++++++
 .../kotlin/blackjack/fixtures/CardFixture.kt  |   8 +-
 .../kotlin/blackjack/fixtures/UserFixtures.kt |   8 -
 36 files changed, 548 insertions(+), 531 deletions(-)
 create mode 100644 src/main/kotlin/blackjack/controller/GameTable.kt
 delete mode 100644 src/main/kotlin/blackjack/domain/Cards.kt
 delete mode 100644 src/main/kotlin/blackjack/domain/Deck.kt
 delete mode 100644 src/main/kotlin/blackjack/domain/GameResult.kt
 delete mode 100644 src/main/kotlin/blackjack/domain/GameTable.kt
 delete mode 100644 src/main/kotlin/blackjack/domain/Participant.kt
 rename src/main/kotlin/blackjack/domain/{ => card}/Card.kt (82%)
 create mode 100644 src/main/kotlin/blackjack/domain/card/Deck.kt
 create mode 100644 src/main/kotlin/blackjack/domain/card/Hand.kt
 rename src/main/kotlin/blackjack/domain/{ => card}/Rank.kt (76%)
 rename src/main/kotlin/blackjack/domain/{ => card}/Suit.kt (82%)
 delete mode 100644 src/main/kotlin/blackjack/domain/dto/PlayerGameResult.kt
 create mode 100644 src/main/kotlin/blackjack/domain/game/GameResult.kt
 rename src/main/kotlin/blackjack/domain/{ => game}/MatchResult.kt (64%)
 rename src/main/kotlin/blackjack/domain/{ => game}/dto/DealerGameResult.kt (57%)
 create mode 100644 src/main/kotlin/blackjack/domain/game/dto/PlayerGameResult.kt
 create mode 100644 src/main/kotlin/blackjack/domain/player/Dealer.kt
 create mode 100644 src/main/kotlin/blackjack/domain/player/Player.kt
 delete mode 100644 src/test/kotlin/blackjack/domain/CardTest.kt
 delete mode 100644 src/test/kotlin/blackjack/domain/DealerTest.kt
 delete mode 100644 src/test/kotlin/blackjack/domain/DeckTest.kt
 delete mode 100644 src/test/kotlin/blackjack/domain/GameTableTest.kt
 delete mode 100644 src/test/kotlin/blackjack/domain/PlayerTest.kt
 create mode 100644 src/test/kotlin/blackjack/domain/card/DeckTest.kt
 rename src/test/kotlin/blackjack/domain/{CardsTest.kt => card/HandTest.kt} (54%)
 rename src/test/kotlin/blackjack/domain/{ => card}/RankTest.kt (96%)
 create mode 100644 src/test/kotlin/blackjack/domain/game/GameResultTest.kt
 create mode 100644 src/test/kotlin/blackjack/domain/player/DealerTest.kt
 create mode 100644 src/test/kotlin/blackjack/domain/player/PlayerTest.kt
 delete mode 100644 src/test/kotlin/blackjack/fixtures/UserFixtures.kt

diff --git a/README.md b/README.md
index 3c69bf851..7817229dc 100644
--- a/README.md
+++ b/README.md
@@ -37,4 +37,8 @@
 ### 기능 구현사항
 - [x] 딜러는 처음받는 2장의 합계가 16이하면 반드시 1장의 카드를 추가로 받는다
 - [x] 딜러가 21을 초과하면 남은 플레이어들은 패에 상관없이 승리한다
-- [x] 게임 완료 후 각 플레이어별로 승패를 출력한다
\ No newline at end of file
+- [x] 게임 완료 후 각 플레이어별로 승패를 출력한다
+- [x] data class , 일반 클래스 설정 일관성 유지
+- [x] 도메인 패키지 분리
+- [x] 객체 상태를 객체가 관리
+- [x] 게임 관련 로직 분리
\ No newline at end of file
diff --git a/src/main/kotlin/blackjack/Main.kt b/src/main/kotlin/blackjack/Main.kt
index 6a4dee803..c857254f0 100644
--- a/src/main/kotlin/blackjack/Main.kt
+++ b/src/main/kotlin/blackjack/Main.kt
@@ -1,9 +1,7 @@
 package blackjack
 
 import blackjack.controller.BlackjackGame
-import blackjack.view.InputView
-import blackjack.view.ResultView
 
 fun main() {
-    BlackjackGame(InputView, ResultView).start()
+    BlackjackGame.start()
 }
diff --git a/src/main/kotlin/blackjack/controller/BlackjackGame.kt b/src/main/kotlin/blackjack/controller/BlackjackGame.kt
index 6a2eb75db..c090e01c1 100644
--- a/src/main/kotlin/blackjack/controller/BlackjackGame.kt
+++ b/src/main/kotlin/blackjack/controller/BlackjackGame.kt
@@ -1,87 +1,35 @@
 package blackjack.controller
 
-import blackjack.domain.Dealer
-import blackjack.domain.Deck
-import blackjack.domain.GameResult
-import blackjack.domain.GameTable
-import blackjack.domain.Participant
-import blackjack.domain.Player
+import blackjack.domain.card.Deck
+import blackjack.domain.player.Dealer
+import blackjack.domain.player.Player
 import blackjack.view.InputView
 import blackjack.view.ResultView
 
-data class BlackjackGame(
-    private val inputView: InputView,
-    private val resultView: ResultView,
-) {
+object BlackjackGame {
     fun start() {
-        val gameTable = GameTable(Deck.create())
-        val participants = playGame(gameTable)
-        printCard(participants)
-        printGameResult(participants)
+        val gameTable = setUp()
+        initDeal(gameTable)
+        turnStart(gameTable)
+        ResultView.printAfterTurn(gameTable)
     }
 
-    private fun playGame(gameTable: GameTable): List<Participant> {
-        val participants = setUpInitCard(gameTable)
-        val (players, dealer) = Participant.separate(participants)
-        val gamedPlayers = playersTurn(players, gameTable)
-        resultView.linebreak()
-        val gamedDealer = dealerTurn(dealer, gameTable)
-        return gamedPlayers + gamedDealer
+    private fun setUp(): GameTable {
+        val gameTable = GameTable(Deck(), Dealer(), getPlayers())
+        ResultView.linebreak()
+        return gameTable
     }
 
-    private fun setUpInitCard(gameTable: GameTable): List<Participant> {
-        val participants = gameTable.dealInitCard(getParticipants())
-        resultView.linebreak()
-        resultView.printInitCardReceive(participants)
-        resultView.printParticipantsCard(participants = participants, printScore = false)
-        resultView.linebreak()
-        return participants
-    }
-
-    private fun getParticipants(): List<Participant> {
-        return buildList {
-            add(Dealer.create())
-            addAll(inputView.inputNames().map { Player.create(name = it) })
-        }
-    }
-
-    private fun playersTurn(
-        participants: List<Participant>,
-        gameTable: GameTable,
-    ): List<Participant> {
-        return participants.map { playerTurn(it, gameTable) }
-    }
-
-    private tailrec fun playerTurn(
-        player: Participant,
-        gameTable: GameTable,
-    ): Participant {
-        if (!player.canHit() || !inputView.inputHit(player)) {
-            return player
-        }
-        val hitPlayer = gameTable.hit(player)
-        resultView.printParticipantCard(participant = hitPlayer, printScore = false)
-        return playerTurn(hitPlayer, gameTable)
-    }
-
-    private tailrec fun dealerTurn(
-        dealer: Participant,
-        gameTable: GameTable,
-    ): Participant {
-        if (!dealer.canHit()) {
-            return dealer
-        }
-        resultView.printDealerHit()
-        return dealerTurn(gameTable.hit(dealer), gameTable)
-    }
+    private fun getPlayers(): List<Player> = InputView.inputNames().map { Player(it) }
 
-    private fun printCard(participants: List<Participant>) {
-        resultView.linebreak()
-        resultView.printParticipantsCard(participants = participants, printScore = true)
+    private fun initDeal(gameTable: GameTable) {
+        gameTable.dealInitCard()
+        ResultView.printDealInitCard(gameTable)
     }
 
-    private fun printGameResult(participants: List<Participant>) {
-        resultView.linebreak()
-        resultView.printGameResult(GameResult.from(participants))
+    private fun turnStart(gameTable: GameTable) {
+        gameTable.playersTurn()
+        ResultView.linebreak()
+        gameTable.dealerTurn()
     }
 }
diff --git a/src/main/kotlin/blackjack/controller/GameTable.kt b/src/main/kotlin/blackjack/controller/GameTable.kt
new file mode 100644
index 000000000..4fa31a914
--- /dev/null
+++ b/src/main/kotlin/blackjack/controller/GameTable.kt
@@ -0,0 +1,46 @@
+package blackjack.controller
+
+import blackjack.domain.card.Deck
+import blackjack.domain.game.GameResult
+import blackjack.domain.player.Dealer
+import blackjack.domain.player.Player
+import blackjack.view.InputView
+import blackjack.view.ResultView
+
+class GameTable(
+    val deck: Deck,
+    val dealer: Dealer,
+    val players: List<Player>,
+) {
+    fun dealInitCard() =
+        repeat(INIT_CARD_DRAW_COUNT) {
+            dealer.hit(deck.draw())
+            players.forEach { it.hit(deck.draw()) }
+        }
+
+    fun playersTurn() = players.forEach { playerTurn(it) }
+
+    fun dealerTurn() {
+        if (!dealer.canHit()) {
+            return
+        }
+        ResultView.printDealerHit()
+        dealer.hit(deck.draw())
+        dealerTurn()
+    }
+
+    fun getGameResult(): GameResult = GameResult.from(dealer, players)
+
+    private fun playerTurn(player: Player) {
+        if (!player.canHit() || !InputView.inputHit(player)) {
+            return
+        }
+        player.hit(deck.draw())
+        ResultView.printPlayerCard(player)
+        playerTurn(player)
+    }
+
+    companion object {
+        const val INIT_CARD_DRAW_COUNT = 2
+    }
+}
diff --git a/src/main/kotlin/blackjack/domain/Cards.kt b/src/main/kotlin/blackjack/domain/Cards.kt
deleted file mode 100644
index aabe71254..000000000
--- a/src/main/kotlin/blackjack/domain/Cards.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package blackjack.domain
-
-import blackjack.domain.MatchResult.DRAW
-import blackjack.domain.MatchResult.LOSS
-import blackjack.domain.MatchResult.WIN
-
-data class Cards(val values: List<Card>) {
-    val score: Int
-        get() = calculateScore()
-
-    val isBust: Boolean
-        get() = calculateScore() > BLACKJACK_SCORE_LIMIT
-
-    fun scoreLowerThan(limit: Int): Boolean {
-        return score < limit
-    }
-
-    fun compareScore(other: Cards): MatchResult {
-        return when {
-            score > other.score -> WIN
-            score < other.score -> LOSS
-            else -> DRAW
-        }
-    }
-
-    fun add(card: Card): Cards {
-        return Cards(values + card)
-    }
-
-    private fun calculateScore(): Int {
-        val totalScore = values.sumOf { it.score }
-        var aceCount = values.count { it.isAce }
-
-        var adjustedScore = totalScore
-        while (adjustedScore > BLACKJACK_SCORE_LIMIT && aceCount > 0) {
-            adjustedScore -= ACE_SCORE_DIFFERENCE
-            aceCount--
-        }
-        return adjustedScore
-    }
-
-    companion object {
-        private const val BLACKJACK_SCORE_LIMIT = 21
-        private const val ACE_SCORE_DIFFERENCE = 10
-    }
-}
diff --git a/src/main/kotlin/blackjack/domain/Deck.kt b/src/main/kotlin/blackjack/domain/Deck.kt
deleted file mode 100644
index d6b33e27c..000000000
--- a/src/main/kotlin/blackjack/domain/Deck.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package blackjack.domain
-
-data class Deck(private val cards: MutableList<Card>) {
-    fun draw(): Card {
-        check(cards.isNotEmpty()) { "카드가 모두 소진되었습니다" }
-        return cards.removeFirst()
-    }
-
-    companion object {
-        fun create(): Deck {
-            return Deck(Card.ALL.shuffled().toMutableList())
-        }
-    }
-}
diff --git a/src/main/kotlin/blackjack/domain/GameResult.kt b/src/main/kotlin/blackjack/domain/GameResult.kt
deleted file mode 100644
index 5cfbb1cfb..000000000
--- a/src/main/kotlin/blackjack/domain/GameResult.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package blackjack.domain
-
-import blackjack.domain.MatchResult.DRAW
-import blackjack.domain.MatchResult.LOSS
-import blackjack.domain.MatchResult.WIN
-import blackjack.domain.dto.DealerGameResult
-import blackjack.domain.dto.PlayerGameResult
-
-data class GameResult(
-    val dealerGameResult: DealerGameResult,
-    val playerGameResults: List<PlayerGameResult>,
-) {
-    companion object {
-        fun from(participants: List<Participant>): GameResult {
-            val (players, dealer) = Participant.separate(participants)
-            val playerGameResults = players.map { player -> PlayerGameResult(player, player.compareScore(dealer)) }
-            return GameResult(
-                DealerGameResult(
-                    winCount = playerGameResults.count { it.result == LOSS },
-                    lossCount = playerGameResults.count { it.result == WIN },
-                    drawCount = playerGameResults.count { it.result == DRAW },
-                ),
-                playerGameResults,
-            )
-        }
-    }
-}
diff --git a/src/main/kotlin/blackjack/domain/GameTable.kt b/src/main/kotlin/blackjack/domain/GameTable.kt
deleted file mode 100644
index 9fda80d74..000000000
--- a/src/main/kotlin/blackjack/domain/GameTable.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package blackjack.domain
-
-data class GameTable(
-    private val deck: Deck,
-) {
-    fun dealInitCard(participants: List<Participant>): List<Participant> {
-        return participants.map { participant ->
-            (1..INIT_CARD_DRAW_COUNT).fold(participant) { acc, _ ->
-                acc.hit(deck.draw())
-            }
-        }
-    }
-
-    fun hit(participant: Participant): Participant {
-        return participant.hit(deck.draw())
-    }
-
-    companion object {
-        const val INIT_CARD_DRAW_COUNT = 2
-    }
-}
diff --git a/src/main/kotlin/blackjack/domain/Participant.kt b/src/main/kotlin/blackjack/domain/Participant.kt
deleted file mode 100644
index fccfc02a7..000000000
--- a/src/main/kotlin/blackjack/domain/Participant.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package blackjack.domain
-
-import blackjack.domain.MatchResult.LOSS
-import blackjack.domain.MatchResult.WIN
-
-sealed class Participant(val name: String, val cards: Cards) {
-    val isBust: Boolean
-        get() = cards.isBust
-
-    abstract fun canHit(): Boolean
-
-    abstract fun hit(card: Card): Participant
-
-    companion object {
-        fun separate(participants: List<Participant>): Pair<List<Player>, Dealer> {
-            return participants.filterIsInstance<Player>() to participants.first { it is Dealer } as Dealer
-        }
-    }
-}
-
-class Player(name: String, cards: Cards) : Participant(name, cards) {
-    override fun canHit(): Boolean {
-        return cards.scoreLowerThan(PLAYER_SCORE_LIMIT)
-    }
-
-    override fun hit(card: Card): Player {
-        return Player(this.name, cards.add(card))
-    }
-
-    fun compareScore(dealer: Dealer): MatchResult {
-        return when {
-            dealer.isBust -> WIN
-            this.isBust -> LOSS
-            else -> cards.compareScore(dealer.cards)
-        }
-    }
-
-    companion object {
-        private const val PLAYER_SCORE_LIMIT = 21
-
-        fun create(name: String): Player {
-            return Player(name, Cards(emptyList()))
-        }
-    }
-}
-
-class Dealer(cards: Cards) : Participant("딜러", cards) {
-    override fun canHit(): Boolean {
-        return cards.scoreLowerThan(DEALER_SCORE_LIMIT)
-    }
-
-    override fun hit(card: Card): Dealer {
-        return Dealer(cards.add(card))
-    }
-
-    companion object {
-        private const val DEALER_SCORE_LIMIT = 17
-
-        fun create(): Dealer {
-            return Dealer(Cards(emptyList()))
-        }
-    }
-}
diff --git a/src/main/kotlin/blackjack/domain/Card.kt b/src/main/kotlin/blackjack/domain/card/Card.kt
similarity index 82%
rename from src/main/kotlin/blackjack/domain/Card.kt
rename to src/main/kotlin/blackjack/domain/card/Card.kt
index 3b1cb0e9d..49e38e89d 100644
--- a/src/main/kotlin/blackjack/domain/Card.kt
+++ b/src/main/kotlin/blackjack/domain/card/Card.kt
@@ -1,6 +1,6 @@
-package blackjack.domain
+package blackjack.domain.card
 
-import blackjack.domain.Rank.ACE
+import blackjack.domain.card.Rank.ACE
 
 data class Card(
     val rank: Rank,
diff --git a/src/main/kotlin/blackjack/domain/card/Deck.kt b/src/main/kotlin/blackjack/domain/card/Deck.kt
new file mode 100644
index 000000000..6f20fd3ce
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/card/Deck.kt
@@ -0,0 +1,8 @@
+package blackjack.domain.card
+
+class Deck(private val cards: MutableList<Card> = Card.ALL.shuffled().toMutableList()) {
+    fun draw(): Card {
+        check(cards.isNotEmpty()) { "카드가 모두 소진되었습니다" }
+        return cards.removeFirst()
+    }
+}
diff --git a/src/main/kotlin/blackjack/domain/card/Hand.kt b/src/main/kotlin/blackjack/domain/card/Hand.kt
new file mode 100644
index 000000000..bebed6e7d
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/card/Hand.kt
@@ -0,0 +1,30 @@
+package blackjack.domain.card
+
+class Hand(private val _cards: MutableList<Card> = mutableListOf()) {
+    val cards: List<Card>
+        get() = _cards.toList()
+
+    val score: Int
+        get() = calculateScore()
+
+    fun add(card: Card) {
+        _cards.add(card)
+    }
+
+    private fun calculateScore(): Int {
+        val totalScore = cards.sumOf { it.score }
+        var aceCount = cards.count { it.isAce }
+
+        var adjustedScore = totalScore
+        while (adjustedScore > BLACKJACK_SCORE_LIMIT && aceCount > 0) {
+            adjustedScore -= ACE_SCORE_DIFFERENCE
+            aceCount--
+        }
+        return adjustedScore
+    }
+
+    companion object {
+        private const val BLACKJACK_SCORE_LIMIT = 21
+        private const val ACE_SCORE_DIFFERENCE = 10
+    }
+}
diff --git a/src/main/kotlin/blackjack/domain/Rank.kt b/src/main/kotlin/blackjack/domain/card/Rank.kt
similarity index 76%
rename from src/main/kotlin/blackjack/domain/Rank.kt
rename to src/main/kotlin/blackjack/domain/card/Rank.kt
index b65d2a669..7b7e1ad82 100644
--- a/src/main/kotlin/blackjack/domain/Rank.kt
+++ b/src/main/kotlin/blackjack/domain/card/Rank.kt
@@ -1,4 +1,4 @@
-package blackjack.domain
+package blackjack.domain.card
 
 enum class Rank(val value: String, val score: Int) {
     ACE("A", 11),
@@ -17,9 +17,8 @@ enum class Rank(val value: String, val score: Int) {
     ;
 
     companion object {
-        fun from(value: String): Rank {
-            return entries.firstOrNull { it.value == value }
+        fun from(value: String): Rank =
+            entries.firstOrNull { it.value == value }
                 ?: throw IllegalArgumentException("유효하지 않은 랭크 값입니다: $value")
-        }
     }
 }
diff --git a/src/main/kotlin/blackjack/domain/Suit.kt b/src/main/kotlin/blackjack/domain/card/Suit.kt
similarity index 82%
rename from src/main/kotlin/blackjack/domain/Suit.kt
rename to src/main/kotlin/blackjack/domain/card/Suit.kt
index bc2d994b1..fd563f625 100644
--- a/src/main/kotlin/blackjack/domain/Suit.kt
+++ b/src/main/kotlin/blackjack/domain/card/Suit.kt
@@ -1,4 +1,4 @@
-package blackjack.domain
+package blackjack.domain.card
 
 enum class Suit(val description: String) {
     SPADE("스페이드"),
diff --git a/src/main/kotlin/blackjack/domain/dto/PlayerGameResult.kt b/src/main/kotlin/blackjack/domain/dto/PlayerGameResult.kt
deleted file mode 100644
index 4651bc2ce..000000000
--- a/src/main/kotlin/blackjack/domain/dto/PlayerGameResult.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package blackjack.domain.dto
-
-import blackjack.domain.MatchResult
-import blackjack.domain.Player
-
-data class PlayerGameResult(
-    val player: Player,
-    val result: MatchResult,
-)
diff --git a/src/main/kotlin/blackjack/domain/game/GameResult.kt b/src/main/kotlin/blackjack/domain/game/GameResult.kt
new file mode 100644
index 000000000..cf7856903
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/game/GameResult.kt
@@ -0,0 +1,31 @@
+package blackjack.domain.game
+
+import blackjack.domain.game.MatchResult.DRAW
+import blackjack.domain.game.MatchResult.LOSE
+import blackjack.domain.game.MatchResult.WIN
+import blackjack.domain.game.dto.DealerGameResult
+import blackjack.domain.game.dto.PlayerGameResult
+import blackjack.domain.player.Dealer
+import blackjack.domain.player.Player
+
+data class GameResult(
+    val dealerGameResult: DealerGameResult,
+    val playerGameResults: List<PlayerGameResult>,
+) {
+    companion object {
+        fun from(
+            dealer: Dealer,
+            players: List<Player>,
+        ): GameResult {
+            val playerGameResults = players.map { player -> PlayerGameResult(player, player.matchHand(dealer)) }
+            return GameResult(
+                DealerGameResult(
+                    winCount = playerGameResults.count { it.result == LOSE },
+                    loseCount = playerGameResults.count { it.result == WIN },
+                    drawCount = playerGameResults.count { it.result == DRAW },
+                ),
+                playerGameResults,
+            )
+        }
+    }
+}
diff --git a/src/main/kotlin/blackjack/domain/MatchResult.kt b/src/main/kotlin/blackjack/domain/game/MatchResult.kt
similarity index 64%
rename from src/main/kotlin/blackjack/domain/MatchResult.kt
rename to src/main/kotlin/blackjack/domain/game/MatchResult.kt
index 610434564..fd60f033b 100644
--- a/src/main/kotlin/blackjack/domain/MatchResult.kt
+++ b/src/main/kotlin/blackjack/domain/game/MatchResult.kt
@@ -1,7 +1,7 @@
-package blackjack.domain
+package blackjack.domain.game
 
 enum class MatchResult(val description: String) {
     WIN("승"),
-    LOSS("패"),
+    LOSE("패"),
     DRAW("무"),
 }
diff --git a/src/main/kotlin/blackjack/domain/dto/DealerGameResult.kt b/src/main/kotlin/blackjack/domain/game/dto/DealerGameResult.kt
similarity index 57%
rename from src/main/kotlin/blackjack/domain/dto/DealerGameResult.kt
rename to src/main/kotlin/blackjack/domain/game/dto/DealerGameResult.kt
index 7a3b7b1b2..aa160e028 100644
--- a/src/main/kotlin/blackjack/domain/dto/DealerGameResult.kt
+++ b/src/main/kotlin/blackjack/domain/game/dto/DealerGameResult.kt
@@ -1,7 +1,7 @@
-package blackjack.domain.dto
+package blackjack.domain.game.dto
 
 data class DealerGameResult(
     val winCount: Int,
-    val lossCount: Int,
+    val loseCount: Int,
     val drawCount: Int,
 )
diff --git a/src/main/kotlin/blackjack/domain/game/dto/PlayerGameResult.kt b/src/main/kotlin/blackjack/domain/game/dto/PlayerGameResult.kt
new file mode 100644
index 000000000..40e60dfc9
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/game/dto/PlayerGameResult.kt
@@ -0,0 +1,9 @@
+package blackjack.domain.game.dto
+
+import blackjack.domain.game.MatchResult
+import blackjack.domain.player.Player
+
+data class PlayerGameResult(
+    val player: Player,
+    val result: MatchResult,
+)
diff --git a/src/main/kotlin/blackjack/domain/player/Dealer.kt b/src/main/kotlin/blackjack/domain/player/Dealer.kt
new file mode 100644
index 000000000..c8d958b08
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/player/Dealer.kt
@@ -0,0 +1,16 @@
+package blackjack.domain.player
+
+import blackjack.domain.card.Card
+
+class Dealer : Player(
+    DEALER_NAME,
+) {
+    override fun canHit(): Boolean = hand.score < DEALER_SCORE_LIMIT
+
+    override fun hit(card: Card) = hand.add(card)
+
+    companion object {
+        private const val DEALER_NAME = "딜러"
+        private const val DEALER_SCORE_LIMIT = 17
+    }
+}
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..ff465d03c
--- /dev/null
+++ b/src/main/kotlin/blackjack/domain/player/Player.kt
@@ -0,0 +1,42 @@
+package blackjack.domain.player
+
+import blackjack.domain.card.Card
+import blackjack.domain.card.Hand
+import blackjack.domain.game.MatchResult
+import blackjack.domain.game.MatchResult.DRAW
+import blackjack.domain.game.MatchResult.LOSE
+import blackjack.domain.game.MatchResult.WIN
+
+open class Player(
+    val name: String,
+    val hand: Hand = Hand(),
+) {
+    open fun canHit(): Boolean = hand.score < PLAYER_SCORE_LIMIT
+
+    open fun hit(card: Card) = hand.add(card)
+
+    fun matchHand(other: Player): MatchResult =
+        when {
+            other.isBust() -> WIN
+            this.isBust() -> LOSE
+            other.isBlackjack() && this.isBlackjack() -> DRAW
+            other.isBlackjack() -> LOSE
+            this.isBlackjack() -> WIN
+            else -> compareScore(other)
+        }
+
+    private fun compareScore(other: Player): MatchResult =
+        when {
+            this.hand.score > other.hand.score -> WIN
+            this.hand.score < other.hand.score -> LOSE
+            else -> DRAW
+        }
+
+    private fun isBust(): Boolean = hand.score > PLAYER_SCORE_LIMIT
+
+    private fun isBlackjack(): Boolean = hand.score == PLAYER_SCORE_LIMIT
+
+    companion object {
+        private const val PLAYER_SCORE_LIMIT = 21
+    }
+}
diff --git a/src/main/kotlin/blackjack/view/InputView.kt b/src/main/kotlin/blackjack/view/InputView.kt
index ec7feab94..79b34c263 100644
--- a/src/main/kotlin/blackjack/view/InputView.kt
+++ b/src/main/kotlin/blackjack/view/InputView.kt
@@ -1,7 +1,6 @@
 package blackjack.view
 
-import blackjack.domain.Dealer
-import blackjack.domain.Participant
+import blackjack.domain.player.Player
 
 object InputView {
     fun inputNames(): List<String> {
@@ -13,12 +12,8 @@ object InputView {
         return names
     }
 
-    fun inputHit(participant: Participant): Boolean {
-        if (participant is Dealer) {
-            return true
-        }
-
-        println("${participant.name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)")
+    fun inputHit(player: Player): Boolean {
+        println("${player.name}는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)")
         return when (readlnOrNull()) {
             "y" -> true
             "n" -> false
diff --git a/src/main/kotlin/blackjack/view/ResultView.kt b/src/main/kotlin/blackjack/view/ResultView.kt
index 463c306f0..80ec6a4d4 100644
--- a/src/main/kotlin/blackjack/view/ResultView.kt
+++ b/src/main/kotlin/blackjack/view/ResultView.kt
@@ -1,52 +1,57 @@
 package blackjack.view
 
-import blackjack.domain.GameResult
-import blackjack.domain.GameTable.Companion.INIT_CARD_DRAW_COUNT
-import blackjack.domain.Participant
+import blackjack.controller.GameTable
+import blackjack.controller.GameTable.Companion.INIT_CARD_DRAW_COUNT
+import blackjack.domain.player.Player
 
 object ResultView {
-    fun printInitCardReceive(participants: List<Participant>) {
-        val players = Participant.separate(participants).first
-        println("딜러와 ${players.joinToString(", ") { it.name }}에게 ${INIT_CARD_DRAW_COUNT}장의 카드를 나누었습니다.")
+    fun printDealerHit() = println("딜러는 16이하라 한장의 카드를 더 받았습니다.")
+
+    fun printDealInitCard(gameTable: GameTable) {
+        println("딜러와 ${gameTable.players.joinToString(", ") { it.name }}에게 ${INIT_CARD_DRAW_COUNT}장의 카드를 나누었습니다.")
+        printPlayerCard(gameTable.dealer)
+        printPlayersCard(gameTable.players)
+        linebreak()
     }
 
-    fun printParticipantsCard(
-        participants: List<Participant>,
-        printScore: Boolean,
-    ) {
-        val (players, dealer) = Participant.separate(participants)
-        printParticipantCard(participant = dealer, printScore = printScore)
-        players.forEach { printParticipantCard(participant = it, printScore = printScore) }
+    fun printAfterTurn(gameTable: GameTable) {
+        printFinalHand(gameTable)
+        printGameResult(gameTable)
     }
 
-    fun printParticipantCard(
-        participant: Participant,
-        printScore: Boolean,
+    fun printPlayerCard(
+        player: Player,
+        printScore: Boolean = false,
     ) {
-        val cards = participant.cards.values.joinToString(", ") { "${it.rank.value}${it.suit.description}" }
-        val scoreText = "- 결과: ${participant.cards.score}"
-        println("${participant.name} 카드: $cards ${if (printScore) scoreText else ""}")
+        val cards = player.hand.cards.joinToString(", ") { "${it.rank.value}${it.suit.description}" }
+        val scoreText = "- 결과: ${player.hand.score}"
+        println("${player.name} 카드: $cards ${if (printScore) scoreText else ""}")
     }
 
-    fun printDealerHit() {
-        println("딜러는 16이하라 한장의 카드를 더 받았습니다.")
+    fun linebreak() = println()
+
+    private fun printFinalHand(gameTable: GameTable) {
+        linebreak()
+        printPlayerCard(gameTable.dealer, printScore = true)
+        printPlayersCard(gameTable.players, printScore = true)
     }
 
-    fun printGameResult(gameResult: GameResult) {
+    private fun printGameResult(gameTable: GameTable) {
+        linebreak()
+        val gameResult = gameTable.getGameResult()
         val winMessage =
             if (gameResult.dealerGameResult.winCount > 0) "${gameResult.dealerGameResult.winCount}승" else ""
         val lossMessage =
-            if (gameResult.dealerGameResult.lossCount > 0) "${gameResult.dealerGameResult.lossCount}패" else ""
+            if (gameResult.dealerGameResult.loseCount > 0) "${gameResult.dealerGameResult.loseCount}패" else ""
         val drawMessage =
             if (gameResult.dealerGameResult.drawCount > 0) "${gameResult.dealerGameResult.drawCount}무" else ""
         println("## 최종 승패")
         println("딜러: $winMessage $lossMessage $drawMessage")
-        gameResult.playerGameResults.forEach {
-            println("${it.player.name} ${it.result.description}")
-        }
+        gameResult.playerGameResults.forEach { println("${it.player.name}: ${it.result.description}") }
     }
 
-    fun linebreak() {
-        println()
-    }
+    private fun printPlayersCard(
+        players: List<Player>,
+        printScore: Boolean = false,
+    ) = players.forEach { printPlayerCard(it, printScore) }
 }
diff --git a/src/test/kotlin/blackjack/domain/CardTest.kt b/src/test/kotlin/blackjack/domain/CardTest.kt
deleted file mode 100644
index 727410f08..000000000
--- a/src/test/kotlin/blackjack/domain/CardTest.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package blackjack.domain
-
-import blackjack.domain.Rank.ACE
-import blackjack.domain.Rank.THREE
-import blackjack.domain.Suit.SPADE
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-
-class CardTest : StringSpec({
-    "카드는 에이스로 만들어진다면 에이스 카드이다" {
-        val card = Card(ACE, SPADE)
-
-        card.isAce shouldBe true
-    }
-
-    "카드는 스페이드로 만들어진다면 에이스 카드가 아니다" {
-        val card = Card(THREE, SPADE)
-
-        card.isAce shouldBe false
-    }
-})
diff --git a/src/test/kotlin/blackjack/domain/DealerTest.kt b/src/test/kotlin/blackjack/domain/DealerTest.kt
deleted file mode 100644
index c915c586d..000000000
--- a/src/test/kotlin/blackjack/domain/DealerTest.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package blackjack.domain
-
-import blackjack.fixtures.createCard
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-import io.kotest.matchers.shouldNotBe
-
-class DealerTest : StringSpec({
-    "딜러는 카드를 가진다" {
-        val dealer = Dealer.create()
-        dealer.cards shouldNotBe null
-    }
-
-    "딜러는 첫 2장의 합이 16이하면 반드시 카드를 받아야 한다" {
-        val dealer = Dealer(Cards(listOf(createCard("10"), createCard("6"))))
-
-        dealer.canHit() shouldBe true
-    }
-})
diff --git a/src/test/kotlin/blackjack/domain/DeckTest.kt b/src/test/kotlin/blackjack/domain/DeckTest.kt
deleted file mode 100644
index bbcd5afbc..000000000
--- a/src/test/kotlin/blackjack/domain/DeckTest.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package blackjack.domain
-
-import io.kotest.assertions.throwables.shouldThrow
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldNotBe
-
-class DeckTest : StringSpec({
-    "덱에서 카드를 하나 꺼낸다" {
-        val deck = Deck.create()
-        val card = deck.draw()
-
-        card shouldNotBe null
-    }
-
-    "덱에 카드가 없을 때 카드를 꺼내면 예외 발생한다" {
-        val deck = Deck.create()
-        repeat(52) { deck.draw() }
-
-        shouldThrow<IllegalStateException> { deck.draw() }
-    }
-})
diff --git a/src/test/kotlin/blackjack/domain/GameTableTest.kt b/src/test/kotlin/blackjack/domain/GameTableTest.kt
deleted file mode 100644
index 8b0517a3b..000000000
--- a/src/test/kotlin/blackjack/domain/GameTableTest.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package blackjack.domain
-
-import blackjack.fixtures.createPlayers
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.matchers.shouldBe
-
-class GameTableTest : StringSpec({
-    "최초 딜 시 카드를 2장 나누어준다" {
-        val players = GameTable(Deck.create()).dealInitCard(createPlayers())
-
-        players.forEach {
-            it.cards.values.size shouldBe 2
-        }
-    }
-})
diff --git a/src/test/kotlin/blackjack/domain/PlayerTest.kt b/src/test/kotlin/blackjack/domain/PlayerTest.kt
deleted file mode 100644
index aa5db26b9..000000000
--- a/src/test/kotlin/blackjack/domain/PlayerTest.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-package blackjack.domain
-
-import blackjack.domain.MatchResult.DRAW
-import blackjack.domain.MatchResult.LOSS
-import blackjack.domain.MatchResult.WIN
-import blackjack.domain.Suit.CLUB
-import blackjack.domain.Suit.DIAMOND
-import blackjack.domain.Suit.HEART
-import blackjack.domain.Suit.SPADE
-import blackjack.fixtures.createCard
-import io.kotest.core.spec.style.StringSpec
-import io.kotest.data.forAll
-import io.kotest.data.headers
-import io.kotest.data.row
-import io.kotest.data.table
-import io.kotest.inspectors.forAll
-import io.kotest.matchers.shouldBe
-
-class PlayerTest : StringSpec({
-    "플레이어는 카드목록의 점수합이 21점 미만일 경우 카드를 더 받을 수 있다" {
-        table(
-            headers("ranks"),
-            row(listOf("2", "3", "4")),
-            row(listOf("4", "5", "10")),
-            row(listOf("K", "Q")),
-            row(listOf("5", "5", "4", "3", "2")),
-        ).forAll { ranks ->
-            val cards = Cards(ranks.map { createCard(it) })
-
-            Player("홍길동", cards).canHit() shouldBe true
-        }
-    }
-
-    "플레이어는 카드목록의 점수합이 21점 이상할 경우 카드를 더 받을 수 없다" {
-        table(
-            headers("ranks", "score"),
-            row(listOf("J", "Q", "K"), 30),
-            row(listOf("A", "K"), 21),
-            row(listOf("Q", "10", "A"), 21),
-            row(listOf("10", "9", "2"), 21),
-            row(listOf("10", "10", "3"), 23),
-        ).forAll { ranks, score ->
-            val cards = Cards(ranks.map { createCard(it) })
-
-            cards.score shouldBe score
-            Player("홍길동", cards).canHit() shouldBe false
-        }
-    }
-
-    "플레이어는 카드 2장을 받은 후 점수 합이 21점인 경우 카드를 더 받지 못한다" {
-        val cards = Cards(emptyList())
-        val player =
-            Player("홍길동", cards)
-                .hit(createCard("A"))
-                .hit(createCard("K"))
-
-        player.canHit() shouldBe false
-    }
-
-    "플레이어는 카드 2장을 받은 후 점수 합이 21점 미만인 경우 카드를 더 받을 수 있다" {
-        val cards = Cards(emptyList())
-        val player =
-            Player("홍길동", cards)
-                .hit(createCard("10"))
-                .hit(createCard("5"))
-
-        player.canHit() shouldBe true
-    }
-
-    "플레이어가 딜러보다 점수가 높다면 승리한다" {
-        val cards = Cards(emptyList())
-        val player =
-            Player("홍길동", cards)
-                .hit(createCard("10"))
-                .hit(createCard("5"))
-        val dealer = Dealer.create().hit(createCard("10"))
-
-        player.compareScore(dealer) shouldBe WIN
-    }
-
-    "플레이어가 딜러보다 점수가 낮다면 패배한다" {
-        val cards = Cards(emptyList())
-        val player =
-            Player("홍길동", cards)
-                .hit(createCard("5"))
-        val dealer =
-            Dealer.create()
-                .hit(createCard("10"))
-
-        player.compareScore(dealer) shouldBe LOSS
-    }
-
-    "플레이어가 딜러보와 점수가 같다면 무승부다" {
-        val cards = Cards(emptyList())
-        val player =
-            Player("홍길동", cards)
-                .hit(createCard("5"))
-        val dealer =
-            Dealer.create()
-                .hit(createCard("5"))
-
-        player.compareScore(dealer) shouldBe DRAW
-    }
-
-    "플레이어의 패에 상관없이 딜러의 점수가 21점이 넘는다면 승리한다" {
-        val cards = Cards(emptyList())
-        val biggerScorePlayer =
-            Player("김큰점수", cards)
-                .hit(createCard("K", SPADE))
-                .hit(createCard("Q", SPADE))
-                .hit(createCard("J", SPADE))
-        val smallerScorePlayer =
-            Player("박작은점수", cards)
-                .hit(createCard("K", HEART))
-                .hit(createCard("Q", HEART))
-                .hit(createCard("J", HEART))
-        val sameScorePlayer =
-            Player("최같은점수", cards)
-                .hit(createCard("10", CLUB))
-                .hit(createCard("10", CLUB))
-                .hit(createCard("3", CLUB))
-        val dealer =
-            Dealer.create()
-                .hit(createCard("10", DIAMOND))
-                .hit(createCard("10", DIAMOND))
-                .hit(createCard("3", DIAMOND))
-
-        listOf(biggerScorePlayer, smallerScorePlayer, sameScorePlayer).forAll { player ->
-            player.compareScore(dealer) shouldBe WIN
-        }
-    }
-})
diff --git a/src/test/kotlin/blackjack/domain/card/DeckTest.kt b/src/test/kotlin/blackjack/domain/card/DeckTest.kt
new file mode 100644
index 000000000..b82dcc0d4
--- /dev/null
+++ b/src/test/kotlin/blackjack/domain/card/DeckTest.kt
@@ -0,0 +1,30 @@
+package blackjack.domain.card
+
+import blackjack.domain.card.Rank.ACE
+import blackjack.fixtures.createCard
+import io.kotest.assertions.throwables.shouldThrow
+import io.kotest.core.spec.style.BehaviorSpec
+import io.kotest.matchers.shouldBe
+
+class DeckTest : BehaviorSpec({
+    Given("덱에 카드가 존재하는 경우") {
+        val deck = Deck(mutableListOf(createCard("A")))
+
+        When("드로우하면") {
+            Then("카드를 반환한다") {
+                deck.draw().rank shouldBe ACE
+            }
+        }
+    }
+    Given("덱에 카드가 없는 경우") {
+        val deck = Deck(mutableListOf())
+
+        When("드로우하면") {
+            Then("예외 발생한다") {
+                shouldThrow<IllegalStateException> {
+                    deck.draw()
+                }
+            }
+        }
+    }
+})
diff --git a/src/test/kotlin/blackjack/domain/CardsTest.kt b/src/test/kotlin/blackjack/domain/card/HandTest.kt
similarity index 54%
rename from src/test/kotlin/blackjack/domain/CardsTest.kt
rename to src/test/kotlin/blackjack/domain/card/HandTest.kt
index 280d5844d..e53a937f6 100644
--- a/src/test/kotlin/blackjack/domain/CardsTest.kt
+++ b/src/test/kotlin/blackjack/domain/card/HandTest.kt
@@ -1,15 +1,15 @@
-package blackjack.domain
+package blackjack.domain.card
 
 import blackjack.fixtures.createCard
-import io.kotest.core.spec.style.StringSpec
+import io.kotest.core.spec.style.BehaviorSpec
 import io.kotest.data.forAll
 import io.kotest.data.headers
 import io.kotest.data.row
 import io.kotest.data.table
 import io.kotest.matchers.shouldBe
 
-class CardsTest : StringSpec({
-    "카드목록의 점수합이 21을 초과할 경우 에이스는 1점으로 보정된다" {
+class HandTest : BehaviorSpec({
+    Given("카드목록의 점수 합이 21점을 초과하는 경우") {
         table(
             headers("ranks", "expected"),
             row(listOf("A", "2", "10"), 13),
@@ -18,9 +18,13 @@ class CardsTest : StringSpec({
             row(listOf("A", "A", "A"), 13),
             row(listOf("A", "3", "9"), 13),
         ).forAll { ranks, expected ->
-            val cards = Cards(ranks.map { createCard(it) })
+            val hand = Hand(ranks.map { createCard(it) }.toMutableList())
 
-            cards.score shouldBe expected
+            When("점수 계산하면") {
+                Then("에이스는 1점으로 보정된다") {
+                    hand.score shouldBe expected
+                }
+            }
         }
     }
 })
diff --git a/src/test/kotlin/blackjack/domain/RankTest.kt b/src/test/kotlin/blackjack/domain/card/RankTest.kt
similarity index 96%
rename from src/test/kotlin/blackjack/domain/RankTest.kt
rename to src/test/kotlin/blackjack/domain/card/RankTest.kt
index dbe94e535..0d099ad8f 100644
--- a/src/test/kotlin/blackjack/domain/RankTest.kt
+++ b/src/test/kotlin/blackjack/domain/card/RankTest.kt
@@ -1,4 +1,4 @@
-package blackjack.domain
+package blackjack.domain.card
 
 import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.core.spec.style.StringSpec
diff --git a/src/test/kotlin/blackjack/domain/game/GameResultTest.kt b/src/test/kotlin/blackjack/domain/game/GameResultTest.kt
new file mode 100644
index 000000000..0864293be
--- /dev/null
+++ b/src/test/kotlin/blackjack/domain/game/GameResultTest.kt
@@ -0,0 +1,66 @@
+package blackjack.domain.game
+
+import blackjack.domain.card.Suit.DIAMOND
+import blackjack.domain.card.Suit.HEART
+import blackjack.domain.card.Suit.SPADE
+import blackjack.domain.player.Dealer
+import blackjack.domain.player.Player
+import blackjack.fixtures.createCard
+import io.kotest.core.spec.style.BehaviorSpec
+import io.kotest.matchers.shouldBe
+
+class GameResultTest : BehaviorSpec({
+    Given("플레이어가 이긴 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("3", SPADE))
+        player1.hit(createCard("4", HEART))
+        player2.hit(createCard("5", DIAMOND))
+
+        When("게임결과를 생성하면") {
+            val actual = GameResult.from(dealer, listOf(player1, player2))
+
+            Then("딜러의 패배 카운트가 증가한다") {
+                actual.dealerGameResult.loseCount shouldBe 2
+            }
+        }
+    }
+
+    Given("플레이어가 진 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("10", SPADE))
+        player1.hit(createCard("4", HEART))
+        player2.hit(createCard("5", DIAMOND))
+
+        When("게임결과를 생성하면") {
+            val actual = GameResult.from(dealer, listOf(player1, player2))
+
+            Then("딜러의 승리 카운트가 증가한다") {
+                actual.dealerGameResult.winCount shouldBe 2
+            }
+        }
+    }
+
+    Given("플레이어가 딜러와 무승부인 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("10", SPADE))
+        player1.hit(createCard("10", HEART))
+        player2.hit(createCard("10", DIAMOND))
+
+        When("게임결과를 생성하면") {
+            val actual = GameResult.from(dealer, listOf(player1, player2))
+
+            Then("딜러의 무승부 카운트가 증가한다") {
+                actual.dealerGameResult.drawCount shouldBe 2
+            }
+        }
+    }
+})
diff --git a/src/test/kotlin/blackjack/domain/player/DealerTest.kt b/src/test/kotlin/blackjack/domain/player/DealerTest.kt
new file mode 100644
index 000000000..7f4b9cb6e
--- /dev/null
+++ b/src/test/kotlin/blackjack/domain/player/DealerTest.kt
@@ -0,0 +1,42 @@
+package blackjack.domain.player
+
+import blackjack.fixtures.createCard
+import io.kotest.core.spec.style.BehaviorSpec
+import io.kotest.matchers.shouldBe
+
+class DealerTest : BehaviorSpec({
+    Given("딜러 히트하는 경우") {
+        val dealer = Dealer()
+
+        When("히트하면") {
+            dealer.hit(createCard("J"))
+
+            Then("카드를 추가로 갖는다") {
+                dealer.hand.cards.count() shouldBe 1
+            }
+        }
+    }
+
+    Given("딜러 핸드가 17점 이상인 경우") {
+        val dealer = Dealer()
+        dealer.hit(createCard("J"))
+        dealer.hit(createCard("Q"))
+
+        When("히트 가능 여부 질의하면") {
+            Then("결과 false 이다") {
+                dealer.canHit() shouldBe false
+            }
+        }
+    }
+
+    Given("딜러 핸드가 17점 미만인 경우") {
+        val dealer = Dealer()
+        dealer.hit(createCard("J"))
+
+        When("히트 가능 여부 질의하면") {
+            Then("결과는 true 이다") {
+                dealer.canHit() shouldBe true
+            }
+        }
+    }
+})
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..6ddb5c533
--- /dev/null
+++ b/src/test/kotlin/blackjack/domain/player/PlayerTest.kt
@@ -0,0 +1,140 @@
+package blackjack.domain.player
+
+import blackjack.domain.card.Hand
+import blackjack.domain.card.Suit.DIAMOND
+import blackjack.domain.card.Suit.HEART
+import blackjack.domain.card.Suit.SPADE
+import blackjack.domain.game.MatchResult.DRAW
+import blackjack.domain.game.MatchResult.LOSE
+import blackjack.domain.game.MatchResult.WIN
+import blackjack.fixtures.createCard
+import io.kotest.core.spec.style.BehaviorSpec
+import io.kotest.data.forAll
+import io.kotest.data.headers
+import io.kotest.data.row
+import io.kotest.data.table
+import io.kotest.inspectors.forAll
+import io.kotest.matchers.shouldBe
+
+class PlayerTest : BehaviorSpec({
+    Given("플레이어 핸드가 21점 미만인 경우") {
+        table(
+            headers("ranks"),
+            row(listOf("2", "3", "4")),
+            row(listOf("4", "5", "10")),
+            row(listOf("K", "Q")),
+            row(listOf("5", "5", "4", "3", "2")),
+        ).forAll { ranks ->
+            val player = Player("홍길동", Hand(ranks.map { createCard(it) }.toMutableList()))
+
+            When("히트 가능 여부 질의하면") {
+                Then("결과는 true 이다") {
+                    player.canHit() shouldBe true
+                }
+            }
+        }
+    }
+
+    Given("플레이어 핸드가 21점 이상인 경우") {
+        table(
+            headers("ranks"),
+            row(listOf("J", "Q", "K")),
+            row(listOf("A", "K")),
+            row(listOf("Q", "10", "A")),
+            row(listOf("10", "9", "2")),
+            row(listOf("10", "10", "3")),
+        ).forAll { ranks ->
+            val player = Player("홍길동", Hand(ranks.map { createCard(it) }.toMutableList()))
+
+            When("히트 가능 여부 질의하면") {
+                Then("결과는 false 이다") {
+                    player.canHit() shouldBe false
+                }
+            }
+        }
+    }
+
+    Given("플레이어가 딜러보다 점수가 높은 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("3"))
+
+        player1.hit(createCard("4"))
+        player2.hit(createCard("5"))
+
+        When("게임 결과 비교하면") {
+            Then("플레이어가 승리한다") {
+                listOf(player1, player2).forAll { player -> player.matchHand(dealer) shouldBe WIN }
+            }
+        }
+    }
+
+    Given("플레이어가 딜러보다 점수가 낮은 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("10"))
+
+        player1.hit(createCard("4"))
+        player2.hit(createCard("5"))
+
+        When("게임 결과 비교하면") {
+            Then("플레이어가 승리한다") {
+                listOf(player1, player2).forAll { player -> player.matchHand(dealer) shouldBe LOSE }
+            }
+        }
+    }
+
+    Given("플레이어가 딜러보다 점수가 동일한 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("10", SPADE))
+
+        player1.hit(createCard("10", HEART))
+        player2.hit(createCard("10", DIAMOND))
+
+        When("게임 결과 비교하면") {
+            Then("플레이어가 승리한다") {
+                listOf(player1, player2).forAll { player -> player.matchHand(dealer) shouldBe DRAW }
+            }
+        }
+    }
+
+    Given("딜러가 버스트인 경우") {
+        val dealer = Dealer()
+        val player1 = Player("유저1")
+        val player2 = Player("유저2")
+
+        dealer.hit(createCard("K", SPADE))
+        dealer.hit(createCard("Q", SPADE))
+        dealer.hit(createCard("J", SPADE))
+
+        player1.hit(createCard("7", HEART))
+        player2.hit(createCard("8", DIAMOND))
+
+        When("게임 결과 비교하면") {
+            Then("모든 플레이어가 승리한다") {
+                listOf(player1, player2).forAll { player -> player.matchHand(dealer) shouldBe WIN }
+            }
+        }
+
+        When("게임 결과 비교하면 플레이어 핸드가 21점을 초과해도") {
+            player1.hit(createCard("K", HEART))
+            player1.hit(createCard("Q", HEART))
+            player1.hit(createCard("J", HEART))
+
+            player2.hit(createCard("K", DIAMOND))
+            player2.hit(createCard("Q", DIAMOND))
+            player2.hit(createCard("J", DIAMOND))
+
+            Then("모든 플레이어가 승리한다") {
+                listOf(player1, player2).forAll { player -> player.matchHand(dealer) shouldBe WIN }
+            }
+        }
+    }
+})
diff --git a/src/test/kotlin/blackjack/fixtures/CardFixture.kt b/src/test/kotlin/blackjack/fixtures/CardFixture.kt
index d4c2c25cf..caf9c481b 100644
--- a/src/test/kotlin/blackjack/fixtures/CardFixture.kt
+++ b/src/test/kotlin/blackjack/fixtures/CardFixture.kt
@@ -1,9 +1,9 @@
 package blackjack.fixtures
 
-import blackjack.domain.Card
-import blackjack.domain.Rank
-import blackjack.domain.Suit
-import blackjack.domain.Suit.SPADE
+import blackjack.domain.card.Card
+import blackjack.domain.card.Rank
+import blackjack.domain.card.Suit
+import blackjack.domain.card.Suit.SPADE
 
 fun createCard(
     rank: String,
diff --git a/src/test/kotlin/blackjack/fixtures/UserFixtures.kt b/src/test/kotlin/blackjack/fixtures/UserFixtures.kt
deleted file mode 100644
index 12edefc85..000000000
--- a/src/test/kotlin/blackjack/fixtures/UserFixtures.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package blackjack.fixtures
-
-import blackjack.domain.Cards
-import blackjack.domain.Player
-
-fun createPlayers(names: List<String> = listOf("홍길동", "홍길덩")): List<Player> {
-    return names.map { Player(it, Cards(emptyList())) }
-}