diff --git a/README.md b/README.md index 72abea04ba..f121f21edd 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,52 @@ # java-blackjack +## 게임 모드 + +### 베팅 모드 +플레이어가 베팅 금액을 걸고 게임 결과에 따라 수익/손실을 계산합니다. + +### 논베팅 모드 +베팅 없이 순수 승/무/패만 집계합니다. + +--- + ## 구현 기능 -- 게임에 참가할 사람 이름 입력 -- 카드 나눠주기 - - 카드 52장 생성 - - 정책: 사용하는 카드에 대해 정의 - - 카드의 종류: 하트, 다이아, 클로버, 스페이드 - - 카드의 숫자: A, 2 부터 10, J, Q, K - - 카드 뽑기 기능이 필요 - - 딜러는 자기 자신의 카드도 뽑는다. - - 딜러는 공개카드 1장과 비공개 카드 1장씩 받고, 공개 카드 출력 - - 참여자 별 카드 주기 - - 참여자들 카드 목록 출력 -- 플레이어 순서대로 카드를 더 받을 것인지 확인 - - 플레이어가 카드를 받았을 경우 현재 카드 목록 출력 -- 딜러의 Bust 판정 - - 카드 점수 계산 - - Ace 1/11 처리 필요 - - 딜러의 카드 합이 16이하면 카드 1장 받기 -- 딜러 카드와 플레이어 카드 출력하기 - - 카드 목록 출력 - - 카드 숫자 합 출력 -- 최종 승패여부 출력 - - 딜러의 승패 출력 - - 플레이어별 승패 출력 + +### 공통 +- 게임에 참가할 사람 이름 입력 +- 카드 52장 생성 + - 카드 종류: 하트, 다이아, 클로버, 스페이드 + - 카드 숫자: A, 2~10, J, Q, K +- 초기 카드 배분 + - 딜러: 공개 카드 1장, 비공개 카드 1장 + - 플레이어: 2장 +- 플레이어 순서대로 추가 카드 수령 여부 결정 + - 카드를 받을 경우 현재 카드 목록 출력 +- 카드 점수 계산 + - Ace: 1 또는 11로 계산 (버스트 방지 우선) +- 딜러 추가 카드 수령 + - 카드 합이 16 이하면 카드 1장 추가 +- 최종 카드 목록 및 점수 출력 + +### 베팅 모드 +- 플레이어별 베팅 금액 입력 + - 최소 1원, 최대 100만원 +- 게임 결과별 수익 계산 + - 승리: 베팅 금액만큼 수익 + - 패배: 베팅 금액만큼 손실 + - 무승부: 수익 없음 + - 블랙잭: 베팅 금액의 1.5배 수익 +- 딜러 및 플레이어별 최종 수익 출력 + +### 논베팅 모드 +- 딜러 및 플레이어별 최종 승/무/패 출력 + +--- + +## 판정 규칙 +- 버스트: 카드 합이 21 초과 시 즉시 패배 +- 블랙잭: 최초 2장으로 합이 21 + - 플레이어만 블랙잭: 1.5배 수익 + - 둘 다 블랙잭: 무승부 + - 딜러만 블랙잭: 플레이어 패배 +- 점수 비교: 버스트/블랙잭 외 경우 점수가 높은 쪽 승리 \ No newline at end of file diff --git a/src/main/java/BlackjackApplication.java b/src/main/java/BlackjackApplication.java index 88cbe2052a..44044d2208 100644 --- a/src/main/java/BlackjackApplication.java +++ b/src/main/java/BlackjackApplication.java @@ -1,22 +1,14 @@ import controller.BlackjackController; -import java.util.List; -import model.BlackjackService; -import model.card.Card; -import model.card.CardFactory; -import model.card.CardShuffler; -import model.card.Deck; +import controller.GameMode; +import model.BlackjackGame; import model.card.SimpleCardShuffler; public class BlackjackApplication { public static void main(String[] args) { - CardShuffler cardShuffler = new SimpleCardShuffler(); - List fullCards = CardFactory.createFullCards(); - List shuffledCards = cardShuffler.shuffle(fullCards); - Deck deck = new Deck(shuffledCards); - BlackjackService blackjackService = new BlackjackService(deck); - - BlackjackController controller = new BlackjackController(blackjackService); - controller.run(); + new BlackjackController<>( + BlackjackGame.setup(new SimpleCardShuffler()), + GameMode.toBettingMode() + ).run(); } } diff --git a/src/main/java/controller/BlackjackController.java b/src/main/java/controller/BlackjackController.java index 7a084f07d7..8dec13cc5a 100644 --- a/src/main/java/controller/BlackjackController.java +++ b/src/main/java/controller/BlackjackController.java @@ -1,8 +1,6 @@ package controller; -import java.util.List; -import model.BlackjackService; -import model.judgement.DealerResult; +import model.BlackjackGame; import model.judgement.Judgement; import model.judgement.PlayerResult; import model.paticipant.Dealer; @@ -11,79 +9,72 @@ import view.InputView; import view.OutputView; -public class BlackjackController { +public class BlackjackController { - private final BlackjackService blackjackService; + private final BlackjackGame blackjackGame; + private final GameMode gameMode; - public BlackjackController(BlackjackService blackjackService) { - this.blackjackService = blackjackService;; + public BlackjackController(BlackjackGame blackjackGame, GameMode gameMode) { + this.blackjackGame = blackjackGame; + this.gameMode = gameMode; } public void run() { Dealer dealer = new Dealer(); - Players players = createPlayers(); + Players players = gameMode.createPlayers(); - drawInitCards(dealer, players); - drawMoreCardByPlayer(dealer, players); - - printFinalCards(dealer, players); - judgeGame(dealer, players); + initialDeal(dealer, players); + proceedTurns(dealer, players); + finishGame(dealer, players); } - private Players createPlayers() { - List names = InputView.readPlayerNames(); - return Players.from(names); + private void initialDeal(Dealer dealer, Players players) { + blackjackGame.drawInitCards(dealer, players); + OutputView.printCardOpen(dealer, players.players()); } - private void drawMoreCardByPlayer(Dealer dealer, Players players) { - players.forEach(this::chooseHitOrStand); + private void proceedTurns(Dealer dealer, Players players) { + players.forEach(this::processPlayerTurn); + while (dealer.canHit()) { OutputView.printToOpenDealerNewCard(dealer); - blackjackService.drawOneCard(dealer); + blackjackGame.drawOneCard(dealer); } } - private void drawInitCards(Dealer dealer, Players players) { - blackjackService.drawTwoCards(dealer, players); - OutputView.printCardOpen(players); - OutputView.printCardByDealer(dealer); - OutputView.printCardByPlayers(players); - } - - private boolean canHitMore(Player player) { - return player.canHit() && readContinuation(player).isContinue(); - } - - private Continuation readContinuation(Player player) { - String inputCommand = InputView.readMoreCard(player); - return Continuation.from(inputCommand); - } + private void processPlayerTurn(Player player) { + if (player.isBlackjack()) { + return; + } - private void chooseHitOrStand(Player player) { - boolean printed = false; - while (canHitMore(player)) { - blackjackService.drawOneCard(player); + boolean hasDrawn = hitUntilStand(player); + if (!hasDrawn) { OutputView.printCardByPlayer(player); - printed = true; } + } - if (!printed) { + private boolean hitUntilStand(Player player) { + boolean hasDrawn = false; + while (canHitMore(player)) { + blackjackGame.drawOneCard(player); OutputView.printCardByPlayer(player); + hasDrawn = true; } + return hasDrawn; } - private void printFinalCards(Dealer dealer, Players players) { - OutputView.printBlank(); - OutputView.printCardByPlayerWithScore(dealer); - players.forEach(OutputView::printCardByPlayerWithScore); + private boolean canHitMore(Player player) { + return player.canHit() && readContinuation(player).isContinue(); } - private void judgeGame(Dealer dealer, Players players) { - PlayerResult playerResult = Judgement.judgeByPlayer(dealer, players); - DealerResult dealerResult = Judgement.judgeByDealer(playerResult); + private Continuation readContinuation(Player player) { + String inputCommand = InputView.readMoreCard(player); + return Continuation.from(inputCommand); + } - OutputView.printFinalResultHeader(); - OutputView.printResultByDealer(dealerResult); - OutputView.printResultByPlayers(playerResult); + private void finishGame(Dealer dealer, Players players) { + OutputView.printFinalCards(dealer, players.players()); + PlayerResult playerResult = Judgement.judgeByPlayer(dealer, players); + gameMode.reportResult(playerResult); } } diff --git a/src/main/java/controller/GameMode.java b/src/main/java/controller/GameMode.java new file mode 100644 index 0000000000..e6e76ca0c8 --- /dev/null +++ b/src/main/java/controller/GameMode.java @@ -0,0 +1,39 @@ +package controller; + +import controller.input.BettingPlayerReader; +import controller.input.NonBettingPlayerReader; +import controller.input.PlayerReader; +import controller.result.BettingResultReporter; +import controller.result.NonBettingResultReporter; +import controller.result.ResultReporter; +import model.judgement.PlayerResult; +import model.paticipant.BettingPlayer; +import model.paticipant.Player; +import model.paticipant.Players; + +public class GameMode { + + private final PlayerReader playerReader; + private final ResultReporter resultReporter; + + private GameMode(PlayerReader playerReader, ResultReporter resultReporter) { + this.playerReader = playerReader; + this.resultReporter = resultReporter; + } + + public static GameMode toBettingMode() { + return new GameMode<>(new BettingPlayerReader(), new BettingResultReporter()); + } + + public static GameMode toNonBettingMode() { + return new GameMode<>(new NonBettingPlayerReader(), new NonBettingResultReporter()); + } + + public Players createPlayers() { + return playerReader.readPlayers(); + } + + public void reportResult(PlayerResult playerResult) { + resultReporter.report(playerResult); + } +} diff --git a/src/main/java/controller/input/BettingPlayerReader.java b/src/main/java/controller/input/BettingPlayerReader.java new file mode 100644 index 0000000000..937d9c22c7 --- /dev/null +++ b/src/main/java/controller/input/BettingPlayerReader.java @@ -0,0 +1,21 @@ +package controller.input; + +import java.util.ArrayList; +import java.util.List; +import model.paticipant.BettingPlayer; +import model.paticipant.Players; +import view.InputView; + +public class BettingPlayerReader implements PlayerReader { + + @Override + public Players readPlayers() { + List names = InputView.readPlayerNames(); + List players = new ArrayList<>(); + for (String name : names) { + int betAmount = InputView.readBetAmount(name); + players.add(new BettingPlayer(name, betAmount)); + } + return new Players<>(players); + } +} diff --git a/src/main/java/controller/input/NonBettingPlayerReader.java b/src/main/java/controller/input/NonBettingPlayerReader.java new file mode 100644 index 0000000000..ac46b055a0 --- /dev/null +++ b/src/main/java/controller/input/NonBettingPlayerReader.java @@ -0,0 +1,19 @@ +package controller.input; + +import java.util.List; +import java.util.stream.Collectors; +import model.paticipant.Player; +import model.paticipant.Players; +import view.InputView; + +public class NonBettingPlayerReader implements PlayerReader { + + @Override + public Players readPlayers() { + List names = InputView.readPlayerNames(); + List players = names.stream() + .map(Player::new) + .collect(Collectors.toList()); + return new Players<>(players); + } +} diff --git a/src/main/java/controller/input/PlayerReader.java b/src/main/java/controller/input/PlayerReader.java new file mode 100644 index 0000000000..424e4d835b --- /dev/null +++ b/src/main/java/controller/input/PlayerReader.java @@ -0,0 +1,10 @@ +package controller.input; + +import model.paticipant.Player; +import model.paticipant.Players; + +public interface PlayerReader { + + Players readPlayers(); + +} diff --git a/src/main/java/controller/result/BettingResultReporter.java b/src/main/java/controller/result/BettingResultReporter.java new file mode 100644 index 0000000000..e5dfb02b38 --- /dev/null +++ b/src/main/java/controller/result/BettingResultReporter.java @@ -0,0 +1,23 @@ +package controller.result; + +import java.util.Map; +import model.judgement.BettingCalculator; +import model.judgement.PlayerResult; +import model.judgement.Profit; +import model.paticipant.BettingPlayer; +import view.ProfitReportView; + +public class BettingResultReporter implements ResultReporter { + + private final BettingCalculator calculator = new BettingCalculator(); + + @Override + public void report(PlayerResult playerResult) { + Map profits = calculator.calculateProfits(playerResult); + Profit dealerProfit = calculator.calculateDealerProfit(profits); + + ProfitReportView.printFinalProfitHeader(); + ProfitReportView.printProfitByDealer(dealerProfit); + ProfitReportView.printProfitByPlayers(profits); + } +} \ No newline at end of file diff --git a/src/main/java/controller/result/NonBettingResultReporter.java b/src/main/java/controller/result/NonBettingResultReporter.java new file mode 100644 index 0000000000..6d6e577f64 --- /dev/null +++ b/src/main/java/controller/result/NonBettingResultReporter.java @@ -0,0 +1,18 @@ +package controller.result; + +import model.judgement.DealerResult; +import model.judgement.PlayerResult; +import model.paticipant.Player; +import view.WinLossReportView; + +public class NonBettingResultReporter implements ResultReporter { + + @Override + public void report(PlayerResult playerResult) { + DealerResult dealerResult = playerResult.calculateDealerResult(); + + WinLossReportView.printFinalResultHeader(); + WinLossReportView.printResultByDealer(dealerResult); + WinLossReportView.printResultByPlayers(playerResult); + } +} diff --git a/src/main/java/controller/result/ResultReporter.java b/src/main/java/controller/result/ResultReporter.java new file mode 100644 index 0000000000..cccacff1bd --- /dev/null +++ b/src/main/java/controller/result/ResultReporter.java @@ -0,0 +1,9 @@ +package controller.result; + +import model.judgement.PlayerResult; +import model.paticipant.Player; + +public interface ResultReporter { + + void report(PlayerResult playerResult); +} diff --git a/src/main/java/model/BlackjackService.java b/src/main/java/model/BlackjackGame.java similarity index 57% rename from src/main/java/model/BlackjackService.java rename to src/main/java/model/BlackjackGame.java index e0422c3073..98919b47a9 100644 --- a/src/main/java/model/BlackjackService.java +++ b/src/main/java/model/BlackjackGame.java @@ -1,22 +1,34 @@ package model; +import java.util.List; +import model.card.Card; +import model.card.CardFactory; +import model.card.CardShuffler; import model.card.Deck; import model.paticipant.Dealer; import model.paticipant.Participant; +import model.paticipant.Player; import model.paticipant.Players; -public class BlackjackService { +public class BlackjackGame { private static final int INITIAL_DISPENSE_COUNT = 2; private static final int BASE_DISPENSE_COUNT = 1; private final Deck deck; - public BlackjackService(Deck deck) { + public BlackjackGame(Deck deck) { this.deck = deck; } - public void drawTwoCards(Dealer dealer, Players players) { + public static BlackjackGame setup(CardShuffler cardShuffler) { + List fullCards = CardFactory.createFullCards(); + List shuffledCards = cardShuffler.shuffle(fullCards); + Deck deck = new Deck(shuffledCards); + return new BlackjackGame(deck); + } + + public void drawInitCards(Dealer dealer, Players players) { drawCardByParticipant(dealer, INITIAL_DISPENSE_COUNT); players.forEach(player -> drawCardByParticipant(player, INITIAL_DISPENSE_COUNT)); } diff --git a/src/main/java/model/card/CardShape.java b/src/main/java/model/card/CardShape.java index c216538e63..358cd32906 100644 --- a/src/main/java/model/card/CardShape.java +++ b/src/main/java/model/card/CardShape.java @@ -1,18 +1,5 @@ package model.card; public enum CardShape { - DIAMOND("다이아몬드"), - HEART("하트"), - CLOVER("클로버"), - SPADE("스페이드"); - - private final String shape; - - CardShape(String shape) { - this.shape = shape; - } - - public String getShape() { - return shape; - } + DIAMOND, HEART, CLOVER, SPADE; } diff --git a/src/main/java/model/card/CardValue.java b/src/main/java/model/card/CardValue.java index 3a12e22c3e..62e2d7924f 100644 --- a/src/main/java/model/card/CardValue.java +++ b/src/main/java/model/card/CardValue.java @@ -1,33 +1,27 @@ package model.card; public enum CardValue { - ACE(1, "A"), - TWO(2, "2"), - THREE(3, "3"), - FOUR(4, "4"), - FIVE(5, "5"), - SIX(6, "6"), - SEVEN(7, "7"), - EIGHT(8, "8"), - NINE(9, "9"), - TEN(10, "10"), - JACK(10, "J"), - QUEEN(10, "Q"), - KING(10, "K"); + ACE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8), + NINE(9), + TEN(10), + JACK(10), + QUEEN(10), + KING(10); private final int score; - private final String symbol; - CardValue(int score, String symbol) { + CardValue(int score) { this.score = score; - this.symbol = symbol; } public int getScore() { return score; } - - public String getSymbol() { - return symbol; - } } diff --git a/src/main/java/model/card/Hand.java b/src/main/java/model/card/Hand.java index 3e92b6074b..6e3a533279 100644 --- a/src/main/java/model/card/Hand.java +++ b/src/main/java/model/card/Hand.java @@ -28,6 +28,10 @@ public int countAce() { .count(); } + public int size() { + return cards.size(); + } + public List getCards() { return Collections.unmodifiableList(this.cards); } diff --git a/src/main/java/model/judgement/BettingCalculator.java b/src/main/java/model/judgement/BettingCalculator.java new file mode 100644 index 0000000000..01f2916115 --- /dev/null +++ b/src/main/java/model/judgement/BettingCalculator.java @@ -0,0 +1,20 @@ +package model.judgement; + +import java.util.LinkedHashMap; +import java.util.Map; +import model.paticipant.BettingPlayer; + +public class BettingCalculator { + + public Map calculateProfits(PlayerResult playerResult) { + Map profits = new LinkedHashMap<>(); + playerResult.result() + .forEach((bettingPlayer, status) -> profits.put(bettingPlayer, bettingPlayer.calculateProfit(status))); + return profits; + } + + public Profit calculateDealerProfit(Map profits) { + return profits.values().stream() + .reduce(Profit.ZERO, (sum, profit) -> sum.add(profit.negate())); + } +} diff --git a/src/main/java/model/judgement/GameStatus.java b/src/main/java/model/judgement/GameStatus.java deleted file mode 100644 index 7d65f7135c..0000000000 --- a/src/main/java/model/judgement/GameStatus.java +++ /dev/null @@ -1,18 +0,0 @@ -package model.judgement; - -public enum GameStatus { - - WIN("승"), - LOSE("패"), - DRAW("무"); - - private final String name; - - GameStatus(String name) { - this.name = name; - } - - public String getName() { - return name; - } -} diff --git a/src/main/java/model/judgement/JudgeStrategy.java b/src/main/java/model/judgement/JudgeStrategy.java new file mode 100644 index 0000000000..3091309825 --- /dev/null +++ b/src/main/java/model/judgement/JudgeStrategy.java @@ -0,0 +1,10 @@ +package model.judgement; + +import model.paticipant.Dealer; +import model.paticipant.Participant; + +public interface JudgeStrategy { + + boolean isApplicable(Dealer dealer, Participant participant); + ResultStatus getResult(Dealer dealer, Participant participant); +} diff --git a/src/main/java/model/judgement/Judgement.java b/src/main/java/model/judgement/Judgement.java index 7f6d9481a3..828a73c69d 100644 --- a/src/main/java/model/judgement/Judgement.java +++ b/src/main/java/model/judgement/Judgement.java @@ -1,51 +1,40 @@ package model.judgement; -import static model.judgement.GameStatus.DRAW; -import static model.judgement.GameStatus.LOSE; -import static model.judgement.GameStatus.WIN; - import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import model.judgement.strategy.BlackjackStrategy; +import model.judgement.strategy.BustStrategy; import model.paticipant.Dealer; +import model.paticipant.Participant; import model.paticipant.Player; import model.paticipant.Players; public class Judgement { - public static PlayerResult judgeByPlayer(Dealer dealer, Players players) { - Map result = new LinkedHashMap<>(); + private static final List STRATEGIES = List.of(new BustStrategy(), new BlackjackStrategy()); + + public static PlayerResult judgeByPlayer(Dealer dealer, Players players) { + Map result = new LinkedHashMap<>(); players.forEach(player -> result.put(player, decide(dealer, player))); - return new PlayerResult(result); + return new PlayerResult<>(result); } - private static GameStatus decide(Dealer dealer, Player player) { - if (player.isBust()) { - return LOSE; - } - if (dealer.isBust()) { - return WIN; - } - - return decideByScore(dealer, player); + private static ResultStatus decide(Dealer dealer, Participant participant) { + return STRATEGIES.stream() + .filter(strategy -> strategy.isApplicable(dealer, participant)) + .findFirst() + .map(strategy -> strategy.getResult(dealer, participant)) + .orElseGet(() -> compareScore(participant.calculateTotalScore(), dealer.calculateTotalScore())); } - private static GameStatus decideByScore(Dealer dealer, Player player) { - int dealerScore = dealer.calculateTotalScore(); - int playerScore = player.calculateTotalScore(); - + private static ResultStatus compareScore(int playerScore, int dealerScore) { if (playerScore > dealerScore) { - return WIN; + return ResultStatus.WIN; } if (playerScore == dealerScore) { - return DRAW; + return ResultStatus.DRAW; } - return LOSE; - } - - public static DealerResult judgeByDealer(PlayerResult playerResult) { - int dealerWinCount = playerResult.countByStatus(GameStatus.LOSE); - int dealerLoseCount = playerResult.countByStatus(GameStatus.WIN); - int dealerDrawCount = playerResult.countByStatus(GameStatus.DRAW); - return new DealerResult(dealerWinCount, dealerLoseCount, dealerDrawCount); + return ResultStatus.LOSE; } } diff --git a/src/main/java/model/judgement/PlayerResult.java b/src/main/java/model/judgement/PlayerResult.java index 4f3e7a11bf..c6ff58db51 100644 --- a/src/main/java/model/judgement/PlayerResult.java +++ b/src/main/java/model/judgement/PlayerResult.java @@ -5,22 +5,23 @@ import java.util.Map; import model.paticipant.Player; -public class PlayerResult { +public record PlayerResult(Map result) { - private final Map result; + public PlayerResult(Map result) { + this.result = Collections.unmodifiableMap(new LinkedHashMap<>(result)); + } - public PlayerResult(Map result) { - this.result = new LinkedHashMap<>(result); + public DealerResult calculateDealerResult() { + int dealerWinCount = countByStatus(ResultStatus.LOSE); + int dealerLoseCount = countByStatus(ResultStatus.WIN) + countByStatus(ResultStatus.BLACKJACK); + int dealerDrawCount = countByStatus(ResultStatus.DRAW); + return new DealerResult(dealerWinCount, dealerLoseCount, dealerDrawCount); } - public int countByStatus(GameStatus gameStatus) { + private int countByStatus(ResultStatus resultStatus) { return (int) result.values() .stream() - .filter(status -> status == gameStatus) + .filter(status -> status == resultStatus) .count(); } - - public Map getResult() { - return Collections.unmodifiableMap(result); - } } diff --git a/src/main/java/model/judgement/Profit.java b/src/main/java/model/judgement/Profit.java new file mode 100644 index 0000000000..6fd1228ac8 --- /dev/null +++ b/src/main/java/model/judgement/Profit.java @@ -0,0 +1,14 @@ +package model.judgement; + +public record Profit(int value) { + + public static final Profit ZERO = new Profit(0); + + public Profit add(Profit other) { + return new Profit(this.value + other.value); + } + + public Profit negate() { + return new Profit(-value); + } +} diff --git a/src/main/java/model/judgement/ResultStatus.java b/src/main/java/model/judgement/ResultStatus.java new file mode 100644 index 0000000000..e11b0a3fc2 --- /dev/null +++ b/src/main/java/model/judgement/ResultStatus.java @@ -0,0 +1,33 @@ +package model.judgement; + +import model.paticipant.BetAmount; + +public enum ResultStatus { + + WIN { + @Override + public Profit calculateProfit(BetAmount betAmount) { + return betAmount.toProfit(); + } + }, + LOSE { + @Override + public Profit calculateProfit(BetAmount betAmount) { + return betAmount.toNegativeProfit(); + } + }, + DRAW { + @Override + public Profit calculateProfit(BetAmount betAmount) { + return Profit.ZERO; + } + }, + BLACKJACK { + @Override + public Profit calculateProfit(BetAmount betAmount) { + return betAmount.toBlackjackProfit(); + } + }; + + public abstract Profit calculateProfit(BetAmount betAmount); +} diff --git a/src/main/java/model/judgement/strategy/BlackjackStrategy.java b/src/main/java/model/judgement/strategy/BlackjackStrategy.java new file mode 100644 index 0000000000..010ed0fdf1 --- /dev/null +++ b/src/main/java/model/judgement/strategy/BlackjackStrategy.java @@ -0,0 +1,25 @@ +package model.judgement.strategy; + +import model.judgement.JudgeStrategy; +import model.judgement.ResultStatus; +import model.paticipant.Dealer; +import model.paticipant.Participant; + +public class BlackjackStrategy implements JudgeStrategy { + + @Override + public boolean isApplicable(Dealer dealer, Participant participant) { + return dealer.isBlackjack() || participant.isBlackjack(); + } + + @Override + public ResultStatus getResult(Dealer dealer, Participant participant) { + if (dealer.isBlackjack() && participant.isBlackjack()) { + return ResultStatus.DRAW; + } + if (participant.isBlackjack()) { + return ResultStatus.BLACKJACK; + } + return ResultStatus.LOSE; + } +} \ No newline at end of file diff --git a/src/main/java/model/judgement/strategy/BustStrategy.java b/src/main/java/model/judgement/strategy/BustStrategy.java new file mode 100644 index 0000000000..583128c943 --- /dev/null +++ b/src/main/java/model/judgement/strategy/BustStrategy.java @@ -0,0 +1,22 @@ +package model.judgement.strategy; + +import model.judgement.JudgeStrategy; +import model.judgement.ResultStatus; +import model.paticipant.Dealer; +import model.paticipant.Participant; + +public class BustStrategy implements JudgeStrategy { + + @Override + public boolean isApplicable(Dealer dealer, Participant participant) { + return participant.isBust() || dealer.isBust(); + } + + @Override + public ResultStatus getResult(Dealer dealer, Participant participant) { + if (participant.isBust()) { + return ResultStatus.LOSE; + } + return ResultStatus.WIN; + } +} \ No newline at end of file diff --git a/src/main/java/model/paticipant/BetAmount.java b/src/main/java/model/paticipant/BetAmount.java new file mode 100644 index 0000000000..aa86c17437 --- /dev/null +++ b/src/main/java/model/paticipant/BetAmount.java @@ -0,0 +1,34 @@ +package model.paticipant; + +import model.judgement.Profit; + +public record BetAmount(int amount) { + + private static final int MIN_BET_AMOUNT = 1; + private static final int MAX_BET_AMOUNT = 1_000_000; + private static final double BLACKJACK_MULTIPLIER = 1.5; + + public BetAmount { + validate(amount); + } + + private static void validate(int amount) { + if (amount < MIN_BET_AMOUNT || amount > MAX_BET_AMOUNT) { + throw new IllegalArgumentException( + String.format("베팅 금액은 %d원 이상 %d원 이하여야 합니다.", MIN_BET_AMOUNT, MAX_BET_AMOUNT) + ); + } + } + + public Profit toProfit() { + return new Profit(amount); + } + + public Profit toNegativeProfit() { + return new Profit(-amount); + } + + public Profit toBlackjackProfit() { + return new Profit((int) (amount * BLACKJACK_MULTIPLIER)); + } +} diff --git a/src/main/java/model/paticipant/BettingPlayer.java b/src/main/java/model/paticipant/BettingPlayer.java new file mode 100644 index 0000000000..500481a2b8 --- /dev/null +++ b/src/main/java/model/paticipant/BettingPlayer.java @@ -0,0 +1,18 @@ +package model.paticipant; + +import model.judgement.Profit; +import model.judgement.ResultStatus; + +public class BettingPlayer extends Player { + + private final BetAmount betAmount; + + public BettingPlayer(String name, int betAmount) { + super(name); + this.betAmount = new BetAmount(betAmount); + } + + public Profit calculateProfit(ResultStatus resultStatus) { + return resultStatus.calculateProfit(betAmount); + } +} diff --git a/src/main/java/model/paticipant/Participant.java b/src/main/java/model/paticipant/Participant.java index e418b3ec99..905d49a606 100644 --- a/src/main/java/model/paticipant/Participant.java +++ b/src/main/java/model/paticipant/Participant.java @@ -8,6 +8,7 @@ public abstract class Participant { private static final int BUST_LIMIT = 21; private static final int ACE_BONUS_SCORE = 10; + private static final int BLACKJACK_CARD_COUNT = 2; private final Name name; private final Hand hand; @@ -32,12 +33,14 @@ public boolean isBust() { return calculateTotalScore() > BUST_LIMIT; } + public boolean isBlackjack() { + return hand.size() == BLACKJACK_CARD_COUNT && calculateTotalScore() == BUST_LIMIT; + } + public void addCard(Card card) { this.hand.add(card); } - public abstract boolean canHit(); - public String getName() { return name.name(); } @@ -45,4 +48,6 @@ public String getName() { public List getCards() { return hand.getCards(); } + + public abstract boolean canHit(); } diff --git a/src/main/java/model/paticipant/Players.java b/src/main/java/model/paticipant/Players.java index 501315cc77..216b3e51fb 100644 --- a/src/main/java/model/paticipant/Players.java +++ b/src/main/java/model/paticipant/Players.java @@ -1,30 +1,15 @@ package model.paticipant; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; -public class Players { +public record Players(List players) { - private final List players; - - public Players(List players) { - this.players = new ArrayList<>(players); - } - - public static Players from(List playerNames) { - List players = playerNames.stream() - .map(Player::new) - .toList(); - return new Players(players); - } - - public List getPlayers() { - return Collections.unmodifiableList(players); + public Players(List players) { + this.players = List.copyOf(players); } - public void forEach(Consumer action) { + public void forEach(Consumer action) { players.forEach(action); } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 7d21549dd3..5a68ed0101 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -4,7 +4,7 @@ import java.util.Arrays; import java.util.List; -import model.paticipant.Player; +import model.paticipant.Participant; public class InputView { @@ -17,8 +17,14 @@ public static List readPlayerNames() { .toList(); } - public static String readMoreCard(Player player) { - System.out.println(player.getName() + "는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); + public static int readBetAmount(String playerName) { + System.out.println(); + System.out.printf("%s의 배팅 금액은?%n", playerName); + return Integer.parseInt(readLine()); + } + + public static String readMoreCard(Participant participant) { + System.out.println(participant.getName() + "는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); return readLine(); } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index ac35e5aba0..b833d21431 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -2,59 +2,44 @@ import java.util.List; import model.card.Card; -import model.judgement.DealerResult; -import model.judgement.PlayerResult; import model.paticipant.Dealer; import model.paticipant.Participant; import model.paticipant.Player; -import model.paticipant.Players; +import view.mapper.EnumMapper; public class OutputView { private OutputView() { } - public static void printCardOpen(Players players) { - List names = players.getPlayers() - .stream() + public static void printCardOpen(Dealer dealer, List participants) { + List names = participants.stream() .map(Player::getName) .toList(); - System.out.println(); - System.out.printf("딜러와 %s에게 2장을 나누었습니다.%n", String.join(", ", names)); - } + System.out.printf("%s와 %s에게 2장을 나누었습니다.%n", dealer.getName(), String.join(", ", names)); - public static void printCardByPlayers(Players players) { - players.getPlayers().forEach(OutputView::printCardByPlayer); - System.out.println(); + printCardByDealer(dealer); + printCardByPlayers(participants); } - public static void printCardByDealer(Dealer dealer) { + private static void printCardByDealer(Dealer dealer) { Card firstCard = dealer.getCards().getFirst(); - String card = convert(firstCard); - System.out.println(dealer.getName() + "카드: " + card); + String card = EnumMapper.convert(firstCard); + System.out.println(dealer.getName() + ": " + card); } - public static void printCardByPlayer(Player player) { - List cards = player.getCards() - .stream() - .map(OutputView::convert) - .toList(); - System.out.printf("%s카드: %s%n", player.getName(), String.join(", ", cards)); + private static void printCardByPlayers(List players) { + players.forEach(OutputView::printCardByPlayer); + System.out.println(); } - - public static void printCardByPlayerWithScore(Participant participant) { - int sum = participant.calculateTotalScore(); + public static void printCardByPlayer(Participant participant) { List cards = participant.getCards() .stream() - .map(OutputView::convert) + .map(EnumMapper::convert) .toList(); - System.out.printf("%s카드: %s - 결과: %d%n", participant.getName(), String.join(", ", cards), sum); - } - - private static String convert(Card card) { - return card.value().getSymbol() + card.shape().getShape(); + System.out.printf("%s카드: %s%n", participant.getName(), String.join(", ", cards)); } public static void printToOpenDealerNewCard(Dealer dealer) { @@ -62,32 +47,18 @@ public static void printToOpenDealerNewCard(Dealer dealer) { System.out.printf("%s는 16 이하라 한장의 카드를 더 받았습니다.%n", dealer.getName()); } - public static void printBlank() { + public static void printFinalCards(Dealer dealer, List participants) { System.out.println(); + printCardByParticipantWithScore(dealer); + participants.forEach(OutputView::printCardByParticipantWithScore); } - public static void printFinalResultHeader() { - System.out.println(); - System.out.println("##최종 승패"); - } - - public static void printResultByDealer(DealerResult dealerResult) { - System.out.print("딜러: "); - StringBuilder result = new StringBuilder(); - if (dealerResult.winCount() > 0) { - result.append(dealerResult.winCount()).append("승 "); - } - if (dealerResult.drawCount() > 0) { - result.append(dealerResult.drawCount()).append("무 "); - } - if (dealerResult.loseCount() > 0) { - result.append(dealerResult.loseCount()).append("패"); - } - System.out.println(result.toString().trim()); - } - - public static void printResultByPlayers(PlayerResult playerResult) { - playerResult.getResult() - .forEach((player, status) -> System.out.printf("%s: %s%n", player.getName(), status.getName())); + public static void printCardByParticipantWithScore(Participant participant) { + int sum = participant.calculateTotalScore(); + List cards = participant.getCards() + .stream() + .map(EnumMapper::convert) + .toList(); + System.out.printf("%s 카드: %s - 결과: %d%n", participant.getName(), String.join(", ", cards), sum); } } \ No newline at end of file diff --git a/src/main/java/view/ProfitReportView.java b/src/main/java/view/ProfitReportView.java new file mode 100644 index 0000000000..32b8555dc7 --- /dev/null +++ b/src/main/java/view/ProfitReportView.java @@ -0,0 +1,25 @@ +package view; + +import java.util.Map; +import model.judgement.Profit; +import model.paticipant.BettingPlayer; + +public class ProfitReportView { + + private ProfitReportView() {} + + public static void printFinalProfitHeader() { + System.out.println(); + System.out.println("## 최종 수익"); + } + + public static void printProfitByDealer(Profit profit) { + System.out.printf("딜러: %d%n", profit.value()); + } + + public static void printProfitByPlayers(Map profits) { + profits.forEach((player, profit) -> + System.out.printf("%s: %d%n", player.getName(), profit.value()) + ); + } +} diff --git a/src/main/java/view/WinLossReportView.java b/src/main/java/view/WinLossReportView.java new file mode 100644 index 0000000000..f35404428b --- /dev/null +++ b/src/main/java/view/WinLossReportView.java @@ -0,0 +1,51 @@ +package view; + +import model.judgement.DealerResult; +import model.judgement.PlayerResult; +import model.paticipant.Player; +import view.mapper.EnumMapper; + +public class WinLossReportView { + + private WinLossReportView() {} + + public static void printFinalResultHeader() { + System.out.println(); + System.out.println("## 최종 승패"); + } + + public static void printResultByDealer(DealerResult dealerResult) { + System.out.print("딜러: "); + String result = extractWinMessage(dealerResult.winCount()) + + extractDrawMessage(dealerResult.drawCount()) + + extractLoseMessage(dealerResult.loseCount()); + System.out.println(result.trim()); + } + + private static String extractWinMessage(int winCount) { + if (winCount > 0) { + return winCount + "승 "; + } + return ""; + } + + private static String extractDrawMessage(int drawCount) { + if (drawCount > 0) { + return drawCount + "무 "; + } + return ""; + } + + private static String extractLoseMessage(int loseCount) { + if (loseCount > 0) { + return loseCount + "패 "; + } + return ""; + } + + public static void printResultByPlayers(PlayerResult playerResult) { + playerResult.result() + .forEach((player, status) -> + System.out.printf("%s: %s%n", player.getName(), EnumMapper.RESULT_STATUS_MAPPER.get(status))); + } +} diff --git a/src/main/java/view/mapper/EnumMapper.java b/src/main/java/view/mapper/EnumMapper.java new file mode 100644 index 0000000000..34c98a3017 --- /dev/null +++ b/src/main/java/view/mapper/EnumMapper.java @@ -0,0 +1,46 @@ +package view.mapper; + +import java.util.Map; +import model.card.Card; +import model.card.CardShape; +import model.card.CardValue; +import model.judgement.ResultStatus; + +public class EnumMapper { + + public static final Map RESULT_STATUS_MAPPER = Map.of( + ResultStatus.WIN, "승", + ResultStatus.LOSE, "패", + ResultStatus.DRAW, "무", + ResultStatus.BLACKJACK, "블랙잭" + ); + + public static final Map CARD_VALUE_MAPPER = Map.ofEntries( + Map.entry(CardValue.ACE, "A"), + Map.entry(CardValue.TWO, "2"), + Map.entry(CardValue.THREE, "3"), + Map.entry(CardValue.FOUR, "4"), + Map.entry(CardValue.FIVE, "5"), + Map.entry(CardValue.SIX, "6"), + Map.entry(CardValue.SEVEN, "7"), + Map.entry(CardValue.EIGHT, "8"), + Map.entry(CardValue.NINE, "9"), + Map.entry(CardValue.TEN, "10"), + Map.entry(CardValue.JACK, "J"), + Map.entry(CardValue.QUEEN, "Q"), + Map.entry(CardValue.KING, "K") + ); + + public static final Map CARD_SHAPE_MAPPER = Map.of( + CardShape.HEART, "하트", + CardShape.SPADE, "스페이드", + CardShape.CLOVER, "클로버", + CardShape.DIAMOND, "다이아몬드" + ); + + private EnumMapper(){} + + public static String convert(Card card) { + return CARD_VALUE_MAPPER.get(card.value()) + CARD_SHAPE_MAPPER.get(card.shape()); + } +} diff --git a/src/test/java/fixture/PlayerResultTestFixture.java b/src/test/java/fixture/PlayerResultTestFixture.java index fadc81b3eb..e84cccf13c 100644 --- a/src/test/java/fixture/PlayerResultTestFixture.java +++ b/src/test/java/fixture/PlayerResultTestFixture.java @@ -1,11 +1,26 @@ package fixture; +import static model.card.CardValue.ACE; +import static model.card.CardValue.EIGHT; +import static model.card.CardValue.FIVE; +import static model.card.CardValue.JACK; +import static model.card.CardValue.KING; +import static model.card.CardValue.NINE; +import static model.card.CardValue.SEVEN; +import static model.card.CardValue.SIX; +import static model.card.CardValue.TEN; +import static model.card.CardValue.THREE; +import static model.card.CardValue.TWO; +import static model.judgement.ResultStatus.BLACKJACK; +import static model.judgement.ResultStatus.DRAW; +import static model.judgement.ResultStatus.LOSE; +import static model.judgement.ResultStatus.WIN; + import java.util.List; import java.util.stream.Stream; import model.card.Card; import model.card.CardShape; import model.card.CardValue; -import model.judgement.GameStatus; import org.junit.jupiter.params.provider.Arguments; public class PlayerResultTestFixture { @@ -14,33 +29,57 @@ public class PlayerResultTestFixture { return Stream.of( // 1. 플레이어 버스트 (딜러 점수 상관없이 패배) Arguments.of( - List.of(card(CardValue.TEN), card(CardValue.JACK), card(CardValue.FIVE)), // 25점 - List.of(card(CardValue.TWO), card(CardValue.THREE)), // 5점 - GameStatus.LOSE + List.of(card(TEN), card(JACK), card(FIVE)), // 25점 + List.of(card(TWO), card(THREE)), // 5점 + LOSE ), // 2. 딜러 버스트, 플레이어는 낫 버스트 (승리) Arguments.of( - List.of(card(CardValue.TEN), card(CardValue.EIGHT)), // 18점 - List.of(card(CardValue.TEN), card(CardValue.FIVE), card(CardValue.SEVEN)), // 22점 - GameStatus.WIN + List.of(card(TEN), card(EIGHT)), // 18점 + List.of(card(TEN), card(FIVE), card(SEVEN)), // 22점 + WIN ), // 3. 둘 다 버스트가 아닐 때 점수 비교 (플레이어가 높은 경우) Arguments.of( - List.of(card(CardValue.ACE), card(CardValue.NINE)), // 20점 - List.of(card(CardValue.TEN), card(CardValue.EIGHT)), // 18점 - GameStatus.WIN + List.of(card(ACE), card(NINE)), // 20점 + List.of(card(TEN), card(EIGHT)), // 18점 + WIN ), // 4. 점수가 같은 경우 (무승부) Arguments.of( - List.of(card(CardValue.TEN), card(CardValue.SEVEN)), // 17점 - List.of(card(CardValue.EIGHT), card(CardValue.NINE)), // 17점 - GameStatus.DRAW + List.of(card(TEN), card(SEVEN)), // 17점 + List.of(card(EIGHT), card(NINE)), // 17점 + DRAW ), // 5. 플레이어가 낮은 경우 (패배) Arguments.of( - List.of(card(CardValue.FIVE), card(CardValue.SEVEN)), // 12점 - List.of(card(CardValue.TEN), card(CardValue.SIX)), // 16점 - GameStatus.LOSE + List.of(card(FIVE), card(SEVEN)), // 12점 + List.of(card(TEN), card(SIX)), // 16점 + LOSE + ), + // 6. 플레이어와 딜러 모두 버스트 (패배) + Arguments.of( + List.of(card(TEN), card(JACK), card(FIVE)), // 25점 + List.of(card(TEN), card(TEN), card(THREE)), // 23점 + LOSE + ), + // 7. 플레이어만 블랙잭인 경우 + Arguments.of( + List.of(card(ACE), card(KING)), // 블랙잭 + List.of(card(TEN), card(NINE)), // 19점 + BLACKJACK + ), + // 8. 플레이어와 딜러 모두 블랙잭인 경우 + Arguments.of( + List.of(card(ACE), card(KING)), // 블랙잭 + List.of(card(ACE), card(KING)), // 블랙잭 + DRAW + ), + // 9. 딜러만 블랙잭인 경우 + Arguments.of( + List.of(card(TEN), card(FIVE), card(SIX)), // 21점 (3장) + List.of(card(ACE), card(KING)), // 블랙잭 + LOSE ) ); } diff --git a/src/test/java/fixture/ResultStatusTestFixture.java b/src/test/java/fixture/ResultStatusTestFixture.java new file mode 100644 index 0000000000..770a8312cf --- /dev/null +++ b/src/test/java/fixture/ResultStatusTestFixture.java @@ -0,0 +1,18 @@ +package fixture; + +import java.util.stream.Stream; +import model.judgement.ResultStatus; +import org.junit.jupiter.params.provider.Arguments; + +public class ResultStatusTestFixture { + + public static Stream 천_원_베팅에_따른_게임_결과별_수익_정보_제공() { + return Stream.of( + Arguments.of(ResultStatus.WIN, 1000), // 수익 금액 그대로 + Arguments.of(ResultStatus.LOSE, -1000), // 수익 금액 반대 - 손실 + Arguments.of(ResultStatus.DRAW, 0), // 무승부 시 수익 없음 + Arguments.of(ResultStatus.BLACKJACK, 1500) // 블랙잭 시 1.5배 수익 + ); + } + +} diff --git a/src/test/java/model/JudgementTest.java b/src/test/java/model/JudgementTest.java index 42efcc2acc..fc5586233f 100644 --- a/src/test/java/model/JudgementTest.java +++ b/src/test/java/model/JudgementTest.java @@ -5,9 +5,10 @@ import java.util.List; import model.card.Card; -import model.judgement.GameStatus; import model.judgement.Judgement; import model.judgement.PlayerResult; +import model.judgement.ResultStatus; +import model.paticipant.BettingPlayer; import model.paticipant.Dealer; import model.paticipant.Player; import model.paticipant.Players; @@ -21,19 +22,19 @@ public class JudgementTest { void 다양한_게임_상황에서_승패를_올바르게_판정한다( List playerCards, List dealerCards, - GameStatus status + ResultStatus status ) { // given Dealer dealer = createDealer(); dealerCards.forEach(dealer::addCard); - Player player = new Player("pobi"); - playerCards.forEach(player::addCard); + Player participant = new BettingPlayer("pobi", 10000); + playerCards.forEach(participant::addCard); // when - PlayerResult playerResult = Judgement.judgeByPlayer(dealer, new Players(List.of(player))); + PlayerResult playerResult = Judgement.judgeByPlayer(dealer, new Players(List.of(participant))); // then - assertThat(playerResult.countByStatus(status)).isEqualTo(1); + assertThat(playerResult.result()).containsEntry(participant, status); } } diff --git a/src/test/java/model/PlayerBustTest.java b/src/test/java/model/PlayerBustTest.java index 22e5cc4be2..b196c7b047 100644 --- a/src/test/java/model/PlayerBustTest.java +++ b/src/test/java/model/PlayerBustTest.java @@ -5,7 +5,8 @@ import model.card.Card; import model.card.CardShape; import model.card.CardValue; -import model.paticipant.Player; +import model.paticipant.BettingPlayer; +import model.paticipant.Participant; import org.junit.jupiter.api.Test; public class PlayerBustTest { @@ -13,7 +14,7 @@ public class PlayerBustTest { @Test void 플레이어의_카드_점수_합이_20_이하_일_때_카드를_더_받을_수_있다() { // given - Player pobi = new Player("pobi"); + Participant pobi = new BettingPlayer("pobi", 10000); pobi.addCard(new Card(CardShape.HEART, CardValue.TEN)); pobi.addCard(new Card(CardShape.DIAMOND, CardValue.TEN)); @@ -24,7 +25,7 @@ public class PlayerBustTest { @Test void 플레이어의_카드_점수_합이_21_이상_일_때_카드를_더_받을_수_없다() { // given - Player pobi = new Player("pobi"); + Participant pobi = new BettingPlayer("pobi", 10000); pobi.addCard(new Card(CardShape.HEART, CardValue.TEN)); pobi.addCard(new Card(CardShape.DIAMOND, CardValue.TEN)); pobi.addCard(new Card(CardShape.DIAMOND, CardValue.ACE)); diff --git a/src/test/java/model/PlayerTest.java b/src/test/java/model/PlayerTest.java index d466413d3d..e54d2f023c 100644 --- a/src/test/java/model/PlayerTest.java +++ b/src/test/java/model/PlayerTest.java @@ -4,7 +4,8 @@ import java.util.List; import model.card.Card; -import model.paticipant.Player; +import model.paticipant.BettingPlayer; +import model.paticipant.Participant; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -14,7 +15,7 @@ class PlayerTest { @MethodSource("fixture.PlayerTestFixture#플레이어별_카드목록_및_점수_제공") void 블랙잭_참여자는_자신의_카드_정보로_현재_점수를_계산할_수_있다(List cards, int expectedScore) { // given - Player pobi = new Player("pobi"); + Participant pobi = new BettingPlayer("pobi", 10000); cards.forEach(pobi::addCard); // when diff --git a/src/test/java/model/betting/BetAmountTest.java b/src/test/java/model/betting/BetAmountTest.java new file mode 100644 index 0000000000..71d4e8dfcb --- /dev/null +++ b/src/test/java/model/betting/BetAmountTest.java @@ -0,0 +1,68 @@ +package model.betting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import model.judgement.Profit; +import model.paticipant.BetAmount; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BetAmountTest { + + @ParameterizedTest + @ValueSource(ints = {0, 1000001}) + void 베팅_금액이_1원미만이거나_100만원을_초과한다면_예외가_발생한다(int invalidAmount) { + // when & then + assertThatThrownBy(() -> new BetAmount(invalidAmount)) + .isInstanceOf(IllegalArgumentException.class); + } + + @ParameterizedTest + @ValueSource(ints = {1, 1000000}) + void 베팅_금액이_1원부터_100만원이라면_올바른_베팅_금액이다(int validAmount) { + // when + BetAmount betAmount = new BetAmount(validAmount); + + // then + assertThat(betAmount.amount()).isEqualTo(validAmount); + } + + @Test + void 베팅_금액을_수익으로_변환할_수_있다() { + // given + BetAmount betAmount = new BetAmount(1000); + + // when + Profit profit = betAmount.toProfit(); + + // then + assertThat(profit).isEqualTo(new Profit(1000)); + } + + + @Test + void 베팅_금액을_손실로_변환할_수_있다() { + // given + BetAmount betAmount = new BetAmount(1000); + + // when + Profit profit = betAmount.toNegativeProfit(); + + // then + assertThat(profit).isEqualTo(new Profit(-1000)); + } + + @Test + void 블랙잭일_경우_베팅_금액의_1점_5배를_수익으로_변환한다() { + // given + BetAmount betAmount = new BetAmount(10000); + + // when + Profit profit = betAmount.toBlackjackProfit(); + + // then + assertThat(profit).isEqualTo(new Profit(15000)); + } +} \ No newline at end of file diff --git a/src/test/java/model/betting/ProfitTest.java b/src/test/java/model/betting/ProfitTest.java new file mode 100644 index 0000000000..dc36d54fe0 --- /dev/null +++ b/src/test/java/model/betting/ProfitTest.java @@ -0,0 +1,40 @@ +package model.betting; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.judgement.Profit; +import org.junit.jupiter.api.Test; + +class ProfitTest { + + @Test + void 수익을_합산할_수_있다() { + // given + Profit profit1 = new Profit(1000); + Profit profit2 = new Profit(2000); + + // when + Profit total = profit1.add(profit2); + + // then + assertThat(total).isEqualTo(new Profit(3000)); + } + + @Test + void 수익의_부호를_반전할_수_있다() { + // given + Profit profit = new Profit(1000); + + // when + Profit negated = profit.negate(); + + // then + assertThat(negated).isEqualTo(new Profit(-1000)); + } + + @Test + void ZERO는_0원의_수익이다() { + // when & then + assertThat(Profit.ZERO).isEqualTo(new Profit(0)); + } +} diff --git a/src/test/java/model/judgement/BettingCalculatorTest.java b/src/test/java/model/judgement/BettingCalculatorTest.java new file mode 100644 index 0000000000..5cfc5568f0 --- /dev/null +++ b/src/test/java/model/judgement/BettingCalculatorTest.java @@ -0,0 +1,71 @@ +package model.judgement; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.LinkedHashMap; +import java.util.Map; +import model.paticipant.BettingPlayer; +import model.paticipant.Player; +import org.junit.jupiter.api.Test; + +class BettingCalculatorTest { + + private BettingCalculator bettingCalculator = new BettingCalculator(); + + @Test + void 플레이어별_수익을_계산한다() { + // given + Player pobi = new BettingPlayer("pobi", 10000); + Player jason = new BettingPlayer("jason", 20000); + + Map result = new LinkedHashMap<>(); + result.put(pobi, ResultStatus.WIN); + result.put(jason, ResultStatus.LOSE); + + PlayerResult playerResult = new PlayerResult(result); + + // when + Map profits = bettingCalculator.calculateProfits(playerResult); + + // then + assertThat(profits.get(pobi)).isEqualTo(new Profit(10000)); + assertThat(profits.get(jason)).isEqualTo(new Profit(-20000)); + } + + @Test + void 딜러의_수익은_플레이어_수익의_합의_반대이다() { + // given + Player pobi = new BettingPlayer("pobi", 10000); + Player jason = new BettingPlayer("jason", 20000); + + Map result = new LinkedHashMap<>(); + result.put(pobi, ResultStatus.WIN); + result.put(jason, ResultStatus.LOSE); + + PlayerResult playerResult = new PlayerResult(result); + Map profitByPlayer = bettingCalculator.calculateProfits(playerResult); + + // when + Profit dealerProfit = bettingCalculator.calculateDealerProfit(profitByPlayer); + + // then + assertThat(dealerProfit).isEqualTo(new Profit(10000)); + } + + @Test + void 무승부인_플레이어의_수익은_0이다() { + // given + Player pobi = new BettingPlayer("pobi", 10000); + + Map result = new LinkedHashMap<>(); + result.put(pobi, ResultStatus.DRAW); + + PlayerResult playerResult = new PlayerResult(result); + + // when + Map profits = bettingCalculator.calculateProfits(playerResult); + + // then + assertThat(profits.get(pobi)).isEqualTo(Profit.ZERO); + } +} \ No newline at end of file diff --git a/src/test/java/model/judgement/ResultStatusTest.java b/src/test/java/model/judgement/ResultStatusTest.java new file mode 100644 index 0000000000..948d2549e7 --- /dev/null +++ b/src/test/java/model/judgement/ResultStatusTest.java @@ -0,0 +1,24 @@ +package model.judgement; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.paticipant.BetAmount; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class ResultStatusTest { + + @ParameterizedTest(name = "베팅 금액이 1000원 일 때 게임 결과가 {0}이라면, {1}원의 수익이 발생한다") + @MethodSource("fixture.ResultStatusTestFixture#천_원_베팅에_따른_게임_결과별_수익_정보_제공") + void 게임_결과에_따라_베팅_금액의_수익을_계산한다(ResultStatus resultStatus, int expectedProfit) { + // given + BetAmount betAmount = new BetAmount(1000); + + // when + Profit profit = resultStatus.calculateProfit(betAmount); + + // then + assertThat(profit.value()).isEqualTo(expectedProfit); + } + +} \ No newline at end of file diff --git a/src/test/java/model/paticipant/ParticipantTest.java b/src/test/java/model/paticipant/ParticipantTest.java new file mode 100644 index 0000000000..42b9cf4c55 --- /dev/null +++ b/src/test/java/model/paticipant/ParticipantTest.java @@ -0,0 +1,54 @@ +package model.paticipant; + +import static org.assertj.core.api.Assertions.assertThat; + +import model.card.Card; +import model.card.CardShape; +import model.card.CardValue; +import org.junit.jupiter.api.Test; + +class ParticipantTest { + + @Test + void 처음_2장의_카드_합이_21이면_블랙잭이다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(CardShape.HEART, CardValue.ACE)); + dealer.addCard(new Card(CardShape.HEART, CardValue.KING)); + + // when + boolean isBlackjack = dealer.isBlackjack(); + + // then + assertThat(isBlackjack).isTrue(); + } + + @Test + void 카드가_2장이상이면_블랙잭이_아니다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(CardShape.HEART, CardValue.TEN)); + dealer.addCard(new Card(CardShape.HEART, CardValue.FIVE)); + dealer.addCard(new Card(CardShape.HEART, CardValue.SIX)); + + // when + boolean isBlackjack = dealer.isBlackjack(); + + // then + assertThat(isBlackjack).isFalse(); + } + + @Test + void 카드_2장의_합이_21이_아니면_블랙잭이_아니다() { + // given + Dealer dealer = new Dealer(); + dealer.addCard(new Card(CardShape.HEART, CardValue.TEN)); + dealer.addCard(new Card(CardShape.HEART, CardValue.FIVE)); + + // when + boolean isBlackjack = dealer.isBlackjack(); + + // then + assertThat(isBlackjack).isFalse(); + } +}