diff --git a/README.md b/README.md index 1be485c8528..5f31a8de203 100644 --- a/README.md +++ b/README.md @@ -8,35 +8,80 @@ 콘솔 기반으로 즐길 수 있는 블랙잭 게임입니다. +## 💡 주요 용어 + +- **Blackjack**: 처음 받은 카드 2장의 합이 21인 상태. (2장을 초과해 21을 만든 경우는 포함하지 않음) +- **Bust**: 카드의 점수 합이 21을 초과한 상태. (즉시 패배 처리) +- **Hit / Stand**: 카드를 추가로 1장 더 받을지(Hit), 여기서 턴을 종료할지(Stand) 결정하는 행위. + ## 🚀 기능 요구 사항 ### 1. 게임 준비 및 카드 분배 - [x] 참여할 플레이어들의 이름을 쉼표(,) 기준으로 입력받는다. - - 예외: 이름이 공백이거나 한글/영문이 아닌 경우. - - 예외: 최대 참여 인원(5명)을 초과하는 경우. + - [x] 사용자 입력값 유효성 검사 + - [x] 이름 목록이 공백인 경우 예외 처리 + - [x] 이름이 공백인 경우 예외 처리 + - [x] 도메인 규칙 검증 + - [x] 최대 참여 인원(5명)을 초과하는 경우 예외 처리 + - [x] 이름에 한글 또는 영문을 제외한 문자가 포함되는 경우 예외 처리 +- [x] 각 플레이어에게 배팅 금액을 입력받는다. + - [x] 사용자 입력값 유효성 검사 + - [x] 배팅 금액이 공백인 경우 예외 처리 + - [x] 숫자가 아닌 문자를 입력한 경우 예외 처리 + - [x] 배팅 금액이 정수 범위를 초과하는 경우 예외 처리 + - [x] 도메인 규칙 검증 + - [x] 배팅 금액이 0 이하인 경우 예외 처리 - [x] 딜러와 플레이어들에게 무작위로 섞인 카드를 2장씩 나누어 준다. -- [x] 분배된 초기 카드를 출력한다. (딜러는 1장만 공개) - -### 2. 카드 추가 발급 (Hit / Stand) - -- [x] 각 플레이어에게 카드를 더 받을지(y/n) 입력받는다. -- [x] 'y'를 입력하면 카드를 1장 추가로 지급하고 현재 카드를 출력한다. -- [x] 카드의 합이 21을 초과(Bust)하면 더 이상 카드를 받을 수 없다. -- [x] 딜러는 플레이어의 턴이 모두 끝난 후, 카드의 합이 16 이하이면 카드를 1장 더 받는다. (17 이상이면 받지 않음) - -### 3. 점수 계산 및 승패 판정 +- [x] 분배된 초기 카드를 출력한다. + - 딜러는 1장만 공개 + +### 2. 카드 추가 분배 (Hit / Stand) + +- [x] 카드 추가 분배는 다음의 공통 제약 조건 내에서만 가능하다. + - [x] 도메인 규칙 검증 + - [x] 이미 보유하고 있는 카드와 동일한 카드를 중복해서 추가하려는 경우 예외 처리 +- [x] 플레이어는 카드 합계가 21 미만이라면 계속해서 카드를 추가로 받을 수 있다. + - [x] 사용자 입력값 유효성 검사 + - [x] 대답이 공백인 경우 예외 처리 + - [x] `y` 또는 `n` 이외의 값을 입력한 경우 예외 처리 + - [x] 입력값에 따라 Hit / Stand 의사 결정 + - `y` 입력 시: Hit + - `n` 입력 시: Stand +- [x] 딜러는 자신의 카드 합계가 16 이하이면 16을 초과할 때까지 무조건 1장을 더 받는다. + - [x] 도메인 규칙 검증 + - [x] 이미 딜러의 카드 합계가 16을 초과해 더 이상 받을 수 없는 경우 예외 처리 + - [x] 딜러의 카드 합계가 16 이하인 상태로 게임이 종료되는 경우 예외 처리 +- [x] 딜러가 카드를 추가로 받았는지 여부를 출력한다. + +### 3. 점수 계산 및 승패/수익 판정 - [x] J, Q, K는 10으로 계산한다. - [x] Ace(A)는 1 또는 11 중 카드 합산 범위(21)를 초과하지 않는 유리한 방향으로 계산한다. -- [x] 딜러와 플레이어의 점수를 비교하여 최종 승패를 결정한다. - - 딜러가 Bust인 경우, Bust되지 않은 플레이어는 모두 승리한다. - - 점수가 같을 경우 무승부로 처리한다. + - [x] Ace가 1장 포함된 경우 + - [x] Ace를 11점으로 계산해도 Bust가 나지 않는다면, Ace를 11점으로 계산 + - [x] Ace를 11점으로 계산 시 Bust가 난다면, Ace를 1점으로 계산 + - [x] Ace가 2장 이상 포함된 경우 + - 2장 이상을 11로 계산하면 무조건 22점 이상으로 Bust가 나므로, 11로 계산할 수 있는 Ace는 최대 1장 + - [x] 1장의 Ace를 11로 계산했을 때 Bust가 난다면, 모든 Ace를 1점으로 계산 +- [x] 딜러와 플레이어의 최종 승패 및 조건에 따른 플레이어의 최종 수익을 계산한다. + - [x] 플레이어가 승리한 경우 + - [x] 플레이어만 Blackjack인 경우: 배팅 금액의 1.5배 수익 + - [x] 딜러만 Bust인 경우: 배팅 금액의 1배 수익 + - [x] 플레이어와 딜러 모두 Bust가 아니고, 플레이어의 점수가 딜러의 점수보다 높은 경우: 배팅 금액의 1배 수익 + - [x] 플레이어가 패배한 경우: 배팅 금액만큼 손실 (-1배) + - [x] 플레이어가 Bust인 경우 + - [x] 딜러만 Blackjack인 경우 + - [x] 플레이어와 딜러 모두 Bust가 아니고, 플레이어의 점수가 딜러의 점수보다 낮은 경우 + - [x] 무승부인 경우: 수익 0 + - [x] 플레이어와 딜러 모두 Blackjack인 경우 + - [x] 플레이어와 딜러 모두 Bust가 아니고, 플레이어와 딜러의 점수가 동일한 경우 +- [x] 딜러의 수익은 플레이어들의 최종 수익 합의 반대로 계산한다. ### 4. 게임 결과 출력 - [x] 모든 참가자의 최종 카드 목록과 점수를 출력한다. -- [x] 딜러와 플레이어들의 최종 승패(승/무/패)를 출력한다. +- [x] 딜러와 플레이어들의 최종 수익을 출력한다. ## 🗂️ 클래스 다이어그램 @@ -52,8 +97,8 @@ classDiagram +generateGameResult() GameResult } class GameResult { - -Map~Player, Result~ playerResults - -Map~Result, Integer~ dealerResult + -Map~Player, Profit~ playerProfits + -Profit dealerProfit +calculate(Dealer, Players)$ GameResult } class Participant { @@ -68,6 +113,12 @@ classDiagram -String name } class Player { + -BetMoney betMoney + +calculateBettingProfit(double) Long + } + class BetMoney { + <> + -int betAmount } class Dealer { } @@ -93,11 +144,15 @@ classDiagram -String number -int score } - class Result { + class PlayerResult { <> - WIN, TIE, LOSS - +determinePlayerResult(...)$ Result - +reverse() Result + BLACKJACK, WIN, TIE, LOSS + -double returnRate + +determinePlayerResult(Dealer, Player)$ PlayerResult + } + class Profit { + <> + -long profit } class CardShuffleStrategy { <> @@ -112,8 +167,7 @@ classDiagram class BlackJackController { -InputView inputView -OutputView outputView - -CardShuffleStrategy strategy - +doGame() + +doGame(CardShuffleStrategy strategy) } class InputView { -Scanner scanner @@ -124,8 +178,8 @@ classDiagram %% 3. DTO 클래스 선언 class GameResultDto { <> - -Map~String, Integer~ dealerWinTieLossResult - -Map~String, String~ playerWinTieLossResults + -Map~String, Long~ playerWinTieLossResults + -long dealerWinTieLossResult } class ParticipantDto { <> @@ -144,25 +198,25 @@ classDiagram Game --> Deck Game --> Dealer Game --> Players - Game ..> GameResult : generates + Game ..> GameResult: generates Participant <|-- Player Participant <|-- Dealer Participant --> Name Participant --> Cards + Player --> BetMoney Players --> Player Cards --> Card Deck --> Card Card --> CardShape Card --> CardContents - GameResult --> Result + GameResult --> Profit + GameResult ..> PlayerResult: uses CardShuffleStrategy <|.. RandomCardShuffleStrategy - %% 컨트롤러 및 뷰 관계 Main --> BlackJackController BlackJackController --> InputView BlackJackController --> OutputView - BlackJackController --> CardShuffleStrategy - + BlackJackController ..> CardShuffleStrategy: uses %% DTO 관계 - GameResultDto --> ParticipantDto ParticipantDto --> CardDto +``` diff --git a/src/main/java/Main.java b/src/main/java/Main.java index e16bbbe943d..570b363ff7a 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -8,8 +8,9 @@ public class Main { public static void main(String[] args) { InputView inputView = new InputView(); OutputView outputView = new OutputView(); - CardShuffleStrategy strategy = new RandomCardShuffleStrategy(); + BlackJackController controller = new BlackJackController(inputView, outputView); - new BlackJackController(inputView, outputView, strategy).doGame(); + CardShuffleStrategy strategy = new RandomCardShuffleStrategy(); + controller.doGame(strategy); } } diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java deleted file mode 100644 index 25df3bb9232..00000000000 --- a/src/main/java/common/ErrorMessage.java +++ /dev/null @@ -1,20 +0,0 @@ -package common; - -public enum ErrorMessage { - HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."), - DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"), - UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "), - NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), - ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), - MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); - - private final String message; - - ErrorMessage(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } -} diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 14fa17861f2..883342ba89f 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -1,13 +1,16 @@ package controller; +import domain.BetMoney; import domain.CardShuffleStrategy; import domain.Dealer; import domain.Game; +import domain.Name; import domain.Participant; import domain.Player; import domain.Players; import dto.GameResultDto; import dto.ParticipantDto; +import java.util.ArrayList; import java.util.List; import view.InputView; import view.OutputView; @@ -15,16 +18,14 @@ public class BlackJackController { private final InputView inputView; private final OutputView outputView; - private final CardShuffleStrategy strategy; - public BlackJackController(InputView inputView, OutputView outputView, CardShuffleStrategy strategy) { + public BlackJackController(InputView inputView, OutputView outputView) { this.inputView = inputView; this.outputView = outputView; - this.strategy = strategy; } - public void doGame() { - Game game = setUpGame(); + public void doGame(CardShuffleStrategy strategy) { + Game game = setUpGame(strategy); drawInitialCardsAndShowResult(game); @@ -35,12 +36,22 @@ public void doGame() { showGameResult(game); } - private Game setUpGame() { - List playerNames = repeatAskPlayerNamesUntilSuccess(); - return Game.registerParticipantsAndPrepareTotalDeck(playerNames, strategy); + private Game setUpGame(CardShuffleStrategy strategy) { + Players players = generatePlayers(); + return Game.registerParticipantsAndPrepareTotalDeck(players, strategy); } - private List repeatAskPlayerNamesUntilSuccess() { + private Players generatePlayers() { + List playerNames = repeatAskPlayerNamesUntilSuccess(); + List players = new ArrayList<>(); + for (Name playerName : playerNames) { + Player player = generatePlayer(playerName); + players.add(player); + } + return Players.of(players); + } + + private List repeatAskPlayerNamesUntilSuccess() { try { return askPlayerNames(); } catch (IllegalArgumentException e) { @@ -49,9 +60,33 @@ private List repeatAskPlayerNamesUntilSuccess() { } } - private List askPlayerNames() { + private List askPlayerNames() { outputView.printNamePrompt(); - return inputView.readNames(); + List playerNames = inputView.readNames(); + Players.validatePlayersCount(playerNames.size()); + return playerNames.stream() + .map(Name::new) + .toList(); + } + + private Player generatePlayer(Name playerName) { + BetMoney betMoney = repeatAskBetMoneyUntilSuccess(playerName); + return new Player(playerName, betMoney); + } + + private BetMoney repeatAskBetMoneyUntilSuccess(Name playerName) { + try { + return askBetAmount(playerName); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e); + return repeatAskBetMoneyUntilSuccess(playerName); + } + } + + private BetMoney askBetAmount(Name playerName) { + outputView.printBetMoneyPrompt(playerName.name()); + int betAmount = inputView.readBetAmount(); + return new BetMoney(betAmount); } private void drawInitialCardsAndShowResult(Game game) { @@ -77,8 +112,19 @@ private void drawCardPerPlayerAndShowResult(Game game) { } private void drawCardUntilBustOrStand(Game game, Player player) { - while (player.isDrawable() && wantToHit(player)) { - drawCardAndPrintResult(game, player); + boolean isPlayerDrawingCard = player.isDrawable(); + while (isPlayerDrawingCard) { + boolean wantToHit = wantToHit(player); + drawCardIfDrawableAndWantToHit(game, player, wantToHit); + isPlayerDrawingCard = player.isDrawable() && wantToHit; + ParticipantDto updatedPlayerDto = ParticipantDto.from(player); + showPlayerCards(updatedPlayerDto); + } + } + + private void drawCardIfDrawableAndWantToHit(Game game, Player player, boolean wantToHit) { + if (wantToHit) { + game.drawCardUnderCondition(player); } } @@ -98,14 +144,7 @@ private boolean repeatAskDrawCardUntilSuccess(ParticipantDto playerDto) { private boolean askDrawCard(ParticipantDto participantDto) { outputView.printHitOrStandPrompt(participantDto); - String input = inputView.readHitOrStand(); - return input.equals("y"); - } - - private void drawCardAndPrintResult(Game game, Player player) { - game.drawCardUnderCondition(player); - ParticipantDto updatedPlayerDto = ParticipantDto.from(player); - showPlayerCards(updatedPlayerDto); + return inputView.readHitOrStand(); } private void showPlayerCards(ParticipantDto participantDto) { @@ -114,13 +153,19 @@ private void showPlayerCards(ParticipantDto participantDto) { private void checkAndAdjustDealerCards(Game game) { Dealer dealer = game.getDealer(); - boolean hasDealerDrawnMoreCard = game.drawCardUnderCondition(dealer); + while (dealer.isDrawable()) { + boolean hasDealerDrawnMoreCard = game.drawCardUnderCondition(dealer); + printDescriptionIfDealerDrewCard(hasDealerDrawnMoreCard); + } + } + + private void printDescriptionIfDealerDrewCard(boolean hasDealerDrawnMoreCard) { if (hasDealerDrawnMoreCard) { outputView.printAdditionalCardForDealerDescription(); } } - public void showGameResult(Game game) { + private void showGameResult(Game game) { List participants = game.getParticipants(); List participantDtos = ParticipantDto.listOf(participants); outputView.printCardInfosWithSum(participantDtos); diff --git a/src/main/java/domain/BetMoney.java b/src/main/java/domain/BetMoney.java new file mode 100644 index 00000000000..8a32fe1f7e6 --- /dev/null +++ b/src/main/java/domain/BetMoney.java @@ -0,0 +1,13 @@ +package domain; + +public record BetMoney(int betAmount) { + public BetMoney { + validateIsPositive(betAmount); + } + + private void validateIsPositive(int betAmount) { + if (betAmount <= 0) { + throw new IllegalArgumentException("배팅 금액은 양수여야 합니다."); + } + } +} diff --git a/src/main/java/domain/Cards.java b/src/main/java/domain/Cards.java index 007abff0230..64760c7e5b0 100644 --- a/src/main/java/domain/Cards.java +++ b/src/main/java/domain/Cards.java @@ -1,13 +1,12 @@ package domain; import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -public class Cards implements Iterable { +public class Cards { private static final int BUST_CRITERIA = 21; private static final int ACE_EXTRA_SCORE = 10; + private static final int BLACKJACK_CARD_SIZE = 2; private final List cards; @@ -16,9 +15,15 @@ public Cards() { } public void addCard(Card card) { + validateAddable(card); cards.add(card); } + private void validateAddable(Card card) { + if (cards.contains(card)) { + throw new IllegalArgumentException("이미 보유하고 있는 카드입니다. 중복 추가할 수 없습니다."); + } + } public int calculateCardScoreSum() { int scoreSum = getScoreSumWithBasicAceScore(); @@ -48,12 +53,11 @@ public boolean isBust() { return calculateCardScoreSum() > BUST_CRITERIA; } - @Override - public Iterator iterator() { - return Collections.unmodifiableList(cards).iterator(); + public boolean isBlackJack() { + return cards.size() == BLACKJACK_CARD_SIZE && calculateCardScoreSum() == BUST_CRITERIA; } public List getCards() { - return Collections.unmodifiableList(cards); + return List.copyOf(cards); } } diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 19f72f83b5a..f92529b281f 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -4,7 +4,7 @@ public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; - private static final String DEALER_NAME = "딜러"; + private static final Name DEALER_NAME = new Name("딜러"); public Dealer() { super(DEALER_NAME); @@ -20,4 +20,16 @@ public List getInitialVisibleCards() { public boolean isDrawable() { return super.getCardsSum() <= MINIMUM_TOTAL_SCORE; } + + @Override + public void addCard(Card card) { + validateAddable(); + super.addCard(card); + } + + private void validateAddable() { + if (super.getCardsSum() > MINIMUM_TOTAL_SCORE) { + throw new IllegalStateException("딜러는 카드 합계가 16을 초과할 시, 카드를 더 받을 수 없습니다."); + } + } } diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 0a3bdccaa29..02688685054 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -1,6 +1,5 @@ package domain; -import common.ErrorMessage; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; @@ -38,7 +37,7 @@ private static void shuffleCards(List cards, CardShuffleStrategy strategy) public Card drawCard() { if (totalDeck.isEmpty()) { - throw new NoSuchElementException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + throw new NoSuchElementException("더 이상 뽑을 수 있는 카드가 없습니다."); } return totalDeck.removeFirst(); } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 0c718fcb03d..2d35756dcaa 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -14,11 +14,10 @@ private Game(Deck totalDeck, Dealer dealer, Players players) { this.players = players; } - public static Game registerParticipantsAndPrepareTotalDeck(List playerNames, + public static Game registerParticipantsAndPrepareTotalDeck(Players players, CardShuffleStrategy strategy) { Deck totalDeck = Deck.createTotalDeckAndShuffle(strategy); Dealer dealer = new Dealer(); - Players players = Players.of(playerNames); return new Game(totalDeck, dealer, players); } diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index 1695d9d5a0b..25d06df6aaa 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -1,59 +1,55 @@ package domain; import java.util.Collections; -import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.Map; public class GameResult { - private final Map playerResults; - private final Map dealerResult; + private final Map playerProfits; + private final Profit dealerProfit; - private GameResult(Map playerResults, Map dealerResult) { - this.playerResults = playerResults; - this.dealerResult = dealerResult; + private GameResult(Map playerProfits, Profit dealerProfit) { + this.playerProfits = playerProfits; + this.dealerProfit = dealerProfit; } public static GameResult calculate(Dealer dealer, Players players) { - Map playerResults = calculatePlayerResults(dealer, players); - Map dealerResult = calculateDealerResult(playerResults); - return new GameResult(playerResults, dealerResult); + validateCalculatable(dealer); + Map playerProfits = calculatePlayerResults(dealer, players); + Profit dealerProfit = calculateDealerResult(playerProfits); + return new GameResult(playerProfits, dealerProfit); } - private static Map calculatePlayerResults(Dealer dealer, Players players) { - boolean isDealerBust = dealer.isBust(); - int dealerScore = dealer.getCardsSum(); - Map playerWinTieLossResults = new LinkedHashMap<>(); - for (Player player : players) { - Result playerResult = calculatePlayerResult(isDealerBust, dealerScore, player); - playerWinTieLossResults.put(player, playerResult); + private static void validateCalculatable(Dealer dealer) { + if (dealer.isDrawable()) { + throw new IllegalStateException("딜러의 카드 합계가 16 이하이므로 아직 게임 결과를 계산할 수 없습니다."); } - return playerWinTieLossResults; } - private static Result calculatePlayerResult(boolean isDealerBust, int dealerScore, Player player) { - return Result.determinePlayerResult( - isDealerBust, - player.isBust(), - dealerScore, - player.getCardsSum() - ); + private static Map calculatePlayerResults(Dealer dealer, Players players) { + Map playerWinTieLossResults = new LinkedHashMap<>(); + for (Player player : players) { + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + double rate = playerResult.getReturnRate(); + long profit = player.calculateBettingProfit(rate); + playerWinTieLossResults.put(player, new Profit(profit)); + } + return playerWinTieLossResults; } - private static Map calculateDealerResult(Map playerResults) { - Map dealerResult = new EnumMap<>(Result.class); - for (Result playerResult : playerResults.values()) { - Result reversed = playerResult.reverse(); - dealerResult.put(reversed, dealerResult.getOrDefault(reversed, 0) + 1); + private static Profit calculateDealerResult(Map playerProfits) { + long playerBettingProfits = 0; + for (Profit playerProfit : playerProfits.values()) { + playerBettingProfits += playerProfit.profit(); } - return dealerResult; + return new Profit(playerBettingProfits * (-1)); } - public Map getPlayerResults() { - return Collections.unmodifiableMap(playerResults); + public Map getPlayerProfits() { + return Collections.unmodifiableMap(playerProfits); } - public Map getDealerResult() { - return Collections.unmodifiableMap(dealerResult); + public long getDealerProfit() { + return dealerProfit.profit(); } } diff --git a/src/main/java/domain/Name.java b/src/main/java/domain/Name.java index 68bca5286d1..68c591a1c03 100644 --- a/src/main/java/domain/Name.java +++ b/src/main/java/domain/Name.java @@ -1,25 +1,17 @@ package domain; -import common.ErrorMessage; import java.util.regex.Pattern; public record Name(String name) { private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); public Name { - validateIsNotBlank(name); validateKoreanAndEnglish(name); } - private static void validateIsNotBlank(String name) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); - } - } - private static void validateKoreanAndEnglish(String name) { if (!NAME_PATTERN.matcher(name).matches()) { - throw new IllegalArgumentException(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); + throw new IllegalArgumentException("이름은 영어 또는 한국어만 가능합니다."); } } } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 9776c4958a7..9ef10e6b7f5 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -6,8 +6,8 @@ public abstract class Participant { private final Name name; private final Cards cards; - protected Participant(String name) { - this.name = new Name(name); + protected Participant(Name name) { + this.name = name; this.cards = new Cards(); } @@ -27,14 +27,18 @@ public void addCard(Card newCard) { cards.addCard(newCard); } - public int getCardsSum() { - return cards.calculateCardScoreSum(); + public boolean isBlackJack() { + return cards.isBlackJack(); } public String getName() { return name.name(); } + public int getCardsSum() { + return cards.calculateCardScoreSum(); + } + public List getCards() { return cards.getCards(); } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 33645652df2..fdfdb1c5ed1 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -3,9 +3,11 @@ import java.util.List; public class Player extends Participant { + private final BetMoney betMoney; - public Player(String name) { + public Player(Name name, BetMoney betMoney) { super(name); + this.betMoney = betMoney; } @Override @@ -17,4 +19,8 @@ public List getInitialVisibleCards() { public boolean isDrawable() { return super.isLessThanMaxScore(); } + + public Long calculateBettingProfit(double returnRate) { + return (long) (betMoney.betAmount() * returnRate); + } } diff --git a/src/main/java/domain/PlayerResult.java b/src/main/java/domain/PlayerResult.java new file mode 100644 index 00000000000..60015a507f8 --- /dev/null +++ b/src/main/java/domain/PlayerResult.java @@ -0,0 +1,38 @@ +package domain; + +public enum PlayerResult { + BLACKJACK(1.5), WIN(1), TIE(0), LOSS(-1); + + private final double returnRate; + + PlayerResult(double returnRate) { + this.returnRate = returnRate; + } + + public static PlayerResult determinePlayerResult(Dealer dealer, Player player) { + if (player.isBust()) { + return PlayerResult.LOSS; + } + if (player.isBlackJack() && !dealer.isBlackJack()) { + return PlayerResult.BLACKJACK; + } + if (dealer.isBust()) { + return PlayerResult.WIN; + } + return comparePlayerScoreWithDealerScore(dealer.getCardsSum(), player.getCardsSum()); + } + + private static PlayerResult comparePlayerScoreWithDealerScore(int dealerScore, int playerScore) { + if (dealerScore > playerScore) { + return PlayerResult.LOSS; + } + if (dealerScore == playerScore) { + return PlayerResult.TIE; + } + return PlayerResult.WIN; + } + + public double getReturnRate() { + return returnRate; + } +} diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 1d4cffcbbdd..d60d824ae5c 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -1,8 +1,5 @@ package domain; -import common.ErrorMessage; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -12,32 +9,22 @@ public class Players implements Iterable { private final List players; private Players(List players) { - this.players = players; + validatePlayersCount(players.size()); + this.players = List.copyOf(players); } - public static Players of(List playerNames) { - validatePlayersCount(playerNames); - List players = createPlayers(playerNames); + public static Players of(List players) { return new Players(players); } - private static void validatePlayersCount(List playerNames) { - if (playerNames.size() > MAX_PLAYER_NUMBER) { - throw new IllegalArgumentException(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); + public static void validatePlayersCount(int playerCount) { + if (playerCount > MAX_PLAYER_NUMBER) { + throw new IllegalArgumentException("플레이 가능한 최대 인원을 초과했습니다."); } } - private static List createPlayers(List playerNames) { - List players = new ArrayList<>(); - for (String playerName : playerNames) { - Player player = new Player(playerName); - players.add(player); - } - return players; - } - @Override public Iterator iterator() { - return Collections.unmodifiableList(players).iterator(); + return players.iterator(); } } diff --git a/src/main/java/domain/Profit.java b/src/main/java/domain/Profit.java new file mode 100644 index 00000000000..a0927c70994 --- /dev/null +++ b/src/main/java/domain/Profit.java @@ -0,0 +1,4 @@ +package domain; + +public record Profit(long profit) { +} diff --git a/src/main/java/domain/Result.java b/src/main/java/domain/Result.java deleted file mode 100644 index ebd40134174..00000000000 --- a/src/main/java/domain/Result.java +++ /dev/null @@ -1,52 +0,0 @@ -package domain; - -public enum Result { - WIN("승"), TIE("무"), LOSS("패"); - - private final String name; - - Result(String name) { - this.name = name; - } - - public static Result determinePlayerResult( - boolean isDealerBust, - boolean isPlayerBust, - int dealerScore, - int playerScore - ) { - if (isPlayerBust) { - return Result.LOSS; - } - if (isDealerBust) { - return Result.WIN; - } - return comparePlayerScoreWithDealerScore(dealerScore, playerScore); - } - - private static Result comparePlayerScoreWithDealerScore(int dealerScore, int playerScore) { - if (dealerScore > playerScore) { - return Result.LOSS; - } - if (dealerScore == playerScore) { - return Result.TIE; - } - return Result.WIN; - } - - public String getName() { - return name; - } - - public Result reverse() { - if (this == Result.WIN) { - return Result.LOSS; - } - - if (this == Result.LOSS) { - return Result.WIN; - } - - return Result.TIE; - } -} diff --git a/src/main/java/dto/GameResultDto.java b/src/main/java/dto/GameResultDto.java index b6fbfffe30c..cda162c87b5 100644 --- a/src/main/java/dto/GameResultDto.java +++ b/src/main/java/dto/GameResultDto.java @@ -2,32 +2,24 @@ import domain.GameResult; import domain.Player; -import domain.Result; +import domain.Profit; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; -public record GameResultDto(Map dealerWinTieLossResult, - Map playerWinTieLossResults) { +public record GameResultDto(Map playerWinTieLossResults, + long dealerWinTieLossResult) { public static GameResultDto from(GameResult gameResult) { - Map dealerResults = convertDealerResult(gameResult.getDealerResult()); - Map playerResults = convertPlayerResult(gameResult.getPlayerResults()); - return new GameResultDto(dealerResults, playerResults); + Map playerProfits = convertPlayerResult(gameResult.getPlayerProfits()); + long dealerProfit = gameResult.getDealerProfit(); + return new GameResultDto(playerProfits, dealerProfit); } - private static Map convertDealerResult(Map dealerWinTieLossResults) { - Map dealerResults = new LinkedHashMap<>(); - for (Entry entry : dealerWinTieLossResults.entrySet()) { - dealerResults.put(entry.getKey().getName(), entry.getValue()); - } - return dealerResults; - } - - private static Map convertPlayerResult(Map playerWinTieLossResults) { - Map playerResults = new LinkedHashMap<>(); - for (Entry entry : playerWinTieLossResults.entrySet()) { - playerResults.put(entry.getKey().getName(), entry.getValue().getName()); + private static Map convertPlayerResult(Map playerWinTieLossResults) { + Map playerResults = new LinkedHashMap<>(); + for (Entry entry : playerWinTieLossResults.entrySet()) { + playerResults.put(entry.getKey().getName(), entry.getValue().profit()); } return playerResults; } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 1b6e3aa468f..5dbaca8c379 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,37 +1,62 @@ package view; -import common.ErrorMessage; import java.util.Arrays; import java.util.List; import java.util.Scanner; public class InputView { - private static final String DELIMITER = ","; - private static final List HIT_OR_STAND = List.of("y", "n"); private static final Scanner scanner = new Scanner(System.in); public List readNames() { - String line = scanner.nextLine(); + String line = readLineAndTrim(); validateIsBlank(line); - return Arrays.stream(line.split(DELIMITER)).toList(); + List names = Arrays.stream(line.split("\\s*,\\s*")).toList(); + names.forEach(this::validateIsBlank); + return names; } - public String readHitOrStand() { - String input = scanner.nextLine().trim(); - validateIsBlank(input); - validateHitOrStandValue(input); - return input; + private String readLineAndTrim() { + return scanner.nextLine().trim(); + } + + private void validateIsBlank(String input) { + if (input.isBlank()) { + throw new IllegalArgumentException("공백은 허용되지 않습니다"); + } + } + + public int readBetAmount() { + String line = readLineAndTrim(); + validateIsBlank(line); + validateIsNumeric(line); + return validateAndParseToInt(line); + } + + private void validateIsNumeric(String input) { + if (input.matches("-?\\d+")) { + return; + } + throw new IllegalArgumentException("숫자가 아닌 문자를 입력할 수 없습니다."); } - private void validateIsBlank(String line) { - if (line.isBlank()) { - throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + private int validateAndParseToInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("배팅 금액은 정수 범위를 초과할 수 없습니다."); } } - private void validateHitOrStandValue(String input) { - if (!HIT_OR_STAND.contains(input)) { - throw new IllegalArgumentException(ErrorMessage.HIT_OR_STAND_VALUE_MIS_MATCH.getMessage()); + public boolean readHitOrStand() { + String input = readLineAndTrim(); + validateIsBlank(input); + input = input.trim(); + if (input.equals("y")) { + return true; + } + if (input.equals("n")) { + return false; } + throw new IllegalArgumentException("y 혹은 n만 입력 가능합니다."); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 4e011b2f77a..77ddd47933e 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -12,13 +12,18 @@ public class OutputView { private static final String DELIMITER = ", "; public void printErrorMessage(Exception e) { - System.out.println(e.getMessage()); + System.out.println("[ERROR] " + e.getMessage()); } public void printNamePrompt() { System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); } + public void printBetMoneyPrompt(String playerName) { + System.out.println(); + System.out.printf("%s의 배팅 금액은?\n", playerName); + } + public void printInitialCardShare(List playerDtos) { List playerNames = new ArrayList<>(); for (ParticipantDto participantDto : playerDtos) { @@ -36,10 +41,10 @@ public void printInitialCardShareDetail(ParticipantDto dealerDto, List participantDtos) { + System.out.println(); for (ParticipantDto participantDto : participantDtos) { String participantCardInfoWithSum = consistParticipantCardInfoWithSum(participantDto); System.out.println(participantCardInfoWithSum); } - System.out.println(); } private String consistParticipantCardInfoWithSum(ParticipantDto participantDto) { @@ -82,7 +86,8 @@ private List getCardInfos(ParticipantDto participantDto) { } public void printWinTieLossResult(GameResultDto gameResultDto) { - System.out.println("## 최종 승패"); + System.out.println(); + System.out.println("## 최종 수익"); String dealerWinTieLossResult = consistDealerWinTieLossResult(gameResultDto.dealerWinTieLossResult()); System.out.println(dealerWinTieLossResult); @@ -93,24 +98,17 @@ public void printWinTieLossResult(GameResultDto gameResultDto) { } } - private String consistDealerWinTieLossResult(Map dealerWinTieLossResult) { - List dealerResult = new ArrayList<>(); - for (Entry result : dealerWinTieLossResult.entrySet()) { - int count = result.getValue(); - String winTieLoss = result.getKey(); - String resultInFormat = String.format("%d%s", count, winTieLoss); - dealerResult.add(resultInFormat); - } - return String.format("딜러: %s", String.join(" ", dealerResult)); + private String consistDealerWinTieLossResult(Long dealerWinTieLossResult) { + return String.format("딜러: %d", dealerWinTieLossResult); } private List consistPlayerWinTieLossResults(GameResultDto gameResultDto) { List playerResult = new ArrayList<>(); - Map playerWinLossResults = gameResultDto.playerWinTieLossResults(); - for (Entry result : playerWinLossResults.entrySet()) { + Map playerWinLossResults = gameResultDto.playerWinTieLossResults(); + for (Entry result : playerWinLossResults.entrySet()) { String name = result.getKey(); - String winTieLoss = result.getValue(); - String resultInFormat = String.format("%s: %s", name, winTieLoss); + Long winTieLoss = result.getValue(); + String resultInFormat = String.format("%s: %d", name, winTieLoss); playerResult.add(resultInFormat); } return playerResult; diff --git a/src/test/java/domain/BetMoneyTest.java b/src/test/java/domain/BetMoneyTest.java new file mode 100644 index 00000000000..d68a9f81392 --- /dev/null +++ b/src/test/java/domain/BetMoneyTest.java @@ -0,0 +1,33 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BetMoneyTest { + @Test + @DisplayName("양수를 입력 받으면 베팅 금액 객체를 정상적으로 생성한다.") + void shouldReturnBetMoneyForValidBetAmount() { + // given + int betAmount = 1000; + + // when + BetMoney betMoney = new BetMoney(betAmount); + + // then + assertThat(betMoney.betAmount()).isEqualTo(betAmount); + } + + @ParameterizedTest + @DisplayName("0 이하의 수를 입력 받으면 예외가 발생한다.") + @ValueSource(ints = {0, -5000}) + void shouldThrowExceptionForInvalidBetAmount(int betAmount) { + // when & then + assertThatThrownBy(() -> new BetMoney(betAmount)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/domain/CardsTest.java b/src/test/java/domain/CardsTest.java index 1cab9fd6559..859f44a5aee 100644 --- a/src/test/java/domain/CardsTest.java +++ b/src/test/java/domain/CardsTest.java @@ -1,6 +1,7 @@ package domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -9,15 +10,114 @@ import org.junit.jupiter.api.Test; class CardsTest { + private Cards createCardsWithCards(Card... cards) { + Cards result = new Cards(); + for (Card card : cards) { + result.addCard(card); + } + return result; + } + + @Nested + class AddCardTest { + @Test + @DisplayName("카드 합계가 21점 미만일 때 카드를 정상적으로 추가할 수 있다.") + void shouldAddCardWhenDeckSumLessThanMaximum() { + // given + Cards cards = new Cards(); + cards.addCard(new Card(CardShape.SPADE, CardContents.TWO)); + + // when + cards.addCard(new Card(CardShape.HEART, CardContents.THREE)); + + // then + assertThat(cards.getCards()).hasSize(2); + } + + @Test + @DisplayName("카드 목록에 이미 존재하는 카드를 중복으로 추가하면 예외가 발생한다.") + void shouldThrowExceptionForDuplicatedCard() { + // given + Cards cards = createCardsWithCards( + new Card(CardShape.DIAMOND, CardContents.TWO) + ); + + Card duplicatedCard = new Card(CardShape.DIAMOND, CardContents.TWO); + + // when & then + assertThatThrownBy(() -> cards.addCard(duplicatedCard)) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Nested + class CalculateCardScoreSumTest { + @Test + @DisplayName("Ace가 없는 경우 카드 점수의 합을 정확히 계산한다.") + void shouldReturnScoreSumWithoutAce() { + // given + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN) + ); + + // when & then + assertThat(cards.calculateCardScoreSum()).isEqualTo(20); + } + + @Test + @DisplayName("Ace가 포함되어 있고 11점으로 계산해도 버스트가 나지 않는다면, Ace를 11점으로 계산한다.") + void shouldReturnScoreSumWithAceCalculatedAsElevenWhenNotBust() { + // given + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.A) + ); + + // when & then + assertThat(cards.calculateCardScoreSum()).isEqualTo(21); + } + + @Test + @DisplayName("Ace가 포함되어 있으나 11점으로 계산 시 버스트가 난다면, Ace를 1점으로 계산한다.") + void shouldReturnScoreSumWithAceCalculatedAsOneWhenElevenCausesBust() { + // given + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.HEART, CardContents.A) + ); + + // when & then + assertThat(cards.calculateCardScoreSum()).isEqualTo(21); + } + + @Test + @DisplayName("Ace가 2장 이상일 때, 최대 1장만 11점으로 계산하고 나머지는 1점으로 계산한다.") + void shouldReturnScoreSumWithMultiplyAces() { + // given + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.A), + new Card(CardShape.HEART, CardContents.A), + new Card(CardShape.CLOVER, CardContents.A), + new Card(CardShape.DIAMOND, CardContents.TEN) + ); + + // when & then + assertThat(cards.calculateCardScoreSum()).isEqualTo(13); + } + } + @Nested class IsLessThanMaxScoreTest { @Test @DisplayName("카드의 합이 21점 미만이면 true를 반환한다.") void shouldReturnTrueWhenDeckSumLessThanMaximum() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN) + ); // when & then assertTrue(cards.isLessThanMaxScore()); @@ -27,10 +127,11 @@ void shouldReturnTrueWhenDeckSumLessThanMaximum() { @DisplayName("카드의 합이 정확히 21점이면 false를 반환한다.") void shouldReturnFalseWhenDeckSumEqualsMaximum() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - cards.addCard(new Card(CardShape.CLOVER, CardContents.A)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.A) + ); // when & then assertFalse(cards.isLessThanMaxScore()); @@ -40,10 +141,11 @@ void shouldReturnFalseWhenDeckSumEqualsMaximum() { @DisplayName("카드의 합이 21점을 초과하면 false를 반환한다.") void shouldReturnFalseWhenDeckSumOverMaximum() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - cards.addCard(new Card(CardShape.CLOVER, CardContents.TWO)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.TWO) + ); // when & then assertFalse(cards.isLessThanMaxScore()); @@ -56,10 +158,11 @@ class IsBustTest { @DisplayName("카드의 합이 21을 초과하면 버스트로 판정한다.") void shouldReturnTrueWhenDeckSumOverMaximum() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - cards.addCard(new Card(CardShape.CLOVER, CardContents.TEN)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.TEN) + ); // when & then assertTrue(cards.isBust()); @@ -69,10 +172,11 @@ void shouldReturnTrueWhenDeckSumOverMaximum() { @DisplayName("카드의 합이 21이하라면 버스트로 판정하지 않는다.") void shouldReturnFalseWhenDeckSumEqualsMaximumOrLess() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.A)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.HEART, CardContents.A) + ); // when & then assertFalse(cards.isBust()); @@ -80,56 +184,32 @@ void shouldReturnFalseWhenDeckSumEqualsMaximumOrLess() { } @Nested - class CalculateCardScoreSumTest { - @Test - @DisplayName("Ace가 없는 경우 카드 점수의 합을 정확히 계산한다.") - void shouldReturnScoreSumWithoutAce() { - // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - - // when & then - assertThat(cards.calculateCardScoreSum()).isEqualTo(20); - } - + class IsBlackJackTest { @Test - @DisplayName("Ace가 포함되어 있고 11점으로 계산해도 버스트가 나지 않는다면, Ace를 11점으로 계산한다.") - void shouldReturnScoreSumWithAceCalculatedAsElevenWhenNotBust() { + @DisplayName("카드가 두장이고, 카드의 합이 21일 때만 블랙잭으로 판정한다.") + void shouldReturnTrueWhenTwoCardsSumEqualsTwentyOne() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.A)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.HEART, CardContents.A) + ); // when & then - assertThat(cards.calculateCardScoreSum()).isEqualTo(21); + assertTrue(cards.isBlackJack()); } @Test - @DisplayName("Ace가 포함되어 있으나 11점으로 계산 시 버스트가 난다면, Ace를 1점으로 계산한다.") - void shouldReturnScoreSumWithAceCalculatedAsOneWhenElevenCausesBust() { + @DisplayName("카드가 두장 이상이고, 카드의 합이 21이라면 블랙잭으로 판정하지 않는다.") + void shouldReturnTrueWhenMoreThanTwoCardsSumEqualsTwentyOne() { // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.TEN)); - cards.addCard(new Card(CardShape.HEART, CardContents.A)); + Cards cards = createCardsWithCards( + new Card(CardShape.SPADE, CardContents.FIVE), + new Card(CardShape.HEART, CardContents.FIVE), + new Card(CardShape.HEART, CardContents.A) + ); // when & then - assertThat(cards.calculateCardScoreSum()).isEqualTo(21); - } - - @Test - @DisplayName("Ace가 2장 이상일 때, 최대 1장만 11점으로 계산하고 나머지는 1점으로 계산한다.") - void shouldReturnScoreSumWithMultiplyAces() { - // given - Cards cards = new Cards(); - cards.addCard(new Card(CardShape.SPADE, CardContents.A)); - cards.addCard(new Card(CardShape.HEART, CardContents.A)); - cards.addCard(new Card(CardShape.CLOVER, CardContents.A)); - cards.addCard(new Card(CardShape.DIAMOND, CardContents.TEN)); - - // when & then - assertThat(cards.calculateCardScoreSum()).isEqualTo(13); + assertFalse(cards.isBlackJack()); } } } diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index c9f6203aa02..75dcbc18dbc 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -1,10 +1,13 @@ package domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; public class DealerTest { @@ -29,30 +32,102 @@ void shouldReturnSingleCardForInitialVisibleCards() { .containsExactly(card1); } - @Test - @DisplayName("딜러는 카드의 합이 16 이하면 카드를 더 뽑을 수 있다.") - void shouldReturnTrueWhenCardScoreSumIsMinimumOrLess() { - // given - Dealer testDealer = createDealerWithCards( - new Card(CardShape.HEART, CardContents.FIVE), - new Card(CardShape.HEART, CardContents.SIX) - ); + @Nested + class IsDrawableTest { + @Test + @DisplayName("딜러는 카드의 합이 16 미만이면 카드를 더 뽑을 수 있다.") + void shouldReturnTrueWhenCardScoreSumLessThanMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.FIVE), + new Card(CardShape.HEART, CardContents.SIX) + ); - // when & then - assertTrue(testDealer.isDrawable()); + // when & then + assertTrue(testDealer.isDrawable()); + } + + @Test + @DisplayName("딜러는 카드의 합이 16이면 카드를 더 뽑을 수 있다.") + void shouldReturnTrueWhenCardScoreSumEqualsMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.HEART, CardContents.SIX) + ); + + // when & then + assertTrue(testDealer.isDrawable()); + } + + @Test + @DisplayName("딜러는 카드의 합이 16을 초과하면 카드를 더 이상 뽑을 수 없다.") + void shouldReturnFalseWheCardScoreSumOverMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.SIX), + new Card(CardShape.SPADE, CardContents.FIVE) + ); + + // when & then + assertFalse(testDealer.isDrawable()); + } } - @Test - @DisplayName("딜러는 카드의 합이 16 이상이면 카드를 더 이상 뽑을 수 없다.") - void shouldReturnFalseWhenNotBust() { - // given - Dealer testDealer = createDealerWithCards( - new Card(CardShape.HEART, CardContents.TEN), - new Card(CardShape.CLOVER, CardContents.TEN), - new Card(CardShape.SPADE, CardContents.TEN) - ); + @Nested + class AddCardTest { + @Test + @DisplayName("딜러는 카드의 합이 16 미만일 때 카드를 정상적으로 추가할 수 있다.") + void shouldAddCardWhenDeckSumLessThanMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.FIVE), + new Card(CardShape.HEART, CardContents.SIX) + ); + Card newCard = new Card(CardShape.CLOVER, CardContents.SIX); - // when & then - assertFalse(testDealer.isDrawable()); + // when + testDealer.addCard(newCard); + + // then + List cards = testDealer.getCards(); + assertThat(cards).hasSize(3); + assertThat(cards).contains(newCard); + } + + @Test + @DisplayName("딜러는 카드의 합이 16일 때 카드를 정상적으로 추가할 수 있다.") + void shouldAddCardWhenDeckSumEqualsMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.HEART, CardContents.SIX) + ); + Card newCard = new Card(CardShape.CLOVER, CardContents.SIX); + + // when + testDealer.addCard(newCard); + + // then + List cards = testDealer.getCards(); + assertThat(cards).hasSize(3); + assertThat(cards).contains(newCard); + } + + @Test + @DisplayName("딜러가 카드의 합이 16을 초과한 상태로 카드를 뽑으려고 하면 예외가 발생한다.") + void shouldThrowExceptionWhenDeckSumOverMinimum() { + // given + Dealer testDealer = createDealerWithCards( + new Card(CardShape.HEART, CardContents.TEN), + new Card(CardShape.HEART, CardContents.EIGHT) + ); + Card newCard = new Card(CardShape.CLOVER, CardContents.SIX); + + // when & then + assertThatThrownBy(() -> testDealer.addCard(newCard)) + .isInstanceOf(IllegalStateException.class); + } } } diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index c8646c5a78a..eb34c6cd016 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import common.ErrorMessage; import java.util.NoSuchElementException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,8 +22,7 @@ void shouldReturnTotalDeckWithAllCards() { // then assertThatThrownBy(totalDeck::drawCard) - .isInstanceOf(NoSuchElementException.class) - .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + .isInstanceOf(NoSuchElementException.class); } @Test @@ -43,7 +41,6 @@ void shouldReturnSingleCardAndRemoveCardFromDeck() { // then assertThat(result).isEqualTo(expected); assertThatThrownBy(totalDeck::drawCard) - .isInstanceOf(NoSuchElementException.class) - .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + .isInstanceOf(NoSuchElementException.class); } } diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java index 6d02d9f346f..2d96782335a 100644 --- a/src/test/java/domain/GameResultTest.java +++ b/src/test/java/domain/GameResultTest.java @@ -1,73 +1,121 @@ 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.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class GameResultTest { + + private Players createDummyPlayer(List playerNames) { + List players = new ArrayList<>(); + for (String playerName : playerNames) { + Player player = new Player(new Name(playerName), new BetMoney(1000)); + players.add(player); + } + return Players.of(players); + } + + private void addCardsToParticipantDeck(Participant participant, Card... cards) { + for (Card card : cards) { + participant.addCard(card); + } + } + @Test @DisplayName("최종 승패 결과 계산이 정확하게 수행된다.") void shouldReturnWinTieLossResult() { // given Dealer dealer = new Dealer(); - addCardsToPlayerDeck(dealer, + addCardsToParticipantDeck(dealer, new Card(CardShape.HEART, CardContents.J), new Card(CardShape.HEART, CardContents.EIGHT) ); List playerNames = List.of("pobi", "terry", "rati", "gump"); - Players players = Players.of(playerNames); + Players players = createDummyPlayer(playerNames); Iterator playerIterator = players.iterator(); Player player1 = playerIterator.next(); - addCardsToPlayerDeck(player1, + addCardsToParticipantDeck(player1, new Card(CardShape.DIAMOND, CardContents.J), - new Card(CardShape.DIAMOND, CardContents.THREE) + new Card(CardShape.DIAMOND, CardContents.A) ); Player player2 = playerIterator.next(); - addCardsToPlayerDeck(player2, + addCardsToParticipantDeck(player2, new Card(CardShape.SPADE, CardContents.J), new Card(CardShape.SPADE, CardContents.THREE) ); Player player3 = playerIterator.next(); - addCardsToPlayerDeck(player3, + addCardsToParticipantDeck(player3, new Card(CardShape.CLOVER, CardContents.J), new Card(CardShape.CLOVER, CardContents.NINE) ); Player player4 = playerIterator.next(); - addCardsToPlayerDeck(player4, + addCardsToParticipantDeck(player4, new Card(CardShape.HEART, CardContents.K), new Card(CardShape.HEART, CardContents.NINE) ); - Map expectPlayerWinLossResults = new LinkedHashMap<>(); - expectPlayerWinLossResults.put(player1, Result.LOSS); - expectPlayerWinLossResults.put(player2, Result.LOSS); - expectPlayerWinLossResults.put(player3, Result.WIN); - expectPlayerWinLossResults.put(player4, Result.WIN); + Map expectPlayerWinLossResults = new LinkedHashMap<>(); + expectPlayerWinLossResults.put(player1, new Profit(1500L)); + expectPlayerWinLossResults.put(player2, new Profit(-1000L)); + expectPlayerWinLossResults.put(player3, new Profit(1000L)); + expectPlayerWinLossResults.put(player4, new Profit(1000L)); - Map expectDealerWinLossResults = new LinkedHashMap<>(); - expectDealerWinLossResults.put(Result.WIN, 2); - expectDealerWinLossResults.put(Result.LOSS, 2); + long expectDealerWinLossResults = -2500L; // when GameResult gameResult = GameResult.calculate(dealer, players); - Map playerResults = gameResult.getPlayerResults(); - Map dealerResults = gameResult.getDealerResult(); + Map playerResults = gameResult.getPlayerProfits(); + long dealerResults = gameResult.getDealerProfit(); // then - assertThat(playerResults).containsAllEntriesOf(expectPlayerWinLossResults); - assertThat(dealerResults).containsAllEntriesOf(expectDealerWinLossResults); + assertThat(playerResults).isEqualTo(expectPlayerWinLossResults); + assertThat(dealerResults).isEqualTo(expectDealerWinLossResults); } - private void addCardsToPlayerDeck(Participant participant, Card... cards) { - for (Card card : cards) { - participant.addCard(card); + @Nested + class GenerateGameResultTest { + @Test + @DisplayName("딜러의 카드 합계가 16 미만인 상태로 게임 결과 계산을 시도하는 경우 예외가 발생한다.") + void shouldThrowExceptionWhenDealerScoreUnderMinimum() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TWO), + new Card(CardShape.CLOVER, CardContents.THREE) + ); + List playerNames = List.of("pobi", "terry"); + Players players = createDummyPlayer(playerNames); + + // when & then + assertThatThrownBy(() -> GameResult.calculate(dealer, players)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + @DisplayName("딜러의 카드 합계가 16인 상태로 게임 결과 계산을 시도하는 경우 예외가 발생한다.") + void shouldThrowExceptionWhenDealerScoreEqualsMinimum() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.SIX) + ); + List playerNames = List.of("pobi", "terry"); + Players players = createDummyPlayer(playerNames); + + // when & then + assertThatThrownBy(() -> GameResult.calculate(dealer, players)) + .isInstanceOf(IllegalStateException.class); } } } diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index ea329ce927f..78a868443ae 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -14,9 +15,24 @@ class GameTest { }; private Game game; + private Players createDummyPlayer() { + List players = new ArrayList<>(); + for (String playerName : PLAYER_NAMES) { + Player player = new Player(new Name(playerName), new BetMoney(1000)); + players.add(player); + } + return Players.of(players); + } + + private void addCardsToParticipantDeck(Participant participant, Card... cards) { + for (Card card : cards) { + participant.addCard(card); + } + } + @BeforeEach void setUp() { - game = Game.registerParticipantsAndPrepareTotalDeck(PLAYER_NAMES, FIXED_SHUFFLE_STRATEGY); + game = Game.registerParticipantsAndPrepareTotalDeck(createDummyPlayer(), FIXED_SHUFFLE_STRATEGY); } @Test @@ -45,13 +61,13 @@ void shouldReadyParticipantDecksWithTwoInitialCards() { void shouldReturnTrueWhenParticipantsCanDrawCardUnderCondition() { // given Dealer dealer = game.getDealer(); - addCardsToPlayerDeck(dealer, + addCardsToParticipantDeck(dealer, new Card(CardShape.SPADE, CardContents.TWO), new Card(CardShape.CLOVER, CardContents.THREE) ); Player player = game.getPlayers().iterator().next(); - addCardsToPlayerDeck(player, + addCardsToParticipantDeck(player, new Card(CardShape.HEART, CardContents.TWO), new Card(CardShape.HEART, CardContents.THREE) ); @@ -64,10 +80,4 @@ void shouldReturnTrueWhenParticipantsCanDrawCardUnderCondition() { assertTrue(dealerResult); assertTrue(playerResult); } - - private void addCardsToPlayerDeck(Participant participant, Card... cards) { - for (Card card : cards) { - participant.addCard(card); - } - } } diff --git a/src/test/java/domain/NameTest.java b/src/test/java/domain/NameTest.java index 782dc7f3239..ce3b1d141a2 100644 --- a/src/test/java/domain/NameTest.java +++ b/src/test/java/domain/NameTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; class NameTest { @@ -22,8 +21,7 @@ void shouldReturnNameForKoreanOrEnglishName(String name) { @ParameterizedTest @DisplayName("이름이 공백이거나 공백이나 특수문자가 포함된 경우 오류가 발생한다.") - @NullAndEmptySource - @ValueSource(strings = {" ", "공 백문자포함", "특수문자포함!"}) + @ValueSource(strings = {"", " ", "공 백문자포함", "특수문자포함!"}) void shouldThrowExceptionForInvalidName(String name) { // when & then assertThatThrownBy( diff --git a/src/test/java/domain/ParticipantTest.java b/src/test/java/domain/ParticipantTest.java index 1de3a50e2a8..a1eade98e65 100644 --- a/src/test/java/domain/ParticipantTest.java +++ b/src/test/java/domain/ParticipantTest.java @@ -11,8 +11,7 @@ class ParticipantTest { @DisplayName("참가자가 카드를 뽑으면, 참가자의 덱에 카드가 추가된다.") void shouldAddCardToParticipantDeck() { // given - String name = "테스트플레이어"; - TestParticipant testParticipant = new TestParticipant(name); + TestParticipant testParticipant = new TestParticipant(new Name("참가자")); Card card = new Card(CardShape.SPADE, CardContents.A); // when @@ -23,7 +22,7 @@ void shouldAddCardToParticipantDeck() { } private static class TestParticipant extends Participant { - public TestParticipant(String name) { + protected TestParticipant(Name name) { super(name); } diff --git a/src/test/java/domain/PlayerResultTest.java b/src/test/java/domain/PlayerResultTest.java new file mode 100644 index 00000000000..93ad9a21fe1 --- /dev/null +++ b/src/test/java/domain/PlayerResultTest.java @@ -0,0 +1,136 @@ +package domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class PlayerResultTest { + private void addCardsToParticipantDeck(Participant participant, Card... cards) { + for (Card card : cards) { + participant.addCard(card); + } + } + + @Nested + class DeterminePlayerResultTest { + @Test + @DisplayName("플레이어가 버스트라면 딜러의 버스트 여부와 상관없이 무조건 패배한다.") + void shouldReturnLossWhenPlayerIsBust() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.THREE), + new Card(CardShape.HEART, CardContents.TEN) + ); + + Player player = new Player(new Name("플레이어"), new BetMoney(1000)); + addCardsToParticipantDeck(player, + new Card(CardShape.DIAMOND, CardContents.TEN), + new Card(CardShape.HEART, CardContents.THREE), + new Card(CardShape.SPADE, CardContents.TEN) + ); + + // when + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + + // then + assertThat(playerResult).isEqualTo(PlayerResult.LOSS); + } + + @Test + @DisplayName("플레이어는 버스트가 아니고 딜러만 버스트인 경우 승리를 반환한다.") + void shouldReturnWinWhenPlayerIsNotBustButDealerIsBust() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.THREE), + new Card(CardShape.HEART, CardContents.TEN) + ); + + Player player = new Player(new Name("플레이어"), new BetMoney(1000)); + addCardsToParticipantDeck(player, + new Card(CardShape.DIAMOND, CardContents.TEN), + new Card(CardShape.HEART, CardContents.THREE) + ); + + // when + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + + // then + assertThat(playerResult).isEqualTo(PlayerResult.WIN); + } + + @Test + @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수가 딜러 점수보다 높으면 승리를 반환한다.") + void shouldReturnWinWhenNeitherIsBustAndPlayerScoreIsHigherThanDealerScore() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.SEVEN) + ); + + Player player = new Player(new Name("플레이어"), new BetMoney(1000)); + addCardsToParticipantDeck(player, + new Card(CardShape.DIAMOND, CardContents.TEN), + new Card(CardShape.HEART, CardContents.EIGHT) + ); + + // when + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + + // then + assertThat(playerResult).isEqualTo(PlayerResult.WIN); + } + + @Test + @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수가 딜러 점수보다 낮으면 패배를 반환한다.") + void shouldReturnLossWhenNeitherIsBustAndPlayerScoreIsLowerThanDealerScore() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.TEN) + ); + + Player player = new Player(new Name("플레이어"), new BetMoney(1000)); + addCardsToParticipantDeck(player, + new Card(CardShape.DIAMOND, CardContents.TEN), + new Card(CardShape.HEART, CardContents.NINE) + ); + + // when + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + + // then + assertThat(playerResult).isEqualTo(PlayerResult.LOSS); + } + + @Test + @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수와 딜러 점수가 같다면 무승부를 반환한다.") + void shouldReturnTieWhenNeitherIsBustAndScoresAreEqual() { + // given + Dealer dealer = new Dealer(); + addCardsToParticipantDeck(dealer, + new Card(CardShape.SPADE, CardContents.TEN), + new Card(CardShape.CLOVER, CardContents.SEVEN) + ); + + Player player = new Player(new Name("플레이어"), new BetMoney(1000)); + addCardsToParticipantDeck(player, + new Card(CardShape.DIAMOND, CardContents.TEN), + new Card(CardShape.HEART, CardContents.SEVEN) + ); + + // when + PlayerResult playerResult = PlayerResult.determinePlayerResult(dealer, player); + + // then + assertThat(playerResult).isEqualTo(PlayerResult.TIE); + } + } +} diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index beb850a4c72..ef1608fcda3 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -49,7 +49,7 @@ void shouldReturnFalseWhenBust() { } private Player createPlayerWithCards(Card... cards) { - Player player = new Player("pobi"); + Player player = new Player(new Name("pobi"), new BetMoney(1000)); for (Card card : cards) { player.addCard(card); } diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 900d50b4097..3cdde8aa0d9 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -3,21 +3,31 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import common.ErrorMessage; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class PlayersTest { + private List createDummyPlayers(List playerNames) { + List players = new ArrayList<>(); + for (String playerName : playerNames) { + Player player = new Player(new Name(playerName), new BetMoney(1000)); + players.add(player); + } + return players; + } + @Test @DisplayName("플레이어가 5명 이하면 정상적으로 Players 객체를 생성한다.") void shouldReturnPlayersWhenPlayerNumberIsMaximumOrLess() { // given List testPlayerNames = List.of("pobi", "terry", "rati", "gump", "junny"); + List testPlayers = createDummyPlayers(testPlayerNames); // when & then assertDoesNotThrow( - () -> Players.of(testPlayerNames) + () -> Players.of(testPlayers) ); } @@ -26,10 +36,10 @@ void shouldReturnPlayersWhenPlayerNumberIsMaximumOrLess() { void shouldThrowExceptionWhenPlayerNumberOverMaximum() { // given List testPlayerNames = List.of("pobi", "terry", "rati", "gump", "junny", "aron"); + List testPlayers = createDummyPlayers(testPlayerNames); // when & then - assertThatThrownBy(() -> Players.of(testPlayerNames)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); + assertThatThrownBy(() -> Players.of(testPlayers)) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/src/test/java/domain/ResultTest.java b/src/test/java/domain/ResultTest.java deleted file mode 100644 index fc49e7c67f8..00000000000 --- a/src/test/java/domain/ResultTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package domain; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class ResultTest { - @Nested - class DeterminePlayerResultTest { - @Test - @DisplayName("플레이어가 버스트라면 딜러의 버스트 여부와 상관없이 무조건 패배한다.") - void shouldReturnLossWhenPlayerIsBust() { - // given - boolean isDealerBust = true; - boolean isPlayerBust = true; - int dealerScore = 23; - int playerScore = 22; - - // when - Result result = Result.determinePlayerResult(isDealerBust, isPlayerBust, dealerScore, playerScore); - - // then - assertThat(result).isEqualTo(Result.LOSS); - } - - @Test - @DisplayName("플레이어는 버스트가 아니고 딜러만 버스트인 경우 승리를 반환한다.") - void shouldReturnWinWhenPlayerIsNotBustButDealerIsBust() { - // given - boolean isDealerBust = true; - boolean isPlayerBust = false; - int dealerScore = 23; - int playerScore = 15; - - // when - Result result = Result.determinePlayerResult(isDealerBust, isPlayerBust, dealerScore, playerScore); - - // then - assertThat(result).isEqualTo(Result.WIN); - } - - @Test - @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수가 딜러 점수보다 높으면 승리를 반환한다.") - void shouldReturnWinWhenNeitherIsBustAndPlayerScoreIsHigherThanDealerScore() { - // given - boolean isDealerBust = false; - boolean isPlayerBust = false; - int dealerScore = 17; - int playerScore = 20; - - // when - Result result = Result.determinePlayerResult(isDealerBust, isPlayerBust, dealerScore, playerScore); - - // then - assertThat(result).isEqualTo(Result.WIN); - } - - @Test - @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수가 딜러 점수보다 낮으면 패배를 반환한다.") - void shouldReturnLossWhenNeitherIsBustAndPlayerScoreIsLowerThanDealerScore() { - // given - boolean isDealerBust = false; - boolean isPlayerBust = false; - int dealerScore = 20; - int playerScore = 15; - - // when - Result result = Result.determinePlayerResult(isDealerBust, isPlayerBust, dealerScore, playerScore); - - // then - assertThat(result).isEqualTo(Result.LOSS); - } - - @Test - @DisplayName("플레이어와 딜러 모두 버스트가 아니고, 플레이어 점수와 딜러 점수가 같다면 무승부를 반환한다.") - void shouldReturnTieWhenNeitherIsBustAndScoresAreEqual() { - // given - boolean isDealerBust = false; - boolean isPlayerBust = false; - int dealerScore = 20; - int playerScore = 20; - - // when - Result result = Result.determinePlayerResult(isDealerBust, isPlayerBust, dealerScore, playerScore); - - // then - assertThat(result).isEqualTo(Result.TIE); - } - } - - @Nested - class ReverseTest { - @Test - @DisplayName("승리를 뒤집으면 패배가 된다.") - void shouldReturnLossWhenReversingWin() { - assertThat(Result.WIN.reverse()).isEqualTo(Result.LOSS); - } - - @Test - @DisplayName("패배를 뒤집으면 승리가 된다.") - void shouldReturnWinWhenReversingLoss() { - assertThat(Result.LOSS.reverse()).isEqualTo(Result.WIN); - } - - @Test - @DisplayName("무승부를 뒤집으면 그대로 무승부가 된다.") - void shouldReturnTieWhenReversingTie() { - assertThat(Result.TIE.reverse()).isEqualTo(Result.TIE); - } - } -}