Repository: woowacourse/java-blackjack
Pull request #800: [1단계 - 블랙잭] 슬링키(김민기) 미션 제출합니다. 
Author: login: supernovaMK
Created: 2025-03-07T07:42:26Z | Merged: 2025-03-12T15:57:14Z

안녕하세요 베루스! 

만나게 되어 반갑습니다! 

이번 주차는 블랙잭 미션을 진행해보았습니다😃

## **체크 리스트**

- [ ]  미션의 필수 요구사항을 모두 구현했나요?
- [x]  Gradle `test`를 실행했을 때, 모든 테스트가 정상적으로 통과했나요?
- [x]  애플리케이션이 정상적으로 실행되나요?
- [x]  [[프롤로그](https://prolog.techcourse.co.kr/studylogs/3995)에 셀프 체크를 작성했나요?

## **객체지향 생활체조 요구사항을 얼마나 잘 충족했다고 생각하시나요?**

### **1~5점 중에서 선택해주세요.**

- [ ]  1 (전혀 충족하지 못함)
- [ ]  2
- [x]  3 (보통)
- [ ]  4
- [ ]  5 (완벽하게 충족)

### **선택한 점수의 이유를 적어주세요.**

객체 생활 체조를 잘 지키려 노력하였던 것 같지만 놓친 부분도 많았던 것 같습니다.

## **어떤 부분에 집중하여 리뷰해야 할까요?**

이번 미션에서는 클린 코드를 지켜보려고 노력하였던 것 같습니다. 

설계에 관해서 생각을 많이 하면서 하다보니, 다른 것을 꽤 놓친 듯한 미션이었던 것 같습니다.

또한 enum이 잘 활용되고 있는지 고민됩니다. 게임에서 사용될 수 있다고 생각되는 필요한 부분을 enum으로 구현하다보니 너무 과하게 구현하게 되었나라는 고민이 됩니다.

또한 제네릭 타입을 써보려 시도하였는데 잘 써보지 못한 부분이 아쉬움이 있었습니다.

IllegalArgumentException과 IllegalStatementException의 사용하는 경우가 모호해서 헷갈렸던 것 같습니다. 베루스의 기준을 들어보고 싶습니다!


Diff:
diff --git a/README.md b/README.md
index 1ff5f7b679..12fde6e3bd 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,72 @@
 # java-blackjack
 
 블랙잭 미션 저장소
+
+## 기능 요구사항 정리
+
+블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다.
+
+- 카드의 숫자 계산은 카드 숫자를 기본으로 하며,
+    - 예외로 Ace는 1 또는 11로 계산할 수 있다.
+    - King, Queen, Jack은 각각 10으로 계산한다.
+
+
+- 게임을 시작하면 플레이어는 두 장의 카드를 지급 받는다.
+    - 두 장의 카드 숫자를 합쳐 21을 초과하지 않으면서 21에 가깝게 만들면 이긴다.
+    - 딜러가 처음에 받는 2장의 카드의 합이 21이라면, 플레이어가 블랙잭이 아닌 경우 패배한다.
+        - 단, 블랙잭일 경우는 무승부이다.
+
+
+- 플레이어는 21을 넘지 않을 경우 원한다면 얼마든지 카드를 계속 뽑을 수 있다.
+    - 단, 플레이어가 21점을 초과하는 순간 무조건 패배한다.
+
+
+- 딜러는 처음에 받은 2장의 합계가 16이하이면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다.
+
+
+- 게임을 완료한 후 각 플레이어별로 승패를 출력한다.
+    - 단, 점수가 같다면 무승부이다.
+    - 플레이어가 21점을 초과하는 순간 무조건 패배한다.
+
+## 프로그래밍 요구사항
+
+- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
+- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
+- 3항 연산자를 쓰지 않는다.
+- else 예약어를 쓰지 않는다.
+- 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
+- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
+- 배열 대신 컬렉션을 사용한다.
+- Java Enum을 적용한다.
+- 모든 원시 값과 문자열을 포장한다.
+- 줄여 쓰지 않는다(축약 금지).
+- 일급 컬렉션을 쓴다.
+- 모든 엔티티를 작게 유지한다.
+- 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
+- 딜러와 플레이어에서 발생하는 중복 코드를 제거해야 한다.
+
+## 구현해야하는 기능 목록
+
+- [x] 블랙잭에 필요한 카드 추가
+- [x] 블랙잭에 필요한 덱 추가
+- [x] 덱에서 카드를 뽑는 기능 추가
+- [x] 블랙잭에 참여하는 플레이어 추가
+- [x] 플레이어와 딜러의 손패 추가
+- [x] 손패에 카드를 추가하는 기능 추가
+- [x] 블랙잭에 참여하는 딜러 추가
+- [x] 블랙잭에 필요한 점수 추가
+- [x] 카드의 점수를 계산하는 기능 추가
+- [x] 블랙잭 게임을 위해 플레이어와 딜러를 생성하는 기능 추가
+- [x] 덱에서 셔플하는 기능 추가
+- [x] 블랙잭 게임에 필요한 초기 세팅을 하는 기능 추가
+- [x] 블랙잭 게임에 룰에서 필요한 플레이어 hit 가능 여부를 판단하는 기능 추가
+- [x] 블랙잭 게임에 필요한 플레이어 hit 진행 기능 추가
+- [x] 블랙잭 게임에 룰에서 필요한 딜러 hit 가능 여부를 판단하는 기능 추가
+- [x] 블랙잭 게임에 필요한 딜러 hit 진행 기능 추가
+- [x] 블랙잭 게임에 필요한 플레이어들 승패 판별 기능 추가
+- [x] 게임 결과에서 승패를 판별하는 기능 추가
+- [x] 블랙잭 게임을 시작할 때 플레이어와 딜러에게 카드를 2장씩 출력하는 기능 추가
+- [x] 블랙잭 게임에서 플레이어의 hit 을 출력하는 기능 추가
+- [x] 블랙잭 게임에서 딜러의 hit 을 출력하는 기능 추가
+- [x] 블랙잭 게임에서 카드의 결과를 출력하는 기능 추가
+- [x] 블랙잭 게임에서 게임 결과를 출력하는 기능 추가
diff --git a/src/main/java/Application.java b/src/main/java/Application.java
new file mode 100644
index 0000000000..bce2b17dcb
--- /dev/null
+++ b/src/main/java/Application.java
@@ -0,0 +1,16 @@
+import controller.BlackJackController;
+import domain.BlackJackGame;
+import view.InputView;
+import view.OutputView;
+
+public class Application {
+
+    public static void main(String[] args) {
+        BlackJackGame blackJackGame = BlackJackGame.create();
+        InputView inputView = new InputView();
+        OutputView outputView = new OutputView();
+        BlackJackController blackJackController = new BlackJackController(inputView, outputView, blackJackGame);
+
+        blackJackController.run();
+    }
+}
diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java
new file mode 100644
index 0000000000..5846179118
--- /dev/null
+++ b/src/main/java/controller/BlackJackController.java
@@ -0,0 +1,128 @@
+package controller;
+
+import controller.dto.CardScoreDto;
+import domain.BlackJackGame;
+import domain.Dealer;
+import domain.GameResult;
+import domain.Player;
+import domain.TrumpCard;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import view.InputView;
+import view.OutputView;
+
+public class BlackJackController {
+
+    private final InputView inputView;
+    private final OutputView outputView;
+    private final BlackJackGame blackJackGame;
+
+    public BlackJackController(InputView inputView, OutputView outputView, BlackJackGame blackJackGame) {
+        this.inputView = inputView;
+        this.outputView = outputView;
+        this.blackJackGame = blackJackGame;
+    }
+
+    public void run() {
+        List<Player> players = executeInitializeCards();
+
+        players.forEach(this::executePlayerHit);
+        executeDealerHit();
+
+        displayCardResult(players);
+        displayGameResult(players);
+    }
+
+    private List<Player> executeInitializeCards() {
+        List<Player> players = createPlayers();
+        Map<String, List<TrumpCard>> playerCards = convertPlayerCards(players);
+        TrumpCard dealerFirstCard = blackJackGame.getDealer().retrieveFirstCard();
+
+        outputView.printInitialCards(playerCards, dealerFirstCard);
+
+        return players;
+    }
+
+    private List<Player> createPlayers() {
+        List<String> playerNames = inputView.readPlayerNames();
+        List<Player> players = new ArrayList<>();
+        playerNames.forEach(name -> {
+            players.add(new Player(name, blackJackGame.createStartingHands()));
+        });
+        return players;
+    }
+
+    private Map<String, List<TrumpCard>> convertPlayerCards(List<Player> players) {
+        return players.stream()
+                .collect(Collectors.toMap(Player::getName, player -> player.getHand().getCards()));
+    }
+
+    private void executePlayerHit(Player player) {
+        while (!player.isBust() &&
+                inputView.readProcessHit(player.getName())) {
+            blackJackGame.processPlayerHit(player);
+            outputView.printPlayerCards(player.getName(), player.getCards());
+        }
+    }
+
+    private void executeDealerHit() {
+        int dealerHitCount = blackJackGame.processDealerHit();
+        outputView.printDealerHitInfo(dealerHitCount);
+    }
+
+    private void displayCardResult(List<Player> players) {
+        Map<String, CardScoreDto> playerCardScoreDto = convertPlayerCardScoreDto(players);
+        CardScoreDto dealerCardScoreDto = convertDealerCardScoreDto(blackJackGame.getDealer());
+        outputView.printCardsResult(playerCardScoreDto, dealerCardScoreDto);
+    }
+
+    private Map<String, CardScoreDto> convertPlayerCardScoreDto(List<Player> players) {
+        return players.stream()
+                .collect(Collectors.toMap(Player::getName, player -> {
+                    List<TrumpCard> playerCards = player.getCards();
+                    int totalScore = player.getTotalScore();
+
+                    return new CardScoreDto(
+                            playerCards,
+                            totalScore);
+                }));
+    }
+
+    private CardScoreDto convertDealerCardScoreDto(Dealer dealer) {
+        List<TrumpCard> cards = dealer.getCards();
+        int totalScore = blackJackGame.calculateScore(dealer);
+
+        return new CardScoreDto(cards, totalScore);
+    }
+
+    private void displayGameResult(List<Player> players) {
+        Map<String, GameResult> playerGameResults = blackJackGame.calculateGameResults(players);
+        List<GameResult> dealerGameResult = createDealerGameResult(playerGameResults);
+        outputView.printGameResult(playerGameResults, dealerGameResult);
+    }
+
+    private List<GameResult> createDealerGameResult(Map<String, GameResult> playerGameResults) {
+        List<GameResult> dealerGameResults = new ArrayList<>();
+        playerGameResults.values().forEach(gameResult -> {
+            calculateDealerGameResult(gameResult, dealerGameResults);
+        });
+
+        return dealerGameResults;
+    }
+
+    private static void calculateDealerGameResult(GameResult gameResult, List<GameResult> dealerGameResults) {
+        if (gameResult == GameResult.WIN) {
+            dealerGameResults.add(GameResult.LOSE);
+            return;
+        }
+
+        if (gameResult == GameResult.LOSE) {
+            dealerGameResults.add(GameResult.WIN);
+            return;
+        }
+
+        dealerGameResults.add(GameResult.DRAW);
+    }
+}
diff --git a/src/main/java/controller/dto/CardScoreDto.java b/src/main/java/controller/dto/CardScoreDto.java
new file mode 100644
index 0000000000..bdfcb98e1c
--- /dev/null
+++ b/src/main/java/controller/dto/CardScoreDto.java
@@ -0,0 +1,8 @@
+package controller.dto;
+
+import domain.TrumpCard;
+import java.util.List;
+
+public record CardScoreDto(List<TrumpCard> cards, int totalScore) {
+
+}
diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java
new file mode 100644
index 0000000000..69120f201d
--- /dev/null
+++ b/src/main/java/domain/BlackJackGame.java
@@ -0,0 +1,89 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+
+
+    public BlackJackGame(Deck deck, Dealer dealer) {
+        validate(deck, dealer);
+        this.deck = deck;
+        this.dealer = dealer;
+
+    }
+
+    private void validate(Deck deck, Dealer dealer) {
+        validateNotNull(deck, dealer);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer) {
+        if (deck == null || dealer == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+
+        return new BlackJackGame(deck, dealer);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (dealer.isHitable()) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public int calculateScore(Dealer player) {
+        return player.getTotalScore();
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            GameResult gameResult = GameResult.from(player, dealer);
+
+            results.put(player.getName(), gameResult);
+        });
+
+        return results;
+    }
+
+    public Dealer getDealer() {
+        return dealer;
+    }
+}
diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java
new file mode 100644
index 0000000000..3e69705193
--- /dev/null
+++ b/src/main/java/domain/Dealer.java
@@ -0,0 +1,59 @@
+package domain;
+
+import java.util.List;
+
+public class Dealer {
+
+    private final Hand hand;
+
+    public Dealer(Hand hand) {
+        validate(hand);
+        this.hand = hand;
+    }
+
+    private void validate(Hand hand) {
+        validateNotNull(hand);
+    }
+
+    private void validateNotNull(Hand hand) {
+        if (hand == null) {
+            throw new IllegalArgumentException("딜러는 손패를 가져야합니다.");
+        }
+    }
+
+    public TrumpCard retrieveFirstCard() {
+        if (hand.getCards().size() != 2) {
+            throw new IllegalStateException("딜러는 2장의 카드를 가지고 있어야 합니다.");
+        }
+
+        return hand.getCards().getFirst();
+    }
+
+    public List<TrumpCard> getCards() {
+        return hand.getCards();
+    }
+
+    public void addCard(TrumpCard drawnCard) {
+        hand.addCard(drawnCard);
+    }
+
+    public Hand getHand() {
+        return hand;
+    }
+
+    public int getTotalScore() {
+        return hand.calculateTotalScore();
+    }
+
+    public boolean isBlackJack() {
+        return hand.isBlackJack();
+    }
+
+    public boolean isBust() {
+        return hand.isBust();
+    }
+
+    public boolean isHitable() {
+        return hand.calculateTotalScore() < 17;
+    }
+}
diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java
new file mode 100644
index 0000000000..c41db0d465
--- /dev/null
+++ b/src/main/java/domain/Deck.java
@@ -0,0 +1,82 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+public class Deck {
+
+    private final List<TrumpCard> cards;
+
+    public Deck(List<TrumpCard> cards) {
+        validate(cards);
+        this.cards = new ArrayList<>(cards);
+    }
+
+    private void validate(List<TrumpCard> cards) {
+        validateNotNull(cards);
+        validateSize(cards);
+        validateNotDuplicate(cards);
+    }
+
+    private void validateNotNull(List<TrumpCard> cards) {
+        if (cards == null) {
+            throw new IllegalArgumentException("덱은 카드를 가지고 있어야합니다.");
+        }
+    }
+
+    private void validateSize(List<TrumpCard> cards) {
+        if (cards.size() != 52) {
+            throw new IllegalArgumentException("덱의 크기는 52여야 합니다.");
+        }
+    }
+
+    private void validateNotDuplicate(List<TrumpCard> cards) {
+        if (cards.stream().distinct().count() != cards.size()) {
+            throw new IllegalArgumentException("덱에 중복된 카드가 있습니다.");
+        }
+    }
+
+    public static Deck create() {
+        List<TrumpCard> cards = initializeCards();
+
+        return new Deck(cards);
+    }
+
+    private static List<TrumpCard> initializeCards() {
+        List<TrumpCard> trumpCard = new ArrayList<>();
+        for (Rank rank : Rank.values()) {
+            for (Suit suit : Suit.values()) {
+                trumpCard.add(new TrumpCard(rank, suit));
+            }
+        }
+        return trumpCard;
+    }
+
+    public TrumpCard draw() {
+        if (cards.isEmpty()) {
+            throw new IllegalStateException("덱에 카드가 없습니다.");
+        }
+
+        return cards.removeLast();
+    }
+
+    public void shuffle() {
+        Collections.shuffle(cards);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Deck deck = (Deck) o;
+        return Objects.equals(cards, deck.cards);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cards);
+    }
+}
diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java
new file mode 100644
index 0000000000..efefc5eb73
--- /dev/null
+++ b/src/main/java/domain/GameResult.java
@@ -0,0 +1,47 @@
+package domain;
+
+public enum GameResult {
+    WIN("승"),
+    LOSE("패"),
+    DRAW("무");
+
+    private final String title;
+
+    GameResult(String title) {
+        this.title = title;
+    }
+
+    public static GameResult from(Player player, Dealer dealer) {
+        if (player.isBust()) {
+            return LOSE;
+        }
+        if (dealer.isBust()) {
+            return WIN;
+        }
+        if (dealer.isBlackJack() && player.isBlackJack()) {
+            return DRAW;
+        }
+        if (dealer.isBlackJack()) {
+            return LOSE;
+        }
+        if (player.isBlackJack()) {
+            return WIN;
+        }
+
+        return getResultByTotalScore(player, dealer);
+    }
+
+    private static GameResult getResultByTotalScore(Player player, Dealer dealer) {
+        if (dealer.getTotalScore() > player.getTotalScore()) {
+            return LOSE;
+        }
+        if (dealer.getTotalScore() == player.getTotalScore()) {
+            return DRAW;
+        }
+        return WIN;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+}
diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java
new file mode 100644
index 0000000000..ce6cab0b00
--- /dev/null
+++ b/src/main/java/domain/Hand.java
@@ -0,0 +1,97 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class Hand {
+
+    private final List<TrumpCard> cards;
+
+    public Hand(List<TrumpCard> cards) {
+        validate(cards);
+        this.cards = new ArrayList<>(cards);
+    }
+
+    private void validate(List<TrumpCard> cards) {
+        validateNotNull(cards);
+        validateNotDuplicate(cards);
+    }
+
+    private void validateNotNull(List<TrumpCard> cards) {
+        if (cards == null) {
+            throw new IllegalArgumentException("손패는 카드를 가지고 있어야합니다.");
+        }
+    }
+
+    private void validateNotDuplicate(List<TrumpCard> cards) {
+        int distinctCount = (int) cards.stream().distinct().count();
+        int originalCount = cards.size();
+        if (distinctCount != originalCount) {
+            throw new IllegalArgumentException("손패에 중복된 카드가 있습니다.");
+        }
+    }
+
+    public static Hand of(TrumpCard firstCard, TrumpCard secondCard) {
+        return new Hand(List.of(firstCard, secondCard));
+    }
+
+    public void addCard(TrumpCard card) {
+        validateNotDuplicate(card);
+        cards.add(card);
+    }
+
+    private void validateNotDuplicate(TrumpCard card) {
+        if (cards.contains(card)) {
+            throw new IllegalArgumentException("이미 손패에 있는 카드입니다.");
+        }
+    }
+
+    public int calculateTotalScore() {
+        int totalScore = getTotalScore(cards);
+        int aceCount = getAceCount(cards);
+        int bonusScore = calculateAceBonusScore(totalScore, aceCount);
+        return totalScore + bonusScore;
+    }
+
+    private int getTotalScore(List<TrumpCard> cards) {
+        return cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+    }
+
+    private int getAceCount(List<TrumpCard> cards) {
+        return (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+    }
+
+    private int calculateAceBonusScore(int totalScore, int aceCount) {
+        if (aceCount > 0 && totalScore + 10 <= 21) {
+            return 10;
+        }
+        return 0;
+    }
+
+    public boolean isBlackJack() {
+        return cards.size() == 2 && calculateTotalScore() == 21;
+    }
+
+    public boolean isBust() {
+        return calculateTotalScore() > 21;
+    }
+
+    public List<TrumpCard> getCards() {
+        return List.copyOf(cards);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Hand hand = (Hand) o;
+        return Objects.equals(cards, hand.cards);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cards);
+    }
+}
diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java
new file mode 100644
index 0000000000..20177fbe67
--- /dev/null
+++ b/src/main/java/domain/Player.java
@@ -0,0 +1,53 @@
+package domain;
+
+import java.util.List;
+
+public class Player {
+
+    private final String name;
+    private final Hand hand;
+
+    public Player(String name, Hand hand) {
+        validate(name, hand);
+        this.name = name;
+        this.hand = hand;
+    }
+
+    private void validate(String name, Hand hand) {
+        validateNotNull(name, hand);
+    }
+
+    private void validateNotNull(String name, Hand hand) {
+        if (name == null || name.isBlank() || hand == null) {
+            throw new IllegalArgumentException("플레이어는 이름과 손패를 가져야합니다.");
+        }
+    }
+
+    public List<TrumpCard> getCards() {
+        return hand.getCards();
+    }
+
+    public void addCard(TrumpCard drawnCard) {
+        hand.addCard(drawnCard);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Hand getHand() {
+        return hand;
+    }
+
+    public int getTotalScore() {
+        return hand.calculateTotalScore();
+    }
+
+    public boolean isBlackJack() {
+        return hand.isBlackJack();
+    }
+
+    public boolean isBust() {
+        return hand.isBust();
+    }
+}
diff --git a/src/main/java/domain/Rank.java b/src/main/java/domain/Rank.java
new file mode 100644
index 0000000000..fa340d4a85
--- /dev/null
+++ b/src/main/java/domain/Rank.java
@@ -0,0 +1,33 @@
+package domain;
+
+public enum Rank {
+    ACE("A", 1),
+    TWO("2", 2),
+    THREE("3", 3),
+    FOUR("4", 4),
+    FIVE("5", 5),
+    SIX("6", 6),
+    SEVEN("7", 7),
+    EIGHT("8", 8),
+    NINE("9", 9),
+    TEN("10", 10),
+    JACK("J", 10),
+    QUEEN("Q", 10),
+    KING("K", 10);
+
+    private final String title;
+    private final int value;
+
+    Rank(String title, int value) {
+        this.title = title;
+        this.value = value;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public int getValue() {
+        return value;
+    }
+}
diff --git a/src/main/java/domain/Suit.java b/src/main/java/domain/Suit.java
new file mode 100644
index 0000000000..48acd3351b
--- /dev/null
+++ b/src/main/java/domain/Suit.java
@@ -0,0 +1,18 @@
+package domain;
+
+public enum Suit {
+    HEARTS("하트"),
+    DIAMONDS("다이아몬드"),
+    CLUBS("클로버"),
+    SPADES("스페이드");
+
+    private final String title;
+
+    Suit(String title) {
+        this.title = title;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+}
diff --git a/src/main/java/domain/TrumpCard.java b/src/main/java/domain/TrumpCard.java
new file mode 100644
index 0000000000..1e0b4b90ec
--- /dev/null
+++ b/src/main/java/domain/TrumpCard.java
@@ -0,0 +1,35 @@
+package domain;
+
+import java.util.Objects;
+
+public class TrumpCard {
+    private final Rank rank;
+    private final Suit suit;
+
+    public TrumpCard(Rank rank, Suit suit) {
+        this.rank = rank;
+        this.suit = suit;
+    }
+
+    public Rank getRank() {
+        return rank;
+    }
+
+    public Suit getSuit() {
+        return suit;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TrumpCard that = (TrumpCard) o;
+        return rank == that.rank && suit == that.suit;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(rank, suit);
+    }
+}
diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java
new file mode 100644
index 0000000000..e517757cd9
--- /dev/null
+++ b/src/main/java/view/InputView.java
@@ -0,0 +1,49 @@
+package view;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.PatternSyntaxException;
+
+public class InputView {
+
+    private static final Scanner scanner = new Scanner(System.in);
+
+    public List<String> readPlayerNames() {
+        System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)");
+        return parsePlayerNames(readInput());
+    }
+
+    private List<String> parsePlayerNames(String playerNames) {
+        try {
+            return Arrays.stream(playerNames.split(",", -1))
+                    .map(String::trim)
+                    .toList();
+        } catch (PatternSyntaxException e) {
+            throw new IllegalArgumentException("플레이어 이름 입력 형식이 잘못되었습니다.");
+        }
+    }
+
+    private String readInput() {
+        return scanner.nextLine();
+    }
+
+    public boolean readProcessHit(String playerName) {
+        System.out.printf("%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n", playerName);
+        String input = readInput();
+
+        return convertYesOrNo(input);
+    }
+
+    private boolean convertYesOrNo(String input) {
+        if ("y".equals(input)) {
+            return true;
+        }
+
+        if ("n".equals(input)) {
+            return false;
+        }
+
+        throw new IllegalArgumentException("선택 입력 형식이 잘못되었습니다.");
+    }
+}
diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java
new file mode 100644
index 0000000000..1c5a70f4d1
--- /dev/null
+++ b/src/main/java/view/OutputView.java
@@ -0,0 +1,106 @@
+package view;
+
+import controller.dto.CardScoreDto;
+import domain.GameResult;
+import domain.TrumpCard;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class OutputView {
+
+    public void printInitialCards(Map<String, List<TrumpCard>> playerCards, TrumpCard dealerFirstCard) {
+        System.out.println(formatPlayersList(playerCards.keySet().stream().toList()));
+        System.out.printf("딜러카드: %s\n", getCardInfo(dealerFirstCard));
+        System.out.println(formatPlayerCards(playerCards));
+    }
+
+    private String formatPlayersList(List<String> players) {
+        return "딜러와 " + String.join(", ", players) + "에게 2장을 나누었습니다.";
+    }
+
+    private String formatPlayerCards(Map<String, List<TrumpCard>> playerCards) {
+        return playerCards.entrySet().stream()
+                .map(entry -> entry.getKey() + "카드: "
+                        + getCardInfo(entry.getValue().getFirst()) + ", "
+                        + getCardInfo(entry.getValue().getLast()))
+                .collect(Collectors.joining("\n"));
+    }
+
+    public void printPlayerCards(String name, List<TrumpCard> playerCards) {
+        System.out.printf("%s카드:", name);
+        System.out.println(playerCards.stream()
+                .map(this::getCardInfo)
+                .collect(Collectors.joining(",")));
+    }
+
+    public void printDealerHitInfo(int dealerHitCount) {
+        for (int i = 0; i < dealerHitCount; i++) {
+            System.out.println("딜러는 16이하라 한장의 카드를 더 받았습니다.");
+        }
+    }
+
+    public void printCardsResult(Map<String, CardScoreDto> playerCardScoreDto, CardScoreDto dealerCardScoreDtos) {
+        System.out.print("딜러카드: ");
+        System.out.print(dealerCardScoreDtos.cards().stream()
+                .map(this::getCardInfo)
+                .collect(Collectors.joining(",")));
+        System.out.printf(" - 결과: %s\n", dealerCardScoreDtos.totalScore());
+
+        playerCardScoreDto.forEach((name, cardScoreDto) -> {
+            System.out.printf("%s카드: ", name);
+            System.out.print(cardScoreDto.cards().stream()
+                    .map(this::getCardInfo)
+                    .collect(Collectors.joining(",")));
+            System.out.printf(" - 결과: %s\n", cardScoreDto.totalScore());
+        });
+    }
+
+    private String getCardInfo(TrumpCard card) {
+        return card.getRank().getTitle() + card.getSuit().getTitle();
+    }
+
+    public void printGameResult(Map<String, GameResult> playerGameResults, List<GameResult> dealerGameResult) {
+        System.out.println("## 최종 승패");
+        displayDealerGameResult(dealerGameResult);
+        displayPlayerGameResult(playerGameResults);
+    }
+
+    private void displayDealerGameResult(List<GameResult> dealerGameResult) {
+        int dealerWinCount = Math.toIntExact(
+                dealerGameResult.stream().filter(gameResult -> gameResult == GameResult.WIN)
+                        .count());
+        int dealerDrawCount = Math.toIntExact(
+                dealerGameResult.stream().filter(gameResult -> gameResult == GameResult.DRAW)
+                        .count());
+        int dealerLoseCount = Math.toIntExact(
+                dealerGameResult.stream().filter(gameResult -> gameResult == GameResult.LOSE)
+                        .count());
+
+        displayDealerGameResultCount(dealerWinCount, dealerLoseCount, dealerDrawCount);
+    }
+
+    private void displayDealerGameResultCount(int dealerWinCount, int dealerLoseCount, int dealerDrawCount) {
+        StringBuilder sb = new StringBuilder("딜러: ");
+
+        if (dealerWinCount > 0) {
+            sb.append(String.format("%d승 ", dealerWinCount));
+        }
+
+        if (dealerLoseCount > 0) {
+            sb.append(String.format("%d패 ", dealerLoseCount));
+        }
+
+        if (dealerDrawCount > 0) {
+            sb.append(String.format("%d무 ", dealerDrawCount));
+        }
+
+        System.out.println(sb.toString().trim());
+    }
+
+    private void displayPlayerGameResult(Map<String, GameResult> playerGameResults) {
+        playerGameResults.forEach((playerName, gameResult) -> {
+            System.out.printf("%s: %s\n", playerName, gameResult.getTitle());
+        });
+    }
+}
diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java
new file mode 100644
index 0000000000..49d45cc49a
--- /dev/null
+++ b/src/test/java/domain/BlackJackGameTest.java
@@ -0,0 +1,75 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class BlackJackGameTest {
+
+    private Deck blackJackDeck;
+    private Dealer dealer;
+
+    @BeforeEach
+    void setUp() {
+        blackJackDeck = Deck.create();
+        dealer = new Dealer(
+                new Hand(List.of(new TrumpCard(Rank.SIX, Suit.CLUBS), new TrumpCard(Rank.SIX, Suit.SPADES))));
+    }
+
+    @Nested
+    class ValidCases {
+
+        @Test
+        @DisplayName("플레이어가 히트하면 카드가 추가된다.")
+        void processPlayerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer);
+            Player player = new Player("Alice",
+                    Hand.of(new TrumpCard(Rank.TWO, Suit.DIAMONDS), new TrumpCard(Rank.FIVE, Suit.SPADES)));
+
+            // when
+            blackJackGame.processPlayerHit(player);
+
+            // then
+            assertThat(player.getHand().getCards()).hasSize(3);
+        }
+
+        @Test
+        @DisplayName("딜러가 히트하면 적절한 횟수만큼 카드가 추가된다.")
+        void processDealerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer);
+            int initialCards = dealer.getHand().getCards().size();
+
+            // when
+            int hitCount = blackJackGame.processDealerHit();
+
+            // then
+            assertThat(dealer.getHand().getCards()).hasSize(initialCards + hitCount);
+        }
+
+
+        @Test
+        @DisplayName("게임 결과를 올바르게 계산한다.")
+        void calculateGameResults() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer);
+            Player player1 = new Player("Alice",
+                    Hand.of(new TrumpCard(Rank.TEN, Suit.DIAMONDS), new TrumpCard(Rank.JACK, Suit.HEARTS)));
+            Player player2 = new Player("Bob",
+                    Hand.of(new TrumpCard(Rank.NINE, Suit.CLUBS), new TrumpCard(Rank.SEVEN, Suit.DIAMONDS)));
+            List<Player> players = List.of(player1, player2);
+
+            // when
+            Map<String, GameResult> results = blackJackGame.calculateGameResults(players);
+
+            // then
+            assertThat(results).containsKeys("Alice", "Bob");
+        }
+    }
+}
diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java
new file mode 100644
index 0000000000..611516efdb
--- /dev/null
+++ b/src/test/java/domain/DealerTest.java
@@ -0,0 +1,65 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DealerTest {
+
+    @Nested
+    class ValidCases {
+
+        @DisplayName("딜러의 첫번째 카드를 가져온다.")
+        @Test
+        void retrieveFirstCard() {
+            // given
+            List<TrumpCard> cards = List.of(
+                    new TrumpCard(Rank.ACE, Suit.SPADES),
+                    new TrumpCard(Rank.TWO, Suit.SPADES)
+            );
+            Hand hand = new Hand(cards);
+
+            // when
+            Dealer dealer = new Dealer(hand);
+
+            // then
+            assertThat(dealer.retrieveFirstCard()).isEqualTo(new TrumpCard(Rank.ACE, Suit.SPADES));
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @DisplayName("딜러는 손패를 가져야 한다.")
+        @Test
+        void validateNotNull() {
+            // given
+            Hand nullHand = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Dealer(nullHand))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("딜러는 손패를 가져야합니다.");
+        }
+
+        @DisplayName("딜러의 첫번째 카드를 가져올 때 딜러는 2장의 카드를 가지고 있어야 한다.")
+        @Test
+        void retrieveFirstCard() {
+            // given
+            List<TrumpCard> cards = List.of(
+                    new TrumpCard(Rank.ACE, Suit.SPADES)
+            );
+            Hand hand = new Hand(cards);
+            Dealer dealer = new Dealer(hand);
+
+            // when & then
+            assertThatThrownBy(dealer::retrieveFirstCard)
+                    .isInstanceOf(IllegalStateException.class)
+                    .hasMessage("딜러는 2장의 카드를 가지고 있어야 합니다.");
+        }
+    }
+}
diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java
new file mode 100644
index 0000000000..3e4ad3cdb3
--- /dev/null
+++ b/src/test/java/domain/DeckTest.java
@@ -0,0 +1,115 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DeckTest {
+
+    @Nested
+    class ValidCase {
+
+        @DisplayName("덱은 마지막 카드를 줘야한다")
+        @Test
+        void drawCard() {
+            // given
+            Deck deck = Deck.create();
+
+            // when & then
+            assertThat(deck.draw()).isInstanceOf(TrumpCard.class);
+        }
+
+        @DisplayName("덱은 카드를 섞을 수 있다")
+        @Test
+        void shuffle() {
+            // given
+
+            Deck originalDeck = Deck.create();
+            Deck firstShuffledDeck = Deck.create();
+            Deck secondShuffledDeck = Deck.create();
+
+            // when
+            firstShuffledDeck.shuffle();
+            secondShuffledDeck.shuffle();
+
+            // then
+            assertSoftly(softly -> {
+                softly.assertThat(firstShuffledDeck)
+                        .isNotEqualTo(originalDeck);
+                softly.assertThat(secondShuffledDeck)
+                        .isNotEqualTo(firstShuffledDeck);
+            });
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @DisplayName("덱은 카드를 가지고 있어야한다.")
+        @Test
+        void validateNotNull() {
+            // given
+            List<TrumpCard> nullCards = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Deck(nullCards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱은 카드를 가지고 있어야합니다.");
+        }
+
+        @DisplayName("덱의 카드는 52장 이어야한다.")
+        @Test
+        void validateSize() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            cards.add(new TrumpCard(Rank.ACE, Suit.CLUBS));
+
+            // then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱의 크기는 52여야 합니다.");
+        }
+
+        @DisplayName("덱의 카드는 중복되지 않아야한다.")
+        @Test
+        void validateNotDuplicate() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            for (int i = 0; i < 52; i++) {
+                cards.add(new TrumpCard(Rank.ACE, Suit.CLUBS));
+            }
+            //then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱에 중복된 카드가 있습니다.");
+        }
+
+        @DisplayName("덱은 카드가 없다면 카드를 줄 수 없다")
+        @Test
+        void nonCardTest() {
+            // given
+            Deck deck = Deck.create();
+
+            // when
+            for (int i = 0; i < 52; i++) {
+                deck.draw();
+            }
+
+            // then
+            assertThatThrownBy(deck::draw)
+                    .isInstanceOf(IllegalStateException.class)
+                    .hasMessage("덱에 카드가 없습니다.");
+        }
+    }
+
+}
diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java
new file mode 100644
index 0000000000..3ab6705824
--- /dev/null
+++ b/src/test/java/domain/GameResultTest.java
@@ -0,0 +1,157 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class GameResultTest {
+
+    @DisplayName("딜러와 플레이어 둘 다 블랙잭인 경우")
+    @Test
+    void bothHasBlackjack() {
+        Hand blackjackHand = createHand(List.of(
+                new TrumpCard(Rank.ACE, Suit.SPADES),
+                new TrumpCard(Rank.KING, Suit.SPADES)
+        ));
+
+        Player player = new Player("slinky", blackjackHand);
+        Dealer dealer = new Dealer(blackjackHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.DRAW);
+    }
+
+    @DisplayName("플레이어가 버스트인 경우")
+    @Test
+    void playerIsBust() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.KING, Suit.CLUBS),
+                new TrumpCard(Rank.KING, Suit.SPADES),
+                new TrumpCard(Rank.QUEEN, Suit.SPADES)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.ACE, Suit.HEARTS),
+                new TrumpCard(Rank.KING, Suit.HEARTS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.LOSE);
+    }
+
+    @DisplayName("딜러가 버스트인 경우")
+    @Test
+    void dealerIsBust() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.KING, Suit.CLUBS),
+                new TrumpCard(Rank.QUEEN, Suit.SPADES)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.KING, Suit.HEARTS),
+                new TrumpCard(Rank.QUEEN, Suit.DIAMONDS),
+                new TrumpCard(Rank.TWO, Suit.CLUBS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.WIN);
+        }
+
+    @DisplayName("플레이어가 블랙잭이고 딜러가 블랙잭이 아닌 경우")
+    @Test
+    void playerHasBlackjack() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.ACE, Suit.HEARTS),
+                new TrumpCard(Rank.KING, Suit.CLUBS)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.SPADES),
+                new TrumpCard(Rank.NINE, Suit.HEARTS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.WIN);
+    }
+
+    @DisplayName("딜러가 블랙잭이고 플레이어가 블랙잭이 아닌 경우")
+    @Test
+    void dealerHasBlackjack() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.CLUBS),
+                new TrumpCard(Rank.NINE, Suit.SPADES)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.ACE, Suit.HEARTS),
+                new TrumpCard(Rank.KING, Suit.DIAMONDS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.LOSE);
+        }
+
+    @DisplayName("딜러와 플레이어 점수가 같은 경우")
+    @Test
+    void sameScoreDraw() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.HEARTS),
+                new TrumpCard(Rank.NINE, Suit.SPADES)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.CLUBS),
+                new TrumpCard(Rank.NINE, Suit.DIAMONDS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.DRAW);
+        }
+
+    @DisplayName("플레이어가 딜러보다 높은 점수를 가진 경우")
+    @Test
+    void playerHasHigherScore() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.KING, Suit.CLUBS),
+                new TrumpCard(Rank.QUEEN, Suit.HEARTS)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.SPADES),
+                new TrumpCard(Rank.NINE, Suit.HEARTS)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.WIN);
+        }
+
+    @DisplayName("딜러가 플레이어보다 높은 점수를 가진 경우")
+    @Test
+    void dealerHasHigherScore() {
+        Hand playerHand = createHand(List.of(
+                new TrumpCard(Rank.TEN, Suit.HEARTS),
+                new TrumpCard(Rank.NINE, Suit.SPADES)
+        ));
+        Hand dealerHand = createHand(List.of(
+                new TrumpCard(Rank.KING, Suit.CLUBS),
+                new TrumpCard(Rank.QUEEN, Suit.SPADES)
+        ));
+
+        Player player = new Player("slinky", playerHand);
+        Dealer dealer = new Dealer(dealerHand);
+
+        assertThat(GameResult.from(player, dealer)).isEqualTo(GameResult.LOSE);
+        }
+
+    private Hand createHand(List<TrumpCard> cards) {
+        return new Hand(cards);
+        }
+    }
+
diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java
new file mode 100644
index 0000000000..e3fe8a2f3a
--- /dev/null
+++ b/src/test/java/domain/HandTest.java
@@ -0,0 +1,80 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class HandTest {
+
+    @Nested
+    class ValidCases {
+
+        @DisplayName("손패는 카드를 추가할 수 있다")
+        @Test
+        void addCard() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+            cards.add(new TrumpCard(Rank.ACE, Suit.SPADES));
+            cards.add(new TrumpCard(Rank.TWO, Suit.SPADES));
+            Hand hand = new Hand(cards);
+
+            // when
+            hand.addCard(new TrumpCard(Rank.THREE, Suit.SPADES));
+
+            // then
+            assertThat(hand.getCards()).containsExactlyInAnyOrder(
+                    new TrumpCard(Rank.ACE, Suit.SPADES),
+                    new TrumpCard(Rank.TWO, Suit.SPADES),
+                    new TrumpCard(Rank.THREE, Suit.SPADES)
+            );
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @DisplayName("손패는 카드가 있어야 한다")
+        @Test
+        void validateNotNull() {
+            // given
+            List<TrumpCard> nullCards = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Hand(nullCards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("손패는 카드를 가지고 있어야합니다.");
+        }
+
+        @DisplayName("손패에 중복된 카드가 있으면 안된다")
+        @Test
+        void validateNotDuplicate() {
+            // given
+            List<TrumpCard> cards = List.of(
+                    new TrumpCard(Rank.ACE, Suit.SPADES),
+                    new TrumpCard(Rank.ACE, Suit.SPADES)
+            );
+
+            // when & then
+            assertThatThrownBy(() -> new Hand(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("손패에 중복된 카드가 있습니다.");
+        }
+
+        @DisplayName("손패와 새로 받은 카드는 중복될 수 없다")
+        @Test
+        void validateAddDuplicateCard() {
+            // given
+            Hand hand = Hand.of(new TrumpCard(Rank.ACE, Suit.SPADES), new TrumpCard(Rank.TWO, Suit.SPADES));
+
+            // when & then
+            assertThatThrownBy(() -> hand.addCard(new TrumpCard(Rank.ACE, Suit.SPADES)))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("이미 손패에 있는 카드입니다.");
+        }
+    }
+}
diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java
new file mode 100644
index 0000000000..a01d7c0997
--- /dev/null
+++ b/src/test/java/domain/PlayerTest.java
@@ -0,0 +1,45 @@
+package domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class PlayerTest {
+
+    @Nested
+    class InvalidCase {
+
+        @ParameterizedTest
+        @CsvSource(value = {
+                "null",
+                "' '"},
+                nullValues = {"null"})
+        @DisplayName("플레이어는 이름을 가져야 한다.")
+        void validateNotNullName(String name) {
+            // given
+            Hand hand = new Hand(List.of(new TrumpCard(Rank.ACE, Suit.SPADES), new TrumpCard(Rank.TEN, Suit.SPADES)));
+
+            // when & then
+            assertThatThrownBy(() -> new Player(name, hand))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("플레이어는 이름과 손패를 가져야합니다.");
+        }
+
+        @Test
+        @DisplayName("플레이어는 손패를 가져야 한다.")
+        void validateNotNullHand() {
+            // given
+            Hand nullHand = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Player("머피", nullHand))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("플레이어는 이름과 손패를 가져야합니다.");
+        }
+    }
+}


Reviews:
user: login: verus-j
body: 안녕하세요 슬링키
블랙잭 미션 진행하시느라 고생많으셨습니다.
몇 가지 피드백 남겨드렸습니다 확인부탁드립니다~
언제든 궁금한 점은 슬랙으로 물어봐주세요!
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-07T16:10:45Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T05:19:17Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T06:02:02Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T06:06:14Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T06:23:11Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T06:26:30Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T06:26:42Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T07:43:56Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T07:49:18Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T07:55:27Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T08:11:00Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T08:16:27Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T08:25:58Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T08:27:14Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T08:28:01Z
user: login: verus-j
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T16:30:34Z
user: login: verus-j
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T16:35:10Z
user: login: verus-j
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-10T16:40:48Z
user: login: verus-j
body: 안녕하세요 슬링키~
피드백 반영하시느라 고생많으셨습니다.
추가 피드백과 답변 남겨놨습니다.
확인부탁드립니다~
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-10T17:33:14Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:00:21Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:04:54Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:06:14Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:09:34Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:09:48Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T06:10:04Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:41:48Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:42:12Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:44:42Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:46:04Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:46:59Z
user: login: supernovaMK
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T12:54:17Z
user: login: verus-j
body: 안녕하세요 슬링키~
블랙잭 미션하시느라 고생많으셨습니다!
1단계는 이만 머지하고 몇가지 간단한 피드백 남겨뒀습니다.
확인부탁드립니다~ 2단계에서 뵙겠습니다!
state: APPROVED
author_association: NONE
submitted_at: 2025-03-12T15:56:52Z

Review comments:
diff_hunk: @@ -0,0 +1,75 @@
+package domain;
+
+public enum TrumpCard {
+    ACE_OF_SPADES(Suit.SPADES, Rank.ACE),
path: src/main/java/domain/TrumpCard.java
user: login: verus-j
body: 특별히 enum 상수로 트럼프 카드를 관리하는 이유가 있을까요?
새로운 문양이나 숫자가 나오면 관리하기 어렵지 않을까요?
몇몇 상수(JACK_OF_DIAMONDS, QUEEN_OF_DIAMONDS 등)는 사용하고 있지도 않군요.
created_at: 2025-03-07T15:24:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
+
+    private static int calculateAceBonus(int totalScore, int aceCount) {
+        while (aceCount-- > 0 && totalScore + 10 <= 21) {
+            totalScore += 10;
+        }
+        return totalScore;
path: src/main/java/domain/Score.java
user: login: verus-j
body: ```suggestion
        if (aceCount > 0 && totalScore + 10 <= 21) {
            return totalScore + 10;
        }
        return totalScore;
```

위와 같이 구현 후 ScoreTest에 아래의 케이스를 추가해도 테스트는 통과하네요. ace가 적용되어 10이 추가되는 경우는 최대 한번인거 아닐까요?
`Arguments.of(List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.ACE_OF_SPADES), Score.TWELVE)`
created_at: 2025-03-07T15:32:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
path: src/main/java/domain/Score.java
user: login: verus-j
body: 이 곳 역시 aceCount를 구하는 로직과 totalCount를 구하는 로직은 calculateAceBonus와 추상화 수준이 안맞는 것 같네요.
created_at: 2025-03-07T15:33:24Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
path: src/main/java/domain/Score.java
user: login: verus-j
body: totalScore를 구하는 로직과 cardCount를 구하는 로직은 Arrays.stream() 로직과 추상화 수준이 안맞는 것 같네요. 추상화 수준을 맞춰보는 건 어떨까요?
created_at: 2025-03-07T15:34:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
path: src/main/java/domain/Score.java
user: login: verus-j
body: Score를 enum 상수로 관리하는 이유가 있을까요??
TrumpCard와 동일하게 사용하지 않는 상수들도 있네요.
created_at: 2025-03-07T15:36:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class Hand {
+
+    private final List<TrumpCard> cards;
+
+    public Hand(List<TrumpCard> cards) {
+        validate(cards);
+        this.cards = new ArrayList<>(cards);
+    }
+
+    private void validate(List<TrumpCard> cards) {
+        validateNotNull(cards);
+        validateNotDuplicate(cards);
+    }
+
+    private void validateNotNull(List<TrumpCard> cards) {
+        if (cards == null) {
+            throw new IllegalArgumentException("손패는 카드를 가지고 있어야합니다.");
+        }
+    }
+
+    private void validateNotDuplicate(List<TrumpCard> cards) {
+        if (cards.stream().distinct().count() != cards.size()) {
path: src/main/java/domain/Hand.java
user: login: verus-j
body: 조건문이 복잡해보이네요. 변수 추출 또는 메서드 추출로 쉽게 볼 수 있도록 하는건 어떨까요?
created_at: 2025-03-07T15:41:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,68 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class GameResultTest {
+
+    @Nested
+    class ValidCases {
+
+        @ParameterizedTest
+        @DisplayName("플레이어가 버스트이거나, 딜러보다 점수가 낮으면 LOSE를 반환한다.")
path: src/test/java/domain/GameResultTest.java
user: login: verus-j
body: ~~딜러가 버스트인 경우는 어떻게 되나요? 테스트 케이스에 보이지 않아서 여쭤봅니다.~~
2단계 미션에 해당 조건에 대한 기능이 있네요! 그 때 수정해보는걸로 해요~
created_at: 2025-03-07T15:43:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,121 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DeckTest {
+
+    @Nested
+    class ValidCase {
+
+        @DisplayName("덱은 마지막 카드를 줘야한다")
+        @Test
+        void drawCard() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck deck = new Deck(cards);
+
+            // when & then
+            assertThat(deck.draw()).isEqualTo(cards.getLast());
+        }
+
+        @DisplayName("덱은 카드를 섞을 수 있다")
+        @Test
+        void shuffle() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck originalDeck = new Deck(cards);
+            Deck firstShuffledDeck = new Deck(cards);
+            Deck secondShuffledDeck = new Deck(cards);
+
+            // when
+            firstShuffledDeck.shuffle();
+            secondShuffledDeck.shuffle();
+
+            // then
+            assertSoftly(softly -> {
+                softly.assertThat(firstShuffledDeck)
+                        .isNotEqualTo(originalDeck);
+                softly.assertThat(secondShuffledDeck)
+                        .isNotEqualTo(firstShuffledDeck);
+            });
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @DisplayName("덱은 카드를 가지고 있어야한다.")
+        @Test
+        void validateNotNull() {
+            // given
+            List<TrumpCard> nullCards = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Deck(nullCards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱은 카드를 가지고 있어야합니다.");
+        }
+
+        @DisplayName("덱의 카드는 52장 이어야한다.")
+        @Test
+        void validateSize() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            cards.add(TrumpCard.ACE_OF_CLUBS);
+
+            // then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱의 크기는 52여야 합니다.");
+        }
+
+        @DisplayName("덱의 카드는 중복되지 않아야한다.")
+        @Test
+        void validateNotDuplicate() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            for (int i = 0; i < 52; i++) {
+                cards.add(TrumpCard.ACE_OF_CLUBS);
+            }
+            //then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱에 중복된 카드가 있습니다.");
+        }
+
+        @DisplayName("덱은 카드가 없다면 카드를 줄 수 없다")
+        @Test
+        void nonCardTest() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck deck = new Deck(cards);
path: src/test/java/domain/DeckTest.java
user: login: verus-j
body: 여기는 `Deck.create()`로 대체할 수 있지 않나요??
created_at: 2025-03-07T15:46:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: `processDealerHit()`에도 동일하게 적용할 수 있는 피드백입니다. 디미터 법칙에 대해서 아시나요?
* player.getHand().getCards()
* player.getHand().addCard()

위의 로직은 디미터 법칙을 지켰을까요?
created_at: 2025-03-07T15:49:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+        Hand hand = dealer.getHand();
+
+        while (rule.isDealerHitAllowed(hand.getCards())) {
+            hand.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 분명 Players는 BlackJackGame에서 생성하는데 인자로 받는 모습이 어색하네요. Players를 이용하는 건 동일하지만, 마치 두 가지 역할을 하는 것 같은 느낌입니다. 어떻게 생각하시나요?
created_at: 2025-03-07T15:56:27Z
author_association: NONE
diff_hunk: @@ -1,3 +1,72 @@
 # java-blackjack
path: README.md
user: login: verus-j
body: > IllegalArgumentException과 IllegalStatementException의 사용하는 경우가 모호해서 헷갈렸던 것 같습니다. 베루스의 기준을 들어보고 싶습니다!

IllegalArgumentException은 원치 않는 인자가 들어와서 예외가 발생하는 경우에 주로 사용합니다. IllegalStateException은 인자와 상관없이 현재 객체의 상태가 메시지를 실행하기에 적절치 않아 예외가 발생하는 경우에 주로 사용합니다.

아래의 예시를 참고하면 좋을 것 같습니다!
```java
// 잘못된 인자로 인해 예외 발생 케이스
Player createPlayer(String name) {
    if (name.isBlank()) {
        throw new IllegalArgumentException();
    }
    ...
}

// 객체의 상태가 비정상적(Deck에 남은 카드 없음)이여서 예외가 발생
Card take() {
    if (deck.isEmpty()) {
        throw new IllegalStateException();
    }
    ...
}
```
created_at: 2025-03-07T16:03:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+        Hand hand = dealer.getHand();
+
+        while (rule.isDealerHitAllowed(hand.getCards())) {
+            hand.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            List<TrumpCard> playerCards = player.getHand().getCards();
+            List<TrumpCard> dealerCards = dealer.getHand().getCards();
+
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 이 곳 역시 디미터 법칙을 고려해보면 좋을 것 같습니다.
created_at: 2025-03-07T16:04:20Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 인자인 player로 인해 예외가 발생하니 IllegalArgumentException이 더 맞을 것 같습니다. 하지만, player의 상태가 이상한 케이스니 IllegalStateException도 어색하진 않네요. 디미터 법칙 관련 피드백을 고민해보면 같이 해답을 찾을 것 같아보이네요!
created_at: 2025-03-07T16:07:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,31 @@
+package domain;
+
+public class Player {
+
+    private final String name;
+    private final Hand hand;
+
+    public Player(String name, Hand hand) {
+        validate(name, hand);
+        this.name = name;
+        this.hand = hand;
+    }
+
+    private void validate(String name, Hand hand) {
+        validateNotNull(name, hand);
+    }
+
+    private void validateNotNull(String name, Hand hand) {
+        if (name == null || name.isBlank() || hand == null) {
+            throw new IllegalStateException("플레이어는 이름과 손패를 가져야합니다.");
path: src/main/java/domain/Player.java
user: login: verus-j
body: 여기는 IllegalArgumentException이 더 올바른 것 같습니다!
created_at: 2025-03-07T16:08:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
+
+    private static int calculateAceBonus(int totalScore, int aceCount) {
+        while (aceCount-- > 0 && totalScore + 10 <= 21) {
+            totalScore += 10;
+        }
+        return totalScore;
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 결국에는 최대로 한번만 추가된다는 점을 놓친 것 같습니다!

수정해보겠습니다!

꼼꼼하게 확인해주셔서 감사합니다!

created_at: 2025-03-10T05:19:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 처음에 BlackJack과 Bust라는 상수 적용하고 싶어 작성하게 되었습니다. 특정 상황의 점수 두 가지를 추가하고 나니, 나머지 점수들에 관해서도 같은 enum에 위치하는게 맞다라는 생각을 하고 enum을 작성했습니다.

 플레이어의 점수가 10점이 나오고 딜러가 블랙잭이 나온다면 두 상수에 관한 비교를 하나의 타입으로 동등하게 비교해야하지 않을까 고민했던 것 같습니다. 

하지만 특정 블랙잭과 버스트 이외의 값들을 사용할때는 enum으로 관리할 필요가 없어보이게 되었습니다. 

 블랙잭 게임 내에서는 나올 수 있는 점수가 정해져있어서 구현해보았는데, 베루스는 어떻게 생각하시나요?
created_at: 2025-03-10T06:02:02Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 이렇게 한번 바꿔보았는데 추상화되었을까요?

```java

    private static int calculateTotalScore(List<TrumpCard> cards) {
        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
        totalScore+= calculateAceBonusScore(totalScore,getAceCount(cards));
        return totalScore;
    }

    private static int getAceCount(List<TrumpCard> cards) {
        return (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
    }

    private static int calculateAceBonusScore(int totalScore, int aceCount) {
        if (aceCount > 0 && totalScore + 10 <= 21) {
            return 10;
        }
        return 0;
    }

```
created_at: 2025-03-10T06:06:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: Arrays.Stream 안에서 사용하려하는 score.condition.apply()에서 총 점수와 카드의 개수를 통해서 반환하고자 필요한 두가지인 totalScore와 cardCount인데 추상화 수준이 다른점이 헷갈립니다.

필요한 값을 계산하는 부분들에 관한 로직은 메서드로 분리를 하였고, 인자로 받은 cards.size()를 따로 메서드화 시키는 점은 과한 메서드라고 생각합니다.

아래의 코드와 같이 적용하면 더 추상화 된 코드인가요?
추상화 수준에 대해서 공부해보니, 제가 이해한 수준을 맞춰본다는 의미는 하나의 메서드가 하나의 작업을 하게끔 만드는 것입니다. 

이 부분에서 이 메서드가 필요한 totalScore와 cardCount를 파라미터로 넘겨주는 것이 더 어색해보이는데, 해결방법이 잘 떠오르지 않는 것같습니다.

베루스 힌트 주실 수 있으실까요?

```java

        return Arrays.stream(Score.values())
                .filter(score -> score.condition.apply(calculateTotalScore(cards), cards.size()))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
```

created_at: 2025-03-10T06:23:11Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class Hand {
+
+    private final List<TrumpCard> cards;
+
+    public Hand(List<TrumpCard> cards) {
+        validate(cards);
+        this.cards = new ArrayList<>(cards);
+    }
+
+    private void validate(List<TrumpCard> cards) {
+        validateNotNull(cards);
+        validateNotDuplicate(cards);
+    }
+
+    private void validateNotNull(List<TrumpCard> cards) {
+        if (cards == null) {
+            throw new IllegalArgumentException("손패는 카드를 가지고 있어야합니다.");
+        }
+    }
+
+    private void validateNotDuplicate(List<TrumpCard> cards) {
+        if (cards.stream().distinct().count() != cards.size()) {
path: src/main/java/domain/Hand.java
user: login: supernovaMK
body: 코드를 줄이고자 노력하다보니 오히려 코드를 이해하기 어려운 수준으로 줄였던 것 같습니다!

확인해주셔서 감사합니다!
created_at: 2025-03-10T06:26:30Z
author_association: NONE
diff_hunk: @@ -0,0 +1,68 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class GameResultTest {
+
+    @Nested
+    class ValidCases {
+
+        @ParameterizedTest
+        @DisplayName("플레이어가 버스트이거나, 딜러보다 점수가 낮으면 LOSE를 반환한다.")
path: src/test/java/domain/GameResultTest.java
user: login: supernovaMK
body: 넵 감사합니다!
created_at: 2025-03-10T06:26:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+        Hand hand = dealer.getHand();
+
+        while (rule.isDealerHitAllowed(hand.getCards())) {
+            hand.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: Players는 컨트롤러에서 관리하고, 각 player가 게임하는 필요한 메서드를 blackjackGame의 메서드를 사용해서 관리하려고 하였습니다.

 player들의 생성에 관해서는 blackJackGame의 역할보다는 컨트롤러에 두는게 본래의 설계에 맞다고 생각이 듭니다.

 blackJackGame에서는 플레이어들을 생성하지 않는 방향으로 다시 설계해보았습니다!
created_at: 2025-03-10T07:43:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,121 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.SoftAssertions.assertSoftly;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class DeckTest {
+
+    @Nested
+    class ValidCase {
+
+        @DisplayName("덱은 마지막 카드를 줘야한다")
+        @Test
+        void drawCard() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck deck = new Deck(cards);
+
+            // when & then
+            assertThat(deck.draw()).isEqualTo(cards.getLast());
+        }
+
+        @DisplayName("덱은 카드를 섞을 수 있다")
+        @Test
+        void shuffle() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck originalDeck = new Deck(cards);
+            Deck firstShuffledDeck = new Deck(cards);
+            Deck secondShuffledDeck = new Deck(cards);
+
+            // when
+            firstShuffledDeck.shuffle();
+            secondShuffledDeck.shuffle();
+
+            // then
+            assertSoftly(softly -> {
+                softly.assertThat(firstShuffledDeck)
+                        .isNotEqualTo(originalDeck);
+                softly.assertThat(secondShuffledDeck)
+                        .isNotEqualTo(firstShuffledDeck);
+            });
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @DisplayName("덱은 카드를 가지고 있어야한다.")
+        @Test
+        void validateNotNull() {
+            // given
+            List<TrumpCard> nullCards = null;
+
+            // when & then
+            assertThatThrownBy(() -> new Deck(nullCards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱은 카드를 가지고 있어야합니다.");
+        }
+
+        @DisplayName("덱의 카드는 52장 이어야한다.")
+        @Test
+        void validateSize() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            cards.add(TrumpCard.ACE_OF_CLUBS);
+
+            // then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱의 크기는 52여야 합니다.");
+        }
+
+        @DisplayName("덱의 카드는 중복되지 않아야한다.")
+        @Test
+        void validateNotDuplicate() {
+            // given
+            List<TrumpCard> cards = new ArrayList<>();
+
+            // when
+            for (int i = 0; i < 52; i++) {
+                cards.add(TrumpCard.ACE_OF_CLUBS);
+            }
+            //then
+            assertThatThrownBy(() -> new Deck(cards))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("덱에 중복된 카드가 있습니다.");
+        }
+
+        @DisplayName("덱은 카드가 없다면 카드를 줄 수 없다")
+        @Test
+        void nonCardTest() {
+            // given
+            List<TrumpCard> cards = Arrays.stream(TrumpCard.values())
+                    .toList();
+            Deck deck = new Deck(cards);
path: src/test/java/domain/DeckTest.java
user: login: supernovaMK
body: 테스트 코드에서 static메서드를 사용하여 객체를 초기화하는게 부자연스러운가?라는 고민이 있어서 직접 주입시켰던 것 같습니다.

하지만 다시 생각해보니 불필요한 과정 같습니다! 대체해보겠습니다!
created_at: 2025-03-10T07:49:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 일급 컬렉션을 사용하면서 생긴 디미터의 법칙을 어기게 된 과정 같습니다.

일급 컬렉션에게 해당 컬렉션에 관한 책임으로 메서드를 넘겼을때

해당 일급 컬렉션(hand)를 사용하는 player에 중개 메서드가 생기게 되는데 자연스러운 현상일까요?

저는 해당 중개 과정이 과하지만 않다면 자연스럽다고 생각하는데, 베루스의 생각도 들어보고 싶습니다.

``` java
  class Player{

    public List<TrumpCard> getCards() {
        return hand.getCards();
    }

    public void addCard(TrumpCard drawnCard) {
        hand.addCard(drawnCard);
    }

```
created_at: 2025-03-10T07:55:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,75 @@
+package domain;
+
+public enum TrumpCard {
+    ACE_OF_SPADES(Suit.SPADES, Rank.ACE),
path: src/main/java/domain/TrumpCard.java
user: login: supernovaMK
body: 트럼프 카드의 Suit와 Rank를 먼저 Enum 상수로 묶어 놓으니 어디까지가 상수들의 묶음인지 고민이 되었던 것 같습니다.

따라서 두 상수를 하나로 가지는 카드 한 장 역시 하나의 상수로 볼 수 있지 않을까 생각했던 것 같습니다. 

만약 해당 Suit와 Rank를 커스텀한 새로운 카드를 추가한다면, enum에서 임의적으로 추가하는 과정이 더 빠르다고 생각이 들었습니다.
 Ex)조커라는 카드를 추가로 생성해야하는 경우

하지만 직접 개발자가 입력해야한다는 점에서 실수를 하게된다면,카드를 하나씩 직접 눈으로 검증해야한다는 점이 마음에 걸립니다.

> 몇몇 상수(JACK_OF_DIAMONDS, QUEEN_OF_DIAMONDS 등)는 사용하고 있지도 않군요.

이 부분에 대해서 제가 이해가 부족한데 혹시 더 설명해주실 수 있으신가요?

상수화를 과도하게 한 것 같은 느낌이 드는데, 과도하다라는게 어떤 근거인지 딱 떠오르는 저 스스로 이유를 찾지 못했다고 생각합니다. 만약 전체 카드가 6장이었다면, 상수화를 진행하고, 카드가 52장이라면 너무 많은 데이터 묶음이라 로직을 사용하여 객체를 만들어야하나?라는 고민이 되면서 어떤 범위 까지가 enum으로 가질 수 있는 범위인지 고민이 됩니다.

Enum 으로 덱을 만들때 장점으로는 각 카드별로 상수화 시킨다면 카드를 직접 로직으로 생성하는 것보다 명확하고 Enum의 싱글톤 관리로 하나의 카드만 사용하게끔 강제할 수 있어 안전하다는 생각도 듭니다.카드 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없다는 점도 있을 것 같습니다.

베루스는 어떻게 생각하시나요? 


created_at: 2025-03-10T08:11:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,31 @@
+package domain;
+
+public class Player {
+
+    private final String name;
+    private final Hand hand;
+
+    public Player(String name, Hand hand) {
+        validate(name, hand);
+        this.name = name;
+        this.hand = hand;
+    }
+
+    private void validate(String name, Hand hand) {
+        validateNotNull(name, hand);
+    }
+
+    private void validateNotNull(String name, Hand hand) {
+        if (name == null || name.isBlank() || hand == null) {
+            throw new IllegalStateException("플레이어는 이름과 손패를 가져야합니다.");
path: src/main/java/domain/Player.java
user: login: supernovaMK
body: 인자가 잘못된 경우로 예외가 발생하므로 IllegalArgumentException이 더 올바르겠군요!

체크해주셔서 감사합니다!
created_at: 2025-03-10T08:16:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 해당 부분에 대한 예외는 앞에서 이미 조건 분기점을 통과했을 때만 작동하도록 해두어서, 발생할 일이 없어 보여 삭제하였습니다.

하지만 삭제 하기 전에 베루스가 말씀해주신 디미터 법칙과의 연관을 찾기 어려운것 같습니다.

제가 최대한 생각해본 점은 cards 의 상태에 관한 예외처리에 관해서는 hand에서 처리할 수 있기에 해당 예외를 cards를 가지는 hand가 갖는 것이 더 적절하다고 생각할 수 있는점 정도 인것 같습니다.

설계 당시에는 더 카드를 뽑을지 말지등은 게임의 Rule이 관리하고 확인하도록 설계하여 게임의 규칙의 변화에 유연하도록 설계를 해두어 해당 카드에 대한 값 상태는 또 다시 Rule로 이동하게 되는데 상태에 대한 관리를 두 곳에서 하는점이 이상할 수 있을까요? 베루스의 생각이 궁금합니다!
created_at: 2025-03-10T08:25:58Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+        Hand hand = dealer.getHand();
+
+        while (rule.isDealerHitAllowed(hand.getCards())) {
+            hand.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            List<TrumpCard> playerCards = player.getHand().getCards();
+            List<TrumpCard> dealerCards = dealer.getHand().getCards();
+
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 넵 감사합니다!
created_at: 2025-03-10T08:27:14Z
author_association: NONE
diff_hunk: @@ -1,3 +1,72 @@
 # java-blackjack
path: README.md
user: login: supernovaMK
body: 객체의 상태를 확인하는 것과 인자를 확인하는 부분에서 차이가 있었군요! 감사합니다! 

추가적으로 공부해보겠습니다!
created_at: 2025-03-10T08:28:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,75 @@
+package domain;
+
+public enum TrumpCard {
+    ACE_OF_SPADES(Suit.SPADES, Rank.ACE),
path: src/main/java/domain/TrumpCard.java
user: login: verus-j
body: > 몇몇 상수(JACK_OF_DIAMONDS, QUEEN_OF_DIAMONDS 등)는 사용하고 있지도 않군요.

실제 구현 코드에서 사용하지 않고 있어 말씀드렸습니다.
![스크린샷 2025-03-11 오전 1 25 08](https://github.com/user-attachments/assets/69261c9b-a1a3-4fb0-b4f6-31245bb1f894)

> 상수화를 과도하게 한 것 같은 느낌이 드는데, 과도하다라는게 어떤 근거인지 딱 떠오르는 저 스스로 이유를 찾지 못했다고 생각합니다.

제 생각에는 위와 같이 사용하지 않는 상수가 있다는 부분이 과도하다고 판단하는 근거가 될 것 같습니다. 사용하지 않는 코드인데, 상수를 추가해두는건 이상하니깐요.

> Enum의 싱글톤 관리로 하나의 카드만 사용하게끔 강제할 수 있어 안전하다는 생각도 듭니다

블랙잭 미션 요구 사항 중 하나의 카드 객체만 사용하게끔 강제해야하는 기능이 있을까요? 다른 객체여도 같은 값을 나타내기만 하면 문제없지 않을까요?

> 카드 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없다는 점도 있을 것 같습니다.

지금과 같은 방법 외에도 다른 방법으로 쉽게 수정하도록 할 수 있을 것 같습니다! 예를 들어 Suit와 Rank를 순회하여 TrumpCard 객체를 생성하는 로직을 사용하는건 어떨까요? 그러면 지금처럼 카드 추가에 유연하게 대처할 수 있지 않을까요? 조커가 들어가도 결국 TrumpCard를 생성하는 로직에 직접 필요한 조커를 List에 추가하는 등의 간단한 수정하면 될 것 같아 보입니다.
created_at: 2025-03-10T16:30:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
path: src/main/java/domain/Score.java
user: login: verus-j
body: 제가 생각한 것과 조금 차이가 있어서 이 부분은 직접적으로 코드를 남겨드릴게요~
추상화 수준에 대해서는 [여기](https://velog.io/@mincho920/%ED%81%B4%EB%A6%B0%EC%BD%94%EB%93%9C-%ED%95%A8%EC%88%98%EC%9D%98-%EC%B6%94%EC%83%81%ED%99%94-%EC%88%98%EC%A4%80-%EA%B7%B8%EB%A6%AC%EA%B3%A0-Switch-%EB%AC%B8)를 참고해보면 좋을 것 같아요.

```java
    private static int calculateTotalScore(List<TrumpCard> cards) {
        int totalScore = getTotalScore(cards);
        int aceCount = getAceCount(cards);
        int bonusScore = calculateAceBonusScore(totalScore, aceCount);
        return totalScore + bonusScore;
    }

    private static int getTotalScore(List<TrumpCard> cards) {
        return cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
    }

    private static int getAceCount(List<TrumpCard> cards) {
        return (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
    }

    private static int calculateAceBonusScore(int totalScore, int aceCount) {
        if (aceCount > 0 && totalScore + 10 <= 21) {
            return 10;
        }
        return 0;
    }
```
created_at: 2025-03-10T16:35:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
path: src/main/java/domain/Score.java
user: login: verus-j
body: 여기도 위의 TrumpCard와 동일한 상황이라고 생각합니다. 마찬가지로 코드에서 사용하지 않는 상수가 제법 있습니다. 사용중인 대부분의 상수도 테스트에서만 쓰고 있는 것 같네요.
![스크린샷 2025-03-11 오전 1 38 45](https://github.com/user-attachments/assets/be474709-b36e-4d47-a20f-094904bbf2ab)

점수를 비교하기 위해 상수를 뒀다면, Score를 enum이 아닌 일반 객체로 만들어 equals & hashcode를 사용해보는건 어떨까요? 동등성과 동일성의 차이점도 한번 검색해보시면 좋을 것 같습니다!

created_at: 2025-03-10T16:40:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
path: src/main/java/domain/Score.java
user: login: verus-j
body: calculateTotalScore(cards)는 카드의 총 점수를 구하는 과정이군요. cards.size()는 카드의 총 갯수를 구하는 과정이네요.

```java
Arrays.stream(Score.values())
                .filter(score -> score.condition.apply(totalScore, cardCount))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
```
위의 코드는 어떤가요? 하고자 하는 행위는 총 점수와 카드의 총 갯수를 사용해 원하는 점수를 구하는 거네요. 하지만 코드 상에 보여지는 로직은 전체 점수 enum을 순회하여 condition.apply를 호출해 원하는 상태의 score를 찾고 그 중에서 첫번째 요소를 가져오는군요. 만약에 존재하지 않으면 예외도 발생하구요. calculateTotalScore와 cards.size에 비해 굉장히 로우한 코드인 것 같습니다. 가볍게 Stream쪽 로직을 메서드 추출로 총 점수와 카드 갯수로 점수를 구하는 과정을 추상화하여 추상화 수준을 맞춰보는게 어떨까 하여 피드백 드렸습니다~
created_at: 2025-03-10T17:00:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 자연스러운 현상이라고 생각합니다! 일단 내부 구현의 결과물을 떠나서 Player의 손패를 나타내는 Hand를 외부로 드러내는 게 문제라고 생각합니다. Player는 객체인가요? 자료 구조인가요? 자료구조라면 내부의 데이터를 외부에 드러내기 위해 getter를 활용하는게 올바른 사용법입니다. 하지만, 객체라면 자신의 내부 구조를 외부에 드러내지 않고 스스로 행동할 줄 알아야합니다. Player에게 카드를 추가하기 위해 내부 구조인 Hand를 드러내고 외부에서 추가하는게 올바른 상황인지 고민해보면 좋을 것 같습니다. 참고로 뷰에 데이터를 보여주기 위해 getter를 사용하는건 논외입니다! 지금은 도메인 로직만 얘기하는겁니다~
created_at: 2025-03-10T17:06:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,31 @@
+package domain;
+
+import java.util.List;
+
+public class Rule {
path: src/main/java/domain/Rule.java
user: login: verus-j
body: Rule은 static이 안 붙었을 뿐 유틸 클래스인 것 같습니다. Rule의 역할은 어떻게 될까요?
created_at: 2025-03-10T17:13:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class BlackJackGameTest {
+
+    private Deck blackJackDeck;
+    private Dealer dealer;
+    private Rule rule;
+
+    @BeforeEach
+    void setUp() {
+        blackJackDeck = new Deck(Arrays.stream(TrumpCard.values()).toList());
+        dealer = new Dealer(new Hand(List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.TWO_OF_HEARTS)));
+        rule = new Rule();
+    }
+
+    @Nested
+    class ValidCases {
+
+
+        @ParameterizedTest
+        @DisplayName("플레이어의 히트 판단 여부를 판단한다")
+        @MethodSource("provideDealerHitAllowedCases")
+        void isPlayerHitAllowed(List<TrumpCard> cards) {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player = new Player("Alice", new Hand(cards));
+
+            // when
+            boolean result = blackJackGame.isPlayerHitAllowed(player.getHand().getCards());
+
+            // then
+            assertThat(result).isTrue();
+        }
+
+        static Stream<Arguments> provideDealerHitAllowedCases() {
+            return Stream.of(
+                    Arguments.of(List.of(TrumpCard.FIVE_OF_CLUBS, TrumpCard.SIX_OF_HEARTS)),
+                    Arguments.of(List.of(TrumpCard.SEVEN_OF_DIAMONDS, TrumpCard.TWO_OF_SPADES)),
+                    Arguments.of(
+                            List.of(TrumpCard.THREE_OF_HEARTS, TrumpCard.THREE_OF_DIAMONDS, TrumpCard.TWO_OF_SPADES))
+            );
+        }
+
+        @Test
+        @DisplayName("플레이어가 히트하면 카드가 추가된다.")
+        void processPlayerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player = new Player("Alice", Hand.of(TrumpCard.TWO_OF_DIAMONDS, TrumpCard.FIVE_OF_SPADES));
+
+            // when
+            blackJackGame.processPlayerHit(player);
+
+            // then
+            assertThat(player.getHand().getCards()).hasSize(3);
+        }
+
+        @Test
+        @DisplayName("딜러가 히트하면 적절한 횟수만큼 카드가 추가된다.")
+        void processDealerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            int initialCards = dealer.getHand().getCards().size();
+
+            // when
+            int hitCount = blackJackGame.processDealerHit();
+
+            // then
+            assertThat(dealer.getHand().getCards()).hasSize(initialCards + hitCount);
+        }
+
+        @Test
+        @DisplayName("주어진 카드 목록의 점수를 올바르게 계산한다.")
+        void calculateScore() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            List<TrumpCard> cards = List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.KING_OF_HEARTS);
+
+            // when
+            Score score = blackJackGame.caculateScore(cards);
+
+            // then
+            assertThat(score).isEqualTo(Score.BLACKJACK);
+        }
+
+        @Test
+        @DisplayName("게임 결과를 올바르게 계산한다.")
+        void calculateGameResults() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player1 = new Player("Alice", Hand.of(TrumpCard.TEN_OF_DIAMONDS, TrumpCard.JACK_OF_HEARTS));
+            Player player2 = new Player("Bob", Hand.of(TrumpCard.NINE_OF_CLUBS, TrumpCard.SEVEN_OF_DIAMONDS));
+            List<Player> players = List.of(player1, player2);
+
+            // when
+            Map<String, GameResult> results = blackJackGame.calculateGameResults(players);
+
+            // then
+            assertThat(results).containsKeys("Alice", "Bob");
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @ParameterizedTest
+        @DisplayName("플레이어가 히트 할 수 없다면 히트를 할 수 없다.")
+        @MethodSource("provideHitNotAllowedCases")
+        void processPlayerHit(List<TrumpCard> cards) {
path: src/test/java/domain/BlackJackGameTest.java
user: login: verus-j
body: 여기 테스트가 깨집니다!
created_at: 2025-03-10T17:15:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,45 @@
+package domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class PlayerTest {
+
+    @Nested
+    class InvalidCase {
+
+        @ParameterizedTest
+        @CsvSource(value = {
+                "null",
+                "' '"},
+                nullValues = {"null"})
+        @DisplayName("플레이어는 이름을 가져야 한다.")
path: src/test/java/domain/PlayerTest.java
user: login: verus-j
body: 테스트가 깨집니다~
created_at: 2025-03-10T17:15:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,45 @@
+package domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class PlayerTest {
+
+    @Nested
+    class InvalidCase {
+
+        @ParameterizedTest
+        @CsvSource(value = {
+                "null",
+                "' '"},
+                nullValues = {"null"})
+        @DisplayName("플레이어는 이름을 가져야 한다.")
+        void validateNotNullName(String name) {
+            // given
+            Hand hand = new Hand(List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.TEN_OF_SPADES));
+
+            // when & then
+            assertThatThrownBy(() -> new Player(name, hand))
+                    .isInstanceOf(IllegalStateException.class)
+                    .hasMessage("플레이어는 이름과 손패를 가져야합니다.");
+        }
+
+        @Test
+        @DisplayName("플레이어는 손패를 가져야 한다.")
path: src/test/java/domain/PlayerTest.java
user: login: verus-j
body: 테스트가 깨집니다~
created_at: 2025-03-10T17:15:39Z
author_association: NONE
diff_hunk: @@ -0,0 +1,99 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (rule.isDealerHitAllowed(dealer.getCards())) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            List<TrumpCard> playerCards = player.getCards();
+            List<TrumpCard> dealerCards = dealer.getCards();
+
+            GameResult gameResult = rule.evaluateGameResult(playerCards, dealerCards);
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: evaluateGameResult를 Player나 Dealer에 옮겨볼 수도 있을 것 같습니다.
created_at: 2025-03-10T17:18:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 디미터 법칙 얘기를 한 이유는 예외처리도 같이 Player로 이동할 수 있지 않을까 싶어서였습니다. IllegalStateException이 어색하지 않다는 건 내부로 옮겨야할 로직이 외부에 드러났기 때문이라고 생각했거든요. 

```java
    public void processPlayerHit(Player player) {
        Hand hand = player.getHand();
        List<TrumpCard> cards = hand.getCards();

        if (!isPlayerHitAllowed(cards)) {
            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
        }

        hand.addCard(deck.draw());
    }
```
위의 로직에서

```java
// BlackJackGame 클래스
    public void processPlayerHit(Player player) {
        player.processPlayerHit(deck);
    }
```

```java
// Player 클래스
    public void processPlayerHit(Deck deck) {
        // 여기도 실은 디미터 법칙을 고려하면 Hand로 옮겨야할 것 같네요
        List<TrumpCard> cards = hand.getCards(); 

        if (!new Rule().isPlayerHitAllowed(cards)) {
            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
        }

        hand.addCard(deck.draw());
    }
```

이렇게도 가능하지 않나요? 저기서 new Rule() 여기는 아무리봐도 유틸 클래스가 맞네요. isPlayerHitAllowed()를 Player로 옮기는게 옳은 방향일 수 있겠네요.

제가 당시에 생각한건 이런 그림이였는데, 컨트롤러의 구조도 바뀌어서 굳이 이렇게 할 필요는 없겠네요~
created_at: 2025-03-10T17:29:00Z
author_association: NONE
diff_hunk: @@ -0,0 +1,75 @@
+package domain;
+
+public enum TrumpCard {
+    ACE_OF_SPADES(Suit.SPADES, Rank.ACE),
path: src/main/java/domain/TrumpCard.java
user: login: supernovaMK
body: 상수화에서 상수화한 객체를 사용하지 않는다면 의미가 없었던 거군요! 그 부분에 대해서 놓친 것 같습니다!

한번 직접 클래스를 생성하는 로직으로 변경해보겠습니다!

친절하게 알려주셔서 감사합니다~
created_at: 2025-03-11T06:00:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
+    }
+
+    private static int calculateTotalScore(List<TrumpCard> cards) {
+        int aceCount = (int) cards.stream().filter(c -> c.getRank() == Rank.ACE).count();
+        int totalScore = cards.stream().mapToInt(c -> c.getRank().getValue()).sum();
+
+        return calculateAceBonus(totalScore, aceCount);
+    }
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 좋은 자료 공유해주셔서 감사합니다~


더 좋은 추상화된 코드에 대해서 추가적으로 학습해보겠습니다!
created_at: 2025-03-11T06:04:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
+        }
+
+        hand.addCard(deck.draw());
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 이번 미션을 수행하면서 디미터의 법칙과 캡슐화에 대해서 추가적으로 공부해보았습니다.

디미터의 법칙이 결국 캡슐화를 지키는 방법중 하나였군요!

좋은 키워드 주셔서 감사합니다!
created_at: 2025-03-11T06:06:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+package domain;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class BlackJackGameTest {
+
+    private Deck blackJackDeck;
+    private Dealer dealer;
+    private Rule rule;
+
+    @BeforeEach
+    void setUp() {
+        blackJackDeck = new Deck(Arrays.stream(TrumpCard.values()).toList());
+        dealer = new Dealer(new Hand(List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.TWO_OF_HEARTS)));
+        rule = new Rule();
+    }
+
+    @Nested
+    class ValidCases {
+
+
+        @ParameterizedTest
+        @DisplayName("플레이어의 히트 판단 여부를 판단한다")
+        @MethodSource("provideDealerHitAllowedCases")
+        void isPlayerHitAllowed(List<TrumpCard> cards) {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player = new Player("Alice", new Hand(cards));
+
+            // when
+            boolean result = blackJackGame.isPlayerHitAllowed(player.getHand().getCards());
+
+            // then
+            assertThat(result).isTrue();
+        }
+
+        static Stream<Arguments> provideDealerHitAllowedCases() {
+            return Stream.of(
+                    Arguments.of(List.of(TrumpCard.FIVE_OF_CLUBS, TrumpCard.SIX_OF_HEARTS)),
+                    Arguments.of(List.of(TrumpCard.SEVEN_OF_DIAMONDS, TrumpCard.TWO_OF_SPADES)),
+                    Arguments.of(
+                            List.of(TrumpCard.THREE_OF_HEARTS, TrumpCard.THREE_OF_DIAMONDS, TrumpCard.TWO_OF_SPADES))
+            );
+        }
+
+        @Test
+        @DisplayName("플레이어가 히트하면 카드가 추가된다.")
+        void processPlayerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player = new Player("Alice", Hand.of(TrumpCard.TWO_OF_DIAMONDS, TrumpCard.FIVE_OF_SPADES));
+
+            // when
+            blackJackGame.processPlayerHit(player);
+
+            // then
+            assertThat(player.getHand().getCards()).hasSize(3);
+        }
+
+        @Test
+        @DisplayName("딜러가 히트하면 적절한 횟수만큼 카드가 추가된다.")
+        void processDealerHit() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            int initialCards = dealer.getHand().getCards().size();
+
+            // when
+            int hitCount = blackJackGame.processDealerHit();
+
+            // then
+            assertThat(dealer.getHand().getCards()).hasSize(initialCards + hitCount);
+        }
+
+        @Test
+        @DisplayName("주어진 카드 목록의 점수를 올바르게 계산한다.")
+        void calculateScore() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            List<TrumpCard> cards = List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.KING_OF_HEARTS);
+
+            // when
+            Score score = blackJackGame.caculateScore(cards);
+
+            // then
+            assertThat(score).isEqualTo(Score.BLACKJACK);
+        }
+
+        @Test
+        @DisplayName("게임 결과를 올바르게 계산한다.")
+        void calculateGameResults() {
+            // given
+            BlackJackGame blackJackGame = new BlackJackGame(blackJackDeck, dealer, rule);
+            Player player1 = new Player("Alice", Hand.of(TrumpCard.TEN_OF_DIAMONDS, TrumpCard.JACK_OF_HEARTS));
+            Player player2 = new Player("Bob", Hand.of(TrumpCard.NINE_OF_CLUBS, TrumpCard.SEVEN_OF_DIAMONDS));
+            List<Player> players = List.of(player1, player2);
+
+            // when
+            Map<String, GameResult> results = blackJackGame.calculateGameResults(players);
+
+            // then
+            assertThat(results).containsKeys("Alice", "Bob");
+        }
+    }
+
+    @Nested
+    class InvalidCases {
+
+        @ParameterizedTest
+        @DisplayName("플레이어가 히트 할 수 없다면 히트를 할 수 없다.")
+        @MethodSource("provideHitNotAllowedCases")
+        void processPlayerHit(List<TrumpCard> cards) {
path: src/test/java/domain/BlackJackGameTest.java
user: login: supernovaMK
body: 체크해주셔서 감사합니다~
created_at: 2025-03-11T06:09:33Z
author_association: NONE
diff_hunk: @@ -0,0 +1,45 @@
+package domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class PlayerTest {
+
+    @Nested
+    class InvalidCase {
+
+        @ParameterizedTest
+        @CsvSource(value = {
+                "null",
+                "' '"},
+                nullValues = {"null"})
+        @DisplayName("플레이어는 이름을 가져야 한다.")
path: src/test/java/domain/PlayerTest.java
user: login: supernovaMK
body: 체크해주셔서 감사합니다~
created_at: 2025-03-11T06:09:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,45 @@
+package domain;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+import java.util.List;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+class PlayerTest {
+
+    @Nested
+    class InvalidCase {
+
+        @ParameterizedTest
+        @CsvSource(value = {
+                "null",
+                "' '"},
+                nullValues = {"null"})
+        @DisplayName("플레이어는 이름을 가져야 한다.")
+        void validateNotNullName(String name) {
+            // given
+            Hand hand = new Hand(List.of(TrumpCard.ACE_OF_SPADES, TrumpCard.TEN_OF_SPADES));
+
+            // when & then
+            assertThatThrownBy(() -> new Player(name, hand))
+                    .isInstanceOf(IllegalStateException.class)
+                    .hasMessage("플레이어는 이름과 손패를 가져야합니다.");
+        }
+
+        @Test
+        @DisplayName("플레이어는 손패를 가져야 한다.")
path: src/test/java/domain/PlayerTest.java
user: login: supernovaMK
body: 체크해주셔서 감사합니다~
created_at: 2025-03-11T06:10:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,31 @@
+package domain;
+
+import java.util.List;
+
+public class Rule {
path: src/main/java/domain/Rule.java
user: login: supernovaMK
body: Rule에 관해서 교체를 유연하게 하면서, 게임에 대한 규칙을 조정하려고 했는데, 리팩토링 과정에서 없어지게 되었습니다!
created_at: 2025-03-11T12:41:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,99 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (rule.isDealerHitAllowed(dealer.getCards())) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            List<TrumpCard> playerCards = player.getCards();
+            List<TrumpCard> dealerCards = dealer.getCards();
+
+            GameResult gameResult = rule.evaluateGameResult(playerCards, dealerCards);
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 넵! 이번에 Rule을 제거하게 되므로써 이동하게 되었습니다!
created_at: 2025-03-11T12:42:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,114 @@
+package domain;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public List<Player> createPlayers(List<String> names) {
+        List<Player> players = new ArrayList<>();
+
+        names.forEach(name -> {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+            players.add(new Player(name, Hand.of(firstCard, secondCard)));
+        });
+
+        return players;
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        Hand hand = player.getHand();
+        List<TrumpCard> cards = hand.getCards();
+
+        if (!isPlayerHitAllowed(cards)) {
+            throw new IllegalStateException("플레이어는 더이상 히트할 수 없습니다.");
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 생각해보니 player로 옮겨지는게 더 자연스럽겠군요! 구체적으로 알려주셔서 감사합니다!
created_at: 2025-03-11T12:44:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 이번에 GameResult라는 Enum 클래스에서 값에 관한 로직을 옮겨보았습니다! 불필요한 상수들을 모두 제거해보았습니다!

좋은 피드백 감사합니다!
created_at: 2025-03-11T12:46:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,74 @@
+package domain;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+
+public enum Score {
+    BLACKJACK("블랙잭", (total, cardCount) -> total == 21 && cardCount == 2, 1),
+    BUST("버스트", (total, cardCount) -> total > 21, 22),
+
+    TWENTY_ONE("21", (total, cardCount) -> total == 21, 2),
+    TWENTY("20", (total, cardCount) -> total == 20, 3),
+    NINETEEN("19", (total, cardCount) -> total == 19, 4),
+    EIGHTEEN("18", (total, cardCount) -> total == 18, 5),
+    SEVENTEEN("17", (total, cardCount) -> total == 17, 6),
+    SIXTEEN("16", (total, cardCount) -> total == 16, 7),
+    FIFTEEN("15", (total, cardCount) -> total == 15, 8),
+    FOURTEEN("14", (total, cardCount) -> total == 14, 9),
+    THIRTEEN("13", (total, cardCount) -> total == 13, 10),
+    TWELVE("12", (total, cardCount) -> total == 12, 11),
+    ELEVEN("11", (total, cardCount) -> total == 11, 12),
+    TEN("10", (total, cardCount) -> total == 10, 13),
+    NINE("9", (total, cardCount) -> total == 9, 14),
+    EIGHT("8", (total, cardCount) -> total == 8, 15),
+    SEVEN("7", (total, cardCount) -> total == 7, 16),
+    SIX("6", (total, cardCount) -> total == 6, 17),
+    FIVE("5", (total, cardCount) -> total == 5, 18),
+    FOUR("4", (total, cardCount) -> total == 4, 19),
+    THREE("3", (total, cardCount) -> total == 3, 20),
+    TWO("2", (total, cardCount) -> total == 2, 21),
+    ONE("1", (total, cardCount) -> total == 1, 22);
+
+    private final String title;
+    private final BiFunction<Integer, Integer, Boolean> condition;
+    private final int rank;
+
+    Score(String title, BiFunction<Integer, Integer, Boolean> condition, int rank) {
+        this.title = title;
+        this.condition = condition;
+        this.rank = rank;
+    }
+
+    public static Score from(List<TrumpCard> cards) {
+        int totalScore = calculateTotalScore(cards);
+        int cardCount = cards.size();
+
+        return Arrays.stream(Score.values())
+                .filter(score -> score.condition.apply(totalScore, cardCount))
+                .findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 점수입니다."));
path: src/main/java/domain/Score.java
user: login: supernovaMK
body: 추상화를 하는 과정이 아직 익숙하지 않아, 공부를 더 해야할 것 같군요!

좋은 피드백 감사합니다!
created_at: 2025-03-11T12:46:59Z
author_association: NONE
diff_hunk: @@ -0,0 +1,99 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+    private final Rule rule;
+
+    public BlackJackGame(Deck deck, Dealer dealer, Rule rule) {
+        validate(deck, dealer, rule);
+        this.deck = deck;
+        this.dealer = dealer;
+        this.rule = rule;
+    }
+
+    private void validate(Deck deck, Dealer dealer, Rule rule) {
+        validateNotNull(deck, dealer, rule);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer, Rule rule) {
+        if (deck == null || dealer == null || rule == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+        Rule rule = initializeRule();
+        return new BlackJackGame(deck, dealer, rule);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+    private static Rule initializeRule() {
+        return new Rule();
+    }
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public boolean isPlayerHitAllowed(List<TrumpCard> playerCards) {
+        return rule.isPlayerHitAllowed(playerCards);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (rule.isDealerHitAllowed(dealer.getCards())) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public Score caculateScore(List<TrumpCard> cards) {
+        return rule.evaluateScore(cards);
+    }
+
+    public Map<String, GameResult> calculateGameResults(List<Player> players) {
+        Map<String, GameResult> results = new HashMap<>();
+
+        players.forEach(player -> {
+            List<TrumpCard> playerCards = player.getCards();
+            List<TrumpCard> dealerCards = dealer.getCards();
+
+            GameResult gameResult = rule.evaluateGameResult(playerCards, dealerCards);
path: src/main/java/domain/BlackJackGame.java
user: login: supernovaMK
body: 베루스 추가적으로 댓글을 만들어 질문을 달아두었습니다! 

확인해주시면 감사하겠습니다!
created_at: 2025-03-11T12:54:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,89 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+
+
+    public BlackJackGame(Deck deck, Dealer dealer) {
+        validate(deck, dealer);
+        this.deck = deck;
+        this.dealer = dealer;
+
+    }
+
+    private void validate(Deck deck, Dealer dealer) {
+        validateNotNull(deck, dealer);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer) {
+        if (deck == null || dealer == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+
+        return new BlackJackGame(deck, dealer);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 인자로 받은 player의 상태를 변경시켜 출력 인자로 사용하고 있는 것 같아 약간 아쉽네요 ㅠㅠ
deck을 BlackJackGame에서 생성하고 인스턴스 변수로 보유하고 있어 어쩔 수 없이 외부에서 생성한 player를 인자로 넣어주고 있군요. 느낌상 Player가 Deck을 가지고 있으면 굳이 BlackJackGame을 거치지 않고 Player가 직접 카드를 뽑아갈 수 있겠네요. step2에서 고민해보면 좋을 것 같습니다!
created_at: 2025-03-12T15:41:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,89 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+
+
+    public BlackJackGame(Deck deck, Dealer dealer) {
+        validate(deck, dealer);
+        this.deck = deck;
+        this.dealer = dealer;
+
+    }
+
+    private void validate(Deck deck, Dealer dealer) {
+        validateNotNull(deck, dealer);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer) {
+        if (deck == null || dealer == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+
+        return new BlackJackGame(deck, dealer);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (dealer.isHitable()) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 여기도 만약에 Dealer가 deck을 들고 있었으면 Dealer로 로직을 옮겨볼 수도 있겠네요~
created_at: 2025-03-12T15:43:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,89 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
+
+    private final Deck deck;
+    private final Dealer dealer;
+
+
+    public BlackJackGame(Deck deck, Dealer dealer) {
+        validate(deck, dealer);
+        this.deck = deck;
+        this.dealer = dealer;
+
+    }
+
+    private void validate(Deck deck, Dealer dealer) {
+        validateNotNull(deck, dealer);
+    }
+
+    private void validateNotNull(Deck deck, Dealer dealer) {
+        if (deck == null || dealer == null) {
+            throw new IllegalArgumentException("블랙잭게임은 덱과 딜러와 룰을 가지고 있어야합니다.");
+        }
+    }
+
+    public static BlackJackGame create() {
+        Deck deck = initializeDeck();
+        Dealer dealer = initializeDealer(deck);
+
+        return new BlackJackGame(deck, dealer);
+    }
+
+    private static Deck initializeDeck() {
+        Deck deck = Deck.create();
+        deck.shuffle();
+
+        return deck;
+    }
+
+    private static Dealer initializeDealer(Deck deck) {
+        return new Dealer(Hand.of(deck.draw(), deck.draw()));
+    }
+
+
+    public Hand createStartingHands() {
+            TrumpCard firstCard = deck.draw();
+            TrumpCard secondCard = deck.draw();
+        return Hand.of(firstCard, secondCard);
+    }
+
+    public void processPlayerHit(Player player) {
+        player.addCard(deck.draw());
+    }
+
+    public int processDealerHit() {
+        int hitCount = 0;
+
+        while (dealer.isHitable()) {
+            dealer.addCard(deck.draw());
+            hitCount++;
+        }
+
+        return hitCount;
+    }
+
+    public int calculateScore(Dealer player) {
+        return player.getTotalScore();
+    }
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 이거는 굳이 BlackJackGame을 거치지 않아도 되는 것 같습니다. static 메서드랑 동일하다고 생각합니다!
외부에서 바로 dealer.getTotalScore()를 호출해보시죠~
created_at: 2025-03-12T15:44:19Z
author_association: NONE
diff_hunk: @@ -0,0 +1,89 @@
+package domain;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BlackJackGame {
path: src/main/java/domain/BlackJackGame.java
user: login: verus-j
body: 전반적으로 살펴보니 덱을 생성하는 로직과 파라미터로 받은 딜러 또는 플레이어를 조작하는 로직이 주로 있네요. 덱을 생성하는 로직은 블랙잭의 책임으로 볼 수 있으나 나머지는 애매한 것 같습니다. 덱을 생성하여 셔플하는 로직을 차라리 Deck에 정적 팩터리 메서드로 한번에 제공하고, 나머지 로직은 외부에서 직접 딜러나 플레이어를 조작하도록 수정해보는건 어떨까요?? step2에서 한번 고민해보세요~
created_at: 2025-03-12T15:46:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,47 @@
+package domain;
+
+public enum GameResult {
+    WIN("승"),
+    LOSE("패"),
+    DRAW("무");
+
+    private final String title;
+
+    GameResult(String title) {
+        this.title = title;
+    }
+
+    public static GameResult from(Player player, Dealer dealer) {
+        if (player.isBust()) {
+            return LOSE;
+        }
+        if (dealer.isBust()) {
+            return WIN;
+        }
+        if (dealer.isBlackJack() && player.isBlackJack()) {
+            return DRAW;
+        }
+        if (dealer.isBlackJack()) {
+            return LOSE;
+        }
+        if (player.isBlackJack()) {
+            return WIN;
+        }
+
+        return getResultByTotalScore(player, dealer);
+    }
path: src/main/java/domain/GameResult.java
user: login: verus-j
body: ```suggestion
    public static GameResult from(Player player, Dealer dealer) {
        if (hasBust(player, dealer)) {
            return getBustCaseResult(player);
        }
        if (hasBlackJack(player, dealer)) {
            return getBlackJackCaseResult(player, dealer);
        }
        return getResultByTotalScore(player, dealer);
    }

    public static boolean hasBust(Player player, Dealer dealer) {
        return player.isBust() || dealer.isBust();
    }

    public static GameResult getBustCaseResult(Player player) {
        if (player.isBust()) {
            return LOSE;
        }
        return WIN;
    }

    public static boolean hasBlackJack(Player player, Dealer dealer) {
        return player.isBlackJack() || dealer.isBlackJack();
    }

    public static GameResult getBlackJackCaseResult(Player player, Dealer dealer) {
        if (player.isBlackJack() && dealer.isBlackJack()) {
            return DRAW;
        }
        if (player.isBlackJack()) {
            return WIN;
        }
        return LOSE;
    } 
```
개인적 취향인데 이렇게 메서드 추출해 볼 수 있을 것 같네요~
created_at: 2025-03-12T15:55:17Z
author_association: NONE

PR comments:
user: login: supernovaMK
created_at: 2025-03-11T12:53:20Z
body: 안녕하세요 베루스! 리뷰 추가로 요청드리면서 궁금해진 부분에 대해서 의견 들어보고 싶습니다!

이번에 enum 에서 WIN,LOSE,DRAW 를 관리하는 GameResult를 만들어보았습니다.

해당 메서드 안에서, Player와 Dealer 두가지 인자를 받아서 두 객체의 점수를 비교하여 enum을 반환하는 static메서드를 enum class안에 넣게 되었습니다.

```java
package domain;

public enum GameResult {
    WIN("승"),
    LOSE("패"),
    DRAW("무");

    private final String title;

    GameResult(String title) {
        this.title = title;
    }

    public static GameResult from(Player player, Dealer dealer) {
        if (player.isBust()) {
            return LOSE;
        }
        if (dealer.isBust()) {
            return WIN;
        }
        if (dealer.isBlackJack() && player.isBlackJack()) {
            return DRAW;
        }
        if (dealer.isBlackJack()) {
            return LOSE;
        }
        if (player.isBlackJack()) {
            return WIN;
        }

        return getResultByTotalScore(player, dealer);
    }

    private static GameResult getResultByTotalScore(Player player, Dealer dealer) {
        if (dealer.getTotalScore() > player.getTotalScore()) {
            return LOSE;
        }
        if (dealer.getTotalScore() == player.getTotalScore()) {
            return DRAW;
        }
        return WIN;
    }

    public String getTitle() {
        return title;
    }
}


```

이 부분에서 enum클래스가 도메인을 의존하고 있다는 점이 어색한가?라는 고민이 되었지만 지난 미션 과정에서 enum클래스를 하나의 객체 클래스로 사용하며,안에 비즈니스 로직을 가지는 것을 어색해 하지 말자라고 생각을 했었습니다.

이 생각에 대해 베루스는 어떠신가요?
author_association: NONE
user: login: verus-j
created_at: 2025-03-12T15:59:45Z
body: https://github.com/woowacourse/java-blackjack/pull/800#issuecomment-2714097662
이거 답변 드리겠습니다!

> 이 부분에서 enum클래스가 도메인을 의존하고 있다는 점이 어색한가?라는 고민이 되었지만 지난 미션 과정에서 enum클래스를 하나의 객체 클래스로 사용하며,안에 비즈니스 로직을 가지는 것을 어색해 하지 말자라고 생각을 했었습니다.

enum 상수도 객체입니다! 당연히 비즈니스 로직을 가져도 됩니다! 적절한 곳으로 잘 배치했다고 생각합니다~
author_association: NONE