diff --git a/README.md b/README.md index 18539137a93..aec4e773566 100644 --- a/README.md +++ b/README.md @@ -149,4 +149,132 @@ shuffle은 랜덤 추출처럼 통제할 수 없는 값이라, 추가로, 지금 인터페이스와 이를 이용한 테스트 코드가 잘 구현되어있는지 궁금합니다! (참고 테스트 코드 : BlackjackServiceTest의 셔플_카드_순서대로_배부_정상_테스트()) -3. 더 추가해야할 테스트가 있을지 궁금합니다. \ No newline at end of file +3. 더 추가해야할 테스트가 있을지 궁금합니다. + + +----- + +## 사이클 2 + +### 기능 +- [x] 배팅 금액 입출력 기능 구현 +- [x] 전체 배팅 금액 구하는 기능 추가 +- [x] 최종 수익 출력하는 기능 구현 +- [x] 배팅 금액 예외 처리 +- [x] 딜러 수익 계산 금액 기능 구현 +- 플레이어 burst면 상금 잃는다. +- 블랙잭이면 1.5배를 받는다. + +*커밋이 꼬여서 이전 커밋까지 모두 뜨게 되었습니다. 🥲 +"feat: 배팅 금액 입출력 기능 구현" 부터 사이클 2의 커밋입니다. + +규칙 : +1. 버스트가 없을 경우, 딜러를 이기면 이긴다. +2. 딜러가 버스트 일경우, 버스트가 아닌 플레이어가 이긴다. +3. 딜러가 버스트가 아닌 경우, 딜러보다 숫자가 크면 승, 같으면 무승부, 작거나 버스트면 패 +이때, 블랙잭은 상금의 1.5배를 받는다. + +리팩토링에서 고려할 피드백 +1. 플레이어는 카드를 꺼내는 행위를 하는데 상속받는 딜러는 불필요한 행위인데 할 수 있게 되어 있는 것으로 보이는데요 +딜러가 불필요한 행위를 할 수 있게 열어두는게 맞을까요? +2. 도메인이 DTO를 알아도 될까요? 도메인과 DTO에 대해서 각각 어떻게 생각하시는지 궁금해요 + + +---- + +## 사이클 2 + + +### 기능 +- [x] 배팅 금액 입출력 기능 구현 +- [x] 전체 배팅 금액 구하는 기능 추가 +- [x] 최종 수익 출력하는 기능 구현 +- [x] 배팅 금액 예외 시 재입력 기능 추가 +- [x] 플레이어 burst면 상금 잃는 기능 추가 +- [x] 딜러 수익 계산 금액 기능 구현 + +### 사이클1 피드백 반역 +- [x] Player의 필드 List를 일급컬렉션 Cards로 변경 +- 기존 Cards는 전체 카드를 의미해서 Player에서 사용 못했습니다. + 이번 수정 과정에서 전체 카드는 Deck이라는 도메인을 추가하고, Cards는 Player가 들고있는 카드 뭉치로 사용했습니다. +- [x] Dealer와 Player의 상속 관계를 제거 +- Dealer와 Player의 상속 관계로 인해 Dealer가 몰라도 되는 Player의 행동을 상속받고 있었습니다. +이를 해결하고자 Player와 겹치는 행동은 Player에서 처리하고, 딜러의 규칙은 곧 게임의 규칙이기에 Game에서 처리하도록 변경했습니다. +- [x] 도메인에서 DTO 직접 생성했던 것을 Service에게 책임 이동 +- [x] Controller에서 출력하는 책임을 OutputView로 이동 +- [x] 하드 코딩 상수화 +- [x] 사용하지 않는 import 정리 + +### To 리뷰어 +1.Dealer - Player +딜러의 행동(ex. 자신이 가진 카드의 합 구하기)은 Player와 중복이고, +딜러의 규칙(ex. 16이하이면 카드를 추가로 받는다.)은 게임의 규칙이기에 Game에서 처리할 수 있다고 생각했습니다. +객체에서의 책임은 현실세계와 차이가 있어서, 꼭 딜러가 하는 행동이라고 Dealer에 있어야하는 것이 아니라 응집도를 고려해 다른 곳에 위치할 수 있다고 생각하는데, +이런 구조로 구현하는 것에 대해 피드백 받고 싶습니다. + +(이런 구조 → 코드 중복을 줄이기 위해 Dealer 객체를 없애고, 관련 행동과 규칙을 적절한 클래스에 둔다) + +2. Domain +지금 변경한 도메인 구조가 잘 짜여진 것인지 피드백 받고 싶습니다. 어찌보면 Game이 aggregate root와 같다고 보이는데, 이런 구조가 적절할까요? + + +3. 테스트 수정 +도메인이 대거 수정되니, 테스트 코드도 다시 수정해야하는 상황이 발생했습니다. +본 코드랑 테스트 코드의 의존성이 큰거 같은데, 테스트 코드가 변경에 유연하게끔 작성하는 방법이 있을까요? + +4. 코드 수정 + 이미 사용중인 서비스에서는 이와 같이 도메인을 대거 수정하는 과정이 어려울거 같은데, 대거 수정이 필요할 경우 어떻게 해야하는지 궁금합니다. + (하나를 고치면 여러개가 에러나는 상황이다 보니, 저는 전부 주석해놓고 처음부터 하나씩 주석을 해제하고 문제해결하는 방식으로 리팩토링했습니다.) + + +*피드백을 반영하는 과정에서 도메인 재구성이 필요하다고 생각했습니다. 그래서 리팩토링 과정에서 많은 파일이 바뀌었습니다.🥲 +*깃을 제대로 못 만져서, 사이클 1의 커밋 내용도 전부 뜨는 것 같습니다. (보기 힘드실거 같아서 죄송합니다..) +*사이클 1에서 달아주신 리뷰 답글은 14일(토)내로 작성해두겠습니다! + +------ + +## 미션 중 기록 +### 1. 기능 추가로 인해 수정한 위치 개수 +기능을 추가하는 과정에서 현재 구조로 확장하기에 어려움이 있었다. +그래서 대부분의 비즈니스 로직을 도메인 안으로 넣어서, 변경에 유연하도록 도메인을 재구성하였다. + +1)BlackjackResult삭제 +원래는 BlackjackResult에서 승패와 같은 게임의 기록을 저장했는데, +배팅 상금이라는 결과가 하나 더 추가 되면서, 출력 결과(response)를 도메인으로 갖는 것은 확장성이 안좋다고 생각했다. +그래서 BlackjackResult를 없애고, Game이라는 도메인에서 그때그떄 원하는 결과를 반환할 수 있도록 메서드만 구현하였다. + +2)Deck 도메인 추가, Cards 일급 컬렉션 수정 +Cards라는 일급 컬렉션을 카드 전체(52장)를 타깃으로 사용했다 보니, Player가 갖는 카드 더미는 Cards를 사용하지 못했다. +전체 카드는 랜덤 생성, 카드 추출 기능에 대한 책임만 가지기 때문에 플레이어가 가지는 카드 뭉치 Cards와 따로 분리하는 것이 낫다고 생각했다. +그래서 Player가 소유한 카드 뭉치를 Cards라는 일급 컬렉션으로 사용하기 위해, 전체 카드를 나타내는 Deck이라는 도메인을 추가하였다. +CardGenerator를 파라미터로 받아서 전체 카드 생성에 관해서 Deck에서 처리하도록 하였다. +카드 생성을 저번에 인터페이스로 해서 분리하니 이부분에 대한 수정은 편리했다. +(CardGenerator를 파라미터로 받아서 메서드로만 호출하면 되니까.) + +4)Game 도메인 추가, Dealer 도메인 삭제 +Game에 대한 비즈니스 로직이나 규칙들이 Player, Dealer, Cards 등에 흩어져있어 테스트 코드 작성에 어려움이 있었다. +따라서, 블랙잭 게임 규칙에 대한 메서드를 모아두기 위해 Game이라는 도메인을 추가하였다. + +Game을 추가하고 나니 Dealer의 필드는 Player와 겹치고, Dealer의 규칙은 Game에서 수행할 수 있을 것으로 보았다. +(16이하이면 카드를 더 받는다는 Dealer의 규칙은 게임의 규칙이라고 볼 수도 있기 때문이다.) +Game과 Dealer는 항상 1대 1로 존재하기 떄문에, 객체 생성-소멸 주기가 같다고 판단하여 관련 로직을 Game에 두었다. + +추가로 Dealer는 게임을 만들 때 외부에서 주입해주는 방식으로 했다. 그래야 GameTest에서 Dealer의 카드를 조작해줄 수 있기 때문이다. +테스트를 작성하며 의존성을 줄이는 방향으로 개발을 하게 되었다. + +### 2. 사이클1 때보다 수정 범위가 줄었는가/늘었는가 +사이클 1에서 단편적인 결과 출력에만 집중해서 도메인을 설계해서 그런지, 기능 추가를 위해 코드 변경이 막막하고 어려웠다. +그래서 도메인을 재구성하였기 때문에, 사이클1 때보다 수정 범위가 훨씬 컸다. +변경된 구조에서는 기능 추가 시 Game에 코드를 추가하면 되기 때문에, 코드 변경이 적을 것으로 예상된다. + +### 3. 규칙 적용으로 변경한 코드 1곳 +(If-Then) 하드코딩된 값이 보인다면, → 상수화한다. +도메인으로 로직을 이동하면서, 상수도 적절한 도메인으로 이동해 응집성 있게 수정하였다. + +(If-Then) 같은 객체를 다루는 클래스여도 목적이 다르면, → 클래스를 분리한다. (e.g. Cards -> Deck, Hand) +Cards를 Cards(Player 소유 카드), Deck(전체카드)으로 나누어서 리팩토링하였다. + +### 4. 테스트가 설계를 도운 순간 1회 +게임 규칙에 대한 테스트를 진행하려고 했는데, +이런 규칙이 Player, Dealer, Cards 등 여러 도메인에 걸쳐서 존재하는 것이 불편했다. +테스트 구현을 통해 도메인의 책임 분리에 대한 재구성 필요 신호를 포착할 수 있어서 설계에 도움이 되었다. \ No newline at end of file diff --git a/src/main/java/constant/GameConstant.java b/src/main/java/constant/GameConstant.java deleted file mode 100644 index b57cf274c97..00000000000 --- a/src/main/java/constant/GameConstant.java +++ /dev/null @@ -1,5 +0,0 @@ -package constant; - -public final class GameConstant { - public static final int GAME_OVER_THRESHOLD_SCORE = 21; -} diff --git a/src/main/java/controller/BlackjackController.java b/src/main/java/controller/BlackjackController.java index 57fa78e1ac9..51d40198817 100644 --- a/src/main/java/controller/BlackjackController.java +++ b/src/main/java/controller/BlackjackController.java @@ -1,23 +1,21 @@ package controller; -import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; -import domain.BlackjackResult; -import domain.Cards; -import domain.Dealer; +import domain.Deck; +import domain.Game; import domain.Player; import domain.Players; import domain.dto.CardContentDto; import domain.dto.FinalCardDto; import service.BlackjackService; -import utils.generator.ShuffledCardsGenerator; import view.InputView; import view.OutputView; public class BlackjackController { private static final int MAX_RETRY = 10; + private final InputView inputView; private final BlackjackService blackjackService; @@ -27,65 +25,64 @@ public BlackjackController(BlackjackService blackjackService) { } public void run() { - Cards cards = blackjackService.generateCards(new ShuffledCardsGenerator()); - List names = inputNames(); + Game game = doInitialGameSetting(); - Dealer dealer = blackjackService.createDealer(cards); - OutputView.displayCardDistribution(names); - Players playerList = blackjackService.createPlayers(names, cards); + addAdditionalCard(game); + addDealerAdditionalCard(game); - List firstCardContents = getCardContentDtos(dealer, playerList); - OutputView.displayCardContent(firstCardContents); - Players players = addAdditionalCard(playerList, cards); + List finalCards = blackjackService.getFinalCardDtos(game); + OutputView.displayFinalCard(finalCards); - if (!players.isAllPlayerBurst()) { - blackjackService.determineAdditionalCardOfDealer(dealer, cards); - } + // 최종 결과 출력 + OutputView.displayMatchResult(blackjackService.getPlayerResultDto(game)); + OutputView.displayBettingResult(blackjackService.getBettingScoreDto(game)); + } - printFinalCards(dealer, players); + private Game doInitialGameSetting() { + Deck deck = blackjackService.generateCards(); + List names = inputNames(); - // 최종 승패 - BlackjackResult blackjackResult = BlackjackResult.from(dealer, players); - OutputView.displayMatchResult(blackjackResult.toResultDto()); - } + Players players = blackjackService.createPlayers(names, deck); + Game game = blackjackService.createGame(deck, players); + setPlayerBetting(players); + OutputView.displayCardDistribution(names); - public List getCardContentDtos(Dealer dealer, Players playerList) { - List firstCardContents = new ArrayList<>(); - firstCardContents.add(new CardContentDto(dealer.getName(), List.of(dealer.getFirstCard()))); - for (Player player : playerList) { - firstCardContents.add(new CardContentDto(player.getName(), player.getCards())); - } - return firstCardContents; + List firstCardContents = blackjackService.getCardContentDtos(game); + OutputView.displayCardContents(firstCardContents); + return game; } - public Players addAdditionalCard(Players players, Cards cards) { - for (Player player : players) { - String name = player.getName(); - boolean hasCard = hasAdditionalCard(name); - handCardWithRetry(player, hasCard, cards, name); + private void addDealerAdditionalCard(Game game) { + if (!game.isAllPlayerBurst()) { + while (game.needAdditionalCard()) { + game.addDealerAdditionalCard(); + OutputView.displayDealerCard(); + } } - return players; } + public void addAdditionalCard(Game game) { + OutputView.displayCardContents(blackjackService.getCardContentDtos(game)); + for (Player player : game.getPlayers()) { + boolean hasCard = hasAdditionalCard(player.getName()); - private void handCardWithRetry(Player player, boolean hasCard, Cards cards, String name) { - while (hasCard) { - if (player.isBust()) { - break; + while (hasCard) { + if (player.isBust()) { + break; + } + game.addCard(player); + OutputView.displayCardContent(blackjackService.getCardContentDto(player)); + hasCard = hasAdditionalCard(player.getName()); } - player.add(cards.pop()); - OutputView.displayCardContent(List.of(player.toCardContentDto())); - hasCard = hasAdditionalCard(name); } } - public void printFinalCards(Dealer dealer, Players players) { - List finalCards = new ArrayList<>(); - finalCards.add(dealer.toFinalCardDto()); - for (Player player : players) { - finalCards.add(player.toFinalCardDto()); + + public void setPlayerBetting(Players playerList) { + for (Player player : playerList) { + int bettingScore = inputBettingPrice(player.getName()); + player.betMoney(bettingScore); } - OutputView.displayFinalCard(finalCards); } private List inputNames() { @@ -94,8 +91,18 @@ private List inputNames() { ); } + public int inputBettingPrice(String name) { + return doRetry( + () -> { + return inputView.readBettingPrice(name); + } + ); + } + private boolean hasAdditionalCard(String name) { - return doRetry(() -> inputView.readAdditionalCard(name)); + return doRetry( + () -> inputView.readAdditionalCard(name) + ); } private T doRetry(Supplier action) { @@ -105,8 +112,7 @@ private T doRetry(Supplier action) { return action.get(); } catch (IllegalArgumentException e) { retry++; - System.out.println(e.getMessage()); - + OutputView.printError(e.getMessage()); if (retry >= MAX_RETRY) { throw new IllegalStateException("입력 횟수를 초과했습니다."); } diff --git a/src/main/java/domain/BlackjackResult.java b/src/main/java/domain/BlackjackResult.java deleted file mode 100644 index 4e91e869662..00000000000 --- a/src/main/java/domain/BlackjackResult.java +++ /dev/null @@ -1,77 +0,0 @@ -package domain; - -import java.util.HashMap; -import java.util.Map; - -import domain.dto.BlackjackResultDto; - -public class BlackjackResult { - private final Map playerWinningMap = new HashMap<>(); - private int dealerWinningCount = 0; - private int drawCount = 0; - private int dealerLoseCount = 0; - - private BlackjackResult(Dealer dealer, Players players) { - calculateMatchResult(dealer, players); - } - - private static boolean isPlayerLose(Player player, boolean dealerBurst, int dealerTotal) { - return player.isBust() || (!dealerBurst && player.getFinalScore() < dealerTotal); - } - - public static BlackjackResult from(Dealer dealer, Players players) { - return new BlackjackResult(dealer, players); - } - - private void calculateMatchResult(Dealer dealer, Players players) { - boolean dealerBurst = dealer.isBust(); - int dealerTotal = dealer.getFinalScore(); - - for (Player player : players) { - determinePlayerResult(player, dealerBurst, dealerTotal); - } - } - - private void determinePlayerResult(Player player, boolean dealerBust, int dealerTotal) { - if (isPlayerLose(player, dealerBust, dealerTotal)) { - addMatchResult(player.getName(), MatchCase.LOSE); - return; - } - if (isPlayerScoreEqualsDealer(player, dealerBust, dealerTotal)) { - addMatchResult(player.getName(), MatchCase.DRAW); - return; - } - addMatchResult(player.getName(), MatchCase.WIN); - } - - private boolean isPlayerScoreEqualsDealer(Player player, boolean dealerBust, int dealerTotal) { - return !(player.isBust() || dealerBust) && (player.getFinalScore() == dealerTotal); - } - - private void addMatchResult(String playerName, MatchCase matchCase) { - playerWinningMap.put(playerName, matchCase); - increaseMatchResult(matchCase); - } - - private void increaseMatchResult(MatchCase matchCase) { - if (matchCase == MatchCase.WIN) { - dealerLoseCount++; - return; - } - - if (matchCase == MatchCase.DRAW) { - drawCount++; - return; - } - dealerWinningCount++; - } - - public BlackjackResultDto toResultDto() { - return new BlackjackResultDto( - this.dealerWinningCount, - this.drawCount, - this.dealerLoseCount, - Map.copyOf(this.playerWinningMap) - ); - } -} diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java index 8c974bfbea5..3828b96782a 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/Card.java @@ -2,6 +2,9 @@ import java.util.Objects; +import domain.enums.CardRank; +import domain.enums.CardShape; + public class Card { private final CardShape cardShape; private final CardRank cardRank; diff --git a/src/main/java/domain/Cards.java b/src/main/java/domain/Cards.java index 36c8df0d17e..a21913b3681 100644 --- a/src/main/java/domain/Cards.java +++ b/src/main/java/domain/Cards.java @@ -1,27 +1,53 @@ package domain; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; +import domain.enums.CardRank; + public class Cards { + public static final int ACE_ADDITIONAL_SCORE = 11; + public static final int CARD_COUNT = 2; + private final List cards; - public Cards(List cards) { - this.cards = cards; + public Cards() { + this.cards = new ArrayList<>(); } - public void shuffle() { - Collections.shuffle(cards); + public void addCard(Card card) { + this.cards.add(card); } - public List getCards() { - return cards; + public boolean isAceExist() { + return cards.stream() + .anyMatch(c -> c.getCardRank().equals(CardRank.ACE)); + } + + public boolean isBlackjack() { + return getFinalScore() == Game.BLACKJACK_VALUE && cards.size() == CARD_COUNT; } - public Card pop() { - return cards.removeFirst(); + public int calculateScore() { + int total = 0; + for (Card card : cards) { + total += card.getCardRank().getNumber(); + } + return total; + } + + private int calculateAceScore() { + if (!isAceExist() || calculateScore() > ACE_ADDITIONAL_SCORE) { + return 0; + } + return ACE_ADDITIONAL_SCORE - 1; + } - // 리스트 비었을 때 처리 필요 + public int getFinalScore() { + return calculateScore() + calculateAceScore(); } + public List getCards() { + return cards; + } } diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java deleted file mode 100644 index 8f341d94e65..00000000000 --- a/src/main/java/domain/Dealer.java +++ /dev/null @@ -1,20 +0,0 @@ -package domain; - -import constant.GameConstant; - -public class Dealer extends Player { - public static final String DEALER_NAME = "딜러"; - public static final int ADDITIONAL_THRESHOLD = 16; - - public Dealer() { - super(DEALER_NAME); - } - - public boolean needAdditionalCard() { - return this.calculateScore() <= ADDITIONAL_THRESHOLD; - } - - public Card getFirstCard() { - return cards.getFirst(); - } -} diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java new file mode 100644 index 00000000000..d46c9c4a180 --- /dev/null +++ b/src/main/java/domain/Deck.java @@ -0,0 +1,27 @@ +package domain; + +import java.util.List; + +import utils.generator.CardsGenerator; + +public class Deck { + private final List cards; + private final CardsGenerator cardsGenerator; + + public Deck(CardsGenerator cardsGenerator) { + this.cards = cardsGenerator.generateShuffledCards(); + this.cardsGenerator = cardsGenerator; + } + + public Card pop() { + if (cards.isEmpty()) { + cards.addAll(cardsGenerator.generateShuffledCards()); + } + return cards.removeFirst(); + } + + public List getCards() { + return cards; + } + +} diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java new file mode 100644 index 00000000000..97c4b8ab154 --- /dev/null +++ b/src/main/java/domain/Game.java @@ -0,0 +1,116 @@ +package domain; + +import java.util.LinkedHashMap; +import java.util.Map; + +import domain.enums.MatchCase; + +public class Game { + public static final int ADDITIONAL_THRESHOLD = 16; + public static final int BLACKJACK_VALUE = 21; + public static final double BLACKJACK_BONUS = 1.5; + public static final String DEALER_NAME = "딜러"; + + private final Deck deck; + private final Player dealer; + private final Players players; + + public Game(Deck deck, Players players, Player dealer) { + this.deck = deck; + this.players = players; + this.dealer = dealer; + } + + public Map calculateDealerMatch(Map playerResult) { + Map dealerMatchResult = new LinkedHashMap<>(); + for (MatchCase matchCase : playerResult.values()) { + dealerMatchResult.put(matchCase, dealerMatchResult.getOrDefault(matchCase, 0) + 1); + } + return dealerMatchResult; + } + + public Map calculateMatch() { + Map matchResult = new LinkedHashMap<>(); + // 1. 참가자들이 모두 burst면 딜러가 승리한다. + if (players.isAllPlayerBurst()) { + return getPlayersAllBurstCase(matchResult); + } + // 2. 딜러가 burst이면 살아남은 참가자는 우승이다. + if (dealer.isBust()) { + return getDealerBurstCase(matchResult); + } + // 3. 딜러가 burst가 아니면, 딜러보다 크면 승, 작으면 패, 같은면 무승부이다. + return getGeneralCase(matchResult); + } + + private Map getGeneralCase(Map matchResult) { + for (Player player : players) { + MatchCase matchCase = player.calculateMatchCase(dealer.getCardsTotalSum()); + matchResult.put(player.getName(), matchCase); + player.calculateMoney(matchCase, dealer.isDealerBlackjack()); + } + return matchResult; + } + + private Map getDealerBurstCase(Map matchResult) { + for (Player player : players) { + if (!player.isBust()) { + matchResult.put(player.getName(), MatchCase.WIN); + player.calculateMoney(MatchCase.WIN, dealer.isDealerBlackjack()); + continue; + } + matchResult.put(player.getName(), MatchCase.LOSE); + player.calculateMoney(MatchCase.LOSE, dealer.isDealerBlackjack()); + } + return matchResult; + } + + private Map getPlayersAllBurstCase(Map matchResult) { + for (Player player : players) { + matchResult.put(player.getName(), MatchCase.LOSE); + player.calculateMoney(MatchCase.LOSE, dealer.isDealerBlackjack()); + } + return matchResult; + } + + public Map getBettingScore(Game game) { + Map bettingResult = new LinkedHashMap<>(); + for (Player player : players) { + bettingResult.put(player.getName(), player.getBettingScore()); + } + return bettingResult; + } + + public boolean needAdditionalCard() { + return dealer.getCardsTotalSum() <= ADDITIONAL_THRESHOLD; + } + + public boolean isAllPlayerBurst() { + return players.isAllPlayerBurst(); + } + + public void addCard(Player player) { + players.addAdditionalCard(player, deck.pop()); + } + + public void addDealerAdditionalCard() { + dealer.add(deck.pop()); + } + + public Card getDealerFirstCard() { + return dealer.getCards().getFirst(); + } + + public int getTotalMoney() { + return players.getTotalBettingScore(); + } + + public Players getPlayers() { + return players; + } + + public Player getDealer() { + return dealer; + } + +} diff --git a/src/main/java/domain/MatchCase.java b/src/main/java/domain/MatchCase.java deleted file mode 100644 index 3bfbdcb071a..00000000000 --- a/src/main/java/domain/MatchCase.java +++ /dev/null @@ -1,15 +0,0 @@ -package domain; - -public enum MatchCase { - WIN("승"), LOSE("패"), DRAW("무"); - - private final String korResult; - - MatchCase(String korResult) { - this.korResult = korResult; - } - - public String getKorResult() { - return korResult; - } -} diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 44f54b4434b..d5053e03287 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,55 +1,75 @@ package domain; - -import constant.GameConstant; -import domain.dto.CardContentDto; -import domain.dto.FinalCardDto; - -import java.util.ArrayList; import java.util.List; +import domain.enums.MatchCase; + public class Player { - private static final int ACE_ADDITIONAL_SCORE = 10; - private static final int ACE_ADDITION_NONE_SCORE = 0; - protected final List cards = new ArrayList<>(); + + private final Cards cards; private final String name; + private int bettingScore; public Player(String name) { this.name = name; + this.bettingScore = 0; + this.cards = new Cards(); } - protected int calculateScore() { - int total = 0; - for (Card card : cards) { - total += card.getCardRank().getNumber(); - } + public void add(Card card) { + cards.addCard(card); + } - return total; + public void addInitializedCard(Deck deck) { + add(deck.pop()); + add(deck.pop()); + } + + public MatchCase calculateMatchCase(int dealerTotal) { + if (cards.getFinalScore() > dealerTotal) { + return MatchCase.WIN; + } + if (cards.getFinalScore() == dealerTotal) { + return MatchCase.DRAW; + } + if (cards.getFinalScore() < dealerTotal) { + return MatchCase.LOSE; + } + throw new IllegalArgumentException("[ERROR] 일치하는 enum이 없습니다."); } - private int calculateAceScore() { - if (!isAceExist() || calculateScore() > 11) { - return ACE_ADDITION_NONE_SCORE; + public void calculateMoney(MatchCase matchCase, boolean isDealerBlackjack) { + if (cards.isBlackjack() && !isDealerBlackjack) { + bettingScore = (int) ((int) bettingScore * Game.BLACKJACK_BONUS); + return; + } + if (matchCase.equals(MatchCase.LOSE)) { + loseMoney(); + return; + } + + if (matchCase.equals(MatchCase.WIN)) { + return; } - return ACE_ADDITIONAL_SCORE; + bettingScore = 0; } - public int getFinalScore() { - return calculateScore() + calculateAceScore(); + public void loseMoney() { + int minusScore = bettingScore * 2; + bettingScore -= minusScore; } - public boolean isBust() { - return getFinalScore() > GameConstant.GAME_OVER_THRESHOLD_SCORE; + public void betMoney(int money) { + bettingScore = money; } - public void add(Card card) { - cards.add(card); + public boolean isBust() { + return cards.getFinalScore() > Game.BLACKJACK_VALUE; } - public void addInitializedCard(Cards totalCards) { - cards.add(totalCards.pop()); - cards.add(totalCards.pop()); + public boolean isDealerBlackjack() { + return cards.isBlackjack(); } public String getName() { @@ -57,19 +77,14 @@ public String getName() { } public List getCards() { - return cards; - } - - public CardContentDto toCardContentDto() { - return new CardContentDto(this.name, this.cards); + return cards.getCards(); } - public FinalCardDto toFinalCardDto() { - return new FinalCardDto(this.name, this.cards, getFinalScore()); + public int getCardsTotalSum() { + return cards.getFinalScore(); } - public boolean isAceExist() { - return cards.stream() - .anyMatch(c -> c.getCardRank().equals(CardRank.ACE)); + public int getBettingScore() { + return bettingScore; } } diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 7ac9d37743f..7af8828a943 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -10,6 +10,16 @@ public Players(List players) { this.players = players; } + public void addAdditionalCard(Player player, Card card) { + player.add(card); + } + + public int getTotalBettingScore(){ + return players.stream() + .mapToInt(Player::getBettingScore) + .sum(); + } + @Override public Iterator iterator() { return players.iterator(); @@ -23,5 +33,4 @@ public Player getPlayer(int index){ return players.get(index); } - } diff --git a/src/main/java/domain/dto/BettingResultDto.java b/src/main/java/domain/dto/BettingResultDto.java new file mode 100644 index 00000000000..ace2e8694f2 --- /dev/null +++ b/src/main/java/domain/dto/BettingResultDto.java @@ -0,0 +1,5 @@ +package domain.dto; + +import java.util.Map; + +public record BettingResultDto(int totalMoney, Map bettingResult) {} diff --git a/src/main/java/domain/dto/BlackjackResultDto.java b/src/main/java/domain/dto/BlackjackResultDto.java deleted file mode 100644 index ca7867d51c0..00000000000 --- a/src/main/java/domain/dto/BlackjackResultDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package domain.dto; - -import domain.MatchCase; - -import java.util.Map; - -public record BlackjackResultDto( - int winCount, - int drawCount, - int loseCount, - Map matchResultMap -) { -} diff --git a/src/main/java/domain/dto/CardContentDto.java b/src/main/java/domain/dto/CardContentDto.java index 4c1bfad2016..789d3acb019 100644 --- a/src/main/java/domain/dto/CardContentDto.java +++ b/src/main/java/domain/dto/CardContentDto.java @@ -4,7 +4,5 @@ import domain.Card; - public record CardContentDto(String name, List cards) { - } diff --git a/src/main/java/domain/dto/MatchResultDto.java b/src/main/java/domain/dto/MatchResultDto.java new file mode 100644 index 00000000000..1d34f07ecfb --- /dev/null +++ b/src/main/java/domain/dto/MatchResultDto.java @@ -0,0 +1,8 @@ +package domain.dto; + +import java.util.Map; + +import domain.enums.MatchCase; + +public record MatchResultDto(Map dealerResult, Map playerResult) { +} diff --git a/src/main/java/domain/CardRank.java b/src/main/java/domain/enums/CardRank.java similarity index 94% rename from src/main/java/domain/CardRank.java rename to src/main/java/domain/enums/CardRank.java index 92f0f92b20c..9bb6bc67e8d 100644 --- a/src/main/java/domain/CardRank.java +++ b/src/main/java/domain/enums/CardRank.java @@ -1,4 +1,4 @@ -package domain; +package domain.enums; public enum CardRank { ACE("A", 1), @@ -18,7 +18,6 @@ public enum CardRank { private final String name; private final int number; - // 생성자 CardRank(String name, int number) { this.name = name; this.number = number; diff --git a/src/main/java/domain/CardShape.java b/src/main/java/domain/enums/CardShape.java similarity index 92% rename from src/main/java/domain/CardShape.java rename to src/main/java/domain/enums/CardShape.java index acf717e1ef7..2d12e47f53e 100644 --- a/src/main/java/domain/CardShape.java +++ b/src/main/java/domain/enums/CardShape.java @@ -1,4 +1,4 @@ -package domain; +package domain.enums; public enum CardShape { SPADE("스페이드"), diff --git a/src/main/java/domain/enums/MatchCase.java b/src/main/java/domain/enums/MatchCase.java new file mode 100644 index 00000000000..7139d82f083 --- /dev/null +++ b/src/main/java/domain/enums/MatchCase.java @@ -0,0 +1,21 @@ +package domain.enums; + +public enum MatchCase { + WIN("승", "패"), DRAW("무", "무"), LOSE("패", "승"); + + private final String korResult; + private final String reverseResult; + + MatchCase(String korResult, String reverseResult) { + this.korResult = korResult; + this.reverseResult = reverseResult; + } + + public String getKorResult() { + return korResult; + } + + public String getReversedKorResult() { + return reverseResult; + } +} diff --git a/src/main/java/service/BlackjackService.java b/src/main/java/service/BlackjackService.java index 6cf0eff965b..0958f843f45 100644 --- a/src/main/java/service/BlackjackService.java +++ b/src/main/java/service/BlackjackService.java @@ -2,44 +2,81 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; -import domain.Cards; -import domain.Dealer; +import domain.Deck; +import domain.Game; import domain.Player; import domain.Players; +import domain.dto.BettingResultDto; +import domain.dto.CardContentDto; +import domain.dto.FinalCardDto; +import domain.dto.MatchResultDto; +import domain.enums.MatchCase; import utils.generator.CardsGenerator; -import view.OutputView; +import utils.generator.ShuffledCardsGenerator; public class BlackjackService { - public Cards generateCards(CardsGenerator cardsGenerator) { - return cardsGenerator.generateShuffledCards(); + public Deck generateCards() { + CardsGenerator cardsGenerator = new ShuffledCardsGenerator(); + return new Deck(cardsGenerator); } - public void giveInitialedCard(Cards cards, Dealer dealer) { - dealer.addInitializedCard(cards); - } - - public Players createPlayers(List names, Cards cards) { + public Players createPlayers(List names, Deck deck) { List playerList = new ArrayList<>(); for (String name : names) { Player player = new Player(name); - player.addInitializedCard(cards); + player.addInitializedCard(deck); playerList.add(player); } return new Players(playerList); } - public Dealer createDealer(Cards cards) { - Dealer dealer = new Dealer(); - giveInitialedCard(cards, dealer); + public Player createDealer(Deck deck) { + Player dealer = new Player(Game.DEALER_NAME); + dealer.addInitializedCard(deck); return dealer; } - public void determineAdditionalCardOfDealer(Dealer dealer, Cards cards) { - while (dealer.needAdditionalCard()) { - dealer.add(cards.pop()); - OutputView.displayDealerCard(); + public Game createGame(Deck deck, Players players) { + Player dealer = createDealer(deck); + return new Game(deck, players, dealer); + } + + public CardContentDto getCardContentDto(Player player) { + return new CardContentDto(player.getName(), player.getCards()); + } + + public List getCardContentDtos(Game game) { + List firstCardContents = new ArrayList<>(); + firstCardContents.add(new CardContentDto(Game.DEALER_NAME, List.of(game.getDealerFirstCard()))); + for (Player player : game.getPlayers()) { + firstCardContents.add(getCardContentDto(player)); + } + return firstCardContents; + } + + public List getFinalCardDtos(Game game) { + List finalCards = new ArrayList<>(); + Player dealer = game.getDealer(); + finalCards.add(new FinalCardDto(dealer.getName(), dealer.getCards(), dealer.getCardsTotalSum())); + + for (Player player : game.getPlayers()) { + finalCards.add(new FinalCardDto(player.getName(), player.getCards(), player.getCardsTotalSum())); } + return finalCards; + } + + public MatchResultDto getPlayerResultDto(Game game) { + Map playerResult = game.calculateMatch(); + Map dealerResult = game.calculateDealerMatch(playerResult); + return new MatchResultDto(dealerResult, playerResult); + + } + + public BettingResultDto getBettingScoreDto(Game game) { + return new BettingResultDto(game.getTotalMoney(), game.getBettingScore(game)); } + } diff --git a/src/main/java/utils/generator/CardsGenerator.java b/src/main/java/utils/generator/CardsGenerator.java index 1f5eb395c0b..82a8fc2d010 100644 --- a/src/main/java/utils/generator/CardsGenerator.java +++ b/src/main/java/utils/generator/CardsGenerator.java @@ -1,7 +1,9 @@ package utils.generator; -import domain.Cards; +import java.util.List; + +import domain.Card; public interface CardsGenerator { - Cards generateShuffledCards(); + List generateShuffledCards(); } diff --git a/src/main/java/utils/generator/ShuffledCardsGenerator.java b/src/main/java/utils/generator/ShuffledCardsGenerator.java index c13d8c5c889..d55924cb23f 100644 --- a/src/main/java/utils/generator/ShuffledCardsGenerator.java +++ b/src/main/java/utils/generator/ShuffledCardsGenerator.java @@ -1,31 +1,31 @@ package utils.generator; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import domain.Card; -import domain.CardRank; -import domain.CardShape; -import domain.Cards; +import domain.enums.CardRank; +import domain.enums.CardShape; public final class ShuffledCardsGenerator implements CardsGenerator { @Override - public Cards generateShuffledCards() { - Cards cards = generate(); + public List generateShuffledCards() { + List cards = generate(); shuffleCards(cards); return cards; } - public void shuffleCards(Cards cards) { - cards.shuffle(); + public void shuffleCards(List cards) { + Collections.shuffle(cards); } - private Cards generate() { + private List generate() { List cards = new ArrayList<>(); for (CardShape cardShape : CardShape.values()) { cards.addAll(createCardsFromRank(cardShape)); } - return new Cards(cards); + return cards; } private List createCardsFromRank(CardShape cardShape) { diff --git a/src/main/java/validator/Validator.java b/src/main/java/validator/Validator.java index f91b4b5b9ed..bf89bfadc50 100644 --- a/src/main/java/validator/Validator.java +++ b/src/main/java/validator/Validator.java @@ -19,6 +19,14 @@ static void validateChoice(String input) { } } + static int validateInteger(String input){ + try{ + return Integer.parseInt(input); + } catch (NumberFormatException e){ + throw new IllegalArgumentException("[ERROR] 숫자가 아닙니다."); + } + } + void validate(String input); } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index a1d69ccd763..e8d20ae6644 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -9,7 +9,6 @@ public class InputView { - private static final String DELIMITER = ","; public List readNames() { @@ -26,10 +25,18 @@ public boolean readAdditionalCard(String name) { Validator::validateNotBlank, Validator::validateChoice )); - return input.equals("y"); } + public int readBettingPrice(String name) { + System.out.println(name + "의 배팅 금액은?"); + String input = readInput(List.of( + Validator::validateNotBlank, + Validator::validateInteger + )); + return Integer.parseInt(input); + } + private String readInput(List validators) { try { String input = new Scanner(System.in).nextLine(); diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 6adcace6c53..3ba8c1d571e 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -4,30 +4,24 @@ import java.util.List; import java.util.Map; -import constant.GameConstant; import domain.Card; -import domain.Dealer; -import domain.MatchCase; -import domain.dto.BlackjackResultDto; +import domain.Game; +import domain.dto.BettingResultDto; import domain.dto.CardContentDto; import domain.dto.FinalCardDto; +import domain.dto.MatchResultDto; +import domain.enums.MatchCase; public final class OutputView { public static void displayCardDistribution(List names) { String nameContent = String.join(", ", names); - System.out.printf("%s가 %s에게 2장을 나누었습니다.\n", Dealer.DEALER_NAME, nameContent); + System.out.printf("%s와 %s에게 2장을 나누었습니다.\n", Game.DEALER_NAME, nameContent); } - public static void displayCardContent(List cardContentDto) { + public static void displayCardContents(List cardContentDto) { for (CardContentDto dto : cardContentDto) { - List cardContents = new ArrayList<>(); - for (Card card : dto.cards()) { - cardContents.add(card.getCardRank().getName() + card.getCardShape().getName()); - } - - System.out.printf("%s카드: %s\n", dto.name(), String.join(", ", cardContents)); + displayCardContent(dto); } - } public static void displayCardContent(CardContentDto dto) { @@ -35,12 +29,11 @@ public static void displayCardContent(CardContentDto dto) { for (Card card : dto.cards()) { cardContents.add(card.getCardRank().getName() + card.getCardShape().getName()); } - System.out.printf("%s카드: %s\n", dto.name(), String.join(", ", cardContents)); } public static void displayDealerCard() { - System.out.printf("%s는 %d 이하라 한장의 카드를 더 받았습니다.\n",Dealer.DEALER_NAME, Dealer.ADDITIONAL_THRESHOLD); + System.out.printf("%s는 %d 이하라 한장의 카드를 더 받았습니다.\n", Game.DEALER_NAME, Game.ADDITIONAL_THRESHOLD); } public static void displayFinalCard(List finalCardDto) { @@ -49,16 +42,38 @@ public static void displayFinalCard(List finalCardDto) { for (Card card : dto.cards()) { cardContents.add(card.getCardRank().getName() + card.getCardShape().getName()); } - System.out.printf("%s카드: %s - 결과: %d\n", dto.name(), String.join(", ", cardContents), dto.total()); } } - public static void displayMatchResult(BlackjackResultDto resultDto) { - System.out.printf("## 최종 승패\n딜러: %d승 %d패\n", resultDto.winCount(), resultDto.loseCount()); - Map resultMap = resultDto.matchResultMap(); - for (Map.Entry playerName : resultMap.entrySet()) { - System.out.printf("%s: %s\n", playerName.getKey(), playerName.getValue().name()); + // 사이클 1의 결과값 + public static void displayMatchResult(MatchResultDto matchResultDto) { + Map dealerMap = matchResultDto.dealerResult(); + Map playerMap = matchResultDto.playerResult(); + + System.out.printf("## 최종 승패\n%s: ", Game.DEALER_NAME); + for (Map.Entry matchcase : dealerMap.entrySet()) { + System.out.printf("%d%s ", matchcase.getValue().intValue(), matchcase.getKey().getReversedKorResult()); + } + + for (Map.Entry playerName : playerMap.entrySet()) { + System.out.printf("\n%s: %s", playerName.getKey(), playerName.getValue().getKorResult()); + } + } + + // 사이클 2의 결과값 + public static void displayBettingResult(BettingResultDto resultDto) { + Map resultMap = resultDto.bettingResult(); + + System.out.println("\n## 최종 수익"); + System.out.printf("%s: %d\n", Game.DEALER_NAME, -resultDto.totalMoney()); + + for (Map.Entry playerName : resultMap.entrySet()) { + System.out.printf("%s: %d\n", playerName.getKey(), playerName.getValue().intValue()); } } + + public static void printError(String error) { + System.out.println(error); + } } diff --git a/src/test/java/domain/BlackjackResultTest.java b/src/test/java/domain/BlackjackResultTest.java deleted file mode 100644 index 1a39fb6b07f..00000000000 --- a/src/test/java/domain/BlackjackResultTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package domain; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import domain.dto.BlackjackResultDto; -import testutil.PlayerTestUtil; - -class BlackjackResultTest { - @Test - @DisplayName("참가자가 딜러보다 낮은 점수면 딜러가 이긴다") - void 참가자가_딜러보다_점수_낮음() { - Player player = PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.JACK) - )); // 20 - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.JACK), - new Card(CardShape.SPADE, CardRank.ACE) - )); // 21 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, createSinglePlayerSet(player)); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.winCount()).isEqualTo(1); - assertThat(blackjackResultDto.loseCount()).isEqualTo(0); - } - - @Test - @DisplayName("참가자가 딜러보다 높은 점수면 딜러가 진다") - void 참가자가_딜러보다_점수_높음() { - Player player = PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.ACE) - )); // 21 - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.JACK) - )); // 20 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, createSinglePlayerSet(player)); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.loseCount()).isEqualTo(1); - assertThat(blackjackResultDto.winCount()).isEqualTo(0); - } - - @DisplayName("참가자와 딜러 점수가 둘 다 같으면 무승부") - @Test - void 참가자_딜러_무승부() { - Player player = PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.ACE) - )); // 21 - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.ACE) - )); // 21 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, createSinglePlayerSet(player)); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.drawCount()).isEqualTo(1); - } - - @DisplayName("참가자가 버스트고 딜러가 살면 딜러 승") - @Test - void 참가자_버스트_딜러_생존() { - Player player = PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.QUEEN), - new Card(CardShape.SPADE, CardRank.TWO) - )); // 22 - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TWO) - )); // 2 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, createSinglePlayerSet(player)); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.winCount()).isEqualTo(1); - assertThat(blackjackResultDto.loseCount()).isEqualTo(0); - } - - @DisplayName("참가자가 생존하고 딜러가 버스트면 딜러 패") - @Test - void 참가자_생존_딜러_버스트() { - Player player = PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.TWO) - )); // 22 - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.QUEEN), - new Card(CardShape.SPADE, CardRank.TWO) - )); // 2 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, createSinglePlayerSet(player)); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.loseCount()).isEqualTo(1); - assertThat(blackjackResultDto.winCount()).isEqualTo(0); - } - - @DisplayName("생존한 참가자 있고 딜러 버스트면 딜러 승리, 패배 하나씩") - @Test - void 생존_참가자_존재_및_딜러_버스트() { - Players players = new Players( - List.of(PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.NINE), - new Card(CardShape.SPADE, CardRank.QUEEN), - new Card(CardShape.SPADE, CardRank.THREE) - )), // 22 - PlayerTestUtil.createPlayer(List.of( - new Card(CardShape.SPADE, CardRank.NINE), - new Card(CardShape.SPADE, CardRank.QUEEN), - new Card(CardShape.SPADE, CardRank.TWO) - ))) // 21 - ); - - Dealer dealer = PlayerTestUtil.createDealer(List.of( - new Card(CardShape.SPADE, CardRank.TEN), - new Card(CardShape.SPADE, CardRank.QUEEN), - new Card(CardShape.SPADE, CardRank.TWO) - )); // 22 - - BlackjackResult blackjackResult = BlackjackResult.from(dealer, players); - BlackjackResultDto blackjackResultDto = blackjackResult.toResultDto(); - assertThat(blackjackResultDto.loseCount()).isEqualTo(1); - assertThat(blackjackResultDto.winCount()).isEqualTo(1); - } - - private Players createSinglePlayerSet(Player player) { - return new Players(List.of(player)); - } -} \ No newline at end of file diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java deleted file mode 100644 index 5fe7bb300e7..00000000000 --- a/src/test/java/domain/DealerTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class DealerTest { - private Dealer dealer; - - @BeforeEach - void setUp() { - Dealer dealer = new Dealer(); - this.dealer = dealer; - } - - @DisplayName("딜러가 받은 카드의 점수 합이 16이하이면 추가로 받는다.") - @Test - void 딜러_카드_추가_배부_필요_정상_테스트() { - Dealer dealer = createDealerFromCards(List.of( - new Card(CardShape.HEART, CardRank.THREE), - new Card(CardShape.HEART, CardRank.TWO) - )); - - assertThat(dealer.needAdditionalCard()).isEqualTo(true); - } - - @DisplayName("딜러가 받은 카드의 점수 합이 17 이상이면 추가로 받지 않는다.") - @Test - void 딜러_카드_추가_배부_불필요_정상_테스트() { - Dealer dealer = createDealerFromCards(List.of( - new Card(CardShape.HEART, CardRank.TEN), - new Card(CardShape.HEART, CardRank.SEVEN) - )); - - assertThat(dealer.needAdditionalCard()).isEqualTo(false); - } - - private Dealer createDealerFromCards(List cards) { - Dealer dealer = new Dealer(); - for (Card card : cards) { - dealer.add(card); - } - return dealer; - } - -} \ No newline at end of file diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java new file mode 100644 index 00000000000..c26b37a1637 --- /dev/null +++ b/src/test/java/domain/DeckTest.java @@ -0,0 +1,37 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import domain.enums.CardRank; +import domain.enums.CardShape; +import testutil.PlayerTestUtil; +import utils.generator.CardsGenerator; +import utils.generator.ShuffledCardsGenerator; + +class DeckTest { + + @DisplayName("처음 전쳬 카드는 52장 생성되야한다.") + @Test + void 처음_전쳬_카드_개수_테스트() { + CardsGenerator cardsGenerator = new ShuffledCardsGenerator(); + + Deck deck= new Deck(cardsGenerator); + + assertThat(deck.getCards().size()).isEqualTo(52); + } + + @DisplayName("셔플된 카드 순서대로 배정된다.") + @Test + void 셔플_카드_배부_테스트() { + CardsGenerator fakeCardsGenerator = new PlayerTestUtil.FakeShuffledCardsGenerator(); + + Deck deck= new Deck(fakeCardsGenerator); + + assertThat(deck.pop()).isEqualTo(new Card(CardShape.SPADE, CardRank.ACE)); + assertThat(deck.pop()).isEqualTo(new Card(CardShape.SPADE, CardRank.TWO)); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java new file mode 100644 index 00000000000..cf4898dcec8 --- /dev/null +++ b/src/test/java/domain/GameTest.java @@ -0,0 +1,286 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import domain.enums.CardRank; +import domain.enums.CardShape; +import domain.enums.MatchCase; +import testutil.PlayerTestUtil; +import utils.generator.CardsGenerator; + +class GameTest { + private static final String DEALER = "딜러"; + private static final String PLAYER_NAME_1 = "아티"; + private static final String PLAYER_NAME_2 = "요크"; + private Deck deck; + + @BeforeEach + void setUp() { + CardsGenerator cardsGenerator = new PlayerTestUtil.FakeShuffledCardsGenerator(); + this.deck = new Deck(cardsGenerator); + } + + @Test + @DisplayName("참가자가 딜러보다 낮은 점수면 딜러가 이긴다") + void 참가자가_딜러보다_점수_낮음() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.JACK) + )); // 20 + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.JACK), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + + Map result = game.calculateMatch(); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.LOSE); + } + + + @Test + @DisplayName("참가자가 딜러보다 높은 점수면 딜러가 진다") + void 참가자가_딜러보다_점수_높음() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.JACK) + )); // 20 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + Map result = game.calculateMatch(); + + assertThat(player.getCardsTotalSum()).isEqualTo(21); + assertThat(dealer.getCardsTotalSum()).isEqualTo(20); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.WIN); + } + + @DisplayName("참가자와 딜러 점수가 둘 다 같으면 무승부") + @Test + void 참가자_딜러_무승부() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + Map result = game.calculateMatch(); + + + assertThat(player.getCardsTotalSum()).isEqualTo(21); + assertThat(dealer.getCardsTotalSum()).isEqualTo(21); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.DRAW); + } + + @DisplayName("참가자가 버스트고 딜러가 살면 딜러 승") + @Test + void 참가자_버스트_딜러_생존() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.QUEEN), + new Card(CardShape.SPADE, CardRank.TWO) + )); // 22 + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + Map result = game.calculateMatch(); + + + assertThat(player.getCardsTotalSum()).isEqualTo(22); + assertThat(dealer.getCardsTotalSum()).isEqualTo(21); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.LOSE); + } + + @DisplayName("참가자가 생존하고 딜러가 버스트면 딜러 패") + @Test + void 참가자_생존_딜러_버스트() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TWO), + new Card(CardShape.SPADE, CardRank.TEN) + )); // 12 + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.TEN) + )); // 29 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + Map result = game.calculateMatch(); + + + assertThat(player.getCardsTotalSum()).isEqualTo(12); + assertThat(dealer.getCardsTotalSum()).isEqualTo(29); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.WIN); + } + + // + @DisplayName("생존한 참가자 있고 딜러 버스트면 딜러 승리, 패배 하나씩") + @Test + void 생존_참가자_존재_및_딜러_버스트() { + Players players = new Players( + List.of(PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.QUEEN) + )), // 19 + PlayerTestUtil.createPlayer( + PLAYER_NAME_2, + List.of( + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.QUEEN), + new Card(CardShape.SPADE, CardRank.THREE) + ))) // 22 + ); + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.TEN) + )); // 29 + + Game game = new Game(deck, players, dealer); + Map result = game.calculateMatch(); + + assertThat(dealer.getCardsTotalSum()).isEqualTo(29); + assertThat(result.get(PLAYER_NAME_1)).isEqualTo(MatchCase.WIN); + assertThat(result.get(PLAYER_NAME_2)).isEqualTo(MatchCase.LOSE); + } + + + @DisplayName("참가자가 bust되면 배팅금액이 마이너스가 된다.") + @Test + void 참가자_bust_배팅금액_마이너스() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.EIGHT) + )); // 27 + player.betMoney(10000); + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.NINE) + )); // 19 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + game.calculateMatch(); + + assertThat(player.getBettingScore()).isEqualTo(-10000); +// assertThat(dealer.getBettingScore()).isEqualTo(10000); + } + + @DisplayName("참가자가 블랙잭이 되면 배팅금액이 1.5배가 된다.") + @Test + void 참가자_블랙잭_배팅금액_보너스() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + player.betMoney(10000); + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.NINE), + new Card(CardShape.SPADE, CardRank.TEN) + )); // 29 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + game.calculateMatch(); + + assertThat(player.getBettingScore()).isEqualTo(15000); +// assertThat(dealer.getBettingScore()).isEqualTo(-15000); + } + + @DisplayName("딜러와 참가자 모두 블랙잭이면 배팅한 금액을 돌려받는다..") + @Test + void 딜러_참가자_블랙잭_배팅금액_반환() { + Player player = PlayerTestUtil.createPlayer( + PLAYER_NAME_1, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + player.betMoney(10000); + + Player dealer = PlayerTestUtil.createPlayer( + DEALER, + List.of( + new Card(CardShape.SPADE, CardRank.TEN), + new Card(CardShape.SPADE, CardRank.ACE) + )); // 21 + + Players players = new Players(List.of(player)); + Game game = new Game(deck, players, dealer); + game.calculateMatch(); + + assertThat(player.getBettingScore()).isEqualTo(0); + assertThat(dealer.getBettingScore()).isEqualTo(0); + } + + private Players createSinglePlayerSet(Player player) { + return new Players(List.of(player)); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 85a84f9c2b6..e1f4e01ae22 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -8,6 +8,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import domain.enums.CardRank; +import domain.enums.CardShape; + class PlayerTest { private Player player; @@ -23,7 +26,7 @@ void setUp() { player.add(new Card(CardShape.SPADE, CardRank.THREE)); player.add(new Card(CardShape.SPADE, CardRank.TWO)); - int result = player.calculateScore(); + int result = player.getCardsTotalSum(); assertThat(result).isEqualTo(5); } @@ -34,7 +37,7 @@ void setUp() { player.add(new Card(CardShape.SPADE, CardRank.ACE)); player.add(new Card(CardShape.SPADE, CardRank.TWO)); - int result = player.getFinalScore(); + int result = player.getCardsTotalSum(); assertThat(result).isEqualTo(13); } @@ -45,7 +48,7 @@ void setUp() { player.add(new Card(CardShape.SPADE, CardRank.NINE)); player.add(new Card(CardShape.SPADE, CardRank.TWO)); - int result = player.getFinalScore(); + int result = player.getCardsTotalSum(); assertThat(result).isEqualTo(12); } @@ -56,7 +59,7 @@ void setUp() { player.add(new Card(CardShape.SPADE, CardRank.ACE)); player.add(new Card(CardShape.SPADE, CardRank.NINE)); - int result = player.getFinalScore(); + int result = player.getCardsTotalSum(); assertThat(result).isEqualTo(21); } @@ -67,7 +70,7 @@ void setUp() { player.add(new Card(CardShape.SPADE, CardRank.ACE)); player.add(new Card(CardShape.SPADE, CardRank.TEN)); - int result = player.getFinalScore(); + int result = player.getCardsTotalSum(); assertThat(result).isEqualTo(12); } @@ -100,7 +103,7 @@ void setUp() { new Card(CardShape.HEART, CardRank.QUEEN), new Card(CardShape.HEART, CardRank.JACK) )); - assertThat(player1.getFinalScore()).isEqualTo(30); + assertThat(player1.getCardsTotalSum()).isEqualTo(30); } private Player createPlayerFromCards(List cards) { diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java new file mode 100644 index 00000000000..37336be4f19 --- /dev/null +++ b/src/test/java/domain/PlayersTest.java @@ -0,0 +1,37 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayersTest { + + private Players players; + + @BeforeEach + void setUp() { + Player player1 = new Player("player1"); + Player player2 = new Player("player2"); + Players players = new Players(List.of(player1, player2)); + this.players = players; + } + + @DisplayName("총 배팅 금액을 구한다.") + @Test + void 총_배팅_금액_계산(){ + players.getPlayer(0).betMoney(10000); + players.getPlayer(1).betMoney(20000); + + int total = players.getTotalBettingScore(); + + assertThat(total).isEqualTo(30000); + } + + + + +} \ No newline at end of file diff --git a/src/test/java/service/BlackjackServiceTest.java b/src/test/java/service/BlackjackServiceTest.java index 102af3d42b7..391c002d651 100644 --- a/src/test/java/service/BlackjackServiceTest.java +++ b/src/test/java/service/BlackjackServiceTest.java @@ -2,70 +2,91 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import domain.Card; -import domain.CardRank; -import domain.CardShape; -import domain.Cards; +import domain.Deck; +import domain.Game; import domain.Players; -import utils.generator.CardsGenerator; -import utils.generator.ShuffledCardsGenerator; class BlackjackServiceTest { private BlackjackService blackjackService; + private Deck deck; @BeforeEach void setUp() { BlackjackService blackjackService = new BlackjackService(); this.blackjackService = blackjackService; - } - - @DisplayName("처음 전쳬 카드는 52장 생성되야한다.") - @Test - void 처음_전쳬_카드_개수_테스트() { - Cards cards = blackjackService.generateCards(new ShuffledCardsGenerator()); - - assertThat(cards.getCards().size()).isEqualTo(52); + this.deck = blackjackService.generateCards(); } @DisplayName("처음에 카드 2장씩 베부된다.") @Test void 초기_카드_2장_배부_테스트() { - Cards cards = blackjackService.generateCards(new ShuffledCardsGenerator()); - Players playerList = blackjackService.createPlayers(List.of("요크", "아티"), cards); - assertThat(playerList.getPlayer(0).getCards().size()).isEqualTo(2); - } + Players players = blackjackService.createPlayers(List.of("요크", "아티"), deck); + Game game = blackjackService.createGame(deck,players); - @DisplayName("셔플된 카드 덱에서 플레이어에게 초기 카드 2장이 순서대로 배부된다.") - @Test - void 셔플_카드_순서대로_배부_정상_테스트() { - Cards cards = blackjackService.generateCards(new FakeShuffledCardsGenerator()); - Players playerList = blackjackService.createPlayers(List.of("요크", "아티"), cards); - - assertThat(playerList.getPlayer(0).getCards().getFirst()).isEqualTo(new Card(CardShape.SPADE, CardRank.ACE)); - assertThat(playerList.getPlayer(0).getCards().get(1)).isEqualTo(new Card(CardShape.SPADE, CardRank.TWO)); - - assertThat(playerList.getPlayer(1).getCards().get(0)).isEqualTo(new Card(CardShape.SPADE, CardRank.THREE)); - assertThat(playerList.getPlayer(1).getCards().get(1)).isEqualTo(new Card(CardShape.SPADE, CardRank.FOUR)); + assertThat(game.getPlayers().getPlayer(0).getCards().size()).isEqualTo(2); } - public static class FakeShuffledCardsGenerator implements CardsGenerator { - @Override - public Cards generateShuffledCards() { - return new Cards( - new ArrayList<>(List.of(new Card(CardShape.SPADE, CardRank.ACE), - new Card(CardShape.SPADE, CardRank.TWO), - new Card(CardShape.SPADE, CardRank.THREE), - new Card(CardShape.SPADE, CardRank.FOUR), - new Card(CardShape.SPADE, CardRank.FIVE)) - )); - } - } +// +// @DisplayName("처음에 카드 2장씩 베부된다.") +// @Test +// void 초기_카드_2장_배_테스트() { +// List cardContentDtos = blackjackService.getCardContentDtos(game); +// assertThat(players.getPlayer(0).getCards().size()).isEqualTo(2); +// } +// +// @DisplayName("처음에 카드 2장씩 베부된다.") +// @Test +// void 초기_카드_2장_배_테d스트() { +// List finalCardDtos = blackjackService.getFinalCardDtos(game); +// assertThat(players.getPlayer(0).getCards().size()).isEqualTo(2); +// } +// +// @DisplayName("처음에 카드 2장씩 베부된다.") +// @Test +// void 초기_카드_2장_배_테d스트() { +// MatchResultDto matchResultDto = blackjackService.getPlayerResultDto(game); +// assertThat(players.getPlayer(0).getCards().size()).isEqualTo(2); +// } +// +// @DisplayName("처음에 카드 2장씩 베부된다.") +// @Test +// void 초기_카드_2장_배_테d스트() { +// BettingResultDto bettingResultDto = blackjackService.getBettingScoreDto(game); +// assertThat(players.getPlayer(0).getCards().size()).isEqualTo(2); +// } +// +// @DisplayName("딜러가 받은 카드의 점수 합이 16이하이면 추가로 받는다.") +// @Test +// void 딜러_카드_추가_배부_필요_정상_테스트() { +// Player dealer = blackjackService.createDealer(deck); +// +// assertThat(dealer.needAdditionalCard()).isEqualTo(true); +// } +// +// @DisplayName("딜러가 받은 카드의 점수 합이 17 이상이면 추가로 받지 않는다.") +// @Test +// void 딜러_카드_추가_배부_불필요_정상_테스트() { +// Dealer dealer = createDealerFromCards(List.of( +// new Card(CardShape.HEART, CardRank.TEN), +// new Card(CardShape.HEART, CardRank.SEVEN) +// )); +// +// assertThat(dealer.needAdditionalCard()).isEqualTo(false); +// } +// +// private Dealer createDealerFromCards(List cards) { +// Dealer dealer = new Dealer(); +// for (Card card : cards) { +// dealer.add(card); +// } +// return dealer; +// } + } \ No newline at end of file diff --git a/src/test/java/testutil/PlayerTestUtil.java b/src/test/java/testutil/PlayerTestUtil.java index 2fed2c90e01..b752c3324d9 100644 --- a/src/test/java/testutil/PlayerTestUtil.java +++ b/src/test/java/testutil/PlayerTestUtil.java @@ -1,21 +1,31 @@ package testutil; import domain.Card; -import domain.Dealer; import domain.Player; +import domain.enums.CardRank; +import domain.enums.CardShape; +import utils.generator.CardsGenerator; +import java.util.ArrayList; import java.util.List; public final class PlayerTestUtil { - public static Player createPlayer(List cards) { - Player player = new Player("AAAA"); + public static Player createPlayer(String name, List cards) { + Player player = new Player(name); cards.forEach(player::add); return player; } - public static Dealer createDealer(List cards) { - Dealer dealer = new Dealer(); - cards.forEach(dealer::add); - return dealer; + public static class FakeShuffledCardsGenerator implements CardsGenerator { + @Override + public List generateShuffledCards() { + return new ArrayList<>( + List.of(new Card(CardShape.SPADE, CardRank.ACE), + new Card(CardShape.SPADE, CardRank.TWO), + new Card(CardShape.SPADE, CardRank.THREE), + new Card(CardShape.SPADE, CardRank.FOUR), + new Card(CardShape.SPADE, CardRank.FIVE)) + ); + } } }