diff --git a/README.md b/README.md index 768c170647c..35bf53825e4 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,20 @@ pobi,jason ``` +✅️ 카드 수령 여부 입력 뷰 +```text +pobi의 배팅 금액은? +10000 +``` + + --- ✅ 카드 수령 여부 입력 뷰 ```text -pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)y +pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) +y ``` # 출력 @@ -63,13 +71,13 @@ jason카드: 7클로버, K스페이드 - 결과: 17 --- -✅ 최종 승패 출력 뷰 +✅ 최종 수익 출력 뷰 ```text -## 최종 승패 -딜러: 1승 1패 -pobi: 승 -jason: 패 +## 최종 수익 +딜러: 10000 +pobi: 10000 +jason: -20000 ``` # 핵심 기능 @@ -143,13 +151,15 @@ jason: 패 --- -✅ 최종 승패 계산 기능 - -- 게임 참가자의 승패 여부를 계산한다. +✅ 최종 수익 계산 기능 +- 딜러와 게임 참가자의 최종 수익을 계산한다. - 딜러가 버스트인 경우에는 아래의 규칙을 따른다. - - 버스트가 아닌 모든 참가자는 승이다. - - 버스트인 모든 참가자는 패다. + - 버스트가 아닌 모든 참가자는 배팅한 금액을 수익으로 가져간다. + - 만약 참가자가 블랙잭이면(처음 받은 카드 2장의 합이 21이면) 배팅한 금액의 1.5배를 수익으로 가져간다. + - 버스트인 모든 참가자는 배팅한 금액을 잃는다. - 딜러가 버스트가 아닌 경우에는 아래의 규칙을 따른다. - - 딜러의 점수보다 낮거나 버스트인 참가자들은 패다. - - 딜러의 점수와 같은 참가자들은 무다. - - 딜러의 점수보다 높음 참가자들은 승이다. + - 딜러의 점수보다 낮거나 버스트인 참가자들은 배팅한 금액만큼 잃는다. + - 딜러의 점수와 같은 참가자들은 수익이 0이다. + - 딜러의 점수보다 높음 참가자들은 배팅한 금액을 수익으로 가져간다. + - 만약 참가자가 블랙잭이면(처음 받은 카드 2장의 합이 21이면) 배팅한 금액의 1.5배를 수익으로 가져간다. + diff --git a/src/main/java/constant/Result.java b/src/main/java/constant/Result.java deleted file mode 100644 index 087eca88b08..00000000000 --- a/src/main/java/constant/Result.java +++ /dev/null @@ -1,18 +0,0 @@ -package constant; - -public enum Result { - WIN("승"), - DRAW("무"), - LOSE("패"), - ; - - private final String result; - - Result(String result) { - this.result = result; - } - - public String getResult() { - return result; - } -} diff --git a/src/main/java/controller/BlackjackController.java b/src/main/java/controller/BlackjackController.java index 9a53ca385af..3d9cccfd47f 100644 --- a/src/main/java/controller/BlackjackController.java +++ b/src/main/java/controller/BlackjackController.java @@ -1,11 +1,14 @@ package controller; -import constant.HitOrStand; +import domain.game.BlackjackGameManager; +import domain.game.HitOrStand; +import domain.participant.BetAmount; +import domain.participant.PlayerName; import dto.BlackjackResultDto; import dto.BlackjackStatisticsDto; import dto.ParticipantDto; +import java.util.ArrayList; import java.util.List; -import domain.game.BlackjackGameManager; import util.Parser; import view.InputView; import view.OutputView; @@ -34,7 +37,10 @@ public void start() { } private void initializeGame() { - inputPlayers(); + List playerNames = inputPlayerNames(); + List betAmounts = inputBetAmounts(playerNames); + blackjackGameManager.createParticipants(playerNames, betAmounts); + blackjackGameManager.drawInitialCards(); List playerDtoList = blackjackGameManager.generatePlayerDtoList(); @@ -44,9 +50,22 @@ private void initializeGame() { outputView.printHandList(dealerDto, playerDtoList); } - private void inputPlayers() { - List input = Parser.parseInput(inputView.inputPlayers()); - blackjackGameManager.createPlayers(input); + private List inputBetAmounts(List playerNames) { + List betAmounts = new ArrayList<>(); + for (PlayerName playerName : playerNames) { + String betAmountInput = inputView.inputBetAmount(playerName.name()); + betAmounts.add(new BetAmount(betAmountInput)); + } + return betAmounts; + } + + private List inputPlayerNames() { + List names = Parser.parseInput(inputView.inputPlayers()); + List playerNames = new ArrayList<>(); + for (String name : names) { + playerNames.add(new PlayerName(name)); + } + return playerNames; } private void inputHitOrStandOnPlayer() { @@ -58,20 +77,23 @@ private void inputHitOrStandOnPlayer() { private void inputHitOrStand(String name, List hand) { HitOrStand hitOrStand = HitOrStand.from(inputView.inputHitOrStand(name)); - if (blackjackGameManager.isStand(hitOrStand)) { + if (hitOrStand.isStand()) { outputView.printlnHand(name, hand); return; } - drawCardOnPlayer(name, hitOrStand); + drawCardOnPlayer(name); } - private void drawCardOnPlayer(String name, HitOrStand hitOrStand) { - while (blackjackGameManager.isHit(hitOrStand)) { + private void drawCardOnPlayer(String name) { + do { ParticipantDto playerDto = blackjackGameManager.updatePlayer(name); outputView.printlnHand(name, playerDto.hand()); - hitOrStand = HitOrStand.from(inputView.inputHitOrStand(name)); - } + } while (canDrawContinue(name)); + } + + private boolean canDrawContinue(String name) { + return !blackjackGameManager.playerIsBust(name) && HitOrStand.from(inputView.inputHitOrStand(name)).isHit(); } private void printBlackjackResult() { diff --git a/src/main/java/domain/card/Card.java b/src/main/java/domain/card/Card.java index 303921458ce..e4727922760 100644 --- a/src/main/java/domain/card/Card.java +++ b/src/main/java/domain/card/Card.java @@ -9,14 +9,4 @@ public int getScore() { public boolean isAce() { return rank.isAce(); } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - Card card = (Card) o; - return rank == card.rank && suit == card.suit; - } - } diff --git a/src/main/java/domain/card/CardMachine.java b/src/main/java/domain/card/CardMachine.java index 60753805ed3..4d5a2eb7e01 100644 --- a/src/main/java/domain/card/CardMachine.java +++ b/src/main/java/domain/card/CardMachine.java @@ -33,11 +33,11 @@ private void setRanksAndSuits(List decks) { private void setSuits(List decks, Rank rank) { for (Suit suit : Suit.values()) { - addRepeatSix(decks, rank, suit); + addRepeatDeckCount(decks, rank, suit); } } - private void addRepeatSix(List decks, Rank rank, Suit suit) { + private void addRepeatDeckCount(List decks, Rank rank, Suit suit) { for (int i = 0; i < DECK_COUNT; i++) { decks.add(new Card(rank, suit)); } diff --git a/src/main/java/domain/card/Hand.java b/src/main/java/domain/card/Hand.java index bfdc980476e..9c6b48035a0 100644 --- a/src/main/java/domain/card/Hand.java +++ b/src/main/java/domain/card/Hand.java @@ -6,6 +6,8 @@ public class Hand { private static final int BLACKJACK_SCORE = 21; + private static final int REDUCED_SCORE_FROM_ACE = 10; + private static final int INITIAL_CARD_COUNT = 2; private final List cards; @@ -17,21 +19,41 @@ public void addCard(Card card) { cards.add(card); } - public int calculateScore() { - int score = cards.stream() - .map(Card::getScore) - .reduce(0, Integer::sum); - int aceCount = cards.stream() - .filter(Card::isAce) - .toList() - .size(); + public int calculateTotalScore() { + int baseScore = calculateBaseScore(); + int aceCount = calculateAceCount(); - return calculateScoreWithBestAce(score, aceCount); + return calculateTotalScoreWithAceCalculation(baseScore, aceCount); } - private int calculateScoreWithBestAce(int score, int aceCount) { + private int calculateInitialScore() { + int baseScore = calculateInitialBaseScore(); + int aceCount = calculateAceCount(); + + return calculateTotalScoreWithAceCalculation(baseScore, aceCount); + } + + private Integer calculateBaseScore() { + return cards.stream() + .map(Card::getScore) + .reduce(0, Integer::sum); + } + + private Integer calculateInitialBaseScore() { + return cards.subList(0, INITIAL_CARD_COUNT).stream() + .map(Card::getScore) + .reduce(0, Integer::sum); + } + + private int calculateAceCount() { + return (int) cards.stream() + .filter(Card::isAce) + .count(); + } + + private int calculateTotalScoreWithAceCalculation(int score, int aceCount) { while (score > BLACKJACK_SCORE && aceCount > 0) { - score -= 10; + score -= REDUCED_SCORE_FROM_ACE; aceCount--; } @@ -39,7 +61,7 @@ private int calculateScoreWithBestAce(int score, int aceCount) { } public boolean isBust() { - return calculateScore() > BLACKJACK_SCORE; + return calculateTotalScore() > BLACKJACK_SCORE; } public Card getFirstCard() { @@ -50,4 +72,8 @@ public List getCard() { return cards.stream() .toList(); } + + public boolean isBlackjack() { + return calculateInitialScore() == BLACKJACK_SCORE; + } } diff --git a/src/main/java/domain/card/Rank.java b/src/main/java/domain/card/Rank.java index 116f3952cbe..1a302964678 100644 --- a/src/main/java/domain/card/Rank.java +++ b/src/main/java/domain/card/Rank.java @@ -24,10 +24,6 @@ public enum Rank { this.score = score; } - public boolean isAce() { - return this.equals(Rank.ACE); - } - public String getRank() { return rank; } @@ -35,4 +31,8 @@ public String getRank() { public int getScore() { return score; } + + public boolean isAce() { + return this.equals(Rank.ACE); + } } diff --git a/src/main/java/domain/game/BlackjackGameManager.java b/src/main/java/domain/game/BlackjackGameManager.java index 58c67dc8146..6daf67fda73 100644 --- a/src/main/java/domain/game/BlackjackGameManager.java +++ b/src/main/java/domain/game/BlackjackGameManager.java @@ -1,16 +1,13 @@ package domain.game; -import constant.HitOrStand; -import constant.Result; import domain.card.Card; import domain.card.CardMachine; -import domain.participant.Dealer; +import domain.participant.BetAmount; import domain.participant.Participants; import domain.participant.Player; -import domain.participant.Players; +import domain.participant.PlayerName; import dto.BlackjackResultDto; import dto.BlackjackStatisticsDto; -import dto.DealerStatisticDto; import dto.ParticipantDto; import dto.PlayerStatisticDto; import java.util.ArrayList; @@ -19,31 +16,25 @@ public class BlackjackGameManager { private final CardMachine cardMachine; + private final BlackjackJudge blackjackJudge; private Participants participants; public BlackjackGameManager() { this.cardMachine = new CardMachine(); + this.blackjackJudge = new BlackjackJudge(); } - public void createPlayers(List names) { - Dealer dealer = new Dealer(); - Players players = new Players(names); - participants = new Participants(dealer, players); + public void createParticipants(List playerNames, List betAmounts) { + participants = Participants.of(playerNames, betAmounts); } public void drawInitialCards() { - participants.dealer().addCard(drawCard()); - participants.dealer().addCard(drawCard()); - for (Player player : participants.players().getPlayers()) { - player.addCard(drawCard()); - player.addCard(drawCard()); - } + participants.drawInitialCards(this::drawCard); } public boolean drawDealerCard() { - Dealer dealer = participants.dealer(); - if (dealer.shouldHit()) { - dealer.addCard(drawCard()); + if (participants.dealerShouldHit()) { + participants.drawCardsByDealer(this::drawCard); return true; } @@ -54,22 +45,23 @@ public Card drawCard() { return cardMachine.drawCard(); } + public ParticipantDto updatePlayer(String name) { + Player player = participants.drawCardsByPlayer(name, this::drawCard); + return ParticipantDto.from(player); + } + public ParticipantDto generateInitialDealerDto() { - Dealer dealer = participants.dealer(); - return ParticipantDto.from(dealer, true); + return ParticipantDto.from(participants.dealer(), true); } public ParticipantDto generateDealerDto() { - Dealer dealer = participants.dealer(); - return ParticipantDto.from(dealer); + return ParticipantDto.from(participants.dealer()); } public List generatePlayerDtoList() { - List playersDto = new ArrayList<>(); - for (Player player : participants.players().getPlayers()) { - playersDto.add(ParticipantDto.from(player)); - } - return playersDto; + return participants.players().getPlayers().stream() + .map(ParticipantDto::from) + .toList(); } public BlackjackResultDto getBlackjackResult() { @@ -80,61 +72,19 @@ public BlackjackResultDto getBlackjackResult() { } public BlackjackStatisticsDto getBlackjackStatistics() { - List playerStatisticDtoList = calculatePlayerResults(); - int win = 0, draw = 0, lose = 0; - for (PlayerStatisticDto playerStatisticDto : playerStatisticDtoList) { - Result result = playerStatisticDto.result(); - win += judgeResult(result, Result.LOSE); - draw += judgeResult(result, Result.DRAW); - lose += judgeResult(result, Result.WIN); - } - return BlackjackStatisticsDto.of(DealerStatisticDto.of(win, draw, lose), playerStatisticDtoList); - } - - public List calculatePlayerResults() { - Dealer dealer = participants.dealer(); List playerStatisticDtoList = new ArrayList<>(); + int dealerProfit = 0; for (Player player : participants.players().getPlayers()) { - Result result = calculatePlayerResult(dealer, player); - playerStatisticDtoList.add(PlayerStatisticDto.of(player, result)); + Result result = blackjackJudge.judgePlayerResult(participants.dealer(), player); + int playerProfit = result.getProfit(player.getBetAmount()); + dealerProfit += playerProfit * -1; + playerStatisticDtoList.add(PlayerStatisticDto.of(player, playerProfit)); } - return playerStatisticDtoList; - } - - private Result calculatePlayerResult(Dealer dealer, Player player) { - if (player.isBust()) { - return Result.LOSE; - } - if (dealer.isBust()) { - return Result.WIN; - } - if (player.calculateScore() > dealer.calculateScore()) { - return Result.WIN; - } - if (player.calculateScore() == dealer.calculateScore()) { - return Result.DRAW; - } - return Result.LOSE; - } - - private int judgeResult(Result result, Result playerResult) { - if (result.equals(playerResult)) { - return 1; - } - return 0; - } - - public ParticipantDto updatePlayer(String name) { - Player player = participants.getPlayer(name); - player.addCard(drawCard()); - return ParticipantDto.from(player); - } - public boolean isHit(HitOrStand hitOrStand) { - return hitOrStand.isHit(); + return BlackjackStatisticsDto.of(dealerProfit, playerStatisticDtoList); } - public boolean isStand(HitOrStand hitOrStand) { - return hitOrStand.isStand(); + public boolean playerIsBust(String name) { + return participants.playerIsBust(name); } } diff --git a/src/main/java/domain/game/BlackjackJudge.java b/src/main/java/domain/game/BlackjackJudge.java new file mode 100644 index 00000000000..82247916a3f --- /dev/null +++ b/src/main/java/domain/game/BlackjackJudge.java @@ -0,0 +1,29 @@ +package domain.game; + +import domain.participant.Dealer; +import domain.participant.Player; + +public record BlackjackJudge() { + + public Result judgePlayerResult(Dealer dealer, Player player) { + if (player.isBust()) { + return Result.LOSE; + } + if (player.isBlackjack() && dealer.isBlackjack()) { + return Result.DRAW; + } + if (player.isBlackjack()) { + return Result.BLACKJACK_WIN; + } + if (dealer.isBust()) { + return Result.WIN; + } + if (player.calculateScore() > dealer.calculateScore()) { + return Result.WIN; + } + if (player.calculateScore() == dealer.calculateScore()) { + return Result.DRAW; + } + return Result.LOSE; + } +} diff --git a/src/main/java/constant/HitOrStand.java b/src/main/java/domain/game/HitOrStand.java similarity index 97% rename from src/main/java/constant/HitOrStand.java rename to src/main/java/domain/game/HitOrStand.java index 50502f919e2..5334c894e46 100644 --- a/src/main/java/constant/HitOrStand.java +++ b/src/main/java/domain/game/HitOrStand.java @@ -1,4 +1,4 @@ -package constant; +package domain.game; import exception.BlackjackException; import java.util.Arrays; diff --git a/src/main/java/domain/game/Result.java b/src/main/java/domain/game/Result.java new file mode 100644 index 00000000000..9aecb8afd95 --- /dev/null +++ b/src/main/java/domain/game/Result.java @@ -0,0 +1,19 @@ +package domain.game; + +public enum Result { + BLACKJACK_WIN(1.5), + WIN(1), + DRAW(0), + LOSE(-1), + ; + + private final double multiple; + + Result(double multiple) { + this.multiple = multiple; + } + + public int getProfit(int betAmount) { + return (int) (betAmount * this.multiple); + } +} diff --git a/src/main/java/domain/participant/BetAmount.java b/src/main/java/domain/participant/BetAmount.java new file mode 100644 index 00000000000..4c414d1198a --- /dev/null +++ b/src/main/java/domain/participant/BetAmount.java @@ -0,0 +1,46 @@ +package domain.participant; + +import exception.BlackjackException; + +public class BetAmount { + + public static final String INVALID_BET_AMOUNT_NUMBER = "배팅 금액은 숫자여야 합니다."; + public static final String INVALID_BET_AMOUNT_POSITIVE = "배팅 금액은 양수여야 합니다."; + private static final String NUMBER_FORMAT = "-?\\d+"; + + private final int betAmount; + + public BetAmount(String betAmountInput) { + validate(betAmountInput); + this.betAmount = Integer.parseInt(betAmountInput); + } + + private void validate(String betAmountInput) { + validateNumber(betAmountInput); + validatePositive(betAmountInput); + } + + private void validateNumber(String betAmountInput) { + if (isNotNumber(betAmountInput)) { + throw new BlackjackException(INVALID_BET_AMOUNT_NUMBER); + } + } + + private static boolean isNotNumber(String betAmountInput) { + return !betAmountInput.matches(NUMBER_FORMAT); + } + + private void validatePositive(String betAmountInput) { + if (isNotPositive(betAmountInput)) { + throw new BlackjackException(INVALID_BET_AMOUNT_POSITIVE); + } + } + + private static boolean isNotPositive(String betAmountInput) { + return Integer.parseInt(betAmountInput) <= 0; + } + + public int getBetAmount() { + return betAmount; + } +} diff --git a/src/main/java/domain/participant/Dealer.java b/src/main/java/domain/participant/Dealer.java index b3bb6f59c63..04d90246feb 100644 --- a/src/main/java/domain/participant/Dealer.java +++ b/src/main/java/domain/participant/Dealer.java @@ -19,11 +19,11 @@ public List getOnlyFirstHand() { return newHand; } - public String getName() { - return DEALER_NAME; - } - public boolean shouldHit() { return this.calculateScore() <= DEALER_HIT_MAX_SCORE; } + + public String getName() { + return DEALER_NAME; + } } diff --git a/src/main/java/domain/participant/Participant.java b/src/main/java/domain/participant/Participant.java index d6582255609..705b3b60a68 100644 --- a/src/main/java/domain/participant/Participant.java +++ b/src/main/java/domain/participant/Participant.java @@ -4,7 +4,7 @@ import domain.card.Hand; import java.util.List; -public class Participant { +public abstract class Participant { protected final Hand hand; @@ -21,10 +21,16 @@ public boolean isBust() { } public int calculateScore() { - return hand.calculateScore(); + return hand.calculateTotalScore(); + } + + public boolean isBlackjack() { + return hand.isBlackjack(); } public List getHand() { return hand.getCard(); } + + public abstract String getName(); } diff --git a/src/main/java/domain/participant/Participants.java b/src/main/java/domain/participant/Participants.java index 7147bb8f4a4..7db7e9fbcc1 100644 --- a/src/main/java/domain/participant/Participants.java +++ b/src/main/java/domain/participant/Participants.java @@ -1,8 +1,37 @@ package domain.participant; +import domain.card.Card; +import java.util.List; +import java.util.function.Supplier; + public record Participants(Dealer dealer, Players players) { - public Player getPlayer(String name) { + public static Participants of(List playerNames, List betAmounts) { + Dealer dealer = new Dealer(); + Players players = new Players(playerNames, betAmounts); + return new Participants(dealer, players); + } + + public boolean dealerShouldHit() { + return dealer.shouldHit(); + } + + public void drawInitialCards(Supplier cardSupplier) { + dealer.addCard(cardSupplier.get()); + dealer.addCard(cardSupplier.get()); + players.drawInitialCards(cardSupplier); + } + + public void drawCardsByDealer(Supplier cardSupplier) { + dealer.addCard(cardSupplier.get()); + } + + public Player drawCardsByPlayer(String name, Supplier cardSupplier) { + players.addCard(name, cardSupplier.get()); return players.getPlayer(name); } + + public boolean playerIsBust(String name) { + return players.playerIsBust(name); + } } diff --git a/src/main/java/domain/participant/Player.java b/src/main/java/domain/participant/Player.java index 2c7eccbb5d0..a877f9b20a6 100644 --- a/src/main/java/domain/participant/Player.java +++ b/src/main/java/domain/participant/Player.java @@ -3,10 +3,16 @@ public class Player extends Participant { private final PlayerName playerName; + private final BetAmount betAmount; - public Player(String name) { + public Player(PlayerName playerName, BetAmount betAmount) { super(); - this.playerName = new PlayerName(name); + this.playerName = playerName; + this.betAmount = betAmount; + } + + public int getBetAmount() { + return betAmount.getBetAmount(); } public String getName() { diff --git a/src/main/java/domain/participant/Players.java b/src/main/java/domain/participant/Players.java index c8a280f2035..6daec7e6a67 100644 --- a/src/main/java/domain/participant/Players.java +++ b/src/main/java/domain/participant/Players.java @@ -5,6 +5,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.function.Supplier; +import domain.card.Card; public class Players { @@ -13,24 +15,25 @@ public class Players { public static final String PLAYER_DUPLICATED = "게임 참가자의 이름은 중복 되어선 안됩니다."; public static final String PLAYER_COUNT_OUT_OF_RANGE = String.format("게임 참가자의 수는 %d~%d명 사이여야 합니다.", PLAYER_MIN_COUNT, PLAYER_MAX_COUNT); + public static final String NOT_FOUND_PLAYER = "플레이어를 찾을 수 없습니다."; private final List players; - public Players(List names) { + public Players(List names, List betAmounts) { validatePlayerNames(names); List players = new ArrayList<>(); - for (String name : names) { - players.add(new Player(name)); + for (int i = 0; i < names.size(); i++) { + players.add(new Player(names.get(i), betAmounts.get(i))); } this.players = players; } - private void validatePlayerNames(List names) { + private void validatePlayerNames(List names) { validatePlayerDuplication(names); validatePlayerCount(names.size()); } - private void validatePlayerDuplication(List names) { + private void validatePlayerDuplication(List names) { if (new HashSet<>(names).size() != names.size()) { throw new BlackjackException(PLAYER_DUPLICATED); } @@ -46,10 +49,26 @@ public Player getPlayer(String name) { return players.stream() .filter(player -> player.getName().equals(name)) .findFirst() - .orElse(null); + .orElseThrow(() -> new BlackjackException(NOT_FOUND_PLAYER)); } public List getPlayers() { return Collections.unmodifiableList(players); } + + public void drawInitialCards(Supplier cardSupplier) { + for (Player player : players) { + player.addCard(cardSupplier.get()); + player.addCard(cardSupplier.get()); + } + } + + public void addCard(String name, Card card) { + Player player = getPlayer(name); + player.addCard(card); + } + + public boolean playerIsBust(String name) { + return getPlayer(name).isBust(); + } } diff --git a/src/main/java/dto/BlackjackStatisticsDto.java b/src/main/java/dto/BlackjackStatisticsDto.java index 3566999171c..be4dacbd54f 100644 --- a/src/main/java/dto/BlackjackStatisticsDto.java +++ b/src/main/java/dto/BlackjackStatisticsDto.java @@ -3,11 +3,11 @@ import java.util.List; public record BlackjackStatisticsDto( - DealerStatisticDto dealerStatisticDto, + int dealerProfit, List playerStatisticDtoList ) { - public static BlackjackStatisticsDto of(DealerStatisticDto dealerStatisticDto, List playerStatisticDtoList) { - return new BlackjackStatisticsDto(dealerStatisticDto, playerStatisticDtoList); + public static BlackjackStatisticsDto of(int dealerProfit, List playerStatisticDtoList) { + return new BlackjackStatisticsDto(dealerProfit, playerStatisticDtoList); } } diff --git a/src/main/java/dto/DealerStatisticDto.java b/src/main/java/dto/DealerStatisticDto.java deleted file mode 100644 index 2e2a4140946..00000000000 --- a/src/main/java/dto/DealerStatisticDto.java +++ /dev/null @@ -1,12 +0,0 @@ -package dto; - -public record DealerStatisticDto( - int win, - int draw, - int lose -) { - - public static DealerStatisticDto of(int win, int draw, int lose) { - return new DealerStatisticDto(win, draw, lose); - } -} diff --git a/src/main/java/dto/PlayerStatisticDto.java b/src/main/java/dto/PlayerStatisticDto.java index 0e1019725eb..1deb8bb3122 100644 --- a/src/main/java/dto/PlayerStatisticDto.java +++ b/src/main/java/dto/PlayerStatisticDto.java @@ -1,14 +1,13 @@ package dto; -import constant.Result; import domain.participant.Player; public record PlayerStatisticDto( String name, - Result result + int profit ) { - public static PlayerStatisticDto of(Player player, Result result) { - return new PlayerStatisticDto(player.getName(), result); + public static PlayerStatisticDto of(Player player, int profit) { + return new PlayerStatisticDto(player.getName(), profit); } } diff --git a/src/main/java/factory/BlackjackControllerFactory.java b/src/main/java/factory/BlackjackControllerFactory.java index eae653e0c42..94079667871 100644 --- a/src/main/java/factory/BlackjackControllerFactory.java +++ b/src/main/java/factory/BlackjackControllerFactory.java @@ -8,10 +8,10 @@ public class BlackjackControllerFactory { public BlackjackController blackjackController() { - return new BlackjackController(inputView(), outputView(), blackjackService()); + return new BlackjackController(inputView(), outputView(), blackjackGameManager()); } - public BlackjackGameManager blackjackService() { + public BlackjackGameManager blackjackGameManager() { return new BlackjackGameManager(); } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 136242d3cf8..e074db01a84 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -6,6 +6,7 @@ public class InputView { private static final String INPUT_PLAYERS_MESSAGE = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; private static final String INPUT_HIT_OR_STAND = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; + private static final String INPUT_BET_AMOUNT = "%s의 배팅 금액은?\n"; private final Scanner scanner = new Scanner(System.in); @@ -18,4 +19,9 @@ public String inputHitOrStand(String name) { System.out.printf(INPUT_HIT_OR_STAND, name); return scanner.nextLine(); } + + public String inputBetAmount(String name) { + System.out.printf(INPUT_BET_AMOUNT, name); + return scanner.nextLine(); + } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 8e06f128729..ee8f9b223f5 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,9 +1,7 @@ package view; -import constant.Result; import dto.BlackjackResultDto; import dto.BlackjackStatisticsDto; -import dto.DealerStatisticDto; import dto.ParticipantDto; import dto.PlayerStatisticDto; import java.util.List; @@ -15,14 +13,14 @@ public class OutputView { private static final String PRINT_HAND_MESSAGE = "%s카드: %s"; private static final String PRINT_DEALER_HIT = "\n딜러는 16이하라 한장의 카드를 더 받았습니다."; private static final String PRINT_BLACKJACK_RESULT_MESSAGE = " - 결과: %d\n"; - private static final String PRINT_BLACKJACK_STATISTICS_HEADER_MESSAGE = "## 최종 승패"; - private static final String PRINT_BLACKJACK_STATISTICS_DEALER_MESSAGE = "딜러:%s%s%s\n"; - private static final String PRINT_BLACKJACK_STATISTICS_PLAYER_MESSAGE = "%s: %s\n"; + private static final String PRINT_BLACKJACK_STATISTICS_HEADER_MESSAGE = "## 최종 수익"; + private static final String PRINT_BLACKJACK_STATISTICS_DEALER_MESSAGE = "딜러: %d\n"; + private static final String PRINT_BLACKJACK_STATISTICS_PLAYER_MESSAGE = "%s: %d\n"; public void printPlayers(List playerDtoList) { List names = playerDtoList.stream() - .map(ParticipantDto::name) - .toList(); + .map(ParticipantDto::name) + .toList(); System.out.printf(PRINT_PLAYERS_MESSAGE, String.join(DELIMITER, names)); } @@ -61,23 +59,11 @@ public void printBlackjackResult(BlackjackResultDto blackjackResult) { } public void printBlackjackStatistics(BlackjackStatisticsDto blackjackStatistics) { - DealerStatisticDto dealerStatisticDto = blackjackStatistics.dealerStatisticDto(); - List playerStatisticDtoList = blackjackStatistics.playerStatisticDtoList(); System.out.println(PRINT_BLACKJACK_STATISTICS_HEADER_MESSAGE); - System.out.printf(PRINT_BLACKJACK_STATISTICS_DEALER_MESSAGE, - printResult(dealerStatisticDto.win(), Result.WIN.getResult()), - printResult(dealerStatisticDto.draw(), Result.DRAW.getResult()), - printResult(dealerStatisticDto.lose(), Result.LOSE.getResult())); - for (PlayerStatisticDto playerStatisticDto : playerStatisticDtoList) { - System.out.printf(PRINT_BLACKJACK_STATISTICS_PLAYER_MESSAGE, playerStatisticDto.name(), - playerStatisticDto.result().getResult()); + System.out.printf(PRINT_BLACKJACK_STATISTICS_DEALER_MESSAGE, blackjackStatistics.dealerProfit()); + for (PlayerStatisticDto playerStatisticDto : blackjackStatistics.playerStatisticDtoList()) { + System.out.printf(PRINT_BLACKJACK_STATISTICS_PLAYER_MESSAGE, + playerStatisticDto.name(), playerStatisticDto.profit()); } } - - private String printResult(int result, String name) { - if (result == 0) { - return ""; - } - return " " + result + name; - } } diff --git a/src/test/java/constant/HitOrStandTest.java b/src/test/java/constant/HitOrStandTest.java deleted file mode 100644 index 8b8f50a7dda..00000000000 --- a/src/test/java/constant/HitOrStandTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package constant; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class HitOrStandTest { - - @Nested - class From { - - @Nested - class Fail { - - @Test - void y_또는_n가_아니면_예외가_발생한다() { - // given - String input = "a"; - - // when & then - assertThatThrownBy(() -> HitOrStand.from(input)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(HitOrStand.INVALID_YES_NO_INPUT); - } - } - } -} \ No newline at end of file diff --git a/src/test/java/domain/card/CardMachineTest.java b/src/test/java/domain/card/CardMachineTest.java index e38beace067..3301f4fc1e3 100644 --- a/src/test/java/domain/card/CardMachineTest.java +++ b/src/test/java/domain/card/CardMachineTest.java @@ -19,13 +19,13 @@ class DrawCardTest { class Success { @Test - void 카드를_뽑으면_null이_아닌_Card를_반환해야_한다() { + void 카드를_정상적으로_뽑으면_Card를_반환해야_한다() { // when Card actual = cardMachine.drawCard(); // then - assertThat(actual).isNotNull(); + assertThat(actual).isInstanceOf(Card.class); } @Test @@ -42,7 +42,6 @@ class Success { // then assertThat(counts).hasSize(52); - assertThat(counts.values()).allMatch(count -> count <= 6); assertThat(counts.values()).allMatch(count -> count == 6); } } diff --git a/src/test/java/domain/card/CardTest.java b/src/test/java/domain/card/CardTest.java deleted file mode 100644 index 82bbe45f30d..00000000000 --- a/src/test/java/domain/card/CardTest.java +++ /dev/null @@ -1,144 +0,0 @@ -package domain.card; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.stream.Stream; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -public class CardTest { - - @Nested - class CalculateScoreTest { - - @Nested - class Success { - - @Test - void J_Q_K_이면_10을_반환_해야_한다() { - - // given - Card card1 = new Card(Rank.J, Suit.HEART); - Card card2 = new Card(Rank.Q, Suit.HEART); - Card card3 = new Card(Rank.K, Suit.HEART); - - // when - int actual1 = card1.getScore(); - int actual2 = card2.getScore(); - int actual3 = card3.getScore(); - - // then - int expected = 10; - Assertions.assertEquals(10, actual1); - Assertions.assertEquals(10, actual2); - Assertions.assertEquals(10, actual3); - } - - @Test - void 에이스_이면_11을_반환_해야_한다() { - - // given - Card card = new Card(Rank.ACE, Suit.CLOVER); - - // when - int actual = card.getScore(); - - // then - Assertions.assertEquals(11, actual); - } - - @ParameterizedTest - @MethodSource("cardProvider") - void 숫자라면_숫자를_반환_해야_한다(Card card, int expected) { - - // when - int actual = card.getScore(); - - // then - Assertions.assertEquals(expected, actual); - } - - static Stream cardProvider() { - return Stream.of( - Arguments.of(new Card(Rank.FIVE, Suit.CLOVER), 5), - Arguments.of(new Card(Rank.TWO, Suit.CLOVER), 2), - Arguments.of(new Card(Rank.TEN, Suit.CLOVER), 10) - ); - } - } - } - - @Nested - class IsAceTest { - - @Nested - class Success { - - @Test - void 에이스_카드는_true를_반환해야_한다() { - - // given - Card card = new Card(Rank.ACE, Suit.HEART); - - // when - boolean actual = card.isAce(); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 에이스가_아닌_카드는_false를_반환해야_한다() { - - // given - Card card = new Card(Rank.TEN, Suit.HEART); - - // when - boolean actual = card.isAce(); - - // then - assertThat(actual).isFalse(); - } - } - } - - @Nested - class EqualsAndHashCodeTest { - - @Nested - class Success { - - @Test - void 랭크와_무늬가_같으면_equals는_true다() { - - // given - Card card = new Card(Rank.TEN, Suit.HEART); - Card other = new Card(Rank.TEN, Suit.HEART); - - // when - boolean actual = card.equals(other); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 랭크와_무늬가_같으면_hashCode도_같다() { - - // given - Card card = new Card(Rank.TEN, Suit.HEART); - Card other = new Card(Rank.TEN, Suit.HEART); - - // when - int actual = card.hashCode(); - - // then - assertThat(actual).isEqualTo(other.hashCode()); - } - } - } -} diff --git a/src/test/java/domain/card/HandTest.java b/src/test/java/domain/card/HandTest.java index 20506b4c07a..3bd0b346b1c 100644 --- a/src/test/java/domain/card/HandTest.java +++ b/src/test/java/domain/card/HandTest.java @@ -1,6 +1,7 @@ package domain.card; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -50,7 +51,7 @@ class Success { } @Nested - class CalculateScoreTest { + class CalculateTotalScoreTest { @Nested class Success { @@ -64,7 +65,7 @@ class Success { hand.addCard(new Card(Rank.THREE, Suit.SPADE)); // when - int actual = hand.calculateScore(); + int actual = hand.calculateTotalScore(); // then assertThat(actual).isEqualTo(13); @@ -80,7 +81,7 @@ class Success { hand.addCard(new Card(Rank.THREE, Suit.DIAMOND)); // when - int actual = hand.calculateScore(); + int actual = hand.calculateTotalScore(); // then assertThat(actual).isEqualTo(14); @@ -96,7 +97,7 @@ class Success { hand.addCard(new Card(Rank.NINE, Suit.DIAMOND)); // when - int actual = hand.calculateScore(); + int actual = hand.calculateTotalScore(); // then assertThat(actual).isEqualTo(21); @@ -105,31 +106,83 @@ class Success { } @Nested - class GetCardTest { + class IsBustTest { @Nested class Success { @Test - void 손패가_비어있다면_빈_목록을_반환해야_한다() { + void 최종_점수가_21_초과이면_버스트이다() { // given Hand hand = new Hand(); + hand.addCard(new Card(Rank.THREE, Suit.HEART)); + hand.addCard(new Card(Rank.K, Suit.SPADE)); + hand.addCard(new Card(Rank.NINE, Suit.DIAMOND)); // when - var actual = hand.getCard(); + boolean actual = hand.isBust(); // then - assertThat(actual).isEmpty(); + assertThat(actual).isTrue(); } @Test - void 손패의_카드들을_이름_목록으로_반환해야_한다() { + void 최종_점수가_21_이하이면_버스트가_아니다() { + + // given + Hand hand = new Hand(); + hand.addCard(new Card(Rank.TWO, Suit.HEART)); + hand.addCard(new Card(Rank.K, Suit.SPADE)); + hand.addCard(new Card(Rank.NINE, Suit.DIAMOND)); + + // when + boolean actual = hand.isBust(); + + // then + assertThat(actual).isFalse(); + } + } + } + + @Nested + class GetFirstCardTest { + + @Nested + class Success { + + @Test + void 첫번째_카드를_반환한다() { + + // given + Hand hand = new Hand(); + Card firstCard = new Card(Rank.ACE, Suit.HEART); + Card secondCard = new Card(Rank.K, Suit.SPADE); + hand.addCard(firstCard); + hand.addCard(secondCard); + + // when + Card actual = hand.getFirstCard(); + + // then + assertThat(actual).isEqualTo(firstCard); + } + } + } + + @Nested + class GetCardTest { + + @Nested + class Success { + + @Test + void 손패의_전체_카드를_순서대로_반환한다() { // given Hand hand = new Hand(); - Card firstCard = new Card(Rank.FIVE, Suit.HEART); - Card secondCard = new Card(Rank.J, Suit.DIAMOND); + Card firstCard = new Card(Rank.ACE, Suit.HEART); + Card secondCard = new Card(Rank.K, Suit.SPADE); hand.addCard(firstCard); hand.addCard(secondCard); @@ -138,8 +191,61 @@ class Success { // then assertThat(actual) - .hasSize(2) - .containsExactly(firstCard, secondCard); + .hasSize(2) + .containsExactly(firstCard, secondCard); + } + + @Test + void 반환한_목록은_외부에서_수정할_수_없다() { + + // given + Hand hand = new Hand(); + hand.addCard(new Card(Rank.ACE, Suit.HEART)); + var cards = hand.getCard(); + + // when & then + assertThatThrownBy(() -> cards.add(new Card(Rank.K, Suit.SPADE))) + .isInstanceOf(UnsupportedOperationException.class); + } + } + } + + @Nested + class IsBlackjackTest { + + @Nested + class Success { + + @Test + void 처음_받은_2장_카드의_최종_점수가_21이면_블랙잭이다() { + + // given + Hand hand = new Hand(); + hand.addCard(new Card(Rank.ACE, Suit.HEART)); + hand.addCard(new Card(Rank.K, Suit.SPADE)); + + // when + boolean actual = hand.isBlackjack(); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 세장_이상을_받아서_최종_점수가_21이면_블랙잭이_아니다() { + + // given + Hand hand = new Hand(); + hand.addCard(new Card(Rank.TEN, Suit.HEART)); + hand.addCard(new Card(Rank.K, Suit.SPADE)); + hand.addCard(new Card(Rank.ACE, Suit.SPADE)); + hand.addCard(new Card(Rank.ACE, Suit.SPADE)); + + // when + boolean actual = hand.isBlackjack(); + + // then + assertThat(actual).isFalse(); } } } diff --git a/src/test/java/domain/game/BlackjackGameManagerTest.java b/src/test/java/domain/game/BlackjackGameManagerTest.java index 1e5983b0221..bee9a88fa32 100644 --- a/src/test/java/domain/game/BlackjackGameManagerTest.java +++ b/src/test/java/domain/game/BlackjackGameManagerTest.java @@ -2,434 +2,373 @@ import static org.assertj.core.api.Assertions.assertThat; -import dto.BlackjackResultDto; -import factory.BlackjackControllerFactory; -import constant.HitOrStand; +import domain.card.Card; import domain.card.Rank; -import constant.Result; import domain.card.Suit; -import domain.card.Card; +import domain.participant.BetAmount; +import domain.participant.PlayerName; +import dto.BlackjackResultDto; import dto.BlackjackStatisticsDto; import dto.ParticipantDto; -import dto.PlayerStatisticDto; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; -import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; class BlackjackGameManagerTest { - private final BlackjackControllerFactory blackjackControllerFactory = new BlackjackControllerFactory(); - private final BlackjackGameManager blackjackGameManager = blackjackControllerFactory.blackjackService(); - @Nested - class CreatePlayersTest { + class DrawDealerCardTest { - @Nested - class Success { + @Test + void 딜러_점수가_16_이하면_카드를_한장_추가한다() { - @ParameterizedTest - @MethodSource("successCases") - void 게임_참가자_조건이_맞으면_정상_작동_해야한다(List input) { + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), + card(Rank.NINE, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + boolean actual = blackjackGameManager.drawDealerCard(); + + // then + ParticipantDto dealerDto = blackjackGameManager.generateDealerDto(); + assertThat(actual).isTrue(); + assertThat(dealerDto.hand()).containsExactly("10하트", "6스페이드", "9클로버"); + } - // when & then - blackjackGameManager.createPlayers(input); - } + @Test + void 딜러_점수가_17_이상이면_카드를_추가하지_않는다() { - static Stream successCases() { - return Stream.of( - Arguments.of(List.of("jacob", "seoye")), - Arguments.of(List.of("aa aa", "성 열")) - ); - } + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.SPADE), + card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + boolean actual = blackjackGameManager.drawDealerCard(); + + // then + ParticipantDto dealerDto = blackjackGameManager.generateDealerDto(); + assertThat(actual).isFalse(); + assertThat(dealerDto.hand()).containsExactly("10하트", "7스페이드"); } } @Nested - class DrawInitialCardsTest { - - @Nested - class Success { + class UpdatePlayerTest { - @Test - void 초기_카드_분배시_최종_결과에는_모두_2장씩_있어야_한다() { + @Test + void 플레이어에게_카드를_추가하고_업데이트된_Dto를_반환한다() { - // given - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); - // when - blackjackGameManager.drawInitialCards(); - BlackjackResultDto actual = blackjackGameManager.getBlackjackResult(); + // when + ParticipantDto actual = blackjackGameManager.updatePlayer("jacob"); - // then - assertThat(actual.playerResultDtoList().get(0).hand()).hasSize(2); - assertThat(actual.playerResultDtoList().get(1).hand()).hasSize(2); - } + // then + assertThat(actual.name()).isEqualTo("jacob"); + assertThat(actual.hand()).containsExactly("2클로버", "3다이아몬드", "A클로버"); + assertThat(actual.score()).isEqualTo(16); } } @Nested class GenerateInitialDealerDtoTest { - @Nested - class Success { - - @Test - void 딜러의_초기_공개_Dto를_생성해야_한다() { + @Test + void 딜러의_초기_공개_Dto를_생성한다() { - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - ParticipantDto actual = blackjackGameManager.generateInitialDealerDto(); - - // then - assertThat(actual.name()).isEqualTo("딜러"); - assertThat(actual.hand()).hasSize(1); - } + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + ParticipantDto actual = blackjackGameManager.generateInitialDealerDto(); + + // then + assertThat(actual.name()).isEqualTo("딜러"); + assertThat(actual.hand()).containsExactly("10하트"); + assertThat(actual.score()).isEqualTo(16); } } @Nested class GenerateDealerDtoTest { - @Nested - class Success { - - @Test - void 딜러의_최종_Dto를_생성해야_한다() { + @Test + void 딜러의_전체_손패_Dto를_생성한다() { - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - ParticipantDto actual = blackjackGameManager.generateDealerDto(); - - // then - assertThat(actual.name()).isEqualTo("딜러"); - assertThat(actual.hand()).hasSize(2); - } + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + ParticipantDto actual = blackjackGameManager.generateDealerDto(); + + // then + assertThat(actual.name()).isEqualTo("딜러"); + assertThat(actual.hand()).containsExactly("10하트", "6스페이드"); + assertThat(actual.score()).isEqualTo(16); } } @Nested class GeneratePlayersDtoTest { - @Nested - class Success { - - @Test - void 플레이어_Dto_목록을_생성해야_한다() { - - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - List actual = blackjackGameManager.generatePlayerDtoList(); - - // then - assertThat(actual).hasSize(2); - assertThat(actual.get(0).name()).isEqualTo("jacob"); - assertThat(actual.get(1).name()).isEqualTo("seoye"); - } + @Test + void 플레이어_Dto_목록을_생성한다() { + + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.NINE, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER), card(Rank.K, Suit.DIAMOND), + card(Rank.NINE, Suit.HEART), card(Rank.SEVEN, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + List actual = blackjackGameManager.generatePlayerDtoList(); + + // then + assertThat(actual).hasSize(2); + assertThat(actual.get(0).name()).isEqualTo("jacob"); + assertThat(actual.get(0).hand()).containsExactly("A클로버", "K다이아몬드"); + assertThat(actual.get(1).name()).isEqualTo("seoye"); + assertThat(actual.get(1).hand()).containsExactly("9하트", "7클로버"); } } @Nested - class DrawDealerCardTest { - - @Nested - class Success { - - @Test - void 딜러_점수가_16_이하면_카드를_추가로_뽑아야_한다() { - - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), - card(Rank.NINE, Suit.CLOVER) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - boolean actual = blackjackGameManager.drawDealerCard(); - - // then - BlackjackResultDto result = blackjackGameManager.getBlackjackResult(); - assertThat(actual).isTrue(); - assertThat(result.playerResultDtoList().getFirst().hand()).hasSize(2); - } - - @Test - void 딜러_점수가_17_이상이면_카드를_추가로_뽑지_않아야_한다() { - - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), - card(Rank.NINE, Suit.CLOVER) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - boolean actual = blackjackGameManager.drawDealerCard(); - - // then - BlackjackResultDto result = blackjackGameManager.getBlackjackResult(); - assertThat(actual).isFalse(); - assertThat(result.playerResultDtoList().getFirst().hand()).hasSize(2); - } - - } - } - - @Nested - class DrawCardTest { - - @Nested - class Success { - - @Test - void 카드를_뽑으면_null이_아닌_카드를_반환해야_한다() { + class getBlackjackResultTest { - // when - Card actual = blackjackGameManager.drawCard(); + @Test + void 플레이어가_카드를_추가로_받으면_최종_결과에_반영된다() { - // then - assertThat(actual).isNotNull(); - } - - @Test - void 카드가_소진되기_전까지는_카드를_반환해야_한다() { - - // given - Card actual = null; - - // when - for (int i = 0; i < 312; i++) { - actual = blackjackGameManager.drawCard(); - } - - // then - assertThat(actual).isNotNull(); - } + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TEN, Suit.CLOVER), card(Rank.NINE, Suit.DIAMOND), + card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + blackjackGameManager.updatePlayer("jacob"); + + // when + BlackjackResultDto actual = blackjackGameManager.getBlackjackResult(); + + // then + assertThat(actual.playerResultDtoList()).hasSize(2); + assertThat(actual.playerResultDtoList().get(0).name()).isEqualTo("jacob"); + assertThat(actual.playerResultDtoList().get(0).hand()).containsExactly("10클로버", "9다이아몬드", "A클로버"); + assertThat(actual.playerResultDtoList().get(1).name()).isEqualTo("seoye"); + assertThat(actual.playerResultDtoList().get(1).hand()).containsExactly("4하트", "5스페이드"); } } @Nested - class CalculatePlayerResultsTest { + class GetBlackjackStatisticsTest { - @Nested - class Success { + @Test + void 플레이어가_버스트면_패배한다() { - @Test - void 플레이어별_결과를_승무패로_계산해야_한다() { - - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.SPADE), + card(Rank.K, Suit.CLOVER), card(Rank.NINE, Suit.DIAMOND), card(Rank.TEN, Suit.CLOVER), card(Rank.EIGHT, Suit.DIAMOND), - card(Rank.TEN, Suit.DIAMOND), card(Rank.SEVEN, Suit.HEART), - card(Rank.NINE, Suit.CLOVER), card(Rank.SEVEN, Suit.DIAMOND) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye", "brown")); - blackjackGameManager.drawInitialCards(); - - // when - List actual = blackjackGameManager.calculatePlayerResults(); - - // then - assertThat(actual).containsExactly( - new PlayerStatisticDto("jacob", Result.WIN), - new PlayerStatisticDto("seoye", Result.DRAW), - new PlayerStatisticDto("brown", Result.LOSE) - ); - } + card(Rank.K, Suit.HEART) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + blackjackGameManager.updatePlayer("seoye"); + + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); + + // then + assertThat(profitOf(actual, "seoye")).isEqualTo(-500); + assertThat(actual.dealerProfit()).isEqualTo(-500); } - } - @Nested - class CalculateDealerResultTest { + @Test + void 플레이어와_딜러가_모두_블랙잭이면_무승부다() { - @Nested - class Success { + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.ACE, Suit.HEART), card(Rank.K, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER), card(Rank.Q, Suit.DIAMOND), + card(Rank.TEN, Suit.CLOVER), card(Rank.NINE, Suit.DIAMOND) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); - @Test - void 딜러_승무패_통계를_플레이어_결과로부터_계산해야_한다() { + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.SPADE), - card(Rank.TEN, Suit.CLOVER), card(Rank.EIGHT, Suit.DIAMOND), - card(Rank.TEN, Suit.DIAMOND), card(Rank.SEVEN, Suit.HEART), - card(Rank.NINE, Suit.CLOVER), card(Rank.SEVEN, Suit.DIAMOND) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye", "brown")); - blackjackGameManager.drawInitialCards(); - - // when - BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); - - // then - assertThat(actual.dealerStatisticDto().win()).isEqualTo(1); - assertThat(actual.dealerStatisticDto().draw()).isEqualTo(1); - assertThat(actual.dealerStatisticDto().lose()).isEqualTo(1); - } + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(0); + assertThat(actual.dealerProfit()).isEqualTo(500); } - } - @Nested - class generateBlackjackResultDto { + @Test + void 플레이어만_블랙잭이면_배팅_금액의_1_5배를_번다() { - @Nested - class Success { + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.NINE, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER), card(Rank.K, Suit.DIAMOND), + card(Rank.EIGHT, Suit.HEART), card(Rank.SEVEN, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); - @Test - void 플레이어가_카드를_추가로_받으면_최종_결과에_반영되어야_한다() { + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), - card(Rank.ACE, Suit.CLOVER) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - blackjackGameManager.updatePlayer("jacob"); - - // when - BlackjackResultDto actual = blackjackGameManager.getBlackjackResult(); - - // then - assertThat(actual.playerResultDtoList().get(0).name()).isEqualTo("jacob"); - assertThat(actual.playerResultDtoList().get(0).hand()).hasSize(3); - assertThat(actual.playerResultDtoList().get(0).hand()).contains("A클로버"); - assertThat(actual.playerResultDtoList().get(1).name()).isEqualTo("seoye"); - assertThat(actual.playerResultDtoList().get(1).hand()).hasSize(2); - } + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(1500); + assertThat(actual.dealerProfit()).isEqualTo(-1000); } - } - @Nested - class UpdatePlayerTest { + @Test + void 딜러가_버스트면_살아있는_플레이어는_승리한다() { - @Nested - class Success { - - @Test - void 플레이어_카드를_추가하고_업데이트된_Dto를_반환한다() { - - // given - BlackjackGameManager blackjackGameManager = new FixedDeckBlackjackGameManager(List.of( - card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), - card(Rank.TWO, Suit.CLOVER), card(Rank.THREE, Suit.DIAMOND), - card(Rank.FOUR, Suit.HEART), card(Rank.FIVE, Suit.SPADE), - card(Rank.ACE, Suit.CLOVER) - )); - blackjackGameManager.createPlayers(List.of("jacob", "seoye")); - blackjackGameManager.drawInitialCards(); - - // when - ParticipantDto actual = blackjackGameManager.updatePlayer("jacob"); - - // then - assertThat(actual.name()).isEqualTo("jacob"); - assertThat(actual.hand()).hasSize(3); - } + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), + card(Rank.TEN, Suit.CLOVER), card(Rank.SEVEN, Suit.DIAMOND), + card(Rank.NINE, Suit.HEART), card(Rank.EIGHT, Suit.CLOVER), + card(Rank.NINE, Suit.DIAMOND) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + blackjackGameManager.drawDealerCard(); + + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); + + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(1000); + assertThat(profitOf(actual, "seoye")).isEqualTo(500); + assertThat(actual.dealerProfit()).isEqualTo(-1500); } - } - @Nested - class IsHitTest { + @Test + void 플레이어_점수가_딜러보다_크면_승리한다() { - @Nested - class Success { + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.EIGHT, Suit.SPADE), + card(Rank.TEN, Suit.CLOVER), card(Rank.NINE, Suit.DIAMOND), + card(Rank.EIGHT, Suit.HEART), card(Rank.SEVEN, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); - @Test - void y를_입력하면_true_를_반환한다() { - // given - HitOrStand hitOrStand = HitOrStand.from(" y "); + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); - // when - boolean actual = blackjackGameManager.isHit(hitOrStand); + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(1000); + assertThat(actual.dealerProfit()).isEqualTo(-500); + } - // then - assertThat(actual).isTrue(); - } + @Test + void 플레이어_점수가_딜러와_같으면_무승부다() { - @Test - void n을_입력하면_false를_반환한다() { - // given - HitOrStand hitOrStand = HitOrStand.from(" n "); + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.EIGHT, Suit.SPADE), + card(Rank.NINE, Suit.CLOVER), card(Rank.NINE, Suit.DIAMOND), + card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); - // when - boolean actual = blackjackGameManager.isHit(hitOrStand); + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); - // then - assertThat(actual).isFalse(); - } + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(0); + assertThat(actual.dealerProfit()).isEqualTo(500); } - } - - @Nested - class IsStandTest { - - @Nested - class Success { - @Test - void n을_입력하면_true를_반환한다() { - // given - HitOrStand hitOrStand = HitOrStand.from(" n "); + @Test + void 플레이어_점수가_딜러보다_작으면_패배한다() { + + // given + BlackjackGameManager blackjackGameManager = fixedDeckBlackjackGameManager(List.of( + card(Rank.TEN, Suit.HEART), card(Rank.EIGHT, Suit.SPADE), + card(Rank.NINE, Suit.CLOVER), card(Rank.EIGHT, Suit.DIAMOND), + card(Rank.TEN, Suit.HEART), card(Rank.SEVEN, Suit.CLOVER) + )); + createParticipants(blackjackGameManager, betAmounts("1000", "500")); + blackjackGameManager.drawInitialCards(); + + // when + BlackjackStatisticsDto actual = blackjackGameManager.getBlackjackStatistics(); + + // then + assertThat(profitOf(actual, "jacob")).isEqualTo(-1000); + assertThat(profitOf(actual, "seoye")).isEqualTo(-500); + assertThat(actual.dealerProfit()).isEqualTo(1500); + } + } - // when - boolean actual = blackjackGameManager.isStand(hitOrStand); + private static int profitOf(BlackjackStatisticsDto blackjackStatisticsDto, String name) { + return blackjackStatisticsDto.playerStatisticDtoList().stream() + .filter(player -> player.name().equals(name)) + .findFirst() + .orElseThrow() + .profit(); + } - // then - assertThat(actual).isTrue(); - } + private static BlackjackGameManager fixedDeckBlackjackGameManager(List cards) { + return new FixedDeckBlackjackGameManager(cards); + } - @Test - void y를_입력하면_false를_반환한다() { - // given - HitOrStand hitOrStand = HitOrStand.from(" y "); + private static void createParticipants(BlackjackGameManager blackjackGameManager, List betAmounts) { + blackjackGameManager.createParticipants(playerNames(), betAmounts); + } - // when - boolean actual = blackjackGameManager.isStand(hitOrStand); + private static List playerNames() { + return List.of(new PlayerName("jacob"), new PlayerName("seoye")); + } - // then - assertThat(actual).isFalse(); - } - } + private static List betAmounts(String first, String second) { + return List.of(new BetAmount(first), new BetAmount(second)); } private static Card card(Rank rank, Suit suit) { @@ -446,9 +385,6 @@ private FixedDeckBlackjackGameManager(List cards) { @Override public Card drawCard() { - if (cards.isEmpty()) { - return null; - } return cards.removeFirst(); } } diff --git a/src/test/java/domain/game/BlackjackJudgeTest.java b/src/test/java/domain/game/BlackjackJudgeTest.java new file mode 100644 index 00000000000..6cfd7003029 --- /dev/null +++ b/src/test/java/domain/game/BlackjackJudgeTest.java @@ -0,0 +1,197 @@ +package domain.game; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; +import domain.participant.BetAmount; +import domain.participant.Dealer; +import domain.participant.Player; +import domain.participant.PlayerName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class BlackjackJudgeTest { + + private final BlackjackJudge blackjackJudge = new BlackjackJudge(); + + @Nested + class JudgePlayerResultTest { + + @Test + void 플레이어가_버스트면_패배를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.TEN, Suit.HEART), + card(Rank.SIX, Suit.SPADE) + ); + Player player = player( + card(Rank.K, Suit.CLOVER), + card(Rank.NINE, Suit.DIAMOND), + card(Rank.FIVE, Suit.HEART) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.LOSE); + } + + @Test + void 플레이어와_딜러가_모두_블랙잭이면_무승부를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.ACE, Suit.HEART), + card(Rank.K, Suit.SPADE) + ); + Player player = player( + card(Rank.ACE, Suit.CLOVER), + card(Rank.Q, Suit.DIAMOND) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.DRAW); + } + + @Test + void 플레이어만_블랙잭이면_블랙잭_승리를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.TEN, Suit.HEART), + card(Rank.NINE, Suit.SPADE) + ); + Player player = player( + card(Rank.ACE, Suit.CLOVER), + card(Rank.K, Suit.DIAMOND) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.BLACKJACK_WIN); + } + + @Test + void 플레이어가_블랙잭이고_딜러가_버스트여도_블랙잭_승리를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.K, Suit.HEART), + card(Rank.NINE, Suit.SPADE), + card(Rank.FIVE, Suit.DIAMOND) + ); + Player player = player( + card(Rank.ACE, Suit.CLOVER), + card(Rank.Q, Suit.HEART) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.BLACKJACK_WIN); + } + + @Test + void 딜러가_버스트면_승리를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.K, Suit.HEART), + card(Rank.NINE, Suit.SPADE), + card(Rank.THREE, Suit.DIAMOND) + ); + Player player = player( + card(Rank.TEN, Suit.CLOVER), + card(Rank.SEVEN, Suit.HEART) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.WIN); + } + + @Test + void 플레이어_점수가_딜러보다_크면_승리를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.TEN, Suit.HEART), + card(Rank.EIGHT, Suit.SPADE) + ); + Player player = player( + card(Rank.TEN, Suit.CLOVER), + card(Rank.NINE, Suit.DIAMOND) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.WIN); + } + + @Test + void 플레이어_점수가_딜러와_같으면_무승부를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.TEN, Suit.HEART), + card(Rank.EIGHT, Suit.SPADE) + ); + Player player = player( + card(Rank.NINE, Suit.CLOVER), + card(Rank.NINE, Suit.DIAMOND) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.DRAW); + } + + @Test + void 모든_우선_조건에_해당하지_않으면_패배를_반환한다() { + // given + Dealer dealer = dealer( + card(Rank.TEN, Suit.HEART), + card(Rank.NINE, Suit.SPADE) + ); + Player player = player( + card(Rank.NINE, Suit.CLOVER), + card(Rank.EIGHT, Suit.DIAMOND) + ); + + // when + Result actual = blackjackJudge.judgePlayerResult(dealer, player); + + // then + assertThat(actual).isEqualTo(Result.LOSE); + } + } + + private static Dealer dealer(Card... cards) { + Dealer dealer = new Dealer(); + for (Card card : cards) { + dealer.addCard(card); + } + return dealer; + } + + private static Player player(Card... cards) { + Player player = new Player(new PlayerName("jacob"), new BetAmount("1000")); + for (Card card : cards) { + player.addCard(card); + } + return player; + } + + private static Card card(Rank rank, Suit suit) { + return new Card(rank, suit); + } +} diff --git a/src/test/java/domain/game/HitOrStandTest.java b/src/test/java/domain/game/HitOrStandTest.java new file mode 100644 index 00000000000..69a5f721db0 --- /dev/null +++ b/src/test/java/domain/game/HitOrStandTest.java @@ -0,0 +1,125 @@ +package domain.game; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class HitOrStandTest { + + @Nested + class FromTest { + + @Nested + class Success { + + @Test + void y를_입력하면_HIT을_반환한다() { + // given + String input = "y"; + + // when + HitOrStand actual = HitOrStand.from(input); + + // then + assertThat(actual).isEqualTo(HitOrStand.HIT); + } + + @Test + void n을_입력하면_STAND를_반환한다() { + // given + String input = "n"; + + // when + HitOrStand actual = HitOrStand.from(input); + + // then + assertThat(actual).isEqualTo(HitOrStand.STAND); + } + + @Test + void 앞뒤_공백이_있어도_정상_처리한다() { + // given + String input = " y "; + + // when + HitOrStand actual = HitOrStand.from(input); + + // then + assertThat(actual).isEqualTo(HitOrStand.HIT); + } + } + + @Nested + class Fail { + + @Test + void y_또는_n가_아니면_예외를_던진다() { + // given + String input = "a"; + + // when & then + assertThatThrownBy(() -> HitOrStand.from(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(HitOrStand.INVALID_YES_NO_INPUT); + } + } + } + + @Nested + class IsHitTest { + + @Test + void HIT이면_true를_반환한다() { + // given + HitOrStand hitOrStand = HitOrStand.HIT; + + // when + boolean actual = hitOrStand.isHit(); + + // then + assertThat(actual).isTrue(); + } + + @Test + void STAND이면_false를_반환한다() { + // given + HitOrStand hitOrStand = HitOrStand.STAND; + + // when + boolean actual = hitOrStand.isHit(); + + // then + assertThat(actual).isFalse(); + } + } + + @Nested + class IsStandTest { + + @Test + void STAND이면_true를_반환한다() { + // given + HitOrStand hitOrStand = HitOrStand.STAND; + + // when + boolean actual = hitOrStand.isStand(); + + // then + assertThat(actual).isTrue(); + } + + @Test + void HIT이면_false를_반환한다() { + // given + HitOrStand hitOrStand = HitOrStand.HIT; + + // when + boolean actual = hitOrStand.isStand(); + + // then + assertThat(actual).isFalse(); + } + } +} diff --git a/src/test/java/domain/game/ResultTest.java b/src/test/java/domain/game/ResultTest.java new file mode 100644 index 00000000000..0caf0083683 --- /dev/null +++ b/src/test/java/domain/game/ResultTest.java @@ -0,0 +1,61 @@ +package domain.game; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ResultTest { + + @Nested + class GetProfitTest { + + @Test + void 블랙잭_승리면_배팅_금액의_1_5배를_반환한다() { + // given + int betAmount = 1000; + + // when + int actual = Result.BLACKJACK_WIN.getProfit(betAmount); + + // then + assertThat(actual).isEqualTo(1500); + } + + @Test + void 일반_승리면_배팅_금액과_같은_수익을_반환한다() { + // given + int betAmount = 1000; + + // when + int actual = Result.WIN.getProfit(betAmount); + + // then + assertThat(actual).isEqualTo(1000); + } + + @Test + void 무승부면_수익은_0이다() { + // given + int betAmount = 1000; + + // when + int actual = Result.DRAW.getProfit(betAmount); + + // then + assertThat(actual).isEqualTo(0); + } + + @Test + void 패배면_배팅_금액만큼_음수_수익을_반환한다() { + // given + int betAmount = 1000; + + // when + int actual = Result.LOSE.getProfit(betAmount); + + // then + assertThat(actual).isEqualTo(-1000); + } + } +} diff --git a/src/test/java/domain/participant/BetAmountTest.java b/src/test/java/domain/participant/BetAmountTest.java new file mode 100644 index 00000000000..7fa1fdc2cf0 --- /dev/null +++ b/src/test/java/domain/participant/BetAmountTest.java @@ -0,0 +1,65 @@ +package domain.participant; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +class BetAmountTest { + + @Nested + class ConstructorTest { + + @Nested + class Success { + + @ParameterizedTest + @MethodSource("successCases") + void 양수_숫자면_배팅_금액이_정상_생성된다(String input, int expected) { + + // when + BetAmount actual = new BetAmount(input); + + // then + assertThat(actual.getBetAmount()).isEqualTo(expected); + } + + static Stream successCases() { + return Stream.of( + Arguments.of("1", 1), + Arguments.of("1000", 1000), + Arguments.of("50000", 50000) + ); + } + } + + @Nested + class Fail { + + @ParameterizedTest + @ValueSource(strings = {"abc", "1a", "12 3", "1.5"}) + void 숫자가_아니면_예외가_발생한다(String input) { + + // when & then + assertThatThrownBy(() -> new BetAmount(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BetAmount.INVALID_BET_AMOUNT_NUMBER); + } + + @ParameterizedTest + @ValueSource(strings = {"0", "-1", "-1000"}) + void 제로_이하이면_예외가_발생한다(String input) { + + // when & then + assertThatThrownBy(() -> new BetAmount(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BetAmount.INVALID_BET_AMOUNT_POSITIVE); + } + } + } +} diff --git a/src/test/java/domain/participant/DealerTest.java b/src/test/java/domain/participant/DealerTest.java index fb5fb6a1c2e..de964478c40 100644 --- a/src/test/java/domain/participant/DealerTest.java +++ b/src/test/java/domain/participant/DealerTest.java @@ -36,4 +36,58 @@ class Success { } } } + + @Nested + class ShouldHitTest { + + @Nested + class Success { + + @Test + void 딜러_점수가_16_이하면_카드를_더_뽑는다() { + + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(Rank.TEN, Suit.HEART)); + dealer.addCard(new Card(Rank.SIX, Suit.SPADE)); + + // when + boolean actual = dealer.shouldHit(); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 딜러_점수가_17이면_카드를_더_뽑지_않는다() { + + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(Rank.TEN, Suit.HEART)); + dealer.addCard(new Card(Rank.SEVEN, Suit.SPADE)); + + // when + boolean actual = dealer.shouldHit(); + + // then + assertThat(actual).isFalse(); + } + + @Test + void 딜러_점수가_21_초과면_카드를_더_뽑지_않는다() { + + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(Rank.TEN, Suit.HEART)); + dealer.addCard(new Card(Rank.NINE, Suit.SPADE)); + dealer.addCard(new Card(Rank.THREE, Suit.CLOVER)); + + // when + boolean actual = dealer.shouldHit(); + + // then + assertThat(actual).isFalse(); + } + } + } } diff --git a/src/test/java/domain/participant/ParticipantTest.java b/src/test/java/domain/participant/ParticipantTest.java deleted file mode 100644 index e76a102dc76..00000000000 --- a/src/test/java/domain/participant/ParticipantTest.java +++ /dev/null @@ -1,138 +0,0 @@ -package domain.participant; - -import static org.assertj.core.api.Assertions.assertThat; - -import domain.card.Card; -import domain.card.Rank; -import domain.card.Suit; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class ParticipantTest { - - @Nested - class AddCardTest { - - @Nested - class Success { - - @Test - void 카드를_추가하면_손패에_저장되어야_한다() { - - // given - Participant participant = new Participant(); - Card card = new Card(Rank.ACE, Suit.HEART); - - // when - participant.addCard(card); - - // then - assertThat(participant.getHand()) - .hasSize(1) - .containsExactly(card); - } - } - } - - @Nested - class GetHandTest { - - @Nested - class Success { - - @Test - void 손패가_비어있으면_빈_리스트를_반환해야_한다() { - - // given - Participant participant = new Participant(); - - // when - var actual = participant.getHand(); - - // then - assertThat(actual).isEmpty(); - } - - @Test - void 손패의_카드_이름을_추가한_순서대로_반환해야_한다() { - - // given - Participant participant = new Participant(); - Card firstCard = new Card(Rank.FIVE, Suit.HEART); - Card secondCard = new Card(Rank.J, Suit.SPADE); - participant.addCard(firstCard); - participant.addCard(secondCard); - - // when - var actual = participant.getHand(); - - // then - assertThat(actual) - .hasSize(2) - .containsExactly(firstCard, secondCard); - } - } - } - - @Nested - class CalculateScoreTest { - - @Nested - class Success { - - @Test - void 현재_손패_점수를_계산해_반환해야_한다() { - - // given - Participant participant = new Participant(); - participant.addCard(new Card(Rank.TEN, Suit.HEART)); - participant.addCard(new Card(Rank.THREE, Suit.SPADE)); - - // when - int actual = participant.calculateScore(); - - // then - assertThat(actual).isEqualTo(13); - } - } - } - - @Nested - class IsBustTest { - - @Nested - class Success { - - @Test - void 점수가_21을_초과하면_true를_반환해야_한다() { - - // given - Participant participant = new Participant(); - participant.addCard(new Card(Rank.TEN, Suit.HEART)); - participant.addCard(new Card(Rank.K, Suit.SPADE)); - participant.addCard(new Card(Rank.TWO, Suit.CLOVER)); - - // when - boolean actual = participant.isBust(); - - // then - assertThat(actual).isTrue(); - } - - @Test - void 점수가_21_이하면_false를_반환해야_한다() { - - // given - Participant participant = new Participant(); - participant.addCard(new Card(Rank.TEN, Suit.HEART)); - participant.addCard(new Card(Rank.ACE, Suit.SPADE)); - - // when - boolean actual = participant.isBust(); - - // then - assertThat(actual).isFalse(); - } - } - } -} diff --git a/src/test/java/domain/participant/ParticipantsTest.java b/src/test/java/domain/participant/ParticipantsTest.java index 80e2a831223..358e4cc86b5 100644 --- a/src/test/java/domain/participant/ParticipantsTest.java +++ b/src/test/java/domain/participant/ParticipantsTest.java @@ -1,51 +1,137 @@ package domain.participant; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class ParticipantsTest { @Nested - class GetPlayerTest { + class OfTest { - @Nested - class Success { + @Test + void 플레이어_이름과_배팅_금액으로_참가자를_생성한다() { + // given + List playerNames = playerNames(); + List betAmounts = betAmounts("1000", "500"); - @Test - void 이름에_해당하는_플레이어를_반환해야_한다() { + // when + Participants actual = Participants.of(playerNames, betAmounts); - // given - Participants participants = createParticipants(); - Player expected = participants.players().getPlayer("jacob"); + // then + assertThat(actual.dealer()).isNotNull(); + assertThat(actual.players().getPlayers()).hasSize(2); + assertThat(actual.players().getPlayers().get(0).getName()).isEqualTo("jacob"); + assertThat(actual.players().getPlayers().get(1).getName()).isEqualTo("seoye"); + } + } + + @Nested + class DrawInitialCardsTest { + + @Test + void 딜러는_2장_모든_플레이어는_각각_2장을_받는다() { + // given + Participants participants = participants(); + Supplier cardSupplier = fixedCardSupplier(List.of( + card(Rank.TEN, Suit.HEART), + card(Rank.SIX, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER), + card(Rank.K, Suit.DIAMOND), + card(Rank.NINE, Suit.HEART), + card(Rank.THREE, Suit.CLOVER) + )); + + // when + participants.drawInitialCards(cardSupplier); + + // then + assertThat(participants.dealer().getHand()) + .containsExactly(card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE)); + assertThat(participants.players().getPlayer("jacob").getHand()) + .containsExactly(card(Rank.ACE, Suit.CLOVER), card(Rank.K, Suit.DIAMOND)); + assertThat(participants.players().getPlayer("seoye").getHand()) + .containsExactly(card(Rank.NINE, Suit.HEART), card(Rank.THREE, Suit.CLOVER)); + } + } + + @Nested + class DrawCardsByDealerTest { + + @Test + void 딜러에게_카드를_한_장_추가한다() { + // given + Participants participants = participants(); + participants.dealer().addCard(card(Rank.TEN, Suit.HEART)); + participants.dealer().addCard(card(Rank.SIX, Suit.SPADE)); + Supplier cardSupplier = fixedCardSupplier(List.of(card(Rank.ACE, Suit.CLOVER))); + + // when + participants.drawCardsByDealer(cardSupplier); - // when - Player actual = participants.getPlayer("jacob"); + // then + assertThat(participants.dealer().getHand()) + .containsExactly(card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE), card(Rank.ACE, Suit.CLOVER)); + } + } - // then - assertThat(actual).isSameAs(expected); - } + @Nested + class DrawCardsByPlayerTest { - @Test - void 해당_이름의_플레이어가_없으면_null을_반환해야_한다() { + @Test + void 이름으로_찾은_플레이어에게_카드를_추가하고_플레이어를_반환한다() { + // given + Participants participants = participants(); + Supplier cardSupplier = fixedCardSupplier(List.of(card(Rank.FOUR, Suit.DIAMOND))); - // given - Participants participants = createParticipants(); + // when + Player actual = participants.drawCardsByPlayer("jacob", cardSupplier); - // when - Player actual = participants.getPlayer("brown"); + // then + assertThat(actual.getName()).isEqualTo("jacob"); + assertThat(actual.getHand()).containsExactly(card(Rank.FOUR, Suit.DIAMOND)); + } - // then - assertThat(actual).isNull(); - } + @Test + void 존재하지_않는_플레이어_이름이면_예외를_던진다() { + // given + Participants participants = participants(); + Supplier cardSupplier = fixedCardSupplier(List.of(card(Rank.FOUR, Suit.DIAMOND))); + + // when & then + assertThatThrownBy(() -> participants.drawCardsByPlayer("brown", cardSupplier)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(Players.NOT_FOUND_PLAYER); } } - private Participants createParticipants() { - Dealer dealer = new Dealer(); - Players players = new Players(List.of("jacob", "seoye")); - return new Participants(dealer, players); + private static Participants participants() { + return Participants.of(playerNames(), betAmounts("1000", "500")); + } + + private static List playerNames() { + return List.of(new PlayerName("jacob"), new PlayerName("seoye")); + } + + private static List betAmounts(String first, String second) { + return List.of(new BetAmount(first), new BetAmount(second)); + } + + private static Supplier fixedCardSupplier(List cards) { + Deque deque = new ArrayDeque<>(cards); + return deque::removeFirst; + } + + private static Card card(Rank rank, Suit suit) { + return new Card(rank, suit); } } diff --git a/src/test/java/domain/participant/PlayerNameTest.java b/src/test/java/domain/participant/PlayerNameTest.java index d2c22c52563..e6873e08d6d 100644 --- a/src/test/java/domain/participant/PlayerNameTest.java +++ b/src/test/java/domain/participant/PlayerNameTest.java @@ -1,7 +1,7 @@ package domain.participant; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import exception.BlackjackException; import java.util.stream.Stream; @@ -21,8 +21,7 @@ class Success { @ParameterizedTest @MethodSource("successCases") - void 이름이_2글자_이상_5글자_이하면_정상_생성된다(String input) { - + void 이름이_2글자_이상_5글자_이하면_생성된다(String input) { // when PlayerName actual = new PlayerName(input); @@ -34,7 +33,7 @@ static Stream successCases() { return Stream.of( Arguments.of("ab"), Arguments.of("abcde"), - Arguments.of("성 열") + Arguments.of("성열") ); } } @@ -43,9 +42,8 @@ static Stream successCases() { class Fail { @ParameterizedTest - @ValueSource(strings = {"a", "abcdef", "aa aaa"}) - void 이름_길이가_2글자_미만_또는_5글자_초과면_예외가_발생한다(String input) { - + @ValueSource(strings = {"a", "abcdef"}) + void 이름_길이가_범위를_벗어나면_예외가_발생한다(String input) { // when & then assertThatThrownBy(() -> new PlayerName(input)) .isInstanceOf(IllegalArgumentException.class) @@ -55,7 +53,6 @@ class Fail { @ParameterizedTest @ValueSource(strings = {"", " ", " "}) void 이름이_공백이면_예외가_발생한다(String input) { - // when & then assertThatThrownBy(() -> new PlayerName(input)) .isInstanceOf(IllegalArgumentException.class) diff --git a/src/test/java/domain/participant/PlayerTest.java b/src/test/java/domain/participant/PlayerTest.java deleted file mode 100644 index 2e0b4c0ef5a..00000000000 --- a/src/test/java/domain/participant/PlayerTest.java +++ /dev/null @@ -1,123 +0,0 @@ -package domain.participant; - -import static org.assertj.core.api.Assertions.assertThat; - -import domain.card.Card; -import domain.card.Rank; -import domain.card.Suit; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class PlayerTest { - - @Nested - class ConstructorTest { - - @Nested - class Success { - - @Test - void 게임_참가자_조건이_맞으면_성공_이다() { - - // given - String name = "jacob"; - - // when - Player player = new Player(name); - String actual = player.getName(); - - // then - String expected = "jacob"; - Assertions.assertEquals(expected, actual); - } - } - } - - @Nested - class AddCardTest { - - @Nested - class Success { - - @Test - void 카드를_추가하면_손패에_카드가_저장되어야_한다() { - - // given - Player player = new Player("jacob"); - Card card = new Card(Rank.ACE, Suit.HEART); - - // when - player.addCard(card); - - // then - assertThat(player.getHand().size()).isEqualTo(1); - } - } - } - - @Nested - class IsBustTest { - - @Nested - class Success { - - @Test - void 플레이어의_점수가_21_초과이면_버스트이다() { - - // given - Player player = new Player("jacob"); - Card card1 = new Card(Rank.TEN, Suit.HEART); - Card card2 = new Card(Rank.TEN, Suit.HEART); - Card card3 = new Card(Rank.TEN, Suit.HEART); - - // when - player.addCard(card1); - player.addCard(card2); - player.addCard(card3); - - // then - assertThat(player.isBust()).isTrue(); - } - - @Test - void 플레이어의_점수가_21_이하이면_버스트가_아니다() { - - // given - Player player = new Player("jacob"); - Card card1 = new Card(Rank.TEN, Suit.HEART); - Card card2 = new Card(Rank.ACE, Suit.HEART); - - // when - player.addCard(card1); - player.addCard(card2); - - // then - assertThat(player.isBust()).isFalse(); - } - } - } - - @Nested - class CalculateScoreTest { - - @Nested - class Success { - - @Test - void 플레이어의_현재_점수를_반환해야_한다() { - - // given - Player player = new Player("jacob"); - player.addCard(new Card(Rank.TEN, Suit.HEART)); - player.addCard(new Card(Rank.THREE, Suit.SPADE)); - - // when - int actual = player.calculateScore(); - - // then - assertThat(actual).isEqualTo(13); - } - } - } -} diff --git a/src/test/java/domain/participant/PlayersTest.java b/src/test/java/domain/participant/PlayersTest.java index 16b13805bbe..bbebadfe2ba 100644 --- a/src/test/java/domain/participant/PlayersTest.java +++ b/src/test/java/domain/participant/PlayersTest.java @@ -1,127 +1,263 @@ package domain.participant; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; import exception.BlackjackException; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; -import java.util.stream.Stream; +import java.util.function.Supplier; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; class PlayersTest { @Nested class ConstructorTest { - @Nested - class Success { + @Test + void 유효한_이름과_배팅_금액이면_플레이어_목록을_생성한다() { + // when + Players actual = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); - @Test - void 플레이어_이름_목록으로_Players를_생성한다() { + // then + assertThat(actual.getPlayers()).hasSize(2); + assertThat(actual.getPlayers().get(0).getName()).isEqualTo("jacob"); + assertThat(actual.getPlayers().get(1).getName()).isEqualTo("seoye"); + } + + @Test + void 플레이어_수가_2명_미만이면_예외가_발생한다() { + // when & then + assertThatThrownBy(() -> players( + List.of("jacob"), + List.of("1000") + )) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.PLAYER_COUNT_OUT_OF_RANGE); + } + + @Test + void 플레이어_수가_8명_초과이면_예외가_발생한다() { + // when & then + assertThatThrownBy(() -> players( + List.of("aa", "bb", "cc", "dd", "ee", "ff", "gg", "hh", "ii"), + List.of("100", "100", "100", "100", "100", "100", "100", "100", "100") + )) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.PLAYER_COUNT_OUT_OF_RANGE); + } + + @Test + void 플레이어_이름이_중복되면_예외가_발생한다() { + // when & then + assertThatThrownBy(() -> players( + List.of("jacob", "jacob"), + List.of("1000", "500") + )) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.PLAYER_DUPLICATED); + } + } + + @Nested + class GetPlayerTest { - // given - List input = List.of("jacob", "seoye"); + @Test + void 이름으로_플레이어를_조회한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); - // when - Players players = new Players(input); + // when + Player actual = players.getPlayer("seoye"); - // then - assertThat(players.getPlayers()).hasSize(2); - assertThat(players.getPlayers()) - .extracting(Player::getName) - .containsExactly("jacob", "seoye"); - } + // then + assertThat(actual.getName()).isEqualTo("seoye"); + assertThat(actual.getBetAmount()).isEqualTo(500); } - @Nested - class Fail { - - @Test - void 플레이어_이름이_중복되면_예외가_발생한다() { - - // given - List input = List.of("jacob", "jacob"); - - // when & then - assertThatThrownBy(() -> new Players(input)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.PLAYER_DUPLICATED); - } - - @ParameterizedTest - @MethodSource("playerCountOutOfRangeCases") - void 플레이어_수가_2명_미만_또는_8명_초과면_예외가_발생한다(List input) { - - // when & then - assertThatThrownBy(() -> new Players(input)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.PLAYER_COUNT_OUT_OF_RANGE); - } - - static Stream playerCountOutOfRangeCases() { - return Stream.of( - Arguments.of(List.of("a1")), - Arguments.of(List.of("a1", "b2", "c3", "d4", "e5", "f6", "g7", "h8", "i9")) - ); - } + @Test + void 존재하지_않는_이름이면_예외가_발생한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + + // when & then + assertThatThrownBy(() -> players.getPlayer("brown")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.NOT_FOUND_PLAYER); } } @Nested class GetPlayersTest { - @Nested - class Success { + @Test + void 전체_플레이어_목록을_조회한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); - @Test - void 플레이어_목록은_수정_불가능한_리스트여야_한다() { + // when + List actual = players.getPlayers(); - // given - Players players = new Players(List.of("jacob", "seoye")); + // then + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getName()).isEqualTo("jacob"); + assertThat(actual.get(1).getName()).isEqualTo("seoye"); + } - // when & then - assertThatThrownBy(() -> players.getPlayers().add(new Player("brown"))) - .isInstanceOf(UnsupportedOperationException.class); - } + @Test + void 반환_목록은_수정할_수_없다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + + // when & then + assertThatThrownBy(() -> players.getPlayers().add(new Player(new PlayerName("brown"), new BetAmount("700")))) + .isInstanceOf(UnsupportedOperationException.class); } } @Nested - class GetPlayerTest { + class DrawInitialCardsTest { + + @Test + void 모든_플레이어는_초기_카드를_2장씩_받는다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + Supplier cardSupplier = fixedCardSupplier(List.of( + card(Rank.TEN, Suit.HEART), + card(Rank.SIX, Suit.SPADE), + card(Rank.ACE, Suit.CLOVER), + card(Rank.K, Suit.DIAMOND) + )); + + // when + players.drawInitialCards(cardSupplier); + + // then + assertThat(players.getPlayer("jacob").getHand()) + .containsExactly(card(Rank.TEN, Suit.HEART), card(Rank.SIX, Suit.SPADE)); + assertThat(players.getPlayer("seoye").getHand()) + .containsExactly(card(Rank.ACE, Suit.CLOVER), card(Rank.K, Suit.DIAMOND)); + } + } - @Nested - class Success { + @Nested + class AddCardTest { + + @Test + void 이름으로_찾은_플레이어에게_카드를_추가한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + Card card = card(Rank.NINE, Suit.HEART); + + // when + players.addCard("jacob", card); + + // then + assertThat(players.getPlayer("jacob").getHand()).containsExactly(card); + } - @Test - void 이름으로_플레이어를_조회한다() { + @Test + void 없는_플레이어에게_카드를_추가하면_예외가_발생한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); - // given - Players players = new Players(List.of("jacob", "seoye")); + // when & then + assertThatThrownBy(() -> players.addCard("brown", card(Rank.NINE, Suit.HEART))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(BlackjackException.ERROR_PREFIX + Players.NOT_FOUND_PLAYER); + } + } - // when - Player actual = players.getPlayer("jacob"); + @Nested + class PlayerIsBustTest { - // then - assertThat(actual).isNotNull(); - assertThat(actual.getName()).isEqualTo("jacob"); - } + @Test + void 플레이어_점수가_21_초과면_true를_반환한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + players.addCard("jacob", card(Rank.TEN, Suit.HEART)); + players.addCard("jacob", card(Rank.NINE, Suit.SPADE)); + players.addCard("jacob", card(Rank.THREE, Suit.CLOVER)); - @Test - void 없는_이름을_조회하면_null을_반환한다() { + // when + boolean actual = players.playerIsBust("jacob"); + + // then + assertThat(actual).isTrue(); + } - // given - Players players = new Players(List.of("jacob", "seoye")); + @Test + void 플레이어_점수가_21_이하면_false를_반환한다() { + // given + Players players = players( + List.of("jacob", "seoye"), + List.of("1000", "500") + ); + players.addCard("jacob", card(Rank.TEN, Suit.HEART)); + players.addCard("jacob", card(Rank.SEVEN, Suit.SPADE)); - // when - Player actual = players.getPlayer("brown"); + // when + boolean actual = players.playerIsBust("jacob"); - // then - assertThat(actual).isNull(); - } + // then + assertThat(actual).isFalse(); } } + + private static Players players(List names, List betAmounts) { + return new Players(toPlayerNames(names), toBetAmounts(betAmounts)); + } + + private static List toPlayerNames(List names) { + return names.stream() + .map(PlayerName::new) + .toList(); + } + + private static List toBetAmounts(List betAmounts) { + return betAmounts.stream() + .map(BetAmount::new) + .toList(); + } + + private static Supplier fixedCardSupplier(List cards) { + Deque deque = new ArrayDeque<>(cards); + return deque::removeFirst; + } + + private static Card card(Rank rank, Suit suit) { + return new Card(rank, suit); + } } diff --git a/src/test/java/dto/BlackjackResultDtoTest.java b/src/test/java/dto/BlackjackResultDtoTest.java new file mode 100644 index 00000000000..c45791c2ce4 --- /dev/null +++ b/src/test/java/dto/BlackjackResultDtoTest.java @@ -0,0 +1,26 @@ +package dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class BlackjackResultDtoTest { + + @Test + void of로_생성하면_딜러와_플레이어_결과를_포함한다() { + // given + ParticipantDto dealerResult = new ParticipantDto("딜러", List.of("10하트", "7스페이드"), 17); + List playerResults = List.of( + new ParticipantDto("jacob", List.of("A클로버", "K다이아몬드"), 21), + new ParticipantDto("seoye", List.of("9하트", "7클로버"), 16) + ); + + // when + BlackjackResultDto actual = BlackjackResultDto.of(dealerResult, playerResults); + + // then + assertThat(actual.dealerResultDto()).isEqualTo(dealerResult); + assertThat(actual.playerResultDtoList()).containsExactlyElementsOf(playerResults); + } +} diff --git a/src/test/java/dto/BlackjackStatisticsDtoTest.java b/src/test/java/dto/BlackjackStatisticsDtoTest.java new file mode 100644 index 00000000000..e1b64f781e8 --- /dev/null +++ b/src/test/java/dto/BlackjackStatisticsDtoTest.java @@ -0,0 +1,26 @@ +package dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class BlackjackStatisticsDtoTest { + + @Test + void of로_생성하면_딜러_수익과_플레이어_통계를_포함한다() { + // given + int dealerProfit = -500; + List playerStatistics = List.of( + new PlayerStatisticDto("jacob", 1000), + new PlayerStatisticDto("seoye", -500) + ); + + // when + BlackjackStatisticsDto actual = BlackjackStatisticsDto.of(dealerProfit, playerStatistics); + + // then + assertThat(actual.dealerProfit()).isEqualTo(dealerProfit); + assertThat(actual.playerStatisticDtoList()).containsExactlyElementsOf(playerStatistics); + } +} diff --git a/src/test/java/dto/ParticipantDtoTest.java b/src/test/java/dto/ParticipantDtoTest.java new file mode 100644 index 00000000000..e80ffaf409d --- /dev/null +++ b/src/test/java/dto/ParticipantDtoTest.java @@ -0,0 +1,92 @@ +package dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.card.Card; +import domain.card.Rank; +import domain.card.Suit; +import domain.participant.BetAmount; +import domain.participant.Dealer; +import domain.participant.Player; +import domain.participant.PlayerName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class ParticipantDtoTest { + + @Nested + class FromDealerTest { + + @Test + void 딜러_정보를_모든_패와_점수로_생성한다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(card(Rank.TEN, Suit.HEART)); + dealer.addCard(card(Rank.SEVEN, Suit.SPADE)); + + // when + ParticipantDto actual = ParticipantDto.from(dealer); + + // then + assertThat(actual.name()).isEqualTo("딜러"); + assertThat(actual.hand()).containsExactly("10하트", "7스페이드"); + assertThat(actual.score()).isEqualTo(17); + } + + @Test + void 딜러_초기_공개_정보를_생성하면_첫_번째_카드만_노출한다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(card(Rank.ACE, Suit.CLOVER)); + dealer.addCard(card(Rank.K, Suit.DIAMOND)); + + // when + ParticipantDto actual = ParticipantDto.from(dealer, true); + + // then + assertThat(actual.name()).isEqualTo("딜러"); + assertThat(actual.hand()).containsExactly("A클로버"); + assertThat(actual.score()).isEqualTo(21); + } + + @Test + void 딜러_초기_공개_여부가_false면_모든_카드를_노출한다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(card(Rank.TEN, Suit.HEART)); + dealer.addCard(card(Rank.SEVEN, Suit.SPADE)); + + // when + ParticipantDto actual = ParticipantDto.from(dealer, false); + + // then + assertThat(actual.name()).isEqualTo("딜러"); + assertThat(actual.hand()).containsExactly("10하트", "7스페이드"); + assertThat(actual.score()).isEqualTo(17); + } + } + + @Nested + class FromPlayerTest { + + @Test + void 플레이어_정보를_패와_점수로_생성한다() { + // given + Player player = new Player(new PlayerName("jacob"), new BetAmount("1000")); + player.addCard(card(Rank.NINE, Suit.HEART)); + player.addCard(card(Rank.EIGHT, Suit.CLOVER)); + + // when + ParticipantDto actual = ParticipantDto.from(player); + + // then + assertThat(actual.name()).isEqualTo("jacob"); + assertThat(actual.hand()).containsExactly("9하트", "8클로버"); + assertThat(actual.score()).isEqualTo(17); + } + } + + private static Card card(Rank rank, Suit suit) { + return new Card(rank, suit); + } +} diff --git a/src/test/java/dto/PlayerStatisticDtoTest.java b/src/test/java/dto/PlayerStatisticDtoTest.java new file mode 100644 index 00000000000..de4cc285bdf --- /dev/null +++ b/src/test/java/dto/PlayerStatisticDtoTest.java @@ -0,0 +1,25 @@ +package dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import domain.participant.BetAmount; +import domain.participant.Player; +import domain.participant.PlayerName; +import org.junit.jupiter.api.Test; + +class PlayerStatisticDtoTest { + + @Test + void of로_생성하면_플레이어_이름과_수익을_반환한다() { + // given + Player player = new Player(new PlayerName("jacob"), new BetAmount("1000")); + int profit = 1200; + + // when + PlayerStatisticDto actual = PlayerStatisticDto.of(player, profit); + + // then + assertThat(actual.name()).isEqualTo("jacob"); + assertThat(actual.profit()).isEqualTo(profit); + } +} diff --git a/src/test/java/util/ParserTest.java b/src/test/java/util/ParserTest.java index 9e5d8a5f737..c323b883ade 100644 --- a/src/test/java/util/ParserTest.java +++ b/src/test/java/util/ParserTest.java @@ -15,15 +15,14 @@ class ParseInputTest { // given String input = "a,b,c"; - String delimiter = ","; // when - List actuals = Parser.parseInput(input); + List actual = Parser.parseInput(input); // then - List expecteds = List.of("a", "b", "c"); + List expected = List.of("a", "b", "c"); for (int i = 0; i < 3; i++) { - Assertions.assertEquals(expecteds.get(i), actuals.get(i)); + Assertions.assertEquals(expected.get(i), actual.get(i)); } } @@ -32,15 +31,14 @@ class ParseInputTest { // given String input = " a ,b ,c "; - String delimiter = ","; // when - List actuals = Parser.parseInput(input); + List actual = Parser.parseInput(input); // then - List expecteds = List.of("a", "b", "c"); + List expected = List.of("a", "b", "c"); for (int i = 0; i < 3; i++) { - Assertions.assertEquals(expecteds.get(i), actuals.get(i)); + Assertions.assertEquals(expected.get(i), actual.get(i)); } } @@ -49,15 +47,14 @@ class ParseInputTest { // given String input = " a b ,b c ,c d "; - String delimiter = ","; // when - List actuals = Parser.parseInput(input); + List actual = Parser.parseInput(input); // then - List expecteds = List.of("a b", "b c", "c d"); + List expected = List.of("a b", "b c", "c d"); for (int i = 0; i < 3; i++) { - Assertions.assertEquals(expecteds.get(i), actuals.get(i)); + Assertions.assertEquals(expected.get(i), actual.get(i)); } } }