diff --git a/README.md b/README.md index 556099c4de..e411b7316c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,108 @@ -# java-blackjack +## 기능 요구 사항 -블랙잭 미션 저장소 +### 플레이어 이름 입력 -## 우아한테크코스 코드리뷰 +1. 플레이어의 이름은 ","을 기준으로 구분한다. +2. 플레이어의 이름은 길이가 1 이상이어야 한다. +3. 플레이어의 이름은 알파벳 대소문자와 숫자로만 구성되어야 한다. +4. 플레이어의 이름은 중복되면 안된다. -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) +### 플레이어 및 딜러 점수 계산 + +1. 플레이어가 소유한 카드의 점수의 합이 플레이어의 점수다. +2. 카드의 점수는 카드의 숫자로 계산한다. +3. Ace 카드는 1 혹은 11 중 카드의 소유자가 더 유리한 것으로 계산한다. +4. King, Queen, Jack은 각각 10으로 계산한다. + +### 게임 진행 규칙 + +1. 플레이어는 돈을 배팅한다. +2. 모든 플레이어와 딜러는 카드를 2장씩 가지고 시작한다. +3. 딜러는 두 장의 카드 중 첫번째 카드를 공개하지 않고, 플레이어는 모든 카드를 공개한다. +4. 처음 가진 점수가 21점인 경우 BlackJack 이라 부른다. 플레이어와 딜러가 모두 BlackJack인 경우 무승부, 그렇지 않은 경우 BlackJack인 참가자가 승리한다. +5. 플레이어는 서로 돌아가면서 자기 턴을 가진다. +6. 플레이어는 자신의 턴에 자신이 카드를 더 받을지 말지 선택할 수 있다. 단, 자신의 점수가 21 이상인 경우 카드를 더 받을 수 없다. +7. 플레이어는 한 번 카드를 받지 않기로 결정한 경우, 앞으로의 턴에서 카드를 더 받을 수 없다. +8. 모든 플레이어가 카드를 더 받을 수 없는 경우, 딜러의 턴으로 넘어간다. +9. 딜러의 턴에서 딜러는 딜러의 점수가 16점 이하인 경우 카드를 더 받는다. 17점 이상인 경우 카드를 더 받지 않는다. +10. 딜러의 턴이 끝나면, 최종 점수 계산 및 게임의 승패를 가린다. +11. 최종 점수가 21점에 가장 가까우면서 21점을 넘기지 않는 사람이 승리한다. 동점인 플레이어(딜러 포함)이 나온 경우, 무승부로 판단한다. +12. 딜러가 21점을 넘긴 경우 딜러가 패배한다. +13. 정해진 규칙대로 배팅금액을 정산한다. + +### 배팅 금액 정산 규칙 + +1. 플레이어가 블랙잭으로 이긴 경우 딜러로부터 배팅 금액의 1.5배를 추가로 받는다. +2. 플레이어가 이긴 경우 딜러로부터 배팅 금액만큼 추가로 받는다. +3. 플레이어가 진 경우 플레이어는 배팅 금액을 모두 잃는다. + +### 게임 진행 상황 출력 + +1. 게임이 시작 되자마자, 딜러와 플레이어가 받은 카드를 출력한다. +2. 단, 딜러는 카드를 한장만 출력한다. +3. 이 후 플레이어의 차례마다 플레이어가 소유한 카드를 출력한다. + +### 게임 결과 출력 + +1. 게임을 완료한 후 각 플레이어(딜러 포함)가 보유한 카드 및 점수를 출력한다. +2. 게임을 완료한 후 각 플레이어별로 승패를 출력한다. + - 딜러는 다른 모든 플레이어에 대한 승패가 출력된다. + - 딜러가 아닌 플레이어는 딜러에 대한 승패가 출력된다. + +### 게임 진행 가이드 출력 + +- 게임의 원활한 진행을 위해 가이드 문구를 출력한다. + +## 기능 목록 + +- [x] 플레이어 이름 입력 기능 +- [x] 카드 점수 계산 기능 +- [x] Ace 카드 점수 보정 기능 +- [x] 플레이어 및 딜러 점수 계산 기능 +- [x] 플레이어 및 딜러간 승부 계산 기능 +- [x] 플레이어 및 딜러 손패 출력 기능 +- [x] 게임 결과 출력 기능 +- [x] 게임 승부 결과 출력 기능 +- [x] 덱 관리 기능 +- [x] 보유한 카드 점수 합계 계산 기능 +- [x] 카드 받을지 여부 입력 기능 +- [x] 전체 게임 진행 기능 +- [x] 딜러 Ace 카드 점수 보정 기능 + +## 1단계 피드백 반영 예정 목록 + +- [x] 드로우가 가능한지 확인하는 책임 컨트롤러에서 다른 곳으로 이동 +- [x] 드로우 여부 결정하는 정책과 드로우 방식을 결정하는 정책 분리 +- [x] 의미 있는 상수화 +- [x] 테스트 코드에서 사용하는 테스트 객체 생성 역할을 분리 +- [x] view만을 위한 enum 추가 +- [x] Card Enum화 + +## 2단계 구현 전 리팩토링 + +- [x] GameResultCalculator가 Gamer를 매개변수로 받도록 리팩토링 +- [x] BlackJackGame 추가 +- [x] 대부분의 도메인 클래스 패키지 프라이빗으로 수정 + +## 2단계 기능 목록 + +- [x] 게임 결과 계산 기능(블랙잭인 경우 포함) +- [x] 배팅 금액 등록 기능 +- [x] 승부에 따른 수익 계산 기능 +- [x] 최종 수익 계산 기능 +- [x] 최종 수익 출력 기능 +- [x] 배팅 금액 입력 기능 + +## 2단계 피드백 반영 예정 목록 + +- [x] BattingMoney 클래스 분리 +- [x] BlackJackController의 메서드 네이밍 수정 +- [x] DealerOutputView, PlayerOutputView 통합 +- [x] BlackJackGame 의 테스트 코드에서만 사용하는 생성자 삭제 +- [x] 메서드 선언 순서 변경 +- [x] enum 활용해 if문 제거 +- [x] 함수형 인터페이스 감싸서 클래스에 의미 부여 +- [x] stream map 내부 출력문 변경 +- [x] Card pool 코드 분리 +- [x] 딜러의 손패를 보여주는 행동을 전략패턴을 이용하도록 변경 +- [x] 플레이어와 딜러에서 카드를 선택하는 전략을 선택할 수 없도록 하기 diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000000..5a4b8c4218 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,8 @@ +import controller.BlackJackController; + +public class Main { + public static void main(String[] args) { + BlackJackController blackJackController = new BlackJackController(); + blackJackController.start(); + } +} diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java new file mode 100644 index 0000000000..0dc3bcbafb --- /dev/null +++ b/src/main/java/controller/BlackJackController.java @@ -0,0 +1,109 @@ +package controller; + +import domain.blackjack.BlackJackGame; +import domain.blackjack.Dealer; +import domain.blackjack.EarningMoney; +import domain.blackjack.Player; +import domain.blackjack.Players; +import domain.blackjack.WithOutFirstCardShowStrategy; +import domain.card.Card; +import domain.card.Deck; +import dto.GameResultDTO; +import dto.GamerDTO; +import java.util.List; +import view.BattingMoneyInputView; +import view.NameInputView; +import view.OutputView; +import view.YesOrNoInputView; +import view.gamer.GamerOutputView; +import view.gameresult.GameResultOutputView; + +public class BlackJackController { + public void start() { + Deck deck = Deck.fullDeck(); + BlackJackGame blackJackGame = generateBlackJackGame(); + initBlackJackGame(deck, blackJackGame); + printAllGamerBeforeRunGame(blackJackGame); + runGame(deck, blackJackGame); + printBlackJackGameResults(blackJackGame); + } + + private BlackJackGame generateBlackJackGame() { + List playerNames = NameInputView.getNames(); + List playersBattingMoney = BattingMoneyInputView.getMoney(playerNames); + return new BlackJackGame(playerNames, playersBattingMoney); + } + + private static void initBlackJackGame(Deck deck, BlackJackGame blackJackGame) { + blackJackGame.initialDraw(deck); + Players players = blackJackGame.getPlayers(); + List playerNames = players.getPlayerNames(); + OutputView.printInitGameDoneMessage(playerNames); + } + + private void printAllGamerBeforeRunGame(BlackJackGame blackJackGame) { + Players players = blackJackGame.getPlayers(); + printPlayers(players); + Dealer dealer = blackJackGame.getDealer(); + printDealerWithoutFirstCard(dealer); + } + + private void printPlayers(Players players) { + players.forEach(this::printPlayer); + } + + private void printPlayer(Player player) { + GamerDTO playerDTO = GamerDTO.playerDTO(player.getRawName(), player.getRawHoldingCards(), + player.calculateSummationCardPointAsInt()); + GamerOutputView.printWithoutSummationCardPoint(playerDTO); + } + + private void printDealerWithoutFirstCard(Dealer dealer) { + List rawHoldingCards = dealer.getRawHoldingCards(WithOutFirstCardShowStrategy.INSTANCE); + GamerDTO dealerDTO = GamerDTO.dealerDTO(rawHoldingCards, dealer.calculateSummationCardPointAsInt()); + GamerOutputView.printWithoutSummationCardPoint(dealerDTO); + } + + private void runGame(Deck deck, BlackJackGame blackJackGame) { + playersTryDraw(deck, blackJackGame); + dealerTryDraw(deck, blackJackGame); + } + + private void playersTryDraw(Deck deck, BlackJackGame blackJackGame) { + blackJackGame.playersDraw(deck, this::printPlayer, YesOrNoInputView::getYNAsBoolean); + } + + private void dealerTryDraw(Deck deck, BlackJackGame blackJackGame) { + boolean isDealerDraw = blackJackGame.dealerTryDraw(deck); + if (isDealerDraw) { + OutputView.printDealerDrawDone(); + } + } + + private void printBlackJackGameResults(BlackJackGame blackJackGame) { + Players players = blackJackGame.getPlayers(); + Dealer dealer = blackJackGame.getDealer(); + printDealerWithPoint(dealer); + printPlayersWithPoint(players); + + List playerNames = players.getPlayerNames(); + List playersEarningMoney = blackJackGame.calculatePlayersEarningMoney(); + EarningMoney dealerEarningMoney = blackJackGame.calculateDealerEarningMoney(); + GameResultDTO gameResultDTO = new GameResultDTO(playerNames, playersEarningMoney, dealerEarningMoney); + GameResultOutputView.print(gameResultDTO); + } + + private void printDealerWithPoint(Dealer dealer) { + GamerDTO dealerDTO = GamerDTO.dealerDTO(dealer.getRawHoldingCards(), + dealer.calculateSummationCardPointAsInt()); + GamerOutputView.print(dealerDTO); + } + + private void printPlayersWithPoint(Players players) { + players.forEach(player -> { + GamerDTO playerDTO = GamerDTO.playerDTO(player.getRawName(), player.getRawHoldingCards(), + player.calculateSummationCardPointAsInt()); + GamerOutputView.print(playerDTO); + }); + } +} diff --git a/src/main/java/domain/blackjack/AllCardShowStrategy.java b/src/main/java/domain/blackjack/AllCardShowStrategy.java new file mode 100644 index 0000000000..fa7f2a5084 --- /dev/null +++ b/src/main/java/domain/blackjack/AllCardShowStrategy.java @@ -0,0 +1,16 @@ +package domain.blackjack; + +import domain.card.Card; +import java.util.List; + +public class AllCardShowStrategy implements CardShowStrategy { + public static AllCardShowStrategy INSTANCE = new AllCardShowStrategy(); + + private AllCardShowStrategy() { + } + + @Override + public List show(List allCards) { + return List.copyOf(allCards); + } +} diff --git a/src/main/java/domain/blackjack/BettingMoney.java b/src/main/java/domain/blackjack/BettingMoney.java new file mode 100644 index 0000000000..2879f13375 --- /dev/null +++ b/src/main/java/domain/blackjack/BettingMoney.java @@ -0,0 +1,14 @@ +package domain.blackjack; + +record BettingMoney(int rawBattingMoney) { + BettingMoney { + if (rawBattingMoney < 0) { + throw new IllegalArgumentException("배팅 금액은 음수일 수 없습니다."); + } + } + + EarningMoney calculateEarningMoney(GameResult gameResult) { + double earnMoneyRate = gameResult.getEarnMoneyRate(); + return new EarningMoney((int) (rawBattingMoney * earnMoneyRate)); + } +} diff --git a/src/main/java/domain/blackjack/BlackJackGame.java b/src/main/java/domain/blackjack/BlackJackGame.java new file mode 100644 index 0000000000..82fdcfd6cf --- /dev/null +++ b/src/main/java/domain/blackjack/BlackJackGame.java @@ -0,0 +1,52 @@ +package domain.blackjack; + +import domain.card.Deck; +import java.util.List; +import java.util.stream.IntStream; + +public class BlackJackGame { + private final Dealer dealer; + private final Players players; + + public BlackJackGame(List playerNames, List battingMoneys) { + this.dealer = Dealer.of(HoldingCards.of()); + this.players = new Players(playerNames, battingMoneys); + } + + public void initialDraw(Deck deck) { + final int initialDrawCount = 2; + IntStream.range(0, initialDrawCount).forEach(index -> { + players.drawOnce(deck); + dealer.draw(deck); + }); + } + + public void playersDraw(Deck deck, PlayerDrawAfterCallBack playerDrawAfterCallBack, + DrawConfirmation drawConfirmation) { + players.draw(deck, playerDrawAfterCallBack, drawConfirmation); + } + + public boolean dealerTryDraw(Deck deck) { + DrawResult drawResult = dealer.draw(deck); + return drawResult.isSuccess(); + } + + public Dealer getDealer() { + return dealer; + } + + public Players getPlayers() { + return players; + } + + public List calculatePlayersEarningMoney() { + return players.calculatePlayersEarningMoney(dealer); + } + + public EarningMoney calculateDealerEarningMoney() { + int dealerRawEarningMoney = -players.calculatePlayersEarningMoney(dealer).stream() + .mapToInt(EarningMoney::rawEarningMoney) + .sum(); + return new EarningMoney(dealerRawEarningMoney); + } +} diff --git a/src/main/java/domain/blackjack/BlackJackGameMachine.java b/src/main/java/domain/blackjack/BlackJackGameMachine.java new file mode 100644 index 0000000000..a3b5b3e95e --- /dev/null +++ b/src/main/java/domain/blackjack/BlackJackGameMachine.java @@ -0,0 +1,80 @@ +package domain.blackjack; + +import static domain.card.CardName.TEN; + +import domain.card.Card; +import domain.card.CardDrawCondition; +import domain.card.CardSelectStrategy; +import domain.card.Deck; +import java.util.List; + +class BlackJackGameMachine { + private static final int INITIAL_CARD_COUNT = 2; + private final HoldingCards holdingCards; + + BlackJackGameMachine(HoldingCards holdingCards) { + this.holdingCards = holdingCards; + } + + DrawResult draw(Deck deck, CardSelectStrategy cardSelectStrategy, CardDrawCondition cardDrawCondition) { + if (canDraw(deck, cardDrawCondition)) { + return DrawResult.fail(); + } + try { + Card draw = deck.draw(cardSelectStrategy); + holdingCards.add(draw); + return DrawResult.success(!isBust()); + } catch (IllegalArgumentException e) { + return DrawResult.fail(e, !isBust()); + } + } + + private boolean canDraw(Deck deck, CardDrawCondition cardDrawCondition) { + return isBust() || !cardDrawCondition.canDraw() || deck.isEmpty(); + } + + List getRawHoldingCards(CardShowStrategy cardShowStrategy) { + List allCards = holdingCards.getHoldingCards(); + return List.copyOf(cardShowStrategy.show(allCards)); + } + + int calculateSummationCardPointAsInt() { + return calculateSummationCardPoint().summationCardPoint(); + } + + SummationCardPoint calculateSummationCardPoint() { + SummationCardPoint summationCardPoint = holdingCards.calculateTotalPoint(); + if (hasAceInHoldingCards()) { + int rawPoint = fixPoint(summationCardPoint.summationCardPoint()); + return new SummationCardPoint(rawPoint); + } + return summationCardPoint; + } + + private boolean hasAceInHoldingCards() { + return holdingCards.hasAce(); + } + + private int fixPoint(int rawPoint) { + SummationCardPoint fixPoint = new SummationCardPoint(rawPoint + TEN.getCardNumber()); + if (fixPoint.isDeadPoint()) { + return rawPoint; + } + return fixPoint.summationCardPoint(); + } + + boolean isBust() { + return calculateSummationCardPoint().isDeadPoint(); + } + + boolean isBlackJack() { + List rawHoldingCards = getRawHoldingCards(); + int holdingCardCount = rawHoldingCards.size(); + SummationCardPoint summationCardPoint = calculateSummationCardPoint(); + return holdingCardCount == INITIAL_CARD_COUNT && summationCardPoint.isBlackJackPoint(); + } + + List getRawHoldingCards() { + return List.copyOf(holdingCards.getHoldingCards()); + } +} diff --git a/src/main/java/domain/blackjack/CardPoint.java b/src/main/java/domain/blackjack/CardPoint.java new file mode 100644 index 0000000000..ba3f1640ca --- /dev/null +++ b/src/main/java/domain/blackjack/CardPoint.java @@ -0,0 +1,5 @@ +package domain.blackjack; + +record CardPoint(int point) { + +} diff --git a/src/main/java/domain/blackjack/CardPointCalculator.java b/src/main/java/domain/blackjack/CardPointCalculator.java new file mode 100644 index 0000000000..318129bd76 --- /dev/null +++ b/src/main/java/domain/blackjack/CardPointCalculator.java @@ -0,0 +1,17 @@ +package domain.blackjack; + +import static domain.card.CardName.TEN; + +import domain.card.Card; +import domain.card.CardName; + +class CardPointCalculator { + static CardPoint calculate(Card card) { + CardName cardName = card.cardName(); + int cardNumber = cardName.getCardNumber(); + if (cardNumber > TEN.getCardNumber()) { + return new CardPoint(TEN.getCardNumber()); + } + return new CardPoint(cardNumber); + } +} diff --git a/src/main/java/domain/blackjack/CardShowStrategy.java b/src/main/java/domain/blackjack/CardShowStrategy.java new file mode 100644 index 0000000000..1c207f1c48 --- /dev/null +++ b/src/main/java/domain/blackjack/CardShowStrategy.java @@ -0,0 +1,8 @@ +package domain.blackjack; + +import domain.card.Card; +import java.util.List; + +public interface CardShowStrategy { + List show(List allCards); +} diff --git a/src/main/java/domain/blackjack/Dealer.java b/src/main/java/domain/blackjack/Dealer.java new file mode 100644 index 0000000000..11ca7e4b9f --- /dev/null +++ b/src/main/java/domain/blackjack/Dealer.java @@ -0,0 +1,21 @@ +package domain.blackjack; + +import domain.card.Deck; +import domain.card.RandomCardSelectStrategy; + +public class Dealer extends Gamer { + + Dealer(BlackJackGameMachine blackJackGameMachine) { + super(blackJackGameMachine); + } + + static Dealer of(HoldingCards holdingCards) { + return new Dealer(new BlackJackGameMachine(holdingCards)); + } + + @Override + DrawResult draw(Deck deck) { + return blackJackGameMachine.draw(deck, RandomCardSelectStrategy.INSTANCE, + new DealerCardDrawCondition(blackJackGameMachine)); + } +} diff --git a/src/main/java/domain/blackjack/DealerCardDrawCondition.java b/src/main/java/domain/blackjack/DealerCardDrawCondition.java new file mode 100644 index 0000000000..dd6ccbd5fa --- /dev/null +++ b/src/main/java/domain/blackjack/DealerCardDrawCondition.java @@ -0,0 +1,20 @@ +package domain.blackjack; + +import domain.card.CardDrawCondition; + +public final class DealerCardDrawCondition implements CardDrawCondition { + public static final int RAW_DEALER_DRAW_THRESHOLD_POINT = 16; + private final BlackJackGameMachine blackJackGameMachine; + + DealerCardDrawCondition(BlackJackGameMachine blackJackGameMachine) { + this.blackJackGameMachine = blackJackGameMachine; + } + + @Override + public boolean canDraw() { + SummationCardPoint dealerDrawThresholdPoint = new SummationCardPoint(RAW_DEALER_DRAW_THRESHOLD_POINT); + + SummationCardPoint summationCardPoint = blackJackGameMachine.calculateSummationCardPoint(); + return !summationCardPoint.isBiggerThan(dealerDrawThresholdPoint); + } +} diff --git a/src/main/java/domain/blackjack/DrawConfirmation.java b/src/main/java/domain/blackjack/DrawConfirmation.java new file mode 100644 index 0000000000..dfd934ed18 --- /dev/null +++ b/src/main/java/domain/blackjack/DrawConfirmation.java @@ -0,0 +1,5 @@ +package domain.blackjack; + +public interface DrawConfirmation { + boolean isDrawDesired(String playerName); +} diff --git a/src/main/java/domain/blackjack/DrawResult.java b/src/main/java/domain/blackjack/DrawResult.java new file mode 100644 index 0000000000..b3972eb2a5 --- /dev/null +++ b/src/main/java/domain/blackjack/DrawResult.java @@ -0,0 +1,35 @@ +package domain.blackjack; + +class DrawResult { + private final String failCause; + private final boolean hasNextChance; + + private DrawResult(String failCause, boolean hasNextChance) { + this.failCause = failCause; + this.hasNextChance = hasNextChance; + } + + static DrawResult success(boolean hasNextChance) { + return new DrawResult(null, hasNextChance); + } + + static DrawResult fail(Exception drawFailCause, boolean hasNextChance) { + return new DrawResult(drawFailCause.getMessage(), hasNextChance); + } + + static DrawResult fail() { + return new DrawResult("카드를 더이상 뽑을 수 없습니다.", false); + } + + boolean hasNextChance() { + return hasNextChance; + } + + boolean isSuccess() { + return failCause == null; + } + + String getFailCause() { + return failCause; + } +} diff --git a/src/main/java/domain/blackjack/EarningMoney.java b/src/main/java/domain/blackjack/EarningMoney.java new file mode 100644 index 0000000000..ba316b1287 --- /dev/null +++ b/src/main/java/domain/blackjack/EarningMoney.java @@ -0,0 +1,4 @@ +package domain.blackjack; + +public record EarningMoney(int rawEarningMoney) { +} diff --git a/src/main/java/domain/blackjack/GameResult.java b/src/main/java/domain/blackjack/GameResult.java new file mode 100644 index 0000000000..d9502e7ade --- /dev/null +++ b/src/main/java/domain/blackjack/GameResult.java @@ -0,0 +1,17 @@ +package domain.blackjack; + +public enum GameResult { + LOSE(-1), + WIN(1), + TIE(0), + WIN_BLACK_JACK(1.5); + private final double earnMoneyRate; + + GameResult(double earnMoneyRate) { + this.earnMoneyRate = earnMoneyRate; + } + + public double getEarnMoneyRate() { + return earnMoneyRate; + } +} diff --git a/src/main/java/domain/blackjack/GameResultCalculator.java b/src/main/java/domain/blackjack/GameResultCalculator.java new file mode 100644 index 0000000000..b5b5c22faf --- /dev/null +++ b/src/main/java/domain/blackjack/GameResultCalculator.java @@ -0,0 +1,49 @@ +package domain.blackjack; + +class GameResultCalculator { + + static GameResult calculate(Player player, Dealer dealer) { + if (player.isBlackJack() || dealer.isBlackJack()) { + return calculateGameResultWhenSomeOneIsBlackJack(player, dealer); + } + if (player.isBust() || dealer.isBust()) { + return calculateGameResultWhenSomeOneIsBust(player, dealer); + } + return calculateWithSummationCardPoint(player, dealer); + } + + private static GameResult calculateGameResultWhenSomeOneIsBlackJack(Player player, Dealer dealer) { + if (dealer.isBlackJack() && player.isBlackJack()) { + return GameResult.TIE; + } + if (dealer.isBlackJack()) { + return GameResult.LOSE; + } + if (player.isBlackJack()) { + return GameResult.WIN_BLACK_JACK; + } + return null; + } + + private static GameResult calculateGameResultWhenSomeOneIsBust(Player player, Dealer dealer) { + if (dealer.isBust()) { + return GameResult.WIN; + } + if (player.isBust()) { + return GameResult.LOSE; + } + return null; + } + + private static GameResult calculateWithSummationCardPoint(Player player, Dealer dealer) { + SummationCardPoint baseSummationCardPoint = player.calculateSummationCardPoint(); + SummationCardPoint otherSummationCardPoint = dealer.calculateSummationCardPoint(); + if (baseSummationCardPoint.isBiggerThan(otherSummationCardPoint)) { + return GameResult.WIN; + } + if (baseSummationCardPoint.equals(otherSummationCardPoint)) { + return GameResult.TIE; + } + return GameResult.LOSE; + } +} diff --git a/src/main/java/domain/blackjack/Gamer.java b/src/main/java/domain/blackjack/Gamer.java new file mode 100644 index 0000000000..5ac8f30d76 --- /dev/null +++ b/src/main/java/domain/blackjack/Gamer.java @@ -0,0 +1,39 @@ +package domain.blackjack; + +import domain.card.Card; +import domain.card.Deck; +import java.util.List; + +abstract class Gamer { + protected final BlackJackGameMachine blackJackGameMachine; + + Gamer(BlackJackGameMachine blackJackGameMachine) { + this.blackJackGameMachine = blackJackGameMachine; + } + + abstract DrawResult draw(Deck deck); + + public final int calculateSummationCardPointAsInt() { + return blackJackGameMachine.calculateSummationCardPointAsInt(); + } + + final SummationCardPoint calculateSummationCardPoint() { + return blackJackGameMachine.calculateSummationCardPoint(); + } + + public final List getRawHoldingCards() { + return getRawHoldingCards(AllCardShowStrategy.INSTANCE); + } + + public final List getRawHoldingCards(CardShowStrategy cardShowStrategy) { + return blackJackGameMachine.getRawHoldingCards(cardShowStrategy); + } + + final boolean isBust() { + return blackJackGameMachine.isBust(); + } + + final boolean isBlackJack() { + return blackJackGameMachine.isBlackJack(); + } +} diff --git a/src/main/java/domain/blackjack/HoldingCards.java b/src/main/java/domain/blackjack/HoldingCards.java new file mode 100644 index 0000000000..2d68d33aaa --- /dev/null +++ b/src/main/java/domain/blackjack/HoldingCards.java @@ -0,0 +1,40 @@ +package domain.blackjack; + +import domain.card.Card; +import domain.card.CardName; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +class HoldingCards { + private final List holdingCards; + + private HoldingCards(List holdingCards) { + this.holdingCards = holdingCards; + } + + static HoldingCards of(Card... cards) { + return new HoldingCards(new ArrayList<>(List.of(cards))); + } + + SummationCardPoint calculateTotalPoint() { + List cardPoints = holdingCards.stream() + .map(CardPointCalculator::calculate) + .toList(); + + return SummationCardPoint.of(cardPoints); + } + + void add(Card card) { + holdingCards.add(card); + } + + List getHoldingCards() { + return Collections.unmodifiableList(holdingCards); + } + + boolean hasAce() { + return holdingCards.stream() + .anyMatch(card -> card.cardName() == CardName.ACE); + } +} diff --git a/src/main/java/domain/blackjack/Player.java b/src/main/java/domain/blackjack/Player.java new file mode 100644 index 0000000000..adbcf1ef84 --- /dev/null +++ b/src/main/java/domain/blackjack/Player.java @@ -0,0 +1,37 @@ +package domain.blackjack; + +import domain.card.Deck; +import domain.card.RandomCardSelectStrategy; + +public class Player extends Gamer { + private final String name; + private final BettingMoney bettingMoney; + + private Player(String name, BlackJackGameMachine blackJackGameMachine, int bettingMoney) { + super(blackJackGameMachine); + this.name = name; + this.bettingMoney = new BettingMoney(bettingMoney); + } + + static Player from(String name, HoldingCards holdingCards) { + return new Player(name, new BlackJackGameMachine(holdingCards), 0); + } + + static Player from(String name, HoldingCards holdingCards, int bettingMoney) { + return new Player(name, new BlackJackGameMachine(holdingCards), bettingMoney); + } + + @Override + DrawResult draw(Deck deck) { + return blackJackGameMachine.draw(deck, RandomCardSelectStrategy.INSTANCE, + new PlayerCardDrawCondition(blackJackGameMachine)); + } + + public String getRawName() { + return name; + } + + final EarningMoney calculateEarningMoney(GameResult gameResult) { + return bettingMoney.calculateEarningMoney(gameResult); + } +} diff --git a/src/main/java/domain/blackjack/PlayerCardDrawCondition.java b/src/main/java/domain/blackjack/PlayerCardDrawCondition.java new file mode 100644 index 0000000000..5f856ed5d7 --- /dev/null +++ b/src/main/java/domain/blackjack/PlayerCardDrawCondition.java @@ -0,0 +1,16 @@ +package domain.blackjack; + +import domain.card.CardDrawCondition; + +final class PlayerCardDrawCondition implements CardDrawCondition { + private final BlackJackGameMachine player; + + PlayerCardDrawCondition(BlackJackGameMachine player) { + this.player = player; + } + + @Override + public boolean canDraw() { + return !player.isBust(); + } +} diff --git a/src/main/java/domain/blackjack/PlayerDrawAfterCallBack.java b/src/main/java/domain/blackjack/PlayerDrawAfterCallBack.java new file mode 100644 index 0000000000..94965577f0 --- /dev/null +++ b/src/main/java/domain/blackjack/PlayerDrawAfterCallBack.java @@ -0,0 +1,5 @@ +package domain.blackjack; + +public interface PlayerDrawAfterCallBack { + void afterDrawProcess(Player player); +} diff --git a/src/main/java/domain/blackjack/Players.java b/src/main/java/domain/blackjack/Players.java new file mode 100644 index 0000000000..52923c0810 --- /dev/null +++ b/src/main/java/domain/blackjack/Players.java @@ -0,0 +1,84 @@ +package domain.blackjack; + +import domain.card.Deck; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class Players { + private final List players; + + Players(List playerNames) { + this.players = playerNames.stream() + .map(playerName -> Player.from(playerName, HoldingCards.of())) + .toList(); + } + + public Players(List playersName, List battingMoneys) { + List players = new ArrayList<>(); + for (int index = 0; index < playersName.size(); index++) { + String playerName = playersName.get(index); + int battingMoney = battingMoneys.get(index); + players.add(Player.from(playerName, HoldingCards.of(), battingMoney)); + } + this.players = Collections.unmodifiableList(players); + } + + List calculateGameResultsWith(Dealer dealer) { + return players.stream() + .map(player -> GameResultCalculator.calculate(player, dealer)) + .toList(); + } + + public void forEach(Consumer consumer) { + players.forEach(consumer); + } + + public List getPlayerNames() { + return players.stream().map(Player::getRawName).toList(); + } + + void draw(Deck deck, PlayerDrawAfterCallBack playerDrawAfterCallBack, DrawConfirmation drawConfirmation) { + for (Player player : players) { + playerDraw(deck, playerDrawAfterCallBack, drawConfirmation, player); + } + } + + private void playerDraw(Deck deck, PlayerDrawAfterCallBack playerDrawAfterCallBack, + DrawConfirmation drawConfirmation, + Player player) { + boolean hasNextDrawChance = true; + while (hasNextDrawChance) { + hasNextDrawChance = playerTryDrawOnce(deck, player, drawConfirmation); + playerDrawAfterCallBack.afterDrawProcess(player); + } + } + + private boolean playerTryDrawOnce(Deck deck, Player player, DrawConfirmation drawConfirmation) { + boolean needToDraw = drawConfirmation.isDrawDesired(player.getRawName()); + DrawResult drawResult = null; + if (needToDraw) { + drawResult = player.draw(deck); + } + if (drawResult == null) { + return false; + } + return drawResult.hasNextChance(); + } + + void drawOnce(Deck deck) { + for (Player player : players) { + player.draw(deck); + } + } + + List calculatePlayersEarningMoney(Dealer dealer) { + return players.stream() + .map(player -> { + GameResult gameResult = GameResultCalculator.calculate(player, dealer); + return player.calculateEarningMoney(gameResult); + }) + .toList(); + } +} diff --git a/src/main/java/domain/blackjack/SummationCardPoint.java b/src/main/java/domain/blackjack/SummationCardPoint.java new file mode 100644 index 0000000000..0eead1a7ef --- /dev/null +++ b/src/main/java/domain/blackjack/SummationCardPoint.java @@ -0,0 +1,27 @@ +package domain.blackjack; + +import java.util.List; + +record SummationCardPoint(int summationCardPoint) { + private static final int DEAD_POINT_THRESHOLD = 21; + + static SummationCardPoint of(List cardPoints) { + int summationCardPoint = cardPoints.stream() + .mapToInt(CardPoint::point) + .sum(); + return new SummationCardPoint(summationCardPoint); + } + + boolean isDeadPoint() { + return this.isBiggerThan(new SummationCardPoint(DEAD_POINT_THRESHOLD)); + } + + boolean isBiggerThan(SummationCardPoint other) { + int otherPoint = other.summationCardPoint(); + return summationCardPoint > otherPoint; + } + + boolean isBlackJackPoint() { + return this.summationCardPoint == DEAD_POINT_THRESHOLD; + } +} diff --git a/src/main/java/domain/blackjack/WithOutFirstCardShowStrategy.java b/src/main/java/domain/blackjack/WithOutFirstCardShowStrategy.java new file mode 100644 index 0000000000..a4719bfc6c --- /dev/null +++ b/src/main/java/domain/blackjack/WithOutFirstCardShowStrategy.java @@ -0,0 +1,16 @@ +package domain.blackjack; + +import domain.card.Card; +import java.util.List; + +public class WithOutFirstCardShowStrategy implements CardShowStrategy { + public static WithOutFirstCardShowStrategy INSTANCE = new WithOutFirstCardShowStrategy(); + + private WithOutFirstCardShowStrategy() { + } + + @Override + public List show(List allCards) { + return allCards.subList(1, allCards.size()); + } +} diff --git a/src/main/java/domain/card/Card.java b/src/main/java/domain/card/Card.java new file mode 100644 index 0000000000..8319c618f0 --- /dev/null +++ b/src/main/java/domain/card/Card.java @@ -0,0 +1,28 @@ +package domain.card; + +public class Card { + + private final CardName cardName; + private final CardType cardType; + + Card(CardName cardName, CardType cardType) { + this.cardName = cardName; + this.cardType = cardType; + } + + public CardName cardName() { + return cardName; + } + + public CardType cardType() { + return cardType; + } + + @Override + public String toString() { + return "Card{" + + "cardName=" + cardName + + ", cardType=" + cardType + + '}'; + } +} diff --git a/src/main/java/domain/card/CardDrawCondition.java b/src/main/java/domain/card/CardDrawCondition.java new file mode 100644 index 0000000000..eb85014bf6 --- /dev/null +++ b/src/main/java/domain/card/CardDrawCondition.java @@ -0,0 +1,5 @@ +package domain.card; + +public interface CardDrawCondition { + boolean canDraw(); +} diff --git a/src/main/java/domain/card/CardName.java b/src/main/java/domain/card/CardName.java new file mode 100644 index 0000000000..8336f679ee --- /dev/null +++ b/src/main/java/domain/card/CardName.java @@ -0,0 +1,27 @@ +package domain.card; + +public enum CardName { + ACE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8), + NINE(9), + TEN(10), + JACK(11), + QUEEN(12), + KING(13); + + private final int cardNumber; + + CardName(int cardNumber) { + this.cardNumber = cardNumber; + } + + public int getCardNumber() { + return cardNumber; + } +} diff --git a/src/main/java/domain/card/CardPool.java b/src/main/java/domain/card/CardPool.java new file mode 100644 index 0000000000..cea963a477 --- /dev/null +++ b/src/main/java/domain/card/CardPool.java @@ -0,0 +1,28 @@ +package domain.card; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CardPool { + static final Map> pool = new ConcurrentHashMap<>(); + + static { + for (CardType cardType : CardType.values()) { + pool.put(cardType, new ConcurrentHashMap<>()); + for (CardName cardName : CardName.values()) { + pool.get(cardType).put(cardName, new Card(cardName, cardType)); + } + } + } + + static List allCards() { + return pool.keySet().stream() + .flatMap(cardType -> pool.get(cardType).values().stream()) + .toList(); + } + + public static Card get(CardType cardType, CardName cardName) { + return pool.get(cardType).get(cardName); + } +} diff --git a/src/main/java/domain/card/CardSelectStrategy.java b/src/main/java/domain/card/CardSelectStrategy.java new file mode 100644 index 0000000000..c0b8c7d026 --- /dev/null +++ b/src/main/java/domain/card/CardSelectStrategy.java @@ -0,0 +1,7 @@ +package domain.card; + +import java.util.List; + +public interface CardSelectStrategy { + Card select(List cards); +} diff --git a/src/main/java/domain/card/CardType.java b/src/main/java/domain/card/CardType.java new file mode 100644 index 0000000000..1a06a857b9 --- /dev/null +++ b/src/main/java/domain/card/CardType.java @@ -0,0 +1,5 @@ +package domain.card; + +public enum CardType { + HEART, SPADE, CLOVER, DIAMOND; +} diff --git a/src/main/java/domain/card/Deck.java b/src/main/java/domain/card/Deck.java new file mode 100644 index 0000000000..e9aa14f79d --- /dev/null +++ b/src/main/java/domain/card/Deck.java @@ -0,0 +1,46 @@ +package domain.card; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Deck { + private final List cards; + + private Deck(List cards) { + validateDuplicateCard(cards); + this.cards = new ArrayList<>(cards); + } + + private void validateDuplicateCard(List cards) { + Set cardSet = new HashSet<>(cards); + if (cardSet.size() != cards.size()) { + throw new IllegalArgumentException("중복되는 카드가 있습니다."); + } + } + + public static Deck of(Card... cards) { + return new Deck(List.of(cards)); + } + + public static Deck fullDeck() { + List cards = CardPool.allCards(); + return new Deck(cards); + } + + public Card draw(CardSelectStrategy cardSelectStrategy) { + if (cards.isEmpty()) { + throw new IllegalArgumentException("덱이 비어있습니다."); + } + + Card card = cardSelectStrategy.select(cards); + cards.remove(card); + + return card; + } + + public boolean isEmpty() { + return cards.isEmpty(); + } +} diff --git a/src/main/java/domain/card/RandomCardSelectStrategy.java b/src/main/java/domain/card/RandomCardSelectStrategy.java new file mode 100644 index 0000000000..08e39c442b --- /dev/null +++ b/src/main/java/domain/card/RandomCardSelectStrategy.java @@ -0,0 +1,16 @@ +package domain.card; + +import java.util.List; +import java.util.Random; + +public final class RandomCardSelectStrategy implements CardSelectStrategy { + + public static final RandomCardSelectStrategy INSTANCE = new RandomCardSelectStrategy(); + private final Random random = new Random(); + + @Override + public Card select(List cards) { + int idx = random.nextInt(cards.size()); + return cards.get(idx); + } +} diff --git a/src/main/java/dto/GameResultDTO.java b/src/main/java/dto/GameResultDTO.java new file mode 100644 index 0000000000..fbbdf3bc94 --- /dev/null +++ b/src/main/java/dto/GameResultDTO.java @@ -0,0 +1,29 @@ +package dto; + +import domain.blackjack.EarningMoney; +import java.util.List; + +public class GameResultDTO { + private final List playersName; + private final List playersEarningMoney; + private final EarningMoney dealerEarningMoney; + + public GameResultDTO(List playersName, List playersEarningMoney, + EarningMoney dealerEarningMoney) { + this.playersName = playersName; + this.playersEarningMoney = playersEarningMoney; + this.dealerEarningMoney = dealerEarningMoney; + } + + public List getPlayersName() { + return playersName; + } + + public List getPlayersEarnMoney() { + return playersEarningMoney; + } + + public EarningMoney getDealerEarningMoney() { + return dealerEarningMoney; + } +} diff --git a/src/main/java/dto/GamerDTO.java b/src/main/java/dto/GamerDTO.java new file mode 100644 index 0000000000..7ba2d114b3 --- /dev/null +++ b/src/main/java/dto/GamerDTO.java @@ -0,0 +1,36 @@ +package dto; + +import domain.card.Card; +import java.util.List; + +public class GamerDTO { + private final String name; + private final List holdingCards; + private final int summationCardPoint; + + private GamerDTO(String name, List holdingCards, int summationCardPoint) { + this.name = name; + this.holdingCards = holdingCards; + this.summationCardPoint = summationCardPoint; + } + + public static GamerDTO playerDTO(String name, List holdingCards, int summationCardPoint) { + return new GamerDTO(name, holdingCards, summationCardPoint); + } + + public static GamerDTO dealerDTO(List holdingCards, int summationCardPoint) { + return new GamerDTO("딜러", holdingCards, summationCardPoint); + } + + public String getName() { + return name; + } + + public List getHoldingCards() { + return holdingCards; + } + + public int getSummationCardPoint() { + return summationCardPoint; + } +} diff --git a/src/main/java/view/BattingMoneyInputView.java b/src/main/java/view/BattingMoneyInputView.java new file mode 100644 index 0000000000..6cd9d12624 --- /dev/null +++ b/src/main/java/view/BattingMoneyInputView.java @@ -0,0 +1,27 @@ +package view; + +import java.util.List; + +public class BattingMoneyInputView { + public static List getMoney(List playersName) { + return playersName.stream() + .peek(playerName -> Console.printf("%s의 배팅 금액은?%n", playerName)) + .map(playerName -> { + String inputFromConsole = Console.getInputFromConsole(); + validateInput(inputFromConsole); + return Integer.parseInt(inputFromConsole); + }).toList(); + } + + private static void validateInput(String input) { + int rawNumber; + try { + rawNumber = Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("입력 형식이 올바르지 않습니다."); + } + if (rawNumber <= 0) { + throw new IllegalArgumentException("입력 형식이 올바르지 않습니다."); + } + } +} diff --git a/src/main/java/view/Console.java b/src/main/java/view/Console.java new file mode 100644 index 0000000000..cdf0540a85 --- /dev/null +++ b/src/main/java/view/Console.java @@ -0,0 +1,30 @@ +package view; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Console { + public static String getInputFromConsole() { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); + String input; + try { + input = bufferedReader.readLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return input; + } + + public static void print(String output) { + System.out.print(output); + } + + public static void printf(String output, Object... objects) { + System.out.printf(output, objects); + } + + public static void println(String output) { + System.out.println(output); + } +} diff --git a/src/main/java/view/NameInputView.java b/src/main/java/view/NameInputView.java new file mode 100644 index 0000000000..5b13df4c7b --- /dev/null +++ b/src/main/java/view/NameInputView.java @@ -0,0 +1,29 @@ +package view; + +import java.util.List; + +public class NameInputView { + private static final String SEPARATOR = ","; + + public static List getNames() { + Console.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); + String input = Console.getInputFromConsole(); + validateSeparator(input); + List splitInput = List.of(input.split(SEPARATOR)); + validateBlank(splitInput); + return splitInput; + } + + private static void validateSeparator(String input) { + if (input.startsWith(SEPARATOR) || input.endsWith(SEPARATOR) || input.contains(SEPARATOR + SEPARATOR)) { + throw new IllegalArgumentException("입력 형식이 올바르지 않습니다."); + } + } + + private static void validateBlank(List split) { + boolean isInputContainsBlank = split.stream().anyMatch(String::isBlank); + if (isInputContainsBlank) { + throw new IllegalArgumentException("입력 형식이 올바르지 않습니다."); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 0000000000..ca69eacaec --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,20 @@ +package view; + +import static domain.blackjack.DealerCardDrawCondition.RAW_DEALER_DRAW_THRESHOLD_POINT; + +import java.util.List; + +public class OutputView { + public static void printInitGameDoneMessage(List playerRawNames) { + String namesOutput = String.join(", ", playerRawNames); + print("딜러와 %s에게 2장을 나누었습니다.".formatted(namesOutput)); + } + + private static void print(String output) { + System.out.println(output); + } + + public static void printDealerDrawDone() { + print("딜러는 %d이하라 한장의 카드를 더 받았습니다.\n".formatted(RAW_DEALER_DRAW_THRESHOLD_POINT)); + } +} diff --git a/src/main/java/view/YesOrNoInputView.java b/src/main/java/view/YesOrNoInputView.java new file mode 100644 index 0000000000..ea8275bee7 --- /dev/null +++ b/src/main/java/view/YesOrNoInputView.java @@ -0,0 +1,16 @@ +package view; + +public class YesOrNoInputView { + public static Boolean getYNAsBoolean(String playerName) { + String inputGuideOutput = "%s은(는) 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)%n".formatted(playerName); + Console.print(inputGuideOutput); + String input = Console.getInputFromConsole(); + if (input.equals("y")) { + return true; + } + if (input.equals("n")) { + return false; + } + throw new IllegalArgumentException("입력 형식이 올바르지 않습니다."); + } +} diff --git a/src/main/java/view/gamer/CardOutputGenerator.java b/src/main/java/view/gamer/CardOutputGenerator.java new file mode 100644 index 0000000000..8d49c0e6de --- /dev/null +++ b/src/main/java/view/gamer/CardOutputGenerator.java @@ -0,0 +1,15 @@ +package view.gamer; + +import domain.card.Card; +import domain.card.CardName; +import domain.card.CardType; + +class CardOutputGenerator { + static String generate(Card card) { + CardName cardName = card.cardName(); + CardType cardType = card.cardType(); + ViewCardName viewCardName = ViewCardName.of(cardName); + ViewCardType viewCardType = ViewCardType.of(cardType); + return viewCardName.getOutput() + viewCardType.getOutput(); + } +} diff --git a/src/main/java/view/gamer/GamerOutputView.java b/src/main/java/view/gamer/GamerOutputView.java new file mode 100644 index 0000000000..62b6390131 --- /dev/null +++ b/src/main/java/view/gamer/GamerOutputView.java @@ -0,0 +1,30 @@ +package view.gamer; + +import domain.card.Card; +import dto.GamerDTO; +import java.util.List; +import java.util.stream.Collectors; +import view.Console; + +public class GamerOutputView { + public static void print(GamerDTO gamerDTO) { + String outputWithoutSummationCardPoint = generateOutputWithoutSummationCardPoint(gamerDTO); + String summationCardPointOutput = "결과: %d".formatted(gamerDTO.getSummationCardPoint()); + Console.printf("%s - %s%n", outputWithoutSummationCardPoint, summationCardPointOutput); + } + + private static String generateOutputWithoutSummationCardPoint(GamerDTO gamerDTO) { + String name = gamerDTO.getName(); + List cards = gamerDTO.getHoldingCards(); + String nameOutput = name + "카드"; + String cardsOutput = cards.stream() + .map(CardOutputGenerator::generate) + .collect(Collectors.joining(", ")); + return "%s: %s".formatted(nameOutput, cardsOutput); + } + + public static void printWithoutSummationCardPoint(GamerDTO gamerDTO) { + String outputWithoutSummationCardPoint = generateOutputWithoutSummationCardPoint(gamerDTO); + Console.println(outputWithoutSummationCardPoint); + } +} diff --git a/src/main/java/view/gamer/ViewCardName.java b/src/main/java/view/gamer/ViewCardName.java new file mode 100644 index 0000000000..bb73f64f64 --- /dev/null +++ b/src/main/java/view/gamer/ViewCardName.java @@ -0,0 +1,33 @@ +package view.gamer; + +import domain.card.CardName; + +enum ViewCardName { + ACE("A"), + TWO("2"), + THREE("3"), + FOUR("4"), + FIVE("5"), + SIX("6"), + SEVEN("7"), + EIGHT("8"), + NINE("9"), + TEN("10"), + JACK("J"), + QUEEN("Q"), + KING("K"); + + private final String output; + + ViewCardName(String output) { + this.output = output; + } + + static ViewCardName of(CardName cardName) { + return valueOf(cardName.name()); + } + + String getOutput() { + return output; + } +} diff --git a/src/main/java/view/gamer/ViewCardType.java b/src/main/java/view/gamer/ViewCardType.java new file mode 100644 index 0000000000..50a45308dc --- /dev/null +++ b/src/main/java/view/gamer/ViewCardType.java @@ -0,0 +1,24 @@ +package view.gamer; + +import domain.card.CardType; + +enum ViewCardType { + HEART("하트"), + SPADE("스페이드"), + CLOVER("클로버"), + DIAMOND("다이아몬드"); + + private final String output; + + ViewCardType(String output) { + this.output = output; + } + + static ViewCardType of(CardType cardType) { + return valueOf(cardType.name()); + } + + public String getOutput() { + return output; + } +} diff --git a/src/main/java/view/gameresult/GameResultOutputView.java b/src/main/java/view/gameresult/GameResultOutputView.java new file mode 100644 index 0000000000..1d44ca7950 --- /dev/null +++ b/src/main/java/view/gameresult/GameResultOutputView.java @@ -0,0 +1,19 @@ +package view.gameresult; + +import domain.blackjack.EarningMoney; +import dto.GameResultDTO; +import java.util.List; +import view.Console; + +public class GameResultOutputView { + public static void print(GameResultDTO gameResultDTO) { + List playersName = gameResultDTO.getPlayersName(); + List playersEarnMoney = gameResultDTO.getPlayersEarnMoney(); + Console.println("## 최종 수익"); + EarningMoney dealerEarningMoney = gameResultDTO.getDealerEarningMoney(); + Console.printf("딜러: %d%n", dealerEarningMoney.rawEarningMoney()); + for (int index = 0; index < playersName.size(); index++) { + Console.printf("%s: %d%n", playersName.get(index), playersEarnMoney.get(index).rawEarningMoney()); + } + } +} diff --git a/src/test/java/domain/blackjack/BettingMoneyTest.java b/src/test/java/domain/blackjack/BettingMoneyTest.java new file mode 100644 index 0000000000..0a1b358696 --- /dev/null +++ b/src/test/java/domain/blackjack/BettingMoneyTest.java @@ -0,0 +1,30 @@ +package domain.blackjack; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class BettingMoneyTest { + + static Stream calculateEarningMoneyParameters() { + return Stream.of( + Arguments.of(GameResult.WIN, new EarningMoney(1000)), + Arguments.of(GameResult.LOSE, new EarningMoney(-1000)), + Arguments.of(GameResult.WIN_BLACK_JACK, new EarningMoney(1500)), + Arguments.of(GameResult.TIE, new EarningMoney(0)) + ); + } + + @ParameterizedTest + @MethodSource("calculateEarningMoneyParameters") + @DisplayName("상금이 잘 계산되는지 검증") + void calculateEarningMoney(GameResult gameResult, EarningMoney expected) { + Player player = Player.from("robin", HoldingCards.of(), 1000); + EarningMoney earningMoney = player.calculateEarningMoney(gameResult); + Assertions.assertThat(earningMoney) + .isEqualTo(expected); + } +} diff --git a/src/test/java/domain/blackjack/BlackJackGameMachineTest.java b/src/test/java/domain/blackjack/BlackJackGameMachineTest.java new file mode 100644 index 0000000000..f5183d04c2 --- /dev/null +++ b/src/test/java/domain/blackjack/BlackJackGameMachineTest.java @@ -0,0 +1,97 @@ +package domain.blackjack; + +import static domain.blackjack.TestHoldingCards.DEAD_CARDS; +import static domain.blackjack.TestHoldingCards.ONLY_SEVEN_HEART; +import static domain.blackjack.TestHoldingCards.WIN_CARDS_WITHOUT_ACE; +import static domain.blackjack.TestHoldingCards.WIN_CARDS_WITH_ACE; +import static domain.card.FirstCardSelectStrategy.FIRST_CARD_SELECT_STRATEGY; +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.EIGHT_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.TWO_HEART; + +import domain.card.Card; +import domain.card.Deck; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +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 BlackJackGameMachineTest { + public static Stream isDeadParameters() { + return Stream.of( + Arguments.of(TWO_HEART, true), Arguments.of(ACE_HEART, false) + ); + } + + public static Stream getSummationCardPointParameters() { + return Stream.of( + Arguments.of(new BlackJackGameMachine(ONLY_SEVEN_HEART), new SummationCardPoint(7)), + Arguments.of(new BlackJackGameMachine(WIN_CARDS_WITH_ACE), new SummationCardPoint(21)), + Arguments.of(new BlackJackGameMachine(WIN_CARDS_WITHOUT_ACE), new SummationCardPoint(21)), + Arguments.of(new BlackJackGameMachine(DEAD_CARDS), new SummationCardPoint(22)) + ); + } + + @Test + @DisplayName("카드를 뽑았을 때 소유중인 카드에 제대로 반영되는지 검증") + void draw() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(HoldingCards.of()); + Deck deck = Deck.of(JACK_HEART, EIGHT_HEART); + blackJackGameMachine.draw(deck, FIRST_CARD_SELECT_STRATEGY, new PlayerCardDrawCondition(blackJackGameMachine)); + + List actual = blackJackGameMachine.getRawHoldingCards(); + + Assertions.assertThat(actual) + .containsExactly(JACK_HEART); + } + + @Test + @DisplayName("드로우 조건에 따라 드로우 가능 여부가 결정되는지 검증") + void validateDrawLimit() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(HoldingCards.of()); + Deck deck = Deck.of(TWO_HEART); + DrawResult drawResult = blackJackGameMachine.draw(deck, FIRST_CARD_SELECT_STRATEGY, () -> true); + Assertions.assertThat(drawResult.isSuccess()).isTrue(); + } + + @ParameterizedTest + @MethodSource("isDeadParameters") + @DisplayName("점수가 21이 넘으면 죽었다고 판단하는지 검증") + void isBust(Card additionalCard, boolean expected) { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine( + HoldingCards.of(JACK_HEART, QUEEN_HEART, additionalCard)); + + Assertions.assertThat(blackJackGameMachine.isBust()).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("getSummationCardPointParameters") + @DisplayName("점수가 잘 계산되는지 검증") + void getSummationCardPoint(BlackJackGameMachine blackJackGameMachine, SummationCardPoint expected) { + SummationCardPoint summationCardPoint = blackJackGameMachine.calculateSummationCardPoint(); + Assertions.assertThat(summationCardPoint) + .isEqualTo(expected); + } + + @Test + @DisplayName("블랙잭인 경우 블랙잭이라 하는지 검증") + void isBlackJack() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(TestHoldingCards.BLACK_JACK); + Assertions.assertThat(blackJackGameMachine.isBlackJack()) + .isTrue(); + } + + @Test + @DisplayName("블랙잭이 아닌 경우 블랙잭이 아니라 하는지 검증") + void isNotBlackJack() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(WIN_CARDS_WITH_ACE); + Assertions.assertThat(blackJackGameMachine.isBlackJack()) + .isFalse(); + } +} diff --git a/src/test/java/domain/blackjack/BlackJackGameTest.java b/src/test/java/domain/blackjack/BlackJackGameTest.java new file mode 100644 index 0000000000..c1d4823dc4 --- /dev/null +++ b/src/test/java/domain/blackjack/BlackJackGameTest.java @@ -0,0 +1,68 @@ +package domain.blackjack; + + +import static domain.card.CardName.TEN; +import static domain.card.CardType.CLOVER; +import static domain.card.CardType.HEART; +import static domain.card.CardType.SPADE; +import static domain.card.TestCards.FIVE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.JACK_SPADE; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.SIX_HEART; +import static domain.card.TestCards.TWO_HEART; + +import domain.card.CardPool; +import domain.card.Deck; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BlackJackGameTest { + + @Test + @DisplayName("플레이어들의 드로우를 했을 때 제대로 드로우 되는지 검증") + void playersDraw() { + BlackJackGame blackJackGame = new BlackJackGame(List.of("player", "robin"), List.of(1, 1)); + List fixedYesOrNo = new ArrayList<>(List.of(true, false, true, false)); + blackJackGame.playersDraw(Deck.of(FIVE_HEART, SIX_HEART), BlackJackGameTest::doNothing, + playerName -> fixedYesOrNo.remove(0)); + + Players players = blackJackGame.getPlayers(); + Dealer bustedDealer = Dealer.of(HoldingCards.of(TWO_HEART)); + List gameResults = players.calculateGameResultsWith(bustedDealer); + Assertions.assertThat(gameResults) + .containsExactly(GameResult.WIN, GameResult.WIN); + } + + private static void doNothing(Player player) { + } + + @Test + @DisplayName("딜러가 드로우를 했을 때 제대로 드로우 되는지 검증") + void dealerDraw() { + BlackJackGame blackJackGame = new BlackJackGame(List.of("player", "robin"), List.of(1, 1)); + blackJackGame.dealerTryDraw(Deck.of(FIVE_HEART)); + Dealer dealer = blackJackGame.getDealer(); + + Player player = Player.from("플레이어", HoldingCards.of(TWO_HEART)); + GameResult gameResult = GameResultCalculator.calculate(player, dealer); + Assertions.assertThat(gameResult) + .isEqualTo(GameResult.LOSE); + } + + @Test + @DisplayName("딜러의 수익이 잘 계산되는지 검증") + void calculateDealerEarningMoney() { + BlackJackGame blackJackGame = new BlackJackGame(List.of("player", "robin"), List.of(100, 100)); + Deck deck = Deck.of(JACK_SPADE, JACK_HEART, QUEEN_HEART, CardPool.get(HEART, TEN), CardPool.get(SPADE, TEN), + CardPool.get(CLOVER, TEN)); + + blackJackGame.playersDraw(deck, BlackJackGameTest::doNothing, s -> true); + EarningMoney dealerEarningMoney = blackJackGame.calculateDealerEarningMoney(); + Assertions.assertThat(dealerEarningMoney) + .isEqualTo(new EarningMoney(200)); + } +} diff --git a/src/test/java/domain/blackjack/CardPointCalculatorTest.java b/src/test/java/domain/blackjack/CardPointCalculatorTest.java new file mode 100644 index 0000000000..dcf4a1fa80 --- /dev/null +++ b/src/test/java/domain/blackjack/CardPointCalculatorTest.java @@ -0,0 +1,43 @@ +package domain.blackjack; + +import static domain.card.CardName.TEN; + +import domain.card.Card; +import domain.card.CardName; +import domain.card.CardType; +import domain.card.Deck; +import domain.card.FirstCardSelectStrategy; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class CardPointCalculatorTest { + static Stream calculateParameter() { + Deck deck = Deck.fullDeck(); + int totalCardCount = CardType.values().length * CardName.values().length; + return IntStream.range(0, totalCardCount) + .mapToObj(value -> deck.draw(new FirstCardSelectStrategy())) + .map(card -> Arguments.of(card, makeCardPoint(card.cardName()))); + } + + private static CardPoint makeCardPoint(CardName cardName) { + CardPoint cardPoint = new CardPoint(cardName.getCardNumber()); + if (cardName.getCardNumber() > TEN.getCardNumber()) { + return new CardPoint(TEN.getCardNumber()); + } + return cardPoint; + } + + @ParameterizedTest + @MethodSource("calculateParameter") + @DisplayName("카드 점수가 제대로 변환되는지 검증") + void calculate(Card card, CardPoint expected) { + CardPoint cardPoint = CardPointCalculator.calculate(card); + Assertions.assertThat(cardPoint) + .isEqualTo(expected); + } +} diff --git a/src/test/java/domain/blackjack/DealerCardDrawConditionTest.java b/src/test/java/domain/blackjack/DealerCardDrawConditionTest.java new file mode 100644 index 0000000000..1c6e5a2bba --- /dev/null +++ b/src/test/java/domain/blackjack/DealerCardDrawConditionTest.java @@ -0,0 +1,36 @@ +package domain.blackjack; + + +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.FIVE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.SEVEN_HEART; +import static domain.card.TestCards.SIX_HEART; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class DealerCardDrawConditionTest { + + public static Stream canDrawParameters() { + return Stream.of( + Arguments.of(HoldingCards.of(ACE_HEART, SIX_HEART), false), + Arguments.of(HoldingCards.of(ACE_HEART, FIVE_HEART), true), + Arguments.of(HoldingCards.of(JACK_HEART, SEVEN_HEART), false), + Arguments.of(HoldingCards.of(JACK_HEART, SIX_HEART), true) + ); + } + + @ParameterizedTest + @MethodSource("canDrawParameters") + @DisplayName("딜러의 드로우 여부가 제대로 판단되는지 검증") + void canDraw(HoldingCards holdingCards, boolean expected) { + BlackJackGameMachine dealerMachine = new BlackJackGameMachine(holdingCards); + Assertions.assertThat(new DealerCardDrawCondition(dealerMachine).canDraw()) + .isEqualTo(expected); + } +} diff --git a/src/test/java/domain/blackjack/DealerTest.java b/src/test/java/domain/blackjack/DealerTest.java new file mode 100644 index 0000000000..35bd8d19bc --- /dev/null +++ b/src/test/java/domain/blackjack/DealerTest.java @@ -0,0 +1,67 @@ +package domain.blackjack; + +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.FIVE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.SEVEN_HEART; +import static domain.card.TestCards.TWO_HEART; + +import domain.card.Card; +import domain.card.Deck; +import java.util.List; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +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 DealerTest { + + static Stream validateDealerHasNextDrawChanceParameters() { + return Stream.of( + Arguments.of(TWO_HEART, false), Arguments.of(ACE_HEART, true) + ); + } + + @Test + @DisplayName("딜러는 총합이 16이 넘으면 카드를 뽑을 수 없는지 검증") + void validateDealerDrawLimit() { + Deck deck = Deck.of(TWO_HEART); + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(HoldingCards.of(JACK_HEART, SEVEN_HEART)); + Dealer dealer = new Dealer(blackJackGameMachine); + + DrawResult drawResult = dealer.draw(deck); + Assertions.assertThat(drawResult.getFailCause()) + .isEqualTo("카드를 더이상 뽑을 수 없습니다."); + } + + @ParameterizedTest + @MethodSource("validateDealerHasNextDrawChanceParameters") + @DisplayName("딜러의 다음 드로우 기회 유무를 제대로 판단하는지 검증") + void validatePlayerHasNextDrawChance(Card cardInHand, boolean expected) { + Deck deck = Deck.of(ACE_HEART); + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine( + HoldingCards.of(FIVE_HEART, QUEEN_HEART, cardInHand)); + Dealer dealer = new Dealer(blackJackGameMachine); + + DrawResult drawResult = dealer.draw(deck); + Assertions.assertThat(drawResult.hasNextChance()) + .isEqualTo(expected); + } + + @Test + @DisplayName("카드 하나가 숨겨진 상태로 조회되는지 검증") + void getRawHoldingCardsWithoutFirstCard() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine( + HoldingCards.of(FIVE_HEART, QUEEN_HEART)); + Dealer dealer = new Dealer(blackJackGameMachine); + List rawHoldingCardsWithoutFirstCard = dealer.getRawHoldingCards(WithOutFirstCardShowStrategy.INSTANCE); + Assertions.assertThat(rawHoldingCardsWithoutFirstCard) + .containsExactly(QUEEN_HEART); + Assertions.assertThat(dealer.getRawHoldingCards()) + .containsExactly(FIVE_HEART, QUEEN_HEART); + } +} diff --git a/src/test/java/domain/blackjack/GameResultCalculatorTest.java b/src/test/java/domain/blackjack/GameResultCalculatorTest.java new file mode 100644 index 0000000000..af32363d20 --- /dev/null +++ b/src/test/java/domain/blackjack/GameResultCalculatorTest.java @@ -0,0 +1,49 @@ +package domain.blackjack; + +import static domain.blackjack.GameResult.LOSE; +import static domain.blackjack.GameResult.TIE; +import static domain.blackjack.GameResult.WIN; +import static domain.blackjack.GameResult.WIN_BLACK_JACK; +import static domain.blackjack.TestHoldingCards.BLACK_JACK; +import static domain.blackjack.TestHoldingCards.DEAD_CARDS; +import static domain.blackjack.TestHoldingCards.ONLY_SEVEN_HEART; +import static domain.blackjack.TestHoldingCards.ONLY_SIX_HEART; +import static domain.blackjack.TestHoldingCards.TWO_SIX_CARDS; +import static domain.blackjack.TestHoldingCards.WIN_CARDS_WITHOUT_ACE; +import static domain.blackjack.TestHoldingCards.WIN_CARDS_WITH_ACE; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class GameResultCalculatorTest { + + public static Stream getGameResultParameters() { + return Stream.of( + Arguments.of(ONLY_SEVEN_HEART, ONLY_SIX_HEART, WIN), + Arguments.of(ONLY_SIX_HEART, ONLY_SEVEN_HEART, LOSE), + Arguments.of(ONLY_SEVEN_HEART, ONLY_SEVEN_HEART, TIE), + Arguments.of(DEAD_CARDS, ONLY_SEVEN_HEART, LOSE), + Arguments.of(ONLY_SEVEN_HEART, DEAD_CARDS, WIN), + Arguments.of(DEAD_CARDS, DEAD_CARDS, WIN), + Arguments.of(WIN_CARDS_WITH_ACE, BLACK_JACK, LOSE), + Arguments.of(BLACK_JACK, TWO_SIX_CARDS, WIN_BLACK_JACK), + Arguments.of(BLACK_JACK, WIN_CARDS_WITHOUT_ACE, WIN_BLACK_JACK), + Arguments.of(BLACK_JACK, WIN_CARDS_WITH_ACE, WIN_BLACK_JACK), + Arguments.of(BLACK_JACK, BLACK_JACK, TIE) + ); + } + + @ParameterizedTest + @MethodSource("getGameResultParameters") + @DisplayName("승부가 잘 결정되는지 검증") + void calculate(HoldingCards holdingCards1, HoldingCards holdingCards2, GameResult expected) { + Player player = Player.from("플레이어", holdingCards1); + Dealer dealer = Dealer.of(holdingCards2); + Assertions.assertThat(GameResultCalculator.calculate(player, dealer)) + .isEqualTo(expected); + } +} diff --git a/src/test/java/domain/blackjack/HoldingCardsTest.java b/src/test/java/domain/blackjack/HoldingCardsTest.java new file mode 100644 index 0000000000..99d18f81e8 --- /dev/null +++ b/src/test/java/domain/blackjack/HoldingCardsTest.java @@ -0,0 +1,31 @@ +package domain.blackjack; + +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.KING_HEART; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.SIX_HEART; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HoldingCardsTest { + @Test + @DisplayName("포함된 카드의 포인트 합계가 올바른지 검증") + void calculateTotalPoint() { + HoldingCards holdingCards = HoldingCards.of(ACE_HEART, SIX_HEART, JACK_HEART, QUEEN_HEART, KING_HEART); + + SummationCardPoint actual = holdingCards.calculateTotalPoint(); + SummationCardPoint expected = new SummationCardPoint(37); + + Assertions.assertThat(actual).isEqualTo(expected); + } + + @Test + @DisplayName("Ace가 포함되었는지 여부 검증") + void hasAce() { + HoldingCards holdingCards = HoldingCards.of(ACE_HEART); + Assertions.assertThat(holdingCards.hasAce()).isTrue(); + } +} diff --git a/src/test/java/domain/blackjack/PlayerCardDrawConditionTest.java b/src/test/java/domain/blackjack/PlayerCardDrawConditionTest.java new file mode 100644 index 0000000000..6084b7217b --- /dev/null +++ b/src/test/java/domain/blackjack/PlayerCardDrawConditionTest.java @@ -0,0 +1,34 @@ +package domain.blackjack; + + +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.TWO_HEART; + +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PlayerCardDrawConditionTest { + + public static Stream canDrawParameters() { + return Stream.of( + Arguments.of(HoldingCards.of(ACE_HEART, JACK_HEART), true), + Arguments.of(HoldingCards.of(TWO_HEART, JACK_HEART, QUEEN_HEART), false), + Arguments.of(HoldingCards.of(JACK_HEART, QUEEN_HEART), true) + ); + } + + @ParameterizedTest + @MethodSource("canDrawParameters") + @DisplayName("플레이어의 드로우 여부가 제대로 판단되는지 검증") + void canDraw(HoldingCards holdingCards, boolean expected) { + BlackJackGameMachine player = new BlackJackGameMachine(holdingCards); + Assertions.assertThat(new PlayerCardDrawCondition(player).canDraw()) + .isEqualTo(expected); + } +} diff --git a/src/test/java/domain/blackjack/PlayerTest.java b/src/test/java/domain/blackjack/PlayerTest.java new file mode 100644 index 0000000000..674503e0d8 --- /dev/null +++ b/src/test/java/domain/blackjack/PlayerTest.java @@ -0,0 +1,132 @@ +package domain.blackjack; + +import static domain.blackjack.TestHoldingCards.BLACK_JACK; +import static domain.blackjack.TestHoldingCards.DEAD_CARDS; +import static domain.blackjack.TestHoldingCards.ONLY_SEVEN_HEART; +import static domain.blackjack.TestHoldingCards.ONLY_SIX_HEART; +import static domain.blackjack.TestHoldingCards.TWO_SIX_CARDS; +import static domain.blackjack.TestHoldingCards.WIN_CARDS_WITHOUT_ACE; +import static domain.card.FirstCardSelectStrategy.FIRST_CARD_SELECT_STRATEGY; +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.EIGHT_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.JACK_SPADE; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.TWO_HEART; + +import domain.card.Card; +import domain.card.Deck; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +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 PlayerTest { + + static Stream validatePlayerHasNextDrawChanceParameters() { + return Stream.of( + Arguments.of(TWO_HEART, false), Arguments.of(ACE_HEART, true) + ); + } + + static Stream calculateBettingMoneyWhenDealerBustParameters() { + return Stream.of( + Arguments.of(BLACK_JACK, 10000, 15000), + Arguments.of(ONLY_SIX_HEART, 10000, 10000), + Arguments.of(DEAD_CARDS, 10000, 10000), + Arguments.of(WIN_CARDS_WITHOUT_ACE, 10000, 10000) + ); + } + + static Stream calculateBettingMoneyWhenDealerNotBustParameters() { + return Stream.of( + Arguments.of(BLACK_JACK, 10000, 15000), + Arguments.of(ONLY_SIX_HEART, 10000, -10000), + Arguments.of(DEAD_CARDS, 10000, -10000), + Arguments.of(WIN_CARDS_WITHOUT_ACE, 10000, 10000), + Arguments.of(TWO_SIX_CARDS, 10000, 10000), + Arguments.of(ONLY_SEVEN_HEART, 10000, 0) + ); + } + + static Stream calculateBettingMoneyWhenDealerIsBLackJackParameters() { + return Stream.of( + Arguments.of(BLACK_JACK, 10000, 0), + Arguments.of(ONLY_SIX_HEART, 10000, -10000), + Arguments.of(DEAD_CARDS, 10000, -10000), + Arguments.of(WIN_CARDS_WITHOUT_ACE, 10000, -10000), + Arguments.of(TWO_SIX_CARDS, 10000, -10000), + Arguments.of(ONLY_SEVEN_HEART, 10000, -10000) + ); + } + + @Test + @DisplayName("플레이어는 총합이 21이 넘으면 카드를 뽑을 수 없는지 검증") + void validateDrawLimit() { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine( + HoldingCards.of(JACK_HEART, EIGHT_HEART, JACK_SPADE)); + Deck deck = Deck.of(TWO_HEART); + DrawResult drawResult = blackJackGameMachine.draw(deck, FIRST_CARD_SELECT_STRATEGY, new PlayerCardDrawCondition( + blackJackGameMachine)); + Assertions.assertThat(drawResult.getFailCause()) + .isEqualTo("카드를 더이상 뽑을 수 없습니다."); + } + + @ParameterizedTest + @MethodSource("validatePlayerHasNextDrawChanceParameters") + @DisplayName("플레이어의 다음 드로우 기회 유무를 제대로 판단하는지 검증") + void validatePlayerHasNextDrawChance(Card cardInDeck, boolean expected) { + BlackJackGameMachine blackJackGameMachine = new BlackJackGameMachine(HoldingCards.of(JACK_HEART, QUEEN_HEART)); + DrawResult drawResult = blackJackGameMachine.draw(Deck.of(cardInDeck), FIRST_CARD_SELECT_STRATEGY, + new PlayerCardDrawCondition(blackJackGameMachine)); + Assertions.assertThat(drawResult.hasNextChance()) + .isEqualTo(expected); + } + + @Test + @DisplayName("배팅 금액이 0 이상의 정수인지 검증") + void validateBettingMoney() { + Assertions.assertThatThrownBy(() -> Player.from("플레이어", HoldingCards.of(), -1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("배팅 금액은 음수일 수 없습니다."); + } + + @ParameterizedTest + @MethodSource("calculateBettingMoneyWhenDealerBustParameters") + @DisplayName("딜러가 버스트일 떼 배팅 상금이 제대로 계산되는지 검증") + void calculateBettingMoneyWhenDealerBust(HoldingCards playerHoldingCards, int bettingMoney, int expected) { + Player player = Player.from("플레이어", playerHoldingCards, bettingMoney); + Dealer dealer = Dealer.of(TestHoldingCards.DEAD_CARDS); + GameResult gameResult = GameResultCalculator.calculate(player, dealer); + + Assertions.assertThat(player.calculateEarningMoney(gameResult)) + .isEqualTo(new EarningMoney(expected)); + } + + @ParameterizedTest + @MethodSource("calculateBettingMoneyWhenDealerNotBustParameters") + @DisplayName("딜러가 버스트가 아닐 때 배팅 상금이 제대로 계산되는지 검증") + void calculateBettingMoneyWhenDealerNotBust(HoldingCards playerHoldingCards, int bettingMoney, int expected) { + Player player = Player.from("플레이어", playerHoldingCards, bettingMoney); + Dealer dealer = Dealer.of(ONLY_SEVEN_HEART); + GameResult gameResult = GameResultCalculator.calculate(player, dealer); + + Assertions.assertThat(player.calculateEarningMoney(gameResult)) + .isEqualTo(new EarningMoney(expected)); + } + + @ParameterizedTest + @MethodSource("calculateBettingMoneyWhenDealerIsBLackJackParameters") + @DisplayName("딜러가 블랙잭일 때 배팅 상금이 제대로 계산되는지 검증") + void calculateBettingMoneyWhenDealerIsBLackJack(HoldingCards playerHoldingCards, int bettingMoney, int expected) { + Player player = Player.from("플레이어", playerHoldingCards, bettingMoney); + Dealer dealer = Dealer.of(BLACK_JACK); + GameResult gameResult = GameResultCalculator.calculate(player, dealer); + + Assertions.assertThat(player.calculateEarningMoney(gameResult)) + .isEqualTo(new EarningMoney(expected)); + } +} diff --git a/src/test/java/domain/blackjack/SummationCardPointTest.java b/src/test/java/domain/blackjack/SummationCardPointTest.java new file mode 100644 index 0000000000..842c69e76f --- /dev/null +++ b/src/test/java/domain/blackjack/SummationCardPointTest.java @@ -0,0 +1,21 @@ +package domain.blackjack; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SummationCardPointTest { + @Test + @DisplayName("카드 포인트 합이 잘 생성되는지 검증") + void getSummationCardPoint() { + List cardPoints = List.of( + new CardPoint(1), new CardPoint(2), + new CardPoint(3), new CardPoint(4) + ); + SummationCardPoint summationCardPoint = SummationCardPoint.of(cardPoints); + + Assertions.assertThat(summationCardPoint) + .isEqualTo(new SummationCardPoint(10)); + } +} diff --git a/src/test/java/domain/blackjack/TestHoldingCards.java b/src/test/java/domain/blackjack/TestHoldingCards.java new file mode 100644 index 0000000000..c9bae1d710 --- /dev/null +++ b/src/test/java/domain/blackjack/TestHoldingCards.java @@ -0,0 +1,23 @@ +package domain.blackjack; + + +import static domain.card.TestCards.ACE_HEART; +import static domain.card.TestCards.JACK_HEART; +import static domain.card.TestCards.JACK_SPADE; +import static domain.card.TestCards.NINE_HEART; +import static domain.card.TestCards.QUEEN_HEART; +import static domain.card.TestCards.SEVEN_HEART; +import static domain.card.TestCards.SIX_DIAMOND; +import static domain.card.TestCards.SIX_HEART; +import static domain.card.TestCards.TWO_HEART; + +public class TestHoldingCards { + static final HoldingCards ONLY_SIX_HEART = HoldingCards.of(SIX_HEART); + static final HoldingCards ONLY_SEVEN_HEART = HoldingCards.of(SEVEN_HEART); + static final HoldingCards DEAD_CARDS = HoldingCards.of(TWO_HEART, JACK_HEART, QUEEN_HEART); + static final HoldingCards WIN_CARDS_WITH_ACE = HoldingCards.of(ACE_HEART, JACK_HEART, JACK_SPADE); + static final HoldingCards WIN_CARDS_WITHOUT_ACE = HoldingCards.of(TWO_HEART, JACK_HEART, NINE_HEART); + static final HoldingCards BLACK_JACK = HoldingCards.of(ACE_HEART, JACK_HEART); + + static final HoldingCards TWO_SIX_CARDS = HoldingCards.of(SIX_HEART, SIX_DIAMOND); +} diff --git a/src/test/java/domain/card/DeckTest.java b/src/test/java/domain/card/DeckTest.java new file mode 100644 index 0000000000..0e4153e06b --- /dev/null +++ b/src/test/java/domain/card/DeckTest.java @@ -0,0 +1,41 @@ +package domain.card; + + +import static domain.card.TestCards.ACE_HEART; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DeckTest { + + private static final FirstCardSelectStrategy FIRST_CARD_SELECT_STRATEGY = new FirstCardSelectStrategy(); + + @Test + @DisplayName("원하는 방식대로 카드가 뽑히는지 검증") + void validateDraw() { + Deck deck = Deck.of(ACE_HEART); + Card card = deck.draw(FIRST_CARD_SELECT_STRATEGY); + Assertions.assertThat(card) + .isEqualTo(ACE_HEART); + } + + @Test + @DisplayName("중복된 카드가 포함된 덱이 생성되지 않는지 검증") + void validateDuplicateCard() { + Assertions.assertThatThrownBy(() -> Deck.of(ACE_HEART, ACE_HEART)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("중복되는 카드가 있습니다."); + } + + @Test + @DisplayName("한 번 뽑힌 카드가 또 뽑히지 않는지 검증") + void validateDrawedCardIsRemoved() { + Deck deck = Deck.of(ACE_HEART); + deck.draw(cards -> cards.get(0)); + + Assertions.assertThatThrownBy(() -> deck.draw(FIRST_CARD_SELECT_STRATEGY)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("덱이 비어있습니다."); + } +} diff --git a/src/test/java/domain/card/FirstCardSelectStrategy.java b/src/test/java/domain/card/FirstCardSelectStrategy.java new file mode 100644 index 0000000000..dd090bf8a5 --- /dev/null +++ b/src/test/java/domain/card/FirstCardSelectStrategy.java @@ -0,0 +1,13 @@ +package domain.card; + +import java.util.List; + +public class FirstCardSelectStrategy implements CardSelectStrategy { + + public static final FirstCardSelectStrategy FIRST_CARD_SELECT_STRATEGY = new FirstCardSelectStrategy(); + + @Override + public Card select(List cards) { + return cards.get(0); + } +} diff --git a/src/test/java/domain/card/TestCards.java b/src/test/java/domain/card/TestCards.java new file mode 100644 index 0000000000..70ab7ffd41 --- /dev/null +++ b/src/test/java/domain/card/TestCards.java @@ -0,0 +1,16 @@ +package domain.card; + +public class TestCards { + public static final Card ACE_HEART = CardPool.get(CardType.HEART, CardName.ACE); + public static final Card TWO_HEART = CardPool.get(CardType.HEART, CardName.TWO); + public static final Card FIVE_HEART = CardPool.get(CardType.HEART, CardName.FIVE); + public static final Card SIX_HEART = CardPool.get(CardType.HEART, CardName.SIX); + public static final Card SEVEN_HEART = CardPool.get(CardType.HEART, CardName.SEVEN); + public static final Card EIGHT_HEART = CardPool.get(CardType.HEART, CardName.EIGHT); + public static final Card NINE_HEART = CardPool.get(CardType.HEART, CardName.NINE); + public static final Card JACK_HEART = CardPool.get(CardType.HEART, CardName.JACK); + public static final Card QUEEN_HEART = CardPool.get(CardType.HEART, CardName.QUEEN); + public static final Card KING_HEART = CardPool.get(CardType.HEART, CardName.KING); + public static final Card JACK_SPADE = CardPool.get(CardType.SPADE, CardName.JACK); + public static final Card SIX_DIAMOND = CardPool.get(CardType.DIAMOND, CardName.SIX); +}