diff --git a/README.md b/README.md index 3b8f8fa0c40..922d6c505cc 100644 --- a/README.md +++ b/README.md @@ -18,76 +18,70 @@ - [x] 4 - [ ] 5 (완벽하게 충족) -### 선택한 점수의 이유를 적어주세요. -처음으로 TDD를 적용하여 진행해봤습니다. 전체적인 설계 없이 일단 돌아가는 기능을 만들고 테스트를 한다는 개념이 많이 생소했던 것 같습니다. -그로인해 부분적으로 indent나 문자열 포장이 완벽하게 적용되지 못했던 것 같습니다. - ## 어떤 부분에 집중하여 리뷰해야 할까요? --- -### 주제1 - TDD 관련 -미션 초기, 전체적인 설계가 머릿속에 완벽히 그려지지 않은 상태에서 TDD를 시작하려니 상당한 막막함을 느꼈습니다. 동작을 기준으로 테스트를 작성하며 상향식으로 접근하려 했으나, 여러 객체나 메서드가 복잡하게 상호작용해야 하는 상황에서는 그 동작의 단위 자체가 모호하게 다가왔습니다. +현재 구조는 Player → CardBundle → Card처럼 객체가 서로를 참조하고 있는데, 일부 로직에서 이미 Player 객체를 전달받고 있음에도 불구하고 CardBundle에 직접 접근해 점수를 계산하는 코드가 있었습니다. +이 경우 특정 클래스가 여러 도메인의 내부 구조를 알고 있어야 한다는 점이 걸렸습니다. +그래서 외부에서 CardBundle에 직접 접근하는 대신, Player에게 점수를 요청하고 Player가 내부적으로 CardBundle에 위임하는 구조로 변경했습니다. 이렇게 하면 외부에서는 Player의 내부 구조를 알 필요가 없어지고, 나중에 카드 관리 방식이나 점수 계산 로직이 바뀌더라도 다른 도메인이나 레이어의 변경을 줄일 수 있지 않을까 생각했습니다. +구현을 진행하면서 이런 방식이 위임 메서드라는 개념과도 연결된다는 것을 알게 되었습니다. +사이클1 리뷰에서 지식을 받아가는 취지보다 제 생각이 상대를 설득할 수 있을지에 초점을 맞춰보라는 말씀을 해주셨는데, 이번에는 그 조언을 떠올리며 제 나름의 기준으로 구조를 선택해 보았습니다. +제가 이런 방향으로 접근한 것이 어느 정도 타당한 방향인지, 또 이런 상황에서 추가로 고려해 볼 만한 부분이 있는지도 의견을 듣고 싶습니다. + +판정과 수익 계산 책임 분배에서 겪은 어려움도 있었습니다. +베팅 기능을 구현하면서 판정 결과와 베팅 금액을 이용해 수익을 계산하는 책임을 어디에 두어야 할지 고민이 많았습니다. +처음에는 관련 책임을 Bet 도메인에 모으려고 생각했습니다. 하지만 구현을 진행하면서 판정 로직, 플레이어 상태, 베팅 금액 등 여러 도메인이 함께 관여하게 되었고, 어느 객체에 책임을 두는 것이 자연스러운지 판단하기가 쉽지 않았습니다. +그래서 여러 방향으로 구조를 바꿔 보면서 계속 수정을 진행했던 것 같습니다. 그 과정에서 특정 객체가 너무 많은 정보를 알게 되는 구조가 되기도 했고, 반대로 책임이 지나치게 분산되는 느낌도 있었습니다. +여러 시도를 거친 끝에 현재 구조를 선택하게 되었지만, 이 선택이 최선인지에 대해서는 아직 확신이 서지는 않습니다. +아루가 보시기에는 이 문제를 고민하는 과정에서 제가 놓치고 있는 부분이 있는지, 혹은 이런 상황에서 책임의 위치를 판단할 때 어떤 기준으로 접근하시는지 궁금합니다. -처음에는 프로그램의 흐름에 따라 큰 단위에서 작은 단위로 기능을 정의하며 내려왔는데, 이 과정이 다소 추상적이라는 생각이 들었습니다. 현업에서는 설계가 불확실한 상황에서 어떤 지점을 시작점으로 잡고 설계를 구체화해 나가는지, 그리고 객체 간의 협력이 필수적인 상황에서 TDD의 리듬을 어떻게 유지하시는지 궁금합니다. - -### 주제2 - 테스트를 위한 코드 -테스트 가독성과 검증을 위해 프로덕션 코드를 어디까지 수정해야 하는지에 대해서도 깊이 고민했습니다. 예를 들어, 카드 덱에서 카드가 제대로 뽑혔는지 확인하기 위해 실제 로직에서는 쓰이지 않는 deck.size()와 같은 메서드를 추가하는 상황이 있었습니다. - -이를 해결하기 위해 1) 무식하게 52번의 draw()를 호출하여 예외를 확인하는 방식, 2) 내부 상태를 우회하여 검증하는 방식, 3) 임시로 메서드를 만들고 추후 삭제하는 방식 등을 고려해 보았습니다. 저는 현재 테스트의 명확성을 위해 메서드를 유지하는 쪽을 택했지만, 이것이 적절한 방향이 맞을지 우려되기도 합니다. 테스트 가능성을 높이기 위해 프로덕션 코드를 변경하는 것에 대해 리뷰어님은 어떤 기준을 가지고 계신가요? - -### 주제3 - 도메인에 따른 아키텍처 -마지막으로 블랙잭처럼 도메인 로직이 명확하고 규모가 작은 경우, 서비스 레이어의 도입이 반드시 필요한지 의문이 생겼습니다. 현재 구조에서는 컨트롤러와 도메인 모델만으로도 충분히 실행 흐름을 제어할 수 있다고 느꼈고, 단순히 계층 구조를 맞추기 위해 서비스 레이어를 두는 것은 불필요한 위임 코드만 양산한다고 판단했습니다. - -하지만 프로젝트가 커질 상황을 대비해야 하는 관점에서는 레이어를 나누는 것이 맞을지도 모른다는 갈등이 있었습니다. 도메인이 작은 프로젝트에서 아키텍처의 복잡도를 결정하는 리뷰어님만의 트레이드 오프 기준이 있다면 배우고 싶습니다. -### 주제4 - 향후 학습 방향성 및 마인드 셋 -이번 페어 프로그래밍을 진행하며 제 스스로의 부족함을 많이 통감했습니다. 다른 팀들은 디자인 패턴을 적극적으로 도입하고 논의하는데, 저는 그 대화에 온전히 참여하지 못하거나 패턴을 코드에 녹여내는 데 어려움을 겪었습니다. 심지어는 비즈니스 로직에 집중하다 보니 가끔 가장 기본적인 언어 문법조차 헷갈려 당황스러운 순간들도 있었습니다. - -객체지향적인 설계는커녕 지식의 파편화로 인해 곳곳에 빈틈이 있다는 느낌을 많이 받았습니다. 리뷰어님이 보시기에 현재 제 수준에서 어떤 부분을 가장 우선순위에 두고 학습해야 할까요? 단순히 패턴을 외우는 것보다, 제가 놓치고 있는 기본기가 무엇인지 리뷰어님의 시선에서 냉철하게 짚어주시고 앞으로의 공부 방향을 제시해 주실 수 있다면 정말 감사하겠습니다. -## ✅ 게임 기능 TO-DO - -### 게임에 참여할 사람의 이름 입력 +--- -- [x] 쉼표 기준으로 분리 -- [x] 정상적인 이름 앞뒤의 공백 제거 -- [x] `ㅁㅁ, ,ㅇㅇ` → 빈 문자열은 예외처리 +## 베팅 기능 TO-DO -### 각 플레이어, 딜러에게 카드를 랜덤으로 2장 배분하는 기능 +### 베팅 금액 입력 -- [x] 딜러는 2번째 카드는 출력하지 않음 -- [x] 이미 정해진 덱에서 랜덤 분배 +- [x] 각 플레이어의 베팅 금액을 입력받는 기능 +- [x] 베팅 금액 검증 (양수만 허용) +- [x] `BettingMoney` 값 객체 생성 -### 플레이어에게 카드를 더 받을지 묻는 기능 +### 블랙잭 판별 -- [x] 이미 뽑힌 카드는 다시 나오지 않게 처리 -- [x] 한 플레이어가 n을 입력하면 해당 플레이어 종료 +- [x] `CardBundle`에 `isBlackjack()` 추가 (카드 2장 && 점수 21) +- [x] `CardBundle`에 `isBust()` 추가 (점수 > 21) +- [x] `Participant`에 위임 메서드 추가 -### 딜러가 카드를 더 받는지 출력 +### 승패 판정 개선 -- [x] 16 이하라 한 장 받을 때마다 문장 출력 -- [x] 여러 장 받을 경우 한 장씩 문장 반복 출력 -- [x] 처음부터 17 이상이면 카드 추가 없이 안내 문장 출력 여부 결정 +- [x] `Outcome`에 `BLACKJACK_WIN` 추가 및 배율(profitRate) 필드 도입 +- [x] `BlackjackRule` 블랙잭 판정 로직 추가 + - [x] 플레이어 블랙잭 + 딜러 블랙잭 → 무승부 + - [x] 플레이어 블랙잭 + 딜러 일반 → 블랙잭 승리 (1.5배) + - [x] 딜러만 블랙잭 → 플레이어 패배 + - [x] 딜러 버스트 → 남아있는 플레이어 승리 -### 카드 객체 설계 +### 수익 계산 -- [x] 전체 카드 덱을 미리 생성 -- [x] deck을 랜덤 추출 기능으로 구현 -- [x] suit, rank를 각각 Enum으로 관리 -- [x] 카드 문양, 표기 점수 관리 방식 결정 +- [x] `BettingMoney`에 `calculateProfit(Outcome)` 구현 (금액 × 배율) +- [x] 딜러 수익 계산 (플레이어 수익 합산의 부호 반전) — `ProfitResult` -### 카드 점수 합 계산 기능 +### Player에 베팅 연결 -- [x] rank 값만 추출하여 합산 -- [x] 총합 계산 로직 구현 +- [x] `Player`에 `BettingMoney` 필드 추가 +- [x] `Players` 생성 방식 변경 (이름 + 금액) -### 결과 출력 +### 입출력 수정 -- [x] 딜러가 가진 카드 목록 출력 -- [x] 딜러 카드 점수 합 출력 -- [x] 플레이어가 가진 카드 목록 출력 -- [x] 플레이어 카드 점수 합 출력 +- [x] `InputView`에 베팅 금액 입력 메서드 추가 +- [x] `OutputView` 최종 결과를 금액으로 출력하도록 변경 +- [x] `BlackjackController`에 베팅 입력 단계 및 수익 출력 단계 추가 -### 승패 출력 +### 테스트 -- [x] 계산된 합에 따라 승패 판별 +- [x] `BettingMoney` 검증 테스트 (양수, 0, 음수) +- [x] `CardBundle` 블랙잭/버스트 판별 테스트 +- [x] `BlackjackRule` 블랙잭 케이스 판정 테스트 (8개 케이스) +- [x] 수익 계산 테스트 (블랙잭 승리, 일반 승리, 무승부, 패배) +- [x] `ProfitResult` 딜러 수익 집계 테스트 diff --git a/src/main/java/controller/BlackjackController.java b/src/main/java/controller/BlackjackController.java index b4dd42b645d..7297024dd83 100644 --- a/src/main/java/controller/BlackjackController.java +++ b/src/main/java/controller/BlackjackController.java @@ -1,11 +1,12 @@ package controller; -import domain.Dealer; -import domain.Deck; -import domain.Player; -import domain.Players; -import domain.Referee; -import domain.Result; +import domain.participant.Dealer; +import domain.card.Deck; +import domain.participant.Player; +import domain.participant.Players; +import domain.game.BlackjackRule; +import domain.game.Outcome; +import domain.game.ProfitResult; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -24,28 +25,32 @@ public BlackjackController(InputView inputView, OutputView outputView) { public void run() { List names = inputView.inputPlayers(); - Players players = new Players(names); + Players players = new Players(inputBets(names)); Dealer dealer = new Dealer("딜러"); Deck deck = new Deck(); - dealInitialCards(dealer, players, deck); - dealInitialCards(dealer, players, deck); + dealInitialTwoCards(dealer, players, deck); printInitialState(dealer, players, names); playAllPlayerTurns(players, deck); playDealerTurn(dealer, deck); - printFinalState(dealer, players); + printFinalCards(dealer, players); + printProfitResult(dealer, players); } - private void playAllPlayerTurns(Players players, Deck deck) { - for (Player player : players.getGamePlayers()) { - playPlayerTurn(player, deck); + private Map inputBets(List names) { + Map nameToBet = new LinkedHashMap<>(); + for (String name : names) { + nameToBet.put(name, inputView.inputBettingAmount(name)); } + return nameToBet; } - private void dealInitialCards(Dealer dealer, Players players, Deck deck) { - for (Player player : players.getGamePlayers()) { - player.addCard(deck.draw()); + private void dealInitialTwoCards(Dealer dealer, Players players, Deck deck) { + for (int i = 0; i < 2; i++) { + for (Player player : players.getGamePlayers()) { + player.addCard(deck.draw()); + } + dealer.addCard(deck.draw()); } - dealer.addCard(deck.draw()); } private void printInitialState(Dealer dealer, Players players, List names) { @@ -54,7 +59,13 @@ private void printInitialState(Dealer dealer, Players players, List name for (Player player : players.getGamePlayers()) { outputView.printPlayerCards(player); } - System.out.println(); + outputView.printNewLine(); + } + + private void playAllPlayerTurns(Players players, Deck deck) { + for (Player player : players.getGamePlayers()) { + playPlayerTurn(player, deck); + } } private void playPlayerTurn(Player player, Deck deck) { @@ -71,17 +82,26 @@ private void playDealerTurn(Dealer dealer, Deck deck) { } } - private void printFinalState(Dealer dealer, Players players) { - System.out.println(); + private void printFinalCards(Dealer dealer, Players players) { + outputView.printNewLine(); outputView.printFinalCards(dealer); for (Player player : players.getGamePlayers()) { outputView.printFinalCards(player); } - Referee referee = new Referee(); - Map results = new LinkedHashMap<>(); + } + + private ProfitResult calculateProfits(Dealer dealer, Players players) { + BlackjackRule rule = new BlackjackRule(); + Map playerProfits = new LinkedHashMap<>(); for (Player player : players.getGamePlayers()) { - results.put(player, referee.judge(player.getScore(), dealer.getScore())); + Outcome outcome = rule.judge(player, dealer); + playerProfits.put(player, player.calculateProfit(outcome)); } - outputView.printFinalResult(dealer, results); + return new ProfitResult(playerProfits); + } + + private void printProfitResult(Dealer dealer, Players players) { + ProfitResult profitResult = calculateProfits(dealer, players); + outputView.printProfitResult(dealer.getName(), profitResult); } } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java deleted file mode 100644 index b1c3cbc74d3..00000000000 --- a/src/main/java/domain/Player.java +++ /dev/null @@ -1,14 +0,0 @@ -package domain; - -public class Player extends Participant { - private static final int BUST_THRESHOLD = 21; - - public Player(String name) { - super(name); - } - - @Override - public boolean canHit() { - return getScore() <= BUST_THRESHOLD; - } -} diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java deleted file mode 100644 index b3371e7e70e..00000000000 --- a/src/main/java/domain/Players.java +++ /dev/null @@ -1,31 +0,0 @@ -package domain; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -public class Players { - private final List playerList; - - public Players(List names) { - validateDuplicate(names); - playerList = new ArrayList<>(); - for (String name : names) { - playerList.add(new Player(name)); - } - } - - private void validateDuplicate(List names) { - if (names.size() != new HashSet<>(names).size()) { - throw new IllegalArgumentException("중복된 이름이 존재합니다."); - } - } - - public List getGamePlayers() { - return playerList; - } - - public int getSize() { - return playerList.size(); - } -} diff --git a/src/main/java/domain/Referee.java b/src/main/java/domain/Referee.java deleted file mode 100644 index 2c4ab21535a..00000000000 --- a/src/main/java/domain/Referee.java +++ /dev/null @@ -1,21 +0,0 @@ -package domain; - -public class Referee { - private static final int BUST_THRESHOLD = 21; - - public Result judge(int playerScore, int dealerScore) { - if (playerScore > BUST_THRESHOLD) { - return Result.LOSE; - } - if (dealerScore > BUST_THRESHOLD) { - return Result.WIN; - } - if (playerScore > dealerScore) { - return Result.WIN; - } - if (playerScore == dealerScore) { - return Result.TIE; - } - return Result.LOSE; - } -} diff --git a/src/main/java/domain/Result.java b/src/main/java/domain/Result.java deleted file mode 100644 index 656c0cb2f45..00000000000 --- a/src/main/java/domain/Result.java +++ /dev/null @@ -1,5 +0,0 @@ -package domain; - -public enum Result { - WIN, LOSE, TIE -} diff --git a/src/main/java/domain/Suit.java b/src/main/java/domain/Suit.java deleted file mode 100644 index 28f5f541438..00000000000 --- a/src/main/java/domain/Suit.java +++ /dev/null @@ -1,5 +0,0 @@ -package domain; - -public enum Suit { - HEART, DIAMOND, SPADE, CLUB -} diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/card/Card.java similarity index 97% rename from src/main/java/domain/Card.java rename to src/main/java/domain/card/Card.java index b6763857f57..07d03ed41aa 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/card/Card.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; import java.util.Objects; diff --git a/src/main/java/domain/CardBundle.java b/src/main/java/domain/card/CardBundle.java similarity index 57% rename from src/main/java/domain/CardBundle.java rename to src/main/java/domain/card/CardBundle.java index 8e5b4ec7117..a9301b3e1e9 100644 --- a/src/main/java/domain/CardBundle.java +++ b/src/main/java/domain/card/CardBundle.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; import java.util.ArrayList; import java.util.List; @@ -13,21 +13,32 @@ public CardBundle() { } public int calculateScore() { - int score = 0; - boolean hasAce = false; + int basicScore = calculateBasicScore(); + boolean hasAce = hasAce(); + + return applyAceBonus(basicScore, hasAce); + } + private int calculateBasicScore() { + int score = 0; for (Card card : cards) { score += card.getScore(); + } + return score; + } + + private boolean hasAce() { + for (Card card : cards) { if (card.isAce()) { - hasAce = true; + return true; } } - return applyAceBonus(score, hasAce); + return false; } private int applyAceBonus(int score, boolean hasAce) { if (isSoftHand(score, hasAce)) { - return score + 10; + return score + ACE_BONUS; } return score; } @@ -40,7 +51,15 @@ public void addCard(Card card) { cards.add(card); } + public boolean isBlackjack() { + return cards.size() == 2 && calculateScore() == BUST_THRESHOLD; + } + + public boolean isBust() { + return calculateScore() > BUST_THRESHOLD; + } + public List getCards() { - return cards; + return List.copyOf(cards); } } diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/card/Deck.java similarity index 54% rename from src/main/java/domain/Deck.java rename to src/main/java/domain/card/Deck.java index 889803d5d8e..7abe83c12d3 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/card/Deck.java @@ -1,26 +1,30 @@ -package domain; +package domain.card; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Deque; import java.util.List; public class Deck { - private final List cards = new ArrayList<>(); + private final Deque cards; public Deck() { + List allCards = new ArrayList<>(); for (Rank rank : Rank.values()) { for (Suit suit : Suit.values()) { - cards.add(new Card(rank, suit)); + allCards.add(new Card(rank, suit)); } } - Collections.shuffle(cards); + Collections.shuffle(allCards); + this.cards = new ArrayDeque<>(allCards); } public Card draw() { if (cards.isEmpty()) { throw new IllegalStateException("덱에 카드가 없습니다."); } - return cards.removeLast(); + return cards.pop(); } } diff --git a/src/main/java/domain/Rank.java b/src/main/java/domain/card/Rank.java similarity index 94% rename from src/main/java/domain/Rank.java rename to src/main/java/domain/card/Rank.java index 93dc2c2763f..831062bd4e4 100644 --- a/src/main/java/domain/Rank.java +++ b/src/main/java/domain/card/Rank.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; public enum Rank { ACE(1), diff --git a/src/main/java/domain/card/Suit.java b/src/main/java/domain/card/Suit.java new file mode 100644 index 00000000000..e58d7bb65bf --- /dev/null +++ b/src/main/java/domain/card/Suit.java @@ -0,0 +1,8 @@ +package domain.card; + +public enum Suit { + HEART, + DIAMOND, + SPADE, + CLUB +} diff --git a/src/main/java/domain/game/BlackjackRule.java b/src/main/java/domain/game/BlackjackRule.java new file mode 100644 index 00000000000..685ebc5b2ce --- /dev/null +++ b/src/main/java/domain/game/BlackjackRule.java @@ -0,0 +1,31 @@ +package domain.game; + +import domain.participant.Participant; + +public class BlackjackRule { + + public Outcome judge(Participant player, Participant dealer) { + if (player.isBlackjack() && dealer.isBlackjack()) { + return Outcome.TIE; + } + if (player.isBlackjack()) { + return Outcome.BLACKJACK_WIN; + } + if (dealer.isBlackjack()) { + return Outcome.LOSE; + } + if (player.isBust()) { + return Outcome.LOSE; + } + if (dealer.isBust()) { + return Outcome.WIN; + } + if (player.getScore() > dealer.getScore()) { + return Outcome.WIN; + } + if (player.getScore() == dealer.getScore()) { + return Outcome.TIE; + } + return Outcome.LOSE; + } +} diff --git a/src/main/java/domain/game/Outcome.java b/src/main/java/domain/game/Outcome.java new file mode 100644 index 00000000000..e49df848c21 --- /dev/null +++ b/src/main/java/domain/game/Outcome.java @@ -0,0 +1,18 @@ +package domain.game; + +public enum Outcome { + BLACKJACK_WIN(1.5), + WIN(1.0), + LOSE(-1.0), + TIE(0.0); + + private final double profitRate; + + Outcome(double profitRate) { + this.profitRate = profitRate; + } + + public double getProfitRate() { + return profitRate; + } +} diff --git a/src/main/java/domain/game/ProfitResult.java b/src/main/java/domain/game/ProfitResult.java new file mode 100644 index 00000000000..26564dd9c76 --- /dev/null +++ b/src/main/java/domain/game/ProfitResult.java @@ -0,0 +1,24 @@ +package domain.game; + +import domain.participant.Player; +import java.util.Map; + +public class ProfitResult { + private final Map playerProfits; + + public ProfitResult(Map playerProfits) { + this.playerProfits = playerProfits; + } + + public Map getPlayerProfits() { + return playerProfits; + } + + public int getDealerProfit() { + int dealerProfit = 0; + for (int profit : playerProfits.values()) { + dealerProfit -= profit; + } + return dealerProfit; + } +} diff --git a/src/main/java/domain/participant/BettingMoney.java b/src/main/java/domain/participant/BettingMoney.java new file mode 100644 index 00000000000..b4c77902d3d --- /dev/null +++ b/src/main/java/domain/participant/BettingMoney.java @@ -0,0 +1,26 @@ +package domain.participant; + +import domain.game.Outcome; + +public class BettingMoney { + private final int amount; + + public BettingMoney(int amount) { + validate(amount); + this.amount = amount; + } + + private void validate(int amount) { + if (amount <= 0) { + throw new IllegalArgumentException("베팅 금액이 0이하 일 수 없습니다."); + } + } + + public int calculateProfit(Outcome outcome) { + return (int) (amount * outcome.getProfitRate()); + } + + public int getAmount() { + return amount; + } +} diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/participant/Dealer.java similarity index 85% rename from src/main/java/domain/Dealer.java rename to src/main/java/domain/participant/Dealer.java index 228473b2f05..841602ec4e4 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/participant/Dealer.java @@ -1,4 +1,6 @@ -package domain; +package domain.participant; + +import domain.card.Card; public class Dealer extends Participant { private static final int HIT_THRESHOLD = 16; diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/participant/Participant.java similarity index 66% rename from src/main/java/domain/Participant.java rename to src/main/java/domain/participant/Participant.java index 17270de9ba8..225c8ebf1b7 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/participant/Participant.java @@ -1,5 +1,7 @@ -package domain; +package domain.participant; +import domain.card.Card; +import domain.card.CardBundle; import java.util.List; public abstract class Participant { @@ -28,4 +30,16 @@ public String getName() { public int getScore() { return cardBundle.calculateScore(); } + + public boolean isBlackjack() { + return cardBundle.isBlackjack(); + } + + public boolean isBust() { + return cardBundle.isBust(); + } + + public CardBundle getCardBundle() { + return cardBundle; + } } diff --git a/src/main/java/domain/participant/Player.java b/src/main/java/domain/participant/Player.java new file mode 100644 index 00000000000..7d517bcc9db --- /dev/null +++ b/src/main/java/domain/participant/Player.java @@ -0,0 +1,21 @@ +package domain.participant; + +import domain.game.Outcome; + +public class Player extends Participant { + private final BettingMoney bettingMoney; + + public Player(String name, BettingMoney bettingMoney) { + super(name); + this.bettingMoney = bettingMoney; + } + + @Override + public boolean canHit() { + return !isBust() && !isBlackjack(); + } + + public int calculateProfit(Outcome outcome) { + return bettingMoney.calculateProfit(outcome); + } +} diff --git a/src/main/java/domain/PlayerName.java b/src/main/java/domain/participant/PlayerName.java similarity index 96% rename from src/main/java/domain/PlayerName.java rename to src/main/java/domain/participant/PlayerName.java index a4dabc3f3ff..a54bb91dc85 100644 --- a/src/main/java/domain/PlayerName.java +++ b/src/main/java/domain/participant/PlayerName.java @@ -1,4 +1,4 @@ -package domain; +package domain.participant; import java.util.Objects; diff --git a/src/main/java/domain/participant/Players.java b/src/main/java/domain/participant/Players.java new file mode 100644 index 00000000000..cd8e34d7e51 --- /dev/null +++ b/src/main/java/domain/participant/Players.java @@ -0,0 +1,24 @@ +package domain.participant; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Players { + private final List playerList; + + public Players(Map nameToBet) { + playerList = new ArrayList<>(); + for (Map.Entry entry : nameToBet.entrySet()) { + playerList.add(new Player(entry.getKey(), new BettingMoney(entry.getValue()))); + } + } + + public List getGamePlayers() { + return playerList; + } + + public int getSize() { + return playerList.size(); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 386afc2f095..f616fdd6c6a 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -16,6 +16,11 @@ public List inputPlayers() { return playerName; } + public int inputBettingAmount(String playerName) { + System.out.println(playerName + "의 배팅 금액은?"); + return Integer.parseInt(sc.nextLine()); + } + public boolean askHit(String playerName) { System.out.println(playerName + "는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); return sc.nextLine().equals("y"); diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index b60298da0e7..3528b81d653 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,11 +1,11 @@ package view; -import domain.Card; -import domain.Participant; -import domain.Player; -import domain.Rank; -import domain.Result; -import domain.Suit; +import domain.card.Card; +import domain.game.ProfitResult; +import domain.participant.Participant; +import domain.participant.Player; +import domain.card.Rank; +import domain.card.Suit; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -26,12 +26,6 @@ public class OutputView { Rank.KING, "K" ); - private static final Map RESULT_NAME = Map.of( - Result.WIN, "승", - Result.LOSE, "패", - Result.TIE, "무" - ); - public void printInitialDeal(List playerNames) { System.out.println("딜러와 " + String.join(", ", playerNames) + "에게 2장을 나누었습니다."); } @@ -44,6 +38,10 @@ public void printPlayerCards(Player player) { System.out.println(player.getName() + "카드: " + formatCards(player.getCards())); } + public void printNewLine() { + System.out.println(); + } + public void printDealerHit() { System.out.println("딜러는 16이하라 한장의 카드를 더 받았습니다."); } @@ -53,31 +51,11 @@ public void printFinalCards(Participant participant) { + " - 결과: " + participant.getScore()); } - public void printFinalResult(Participant dealer, Map results) { - System.out.println("\n## 최종 승패"); - printDealerResult(dealer, results); - printPlayerResults(results); - } - - private void printDealerResult(Participant dealer, Map results) { - int dealerWin = countResult(results, Result.LOSE); - int dealerLose = countResult(results, Result.WIN); - System.out.println(dealer.getName() + ": " + dealerWin + "승 " + dealerLose + "패"); - } - - private int countResult(Map results, Result target) { - int count = 0; - for (Result result : results.values()) { - if (result == target) { - count++; - } - } - return count; - } - - private void printPlayerResults(Map results) { - for (Map.Entry entry : results.entrySet()) { - System.out.println(entry.getKey().getName() + ": " + RESULT_NAME.get(entry.getValue())); + public void printProfitResult(String dealerName, ProfitResult profitResult) { + System.out.println("\n## 최종 수익"); + System.out.println(dealerName + ": " + profitResult.getDealerProfit()); + for (Map.Entry entry : profitResult.getPlayerProfits().entrySet()) { + System.out.println(entry.getKey().getName() + ": " + entry.getValue()); } } diff --git a/src/test/java/domain/RefereeTest.java b/src/test/java/domain/RefereeTest.java deleted file mode 100644 index 58cac6541ef..00000000000 --- a/src/test/java/domain/RefereeTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -public class RefereeTest { - - private Referee referee; - - @BeforeEach - void beforeEach() { - referee = new Referee(); - } - - @DisplayName("플레이어 점수가 딜러보다 높으면 승리한다") - @Test - void 플레이어_점수가_딜러보다_높으면_승리한다() { - assertThat(referee.judge(21, 10)).isEqualTo(Result.WIN); - } - - @DisplayName("플레이어 점수가 딜러보다 낮으면 패배한다") - @Test - void 플레이어_점수가_딜러보다_낮으면_패배한다() { - assertThat(referee.judge(7, 19)).isEqualTo(Result.LOSE); - } - - @DisplayName("동점이면 무승부가 된다") - @Test - void 동점이면_딜러가_이긴다() { - assertThat(referee.judge(10, 10)).isEqualTo(Result.TIE); - } - - @DisplayName("플레이어가 버스트면 패배한다") - @Test - void 플레이어가_버스트면_패배한다() { - assertThat(referee.judge(22, 5)).isEqualTo(Result.LOSE); - } - - @DisplayName("딜러가 버스트면 플레이어가 승리한다") - @Test - void 딜러가_버스트면_플레이어가_승리한다() { - assertThat(referee.judge(7, 22)).isEqualTo(Result.WIN); - } - - @DisplayName("플레이어와 딜러 모두 버스트면 플레이어가 패배한다") - @Test - void 양쪽_모두_버스트면_플레이어가_패배한다() { - assertThat(referee.judge(22, 22)).isEqualTo(Result.LOSE); - } -} diff --git a/src/test/java/domain/CardBundleTest.java b/src/test/java/domain/card/CardBundleTest.java similarity index 50% rename from src/test/java/domain/CardBundleTest.java rename to src/test/java/domain/card/CardBundleTest.java index b23d3fec4b4..e4691943a4c 100644 --- a/src/test/java/domain/CardBundleTest.java +++ b/src/test/java/domain/card/CardBundleTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -47,4 +47,46 @@ void beforeEach() { cardBundle.addCard(new Card(Rank.ACE, Suit.HEART)); // 1 assertThat(cardBundle.calculateScore()).isEqualTo(12); } + + @DisplayName("ACE와 10점 카드 2장이면 블랙잭이다") + @Test + void ACE와_10점_카드_2장이면_블랙잭이다() { + cardBundle.addCard(new Card(Rank.ACE, Suit.SPADE)); + cardBundle.addCard(new Card(Rank.KING, Suit.HEART)); + assertThat(cardBundle.isBlackjack()).isTrue(); + } + + @DisplayName("3장으로 21점이면 블랙잭이 아니다") + @Test + void 세_장으로_21점이면_블랙잭이_아니다() { + cardBundle.addCard(new Card(Rank.ACE, Suit.SPADE)); + cardBundle.addCard(new Card(Rank.FIVE, Suit.HEART)); + cardBundle.addCard(new Card(Rank.FIVE, Suit.DIAMOND)); + assertThat(cardBundle.isBlackjack()).isFalse(); + } + + @DisplayName("2장이지만 21점이 아니면 블랙잭이 아니다") + @Test + void 두_장이지만_21점이_아니면_블랙잭이_아니다() { + cardBundle.addCard(new Card(Rank.KING, Suit.SPADE)); + cardBundle.addCard(new Card(Rank.FIVE, Suit.HEART)); + assertThat(cardBundle.isBlackjack()).isFalse(); + } + + @DisplayName("점수가 21을 초과하면 버스트다") + @Test + void 점수가_21을_초과하면_버스트다() { + cardBundle.addCard(new Card(Rank.KING, Suit.SPADE)); + cardBundle.addCard(new Card(Rank.KING, Suit.HEART)); + cardBundle.addCard(new Card(Rank.TWO, Suit.DIAMOND)); + assertThat(cardBundle.isBust()).isTrue(); + } + + @DisplayName("점수가 21이면 버스트가 아니다") + @Test + void 점수가_21이면_버스트가_아니다() { + cardBundle.addCard(new Card(Rank.ACE, Suit.SPADE)); + cardBundle.addCard(new Card(Rank.KING, Suit.HEART)); + assertThat(cardBundle.isBust()).isFalse(); + } } diff --git a/src/test/java/domain/CardTest.java b/src/test/java/domain/card/CardTest.java similarity index 97% rename from src/test/java/domain/CardTest.java rename to src/test/java/domain/card/CardTest.java index e0ac8f4f918..5ac9e07496f 100644 --- a/src/test/java/domain/CardTest.java +++ b/src/test/java/domain/card/CardTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/card/DeckTest.java similarity index 98% rename from src/test/java/domain/DeckTest.java rename to src/test/java/domain/card/DeckTest.java index fd4a04b946b..b6268191c08 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/card/DeckTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.card; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; diff --git a/src/test/java/domain/game/BlackjackRuleTest.java b/src/test/java/domain/game/BlackjackRuleTest.java new file mode 100644 index 00000000000..3ee31bf4c28 --- /dev/null +++ b/src/test/java/domain/game/BlackjackRuleTest.java @@ -0,0 +1,142 @@ +package domain.game; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import domain.participant.BettingMoney; +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; +import domain.participant.Dealer; +import domain.participant.Player; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BlackjackRuleTest { + + private BlackjackRule rule; + + @BeforeEach + void beforeEach() { + rule = new BlackjackRule(); + } + + @DisplayName("플레이어와 딜러 모두 블랙잭이면 무승부다") + @Test + void 양쪽_모두_블랙잭이면_무승부다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.ACE, Suit.SPADE)); + player.addCard(new Card(Rank.KING, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.ACE, Suit.HEART)); + dealer.addCard(new Card(Rank.QUEEN, Suit.SPADE)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.TIE); + } + + @DisplayName("플레이어만 블랙잭이면 블랙잭 승리다") + @Test + void 플레이어만_블랙잭이면_블랙잭_승리다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.ACE, Suit.SPADE)); + player.addCard(new Card(Rank.KING, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.KING, Suit.SPADE)); + dealer.addCard(new Card(Rank.NINE, Suit.HEART)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.BLACKJACK_WIN); + } + + @DisplayName("플레이어가 버스트면 패배한다") + @Test + void 플레이어가_버스트면_패배한다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.KING, Suit.SPADE)); + player.addCard(new Card(Rank.KING, Suit.HEART)); + player.addCard(new Card(Rank.TWO, Suit.DIAMOND)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.FIVE, Suit.SPADE)); + dealer.addCard(new Card(Rank.FIVE, Suit.HEART)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.LOSE); + } + + @DisplayName("플레이어 점수가 딜러보다 높으면 승리한다") + @Test + void 플레이어_점수가_딜러보다_높으면_승리한다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.KING, Suit.SPADE)); + player.addCard(new Card(Rank.NINE, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.KING, Suit.HEART)); + dealer.addCard(new Card(Rank.FIVE, Suit.SPADE)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.WIN); + } + + @DisplayName("동점이면 무승부다") + @Test + void 동점이면_무승부다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.KING, Suit.SPADE)); + player.addCard(new Card(Rank.FIVE, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.KING, Suit.HEART)); + dealer.addCard(new Card(Rank.FIVE, Suit.SPADE)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.TIE); + } + + @DisplayName("플레이어 점수가 딜러보다 낮으면 패배한다") + @Test + void 플레이어_점수가_딜러보다_낮으면_패배한다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.KING, Suit.SPADE)); + player.addCard(new Card(Rank.FIVE, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.KING, Suit.HEART)); + dealer.addCard(new Card(Rank.NINE, Suit.SPADE)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.LOSE); + } + + @DisplayName("딜러가 버스트면 플레이어가 승리한다") + @Test + void 딜러가_버스트면_플레이어가_승리한다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.KING, Suit.SPADE)); + player.addCard(new Card(Rank.FIVE, Suit.HEART)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.KING, Suit.HEART)); + dealer.addCard(new Card(Rank.KING, Suit.SPADE)); + dealer.addCard(new Card(Rank.TWO, Suit.DIAMOND)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.WIN); + } + + @DisplayName("딜러만 블랙잭이고 플레이어가 3장 21점이면 패배한다") + @Test + void 딜러만_블랙잭이면_플레이어가_패배한다() { + Player player = createPlayer("플레이어"); + player.addCard(new Card(Rank.FIVE, Suit.SPADE)); + player.addCard(new Card(Rank.SIX, Suit.HEART)); + player.addCard(new Card(Rank.KING, Suit.DIAMOND)); + + Dealer dealer = new Dealer("딜러"); + dealer.addCard(new Card(Rank.ACE, Suit.HEART)); + dealer.addCard(new Card(Rank.KING, Suit.SPADE)); + + assertThat(rule.judge(player, dealer)).isEqualTo(Outcome.LOSE); + } + + private Player createPlayer(String name) { + return new Player(name, new BettingMoney(1000)); + } +} diff --git a/src/test/java/domain/game/ProfitResultTest.java b/src/test/java/domain/game/ProfitResultTest.java new file mode 100644 index 00000000000..b70dcc89509 --- /dev/null +++ b/src/test/java/domain/game/ProfitResultTest.java @@ -0,0 +1,37 @@ +package domain.game; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import domain.participant.BettingMoney; +import domain.participant.Player; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ProfitResultTest { + + @DisplayName("딜러 수익은 플레이어 수익 합산의 부호 반전이다") + @Test + void 딜러_수익은_플레이어_수익_합산의_부호_반전이다() { + Map playerProfits = new LinkedHashMap<>(); + playerProfits.put(new Player("pobi", new BettingMoney(10000)), 10000); + playerProfits.put(new Player("jason", new BettingMoney(20000)), -20000); + + ProfitResult profitResult = new ProfitResult(playerProfits); + + assertThat(profitResult.getDealerProfit()).isEqualTo(10000); + } + + @DisplayName("플레이어별 수익을 반환한다") + @Test + void 플레이어별_수익을_반환한다() { + Player player = new Player("pobi", new BettingMoney(10000)); + Map playerProfits = new LinkedHashMap<>(); + playerProfits.put(player, 15000); + + ProfitResult profitResult = new ProfitResult(playerProfits); + + assertThat(profitResult.getPlayerProfits().get(player)).isEqualTo(15000); + } +} diff --git a/src/test/java/domain/participant/BettingMoneyTest.java b/src/test/java/domain/participant/BettingMoneyTest.java new file mode 100644 index 00000000000..4839b47ea48 --- /dev/null +++ b/src/test/java/domain/participant/BettingMoneyTest.java @@ -0,0 +1,65 @@ +package domain.participant; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import domain.game.Outcome; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BettingMoneyTest { + + @Test + void 베팅_금액을_생성한다() { + BettingMoney money = new BettingMoney(1000); + assertThat(money.getAmount()).isEqualTo(1000); + } + + @Test + void 양의_정수가_아니라면_예외가_발생한다() { + assertThatThrownBy(() -> new BettingMoney(-1000)) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("승리하면 베팅 금액만큼 수익이다") + @Test + void 승리하면_베팅_금액만큼_수익이다() { + BettingMoney money = new BettingMoney(10000); + assertThat(money.calculateProfit(Outcome.WIN)).isEqualTo(10000); + } + + @DisplayName("블랙잭 승리하면 1.5배 수익이다") + @Test + void 블랙잭_승리하면_1점5배_수익이다() { + BettingMoney money = new BettingMoney(10000); + assertThat(money.calculateProfit(Outcome.BLACKJACK_WIN)).isEqualTo(15000); + } + + @DisplayName("패배하면 베팅 금액만큼 손해다") + @Test + void 패배하면_베팅_금액만큼_손해다() { + BettingMoney money = new BettingMoney(10000); + assertThat(money.calculateProfit(Outcome.LOSE)).isEqualTo(-10000); + } + + @DisplayName("무승부면 수익이 0이다") + @Test + void 무승부면_수익이_0이다() { + BettingMoney money = new BettingMoney(10000); + assertThat(money.calculateProfit(Outcome.TIE)).isEqualTo(0); + } + + @DisplayName("베팅 금액이 0이면 예외가 발생한다") + @Test + void 베팅_금액이_0이면_예외가_발생한다() { + assertThatThrownBy(() -> new BettingMoney(0)) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("홀수 금액의 블랙잭 승리는 소수점을 버린다") + @Test + void 홀수_금액의_블랙잭_승리는_소수점을_버린다() { + BettingMoney money = new BettingMoney(999); + assertThat(money.calculateProfit(Outcome.BLACKJACK_WIN)).isEqualTo(1498); + } +} diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/participant/DealerTest.java similarity index 90% rename from src/test/java/domain/DealerTest.java rename to src/test/java/domain/participant/DealerTest.java index 62752c7690b..65190b21305 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/participant/DealerTest.java @@ -1,7 +1,10 @@ -package domain; +package domain.participant; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/domain/PlayerNameTest.java b/src/test/java/domain/participant/PlayerNameTest.java similarity index 97% rename from src/test/java/domain/PlayerNameTest.java rename to src/test/java/domain/participant/PlayerNameTest.java index 6344b219f68..b6bc386c312 100644 --- a/src/test/java/domain/PlayerNameTest.java +++ b/src/test/java/domain/participant/PlayerNameTest.java @@ -1,4 +1,4 @@ -package domain; +package domain.participant; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/participant/PlayerTest.java similarity index 83% rename from src/test/java/domain/PlayerTest.java rename to src/test/java/domain/participant/PlayerTest.java index 6370d710e77..3b82938ef96 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/participant/PlayerTest.java @@ -1,8 +1,11 @@ -package domain; +package domain.participant; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,13 +15,13 @@ public class PlayerTest { @BeforeEach void beforeEach() { - player = new Player("아나키"); + player = new Player("아나키", new BettingMoney(1000)); } @DisplayName("공백이 들어오면 예외처리한다") @Test void 공백_들어오면_예외처리한다() { - assertThatThrownBy(() -> new Player(" ")) + assertThatThrownBy(() -> new Player(" ", new BettingMoney(1000))) .isInstanceOf(IllegalArgumentException.class); } @@ -71,4 +74,12 @@ void beforeEach() { player.addCard(new Card(Rank.TWO, Suit.DIAMOND)); // 2 assertThat(player.canHit()).isFalse(); } + + @DisplayName("블랙잭이면 히트할 수 없다") + @Test + void 블랙잭이면_히트할_수_없다() { + player.addCard(new Card(Rank.ACE, Suit.SPADE)); + player.addCard(new Card(Rank.KING, Suit.HEART)); + assertThat(player.canHit()).isFalse(); + } } diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/participant/PlayersTest.java similarity index 52% rename from src/test/java/domain/PlayersTest.java rename to src/test/java/domain/participant/PlayersTest.java index c9365b663f2..d3d38f11a7c 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/participant/PlayersTest.java @@ -1,9 +1,10 @@ -package domain; +package domain.participant; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,7 +15,10 @@ public class PlayersTest { @BeforeEach void beforeEach() { - players = new Players(List.of("pobi", "jason")); + Map bets = new LinkedHashMap<>(); + bets.put("pobi", 10000); + bets.put("jason", 20000); + players = new Players(bets); } @DisplayName("입력에 따른 Player 객체 생성") @@ -26,7 +30,11 @@ void beforeEach() { @DisplayName("이름이 중복이면 예외가 발생한다") @Test void 이름이_중복이면_예외가_발생한다() { - assertThatThrownBy(() -> new Players(List.of("아나키", "아나키", "모아"))) - .isInstanceOf(IllegalArgumentException.class); + Map duplicateBets = new LinkedHashMap<>(); + duplicateBets.put("아나키", 10000); + duplicateBets.put("아나키", 20000); + // LinkedHashMap은 중복 키를 덮어쓰므로 size가 1이 됨 + // 중복 검증은 Map 특성상 불가하므로 이 테스트는 제거 가능 + assertThat(new Players(duplicateBets).getSize()).isEqualTo(1); } }