diff --git a/README.md b/README.md index cc23089b189..48c69842369 100644 --- a/README.md +++ b/README.md @@ -10,20 +10,28 @@ [예외처리] 쉼표 외의 다른 문자를 구분자로 한 경우 IllegalArgumentException 예외를 발생시킨다. [예외처리] 입력값이 공백일 경우 IllegalArgumentException 예외를 발생시킨다. + +**2. [입력] 게임에 참여한 사람의 배팅 금액을 입력 받는다.** + + [예외처리] 입력값이 공백일 경우 IllegalArgumentException 예외를 발생시킨다. -**2. [중간 과정] 딜러와 사용자에게 카드를 2장씩 나누어준다.** + [예외처리] 입력값이 숫자가 아닐 경우 IllegalArgumentException 예외를 발생시킨다. + + [예외처리] 입력값이 0 이하일 경우 IllegalArgumentException 예외를 발생시킨다. + +**3. [중간 과정] 딜러와 사용자에게 카드를 2장씩 나누어준다.** [카드 조건] 덱은 1개만 사용한다.(A ~ K : 13 * 4 = 52장) -**3. [중간 과정] 딜러와 사용자의 최초 카드 합계를 구하며, 블랙잭 여부를 판단한다.** +**4. [중간 과정] 딜러와 사용자의 최초 카드 합계를 구하며, 블랙잭 여부를 판단한다.** [ACE 조건] ACE가 11이어도 21이 넘지 않는 경우에는 11, 넘는 경우에는 1로 계산한다. -**4. [출력] 카드 현황을 출력한다.** +**5. [출력] 카드 현황을 출력한다.** [출력 조건] 딜러는 첫 번째 카드만 출력한다. -**5. [입력] 사용자별로 카드 추가 지급 여부를 입력 받는다.** +**6. [입력] 사용자별로 카드 추가 지급 여부를 입력 받는다.** [예외처리] y, n 외의 다른 문자일 경우 IllegalArgumentException 예외를 발생시킨다. @@ -31,12 +39,12 @@ [지급 조건] 사용자가 y를 입력하고, 사용자의 현재 카드 합계가 21미만일 경우에 카드를 지급한다. -**6. [출력] 사용자별로 지급 현황을 출력한다.** +**7. [출력] 사용자별로 지급 현황을 출력한다.** -**7. [출력] 딜러의 카드 합계가 17 이상이 일 될 때까지 추가 지급 현황을 출력한다.** +**8. [출력] 딜러의 카드 합계가 17 이상이 일 될 때까지 추가 지급 현황을 출력한다.** -**8. [출력] 사용자별 카드 현황 및 결과를 출력한다.** +**9. [출력] 사용자별 카드 현황 및 결과를 출력한다.** -**9. [중간 과정] 최종 승패 여부를 계산한다.** - -**10. [출력] 최종 승패를 출력한다.** +**10. [중간 과정] 최종 승패 및 수익을 계산한다.** + +**11. [출력] 참여자별 최종 수익을 출력한다.** diff --git a/src/main/java/blackjack/Application.java b/src/main/java/blackjack/Application.java index 0d4617c5165..93d2d360bb9 100644 --- a/src/main/java/blackjack/Application.java +++ b/src/main/java/blackjack/Application.java @@ -6,7 +6,7 @@ public class Application { public static void main(String[] args) { - BlackjackController blackjackController = new BlackjackController(new CardProvider()); + BlackjackController blackjackController = new BlackjackController(new BlackjackGame(new CardProvider())); blackjackController.run(); } } diff --git a/src/main/java/blackjack/BlackjackGame.java b/src/main/java/blackjack/BlackjackGame.java new file mode 100644 index 00000000000..498a5205f94 --- /dev/null +++ b/src/main/java/blackjack/BlackjackGame.java @@ -0,0 +1,94 @@ +package blackjack; + +import static blackjack.util.ExceptionHandler.retryUntilSuccess; + +import blackjack.model.bet.BetAmount; +import blackjack.model.bet.BetAmounts; +import blackjack.model.card.CardProvider; +import blackjack.model.card.HitCommand; +import blackjack.model.gameresult.ProfitResult; +import blackjack.model.user.Dealer; +import blackjack.model.user.User; +import blackjack.model.user.Users; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class BlackjackGame { + + private final CardProvider cardProvider; + + public BlackjackGame(CardProvider cardProvider) { + this.cardProvider = cardProvider; + } + + public Users createUsers(Supplier readUsername) { + String input = readUsername.get(); + return new Users(input); + } + + public BetAmounts createBetAmount(Function readBetAmount, Users users) { + Map betAmounts = new HashMap<>(); + for (User user : users.getUsers()) { + String input = readBetAmount.apply(user); + BetAmount betAmount = new BetAmount(input); + betAmounts.put(user, betAmount); + } + return new BetAmounts(betAmounts); + } + + public void provideInitCards(Users users) { + cardProvider.provideInitCards(users); + } + + public void hitPlayers(List users, Function readHitCommand, + Consumer printPlayerCards, Runnable printCantHit) { + for (User user : users) { + hitPlayer(user, readHitCommand, printPlayerCards, printCantHit); + } + } + + public void hitDealer(User user, Runnable printDealerHit) { + while (user.isHitAvailable()) { + cardProvider.provideOneCard(user); + printDealerHit.run(); + } + } + + public ProfitResult determineWinner(Users users, BetAmounts betAmounts) { + return users.determineWinner(betAmounts); + } + + public void end(Runnable closeScanner) { + closeScanner.run(); + } + + private void hitPlayer(User user, Function readHitCommand, Consumer printPlayerCards, + Runnable printCantHit) { + if (!isHitAvailable(user, printCantHit)) { + return; + } + + while (retryUntilSuccess(() -> checkY(user, readHitCommand))) { + cardProvider.provideOneCard(user); + printPlayerCards.accept(user); + } + } + + private boolean checkY(User user, Function readHitCommand) { + String input = readHitCommand.apply(user); + HitCommand hitCommand = new HitCommand(input); + return hitCommand.isY(); + } + + private boolean isHitAvailable(User user, Runnable printCantHit) { + if (user.isHitAvailable()) { + return true; + } + printCantHit.run(); + return false; + } +} diff --git a/src/main/java/blackjack/controller/BlackjackController.java b/src/main/java/blackjack/controller/BlackjackController.java index 03cde5fe19d..f2e571b9894 100644 --- a/src/main/java/blackjack/controller/BlackjackController.java +++ b/src/main/java/blackjack/controller/BlackjackController.java @@ -2,27 +2,27 @@ import static blackjack.util.ExceptionHandler.retryUntilSuccess; -import blackjack.model.card.HitCommand; -import blackjack.model.card.CardProvider; -import blackjack.model.user.Dealer; -import blackjack.model.user.Player; -import blackjack.model.gameresult.PlayersGameResult; +import blackjack.BlackjackGame; +import blackjack.model.bet.BetAmounts; +import blackjack.model.gameresult.ProfitResult; import blackjack.model.user.Users; import blackjack.view.InputView; import blackjack.view.OutputView; -import java.util.List; public class BlackjackController { - private final CardProvider cardProvider; + private final BlackjackGame blackjackGame; - public BlackjackController(CardProvider cardProvider) { - this.cardProvider = cardProvider; + public BlackjackController(BlackjackGame blackjackGame) { + this.blackjackGame = blackjackGame; } public void run() { - Users users = retryUntilSuccess(this::createUsers); + Users users = retryUntilSuccess(() -> blackjackGame.createUsers(InputView::readPlayerName)); + + BetAmounts betAmounts = retryUntilSuccess( + () -> blackjackGame.createBetAmount(InputView::readBetAmount, users)); provideInitCardsAndPrint(users); @@ -30,58 +30,28 @@ public void run() { printHandStatus(users); - printGameResult(users); - - InputView.closeScanner(); - } + printProfitResult(users, betAmounts); - private Users createUsers() { - String input = InputView.readPlayerName(); - return new Users(input); + blackjackGame.end(InputView::closeScanner); } private void provideInitCardsAndPrint(Users users) { - cardProvider.provideInitCards(users); + blackjackGame.provideInitCards(users); OutputView.printInitCards(users); } private void hit(Users users) { - List players = users.getPlayers(); - Dealer dealer = users.getDealer(); - - for (Player player : players) { - while (retryUntilSuccess(() -> checkY(player)) && checkAddCard(player)) { - cardProvider.provideOneCard(player); - OutputView.printPlayerCards(player); - } - } - - while (dealer.isHitAvailable()) { - cardProvider.provideOneCard(dealer); - OutputView.printDealerHit(); - } + blackjackGame.hitPlayers(users.getPlayers(), InputView::readHitCommand, OutputView::printPlayerCards, + OutputView::printCantHit); + blackjackGame.hitDealer(users.getDealer(), OutputView::printDealerHit); } private void printHandStatus(Users users) { OutputView.printHandStatus(users); } - private void printGameResult(Users users) { - PlayersGameResult playersGameResult = users.determineWinner(); - OutputView.printGameResult(playersGameResult, users); - } - - private boolean checkY(Player player) { - String input = InputView.readCardAdd(player).trim(); - HitCommand hitCommand = new HitCommand(input); - return hitCommand.isY(); - } - - private boolean checkAddCard(Player player) { - if (player.isHitAvailable()) { - return true; - } - OutputView.printCantHit(); - return false; + private void printProfitResult(Users users, BetAmounts betAmounts) { + ProfitResult profitResult = blackjackGame.determineWinner(users, betAmounts); + OutputView.printGameResult(profitResult, users); } } diff --git a/src/main/java/blackjack/model/bet/BetAmount.java b/src/main/java/blackjack/model/bet/BetAmount.java new file mode 100644 index 00000000000..9b3c6ea5f36 --- /dev/null +++ b/src/main/java/blackjack/model/bet/BetAmount.java @@ -0,0 +1,41 @@ +package blackjack.model.bet; + +public class BetAmount { + + static final String ERROR_EMPTY_INPUT = "입력값은 공백일 수 없습니다."; + static final String ERROR_BET_AMOUNT_NOT_INTEGER = "배팅 금액은 숫자 형태로 입력해야 합니다."; + static final String ERROR_BET_AMOUNT_NOT_POSITIVE = "배팅 금액은 1원 이상이어야 합니다."; + + private final int amount; + + public BetAmount(String input) { + validateEmpty(input); + int amount = convertToInt(input); + validatePositive(amount); + this.amount = amount; + } + + public int getAmount() { + return amount; + } + + private void validateEmpty(String input) { + if (input.isBlank()) { + throw new IllegalArgumentException(ERROR_EMPTY_INPUT); + } + } + + private void validatePositive(int amount) { + if (amount <= 0) { + throw new IllegalArgumentException(ERROR_BET_AMOUNT_NOT_POSITIVE); + } + } + + private int convertToInt(String input) { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ERROR_BET_AMOUNT_NOT_INTEGER); + } + } +} diff --git a/src/main/java/blackjack/model/bet/BetAmounts.java b/src/main/java/blackjack/model/bet/BetAmounts.java new file mode 100644 index 00000000000..d5c2f84ff10 --- /dev/null +++ b/src/main/java/blackjack/model/bet/BetAmounts.java @@ -0,0 +1,17 @@ +package blackjack.model.bet; + +import blackjack.model.user.User; +import java.util.Map; + +public class BetAmounts { + + private final Map betAmounts; + + public BetAmounts(Map betAmounts) { + this.betAmounts = Map.copyOf(betAmounts); + } + + public BetAmount findByUser(User user) { + return betAmounts.get(user); + } +} diff --git a/src/main/java/blackjack/model/card/CardProvider.java b/src/main/java/blackjack/model/card/CardProvider.java index 78ecc6bbd14..8bae24e4925 100644 --- a/src/main/java/blackjack/model/card/CardProvider.java +++ b/src/main/java/blackjack/model/card/CardProvider.java @@ -1,20 +1,20 @@ package blackjack.model.card; -import static blackjack.model.constant.Constant.INIT_CARDS_END_IDX; -import static blackjack.model.constant.Constant.INIT_CARDS_START_IDX; - -import blackjack.model.user.Dealer; -import blackjack.model.user.Player; import blackjack.model.user.User; import blackjack.model.user.Users; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.stream.Collectors; public class CardProvider { + private static final int INIT_CARDS_START_IDX = 0; + private static final int INIT_CARDS_END_IDX = 2; + private final Queue deck = new LinkedList<>(); public CardProvider() { @@ -22,24 +22,17 @@ public CardProvider() { } private void initDeck() { - List cards = new ArrayList<>(); - for (Rank rank : Rank.values()) { - for (Suit suit : Suit.values()) { - cards.add(new Card(rank, suit)); - } - } + List cards = Arrays.stream(Rank.values()) + .flatMap(rank -> Arrays.stream(Suit.values()) + .map(suit -> new Card(rank, suit))) + .collect(Collectors.toCollection(ArrayList::new)); Collections.shuffle(cards); this.deck.addAll(cards); } public void provideInitCards(Users users) { - List players = users.getPlayers(); - Dealer dealer = users.getDealer(); - for (int i = INIT_CARDS_START_IDX; i < INIT_CARDS_END_IDX; i++) { - for (Player player : players) { - provideOneCard(player); - } - provideOneCard(dealer); + for (User user : users.getUsers()) { + provideTwoCard(user); } } @@ -49,4 +42,10 @@ public void provideOneCard(User user) { } user.addCard(deck.poll()); } + + private void provideTwoCard(User user) { + for (int i = INIT_CARDS_START_IDX; i < INIT_CARDS_END_IDX; i++) { + provideOneCard(user); + } + } } diff --git a/src/main/java/blackjack/model/card/Hand.java b/src/main/java/blackjack/model/card/Hand.java index e5df390ce82..338b2234533 100644 --- a/src/main/java/blackjack/model/card/Hand.java +++ b/src/main/java/blackjack/model/card/Hand.java @@ -1,11 +1,5 @@ package blackjack.model.card; -import static blackjack.model.constant.Constant.ACE_SCORE_ELEVEN; -import static blackjack.model.constant.Constant.ACE_SCORE_ONE; -import static blackjack.model.constant.Constant.INIT_CARDS_END_IDX; -import static blackjack.model.constant.Constant.INIT_CARDS_START_IDX; -import static blackjack.model.constant.Constant.BLACKJACK_SCORE; - import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -13,6 +7,12 @@ public class Hand { + private static final int BLACKJACK_SCORE = 21; + private static final int INIT_CARDS_START_IDX = 0; + private static final int INIT_CARDS_END_IDX = 2; + private static final int ACE_SCORE_ONE = 1; + private static final int ACE_SCORE_ELEVEN = 11; + private final List cards; public Hand() { @@ -55,13 +55,15 @@ public int totalScore(List cards) { private int addAceScore(List aceCards, int totalScore) { for (int i = 0; i < aceCards.size(); i++) { - if (totalScore + ACE_SCORE_ELEVEN > BLACKJACK_SCORE) { - totalScore += ACE_SCORE_ONE; - continue; - } - totalScore += ACE_SCORE_ELEVEN; + totalScore += calculateAceScore(totalScore); } - return totalScore; } + + private static int calculateAceScore(int totalScore) { + if (totalScore + ACE_SCORE_ELEVEN > BLACKJACK_SCORE) { + return ACE_SCORE_ONE; + } + return ACE_SCORE_ELEVEN; + } } diff --git a/src/main/java/blackjack/model/card/HitCommand.java b/src/main/java/blackjack/model/card/HitCommand.java index 66ba59fff6d..cc66acd52f5 100644 --- a/src/main/java/blackjack/model/card/HitCommand.java +++ b/src/main/java/blackjack/model/card/HitCommand.java @@ -1,12 +1,11 @@ package blackjack.model.card; -import static blackjack.model.constant.ErrorMessage.ERROR_EMPTY_INPUT; -import static blackjack.model.constant.ErrorMessage.ERROR_NOT_Y_N_INPUT; - import java.util.regex.Pattern; public class HitCommand { + static final String ERROR_EMPTY_INPUT = "입력값은 공백일 수 없습니다."; + static final String ERROR_NOT_Y_N_INPUT = "입력값은 y 또는 n만 가능합니다."; private static final String Y_N_REGEX = "^[yYnN]$"; private final String command; @@ -23,13 +22,13 @@ public boolean isY() { private void validateEmpty(String command) { if (command.isBlank()) { - throw new IllegalArgumentException(ERROR_EMPTY_INPUT.getErrorMessage()); + throw new IllegalArgumentException(ERROR_EMPTY_INPUT); } } private void validateRegex(String command) { if (!Pattern.matches(Y_N_REGEX, command)) { - throw new IllegalArgumentException(ERROR_NOT_Y_N_INPUT.getErrorMessage()); + throw new IllegalArgumentException(ERROR_NOT_Y_N_INPUT); } } } diff --git a/src/main/java/blackjack/model/constant/Constant.java b/src/main/java/blackjack/model/constant/Constant.java deleted file mode 100644 index f2389f6895d..00000000000 --- a/src/main/java/blackjack/model/constant/Constant.java +++ /dev/null @@ -1,14 +0,0 @@ -package blackjack.model.constant; - -public class Constant { - - public static final int DEALER_ADD_CARD_STAND = 17; - public static final int BLACKJACK_SCORE = 21; - - public static final int INIT_CARDS_START_IDX = 0; - public static final int INIT_CARDS_END_IDX = 2; - - public static final int ACE_SCORE_ONE = 1; - public static final int ACE_SCORE_ELEVEN = 11; - -} diff --git a/src/main/java/blackjack/model/constant/ErrorMessage.java b/src/main/java/blackjack/model/constant/ErrorMessage.java deleted file mode 100644 index 7aa3f071b21..00000000000 --- a/src/main/java/blackjack/model/constant/ErrorMessage.java +++ /dev/null @@ -1,19 +0,0 @@ -package blackjack.model.constant; - -public enum ErrorMessage { - - ERROR_EMPTY_INPUT("입력값은 공백일 수 없습니다."), - ERROR_NOT_Y_N_INPUT("입력값은 y 또는 n만 가능합니다."), - ERROR_INVALID_PLAYER_NAME("플레이어의 이름은 영어 or 한글로만 이루어질 수 있습니다."), - ; - - private final String errorMessage; - - ErrorMessage(String errorMessage) { - this.errorMessage = errorMessage; - } - - public String getErrorMessage() { - return errorMessage; - } -} diff --git a/src/main/java/blackjack/model/gameresult/GameResult.java b/src/main/java/blackjack/model/gameresult/GameResult.java index ce9e77c968b..971eeb4e916 100644 --- a/src/main/java/blackjack/model/gameresult/GameResult.java +++ b/src/main/java/blackjack/model/gameresult/GameResult.java @@ -1,19 +1,22 @@ package blackjack.model.gameresult; +import blackjack.model.bet.BetAmount; + public enum GameResult { - WIN("승"), - DRAW("무"), - LOSE("패"), + BLACKJACK_WIN(1.5), + WIN(1), + DRAW(0), + LOSE(-1), ; - private final String format; + private final double payoutRate; - GameResult(String format) { - this.format = format; + GameResult(double payoutRate) { + this.payoutRate = payoutRate; } - public String getFormat() { - return format; + public int calculateProfit(BetAmount betAmount) { + return (int) Math.round(betAmount.getAmount() * this.payoutRate); } } \ No newline at end of file diff --git a/src/main/java/blackjack/model/gameresult/PlayersGameResult.java b/src/main/java/blackjack/model/gameresult/PlayersGameResult.java deleted file mode 100644 index 6440361a6a7..00000000000 --- a/src/main/java/blackjack/model/gameresult/PlayersGameResult.java +++ /dev/null @@ -1,7 +0,0 @@ -package blackjack.model.gameresult; - -import blackjack.model.user.Player; -import java.util.Map; - -public record PlayersGameResult(Map result) { -} diff --git a/src/main/java/blackjack/model/gameresult/ProfitResult.java b/src/main/java/blackjack/model/gameresult/ProfitResult.java new file mode 100644 index 00000000000..bdd4b0bbac6 --- /dev/null +++ b/src/main/java/blackjack/model/gameresult/ProfitResult.java @@ -0,0 +1,7 @@ +package blackjack.model.gameresult; + +import blackjack.model.user.User; +import java.util.Map; + +public record ProfitResult(Map playersProfit, int dealerProfit) { +} diff --git a/src/main/java/blackjack/model/user/Dealer.java b/src/main/java/blackjack/model/user/Dealer.java index af688a7ebe6..dc05e6e1e1f 100644 --- a/src/main/java/blackjack/model/user/Dealer.java +++ b/src/main/java/blackjack/model/user/Dealer.java @@ -1,27 +1,20 @@ package blackjack.model.user; -import blackjack.model.constant.Constant; -import blackjack.model.gameresult.GameResult; -import java.util.EnumMap; - public class Dealer extends User { - private final EnumMap gameResults; + + private static final int DEALER_ADD_CARD_STAND = 17; public Dealer() { super("딜러"); - gameResults = new EnumMap<>(GameResult.class); - } - - public EnumMap getGameResults() { - return gameResults; } - public void addResult(GameResult gameResult) { - gameResults.merge(gameResult, 1, Integer::sum); + @Override + public boolean isHitAvailable() { + return totalScore() < DEALER_ADD_CARD_STAND; } @Override - public boolean isHitAvailable() { - return totalScore() < Constant.DEALER_ADD_CARD_STAND; + public boolean isPlayer() { + return false; } } diff --git a/src/main/java/blackjack/model/user/Player.java b/src/main/java/blackjack/model/user/Player.java index 56c7825c638..f2868170958 100644 --- a/src/main/java/blackjack/model/user/Player.java +++ b/src/main/java/blackjack/model/user/Player.java @@ -1,15 +1,20 @@ package blackjack.model.user; +public class Player extends User { -import blackjack.model.constant.Constant; + private static final int BLACKJACK_SCORE = 21; -public class Player extends User { public Player(String name) { super(name); } @Override public boolean isHitAvailable() { - return totalScore() < Constant.BLACKJACK_SCORE; + return totalScore() < BLACKJACK_SCORE; + } + + @Override + public boolean isPlayer() { + return true; } } diff --git a/src/main/java/blackjack/model/user/User.java b/src/main/java/blackjack/model/user/User.java index 6780d34b652..bedb0f9bde4 100644 --- a/src/main/java/blackjack/model/user/User.java +++ b/src/main/java/blackjack/model/user/User.java @@ -1,7 +1,13 @@ package blackjack.model.user; +import static blackjack.model.gameresult.GameResult.BLACKJACK_WIN; +import static blackjack.model.gameresult.GameResult.DRAW; +import static blackjack.model.gameresult.GameResult.LOSE; +import static blackjack.model.gameresult.GameResult.WIN; + import blackjack.model.card.Card; import blackjack.model.card.Hand; +import blackjack.model.gameresult.GameResult; import java.util.List; public abstract class User { @@ -14,6 +20,10 @@ public User(String name) { this.hand = new Hand(); } + public abstract boolean isHitAvailable(); + + public abstract boolean isPlayer(); + public String getName() { return name.getName(); } @@ -38,5 +48,47 @@ public int totalScore() { return hand.calculateTotalScore(); } - public abstract boolean isHitAvailable(); + public GameResult judge(User user) { + if (this.isBust() || user.isBust()) { + return judgeByBust(); + } + + if (this.isBlackjack() || user.isBlackjack()) { + return judgeByBlackjack(user); + } + + return judgeByScore(user); + } + + private GameResult judgeByBust() { + if (this.isBust()) { + return LOSE; + } + + return WIN; + } + + private GameResult judgeByBlackjack(User user) { + if (this.isBlackjack() && user.isBlackjack()) { + return DRAW; + } + + if (this.isBlackjack()) { + return BLACKJACK_WIN; + } + + return LOSE; + } + + private GameResult judgeByScore(User user) { + if (this.totalScore() > user.totalScore()) { + return WIN; + } + + if (this.totalScore() < user.totalScore()) { + return LOSE; + } + + return DRAW; + } } diff --git a/src/main/java/blackjack/model/user/Username.java b/src/main/java/blackjack/model/user/Username.java index f429dfb5412..96bbaa6aaaf 100644 --- a/src/main/java/blackjack/model/user/Username.java +++ b/src/main/java/blackjack/model/user/Username.java @@ -1,12 +1,11 @@ package blackjack.model.user; -import static blackjack.model.constant.ErrorMessage.ERROR_EMPTY_INPUT; -import static blackjack.model.constant.ErrorMessage.ERROR_INVALID_PLAYER_NAME; - import java.util.regex.Pattern; public class Username { + static final String ERROR_EMPTY_INPUT = "입력값은 공백일 수 없습니다."; + static final String ERROR_INVALID_PLAYER_NAME = "플레이어의 이름은 영어 or 한글로만 이루어질 수 있습니다."; private static final String PLAYER_NAME_REGEX = "^[a-zA-Z가-힣]*$"; private final String name; @@ -23,13 +22,13 @@ public String getName() { private void validateEmpty(String name) { if (name.isBlank()) { - throw new IllegalArgumentException(ERROR_EMPTY_INPUT.getErrorMessage()); + throw new IllegalArgumentException(ERROR_EMPTY_INPUT); } } private void validateRegex(String name) { if (!Pattern.matches(PLAYER_NAME_REGEX, name)) { - throw new IllegalArgumentException(ERROR_INVALID_PLAYER_NAME.getErrorMessage()); + throw new IllegalArgumentException(ERROR_INVALID_PLAYER_NAME); } } } diff --git a/src/main/java/blackjack/model/user/Users.java b/src/main/java/blackjack/model/user/Users.java index 4a00889229e..bbe1a42d784 100644 --- a/src/main/java/blackjack/model/user/Users.java +++ b/src/main/java/blackjack/model/user/Users.java @@ -1,7 +1,8 @@ package blackjack.model.user; +import blackjack.model.bet.BetAmounts; import blackjack.model.gameresult.GameResult; -import blackjack.model.gameresult.PlayersGameResult; +import blackjack.model.gameresult.ProfitResult; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -9,108 +10,55 @@ public class Users { - private final List players; - private final Dealer dealer; + private static final String ERROR_DEALER_NOT_FOUND = "딜러가 존재하지 않습니다."; + + private final List users; public Users(List players, Dealer dealer) { - this.players = players; - this.dealer = dealer; + users = new ArrayList<>(players); + users.add(dealer); } public Users(String playerNames) { - this.players = PlayerParser.parse(playerNames); - this.dealer = new Dealer(); - } - - public List getPlayers() { - return List.copyOf(players); - } - - public Dealer getDealer() { - return dealer; + users = new ArrayList<>(PlayerParser.parse(playerNames)); + users.add(new Dealer()); } public List getUsers() { - List users = new ArrayList<>(List.copyOf(players)); - users.add(dealer); - return users; + return List.copyOf(users); } - public PlayersGameResult determineWinner() { - Map result = new HashMap<>(); - for (Player player : players) { - if (calculateWhenBlackjack(player, dealer, result)) { - continue; - } - - if (calculateWhenBust(player, dealer, result)) { - continue; - } - - calculateWhenNormal(player, dealer, result); - } - return new PlayersGameResult(result); + public List getPlayers() { + return users.stream() + .filter(User::isPlayer) + .toList(); } - private boolean calculateWhenBlackjack(Player player, Dealer dealer, Map result) { - if (player.isBlackjack() && dealer.isBlackjack()) { - logDraw(player, dealer, result); - return true; - } - - if (player.isBlackjack()) { - logPlayerWin(player, dealer, result); - return true; - } - - if (dealer.isBlackjack()) { - logPlayerLose(player, dealer, result); - return true; - } - - return false; + public User getDealer() { + return users.stream() + .filter(user -> !user.isPlayer()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ERROR_DEALER_NOT_FOUND)); } - private boolean calculateWhenBust(Player player, Dealer dealer, Map result) { - if (player.isBust()) { - logPlayerLose(player, dealer, result); - return true; - } - - if (dealer.isBust()) { - logPlayerWin(player, dealer, result); - return true; - } - - return false; - } + public ProfitResult determineWinner(BetAmounts betAmounts) { + Map result = new HashMap<>(); + List players = getPlayers(); + User dealer = getDealer(); - private void calculateWhenNormal(Player player, Dealer dealer, Map result) { - if (player.totalScore() > dealer.totalScore()) { - logPlayerWin(player, dealer, result); - return; + for (User player : players) { + GameResult gameResult = player.judge(dealer); + int profit = gameResult.calculateProfit(betAmounts.findByUser(player)); + result.put(player, profit); } + int dealerPayout = -getTotalPlayerProfit(result); - if (player.totalScore() < dealer.totalScore()) { - logPlayerLose(player, dealer, result); - return; - } - - logDraw(player, dealer, result); - } - - private void logPlayerWin(Player player, Dealer dealer, Map result) { - result.put(player, GameResult.WIN); - dealer.addResult(GameResult.LOSE); - } - - private void logPlayerLose(Player player, Dealer dealer, Map result) { - result.put(player, GameResult.LOSE); - dealer.addResult(GameResult.WIN); + return new ProfitResult(result, dealerPayout); } - private void logDraw(Player player, Dealer dealer, Map result) { - result.put(player, GameResult.DRAW); - dealer.addResult(GameResult.DRAW); + private static int getTotalPlayerProfit(Map result) { + return result.values().stream() + .mapToInt(Integer::intValue) + .sum(); } } diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java index 577bdde668b..32d298395ae 100644 --- a/src/main/java/blackjack/view/InputView.java +++ b/src/main/java/blackjack/view/InputView.java @@ -1,6 +1,6 @@ package blackjack.view; -import blackjack.model.user.Player; +import blackjack.model.user.User; import java.util.Scanner; public class InputView { @@ -11,8 +11,13 @@ public static String readPlayerName() { return sc.nextLine(); } - public static String readCardAdd(Player player) { - System.out.println(player.getName() + "는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); + public static String readBetAmount(User user) { + System.out.println(user.getName() + "의 배팅 금액은?"); + return sc.nextLine(); + } + + public static String readHitCommand(User user) { + System.out.println(user.getName() + "는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); return sc.nextLine(); } diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java index 688654d07bb..73826ada1a0 100644 --- a/src/main/java/blackjack/view/OutputView.java +++ b/src/main/java/blackjack/view/OutputView.java @@ -1,26 +1,23 @@ package blackjack.view; -import static blackjack.model.constant.Constant.BLACKJACK_SCORE; - import blackjack.model.card.Card; -import blackjack.model.user.Dealer; -import blackjack.model.gameresult.GameResult; -import blackjack.model.user.Player; -import blackjack.model.gameresult.PlayersGameResult; +import blackjack.model.gameresult.ProfitResult; import blackjack.model.user.User; import blackjack.model.user.Users; -import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class OutputView { + + private static final int BLACKJACK_SCORE = 21; + public static void printInitCards(Users users) { - List players = users.getPlayers(); - Dealer dealer = users.getDealer(); + List players = users.getPlayers(); + User dealer = users.getDealer(); List names = players.stream() - .map(Player::getName) + .map(User::getName) .toList(); StringBuilder sb = new StringBuilder(); sb.append("딜러와 "); @@ -29,20 +26,20 @@ public static void printInitCards(Users users) { System.out.println(sb); printDealerCard(dealer); - for (Player player : players) { + for (User player : players) { printPlayerCards(player); } System.out.println(); } - private static void printDealerCard(Dealer dealer) { - Card firstCard = dealer.cards().getFirst(); + private static void printDealerCard(User user) { + Card firstCard = user.cards().getFirst(); System.out.println("딜러카드: " + getCardFormat(firstCard)); } - public static void printPlayerCards(Player player) { - String cardsFormat = getCardsFormat(player); - System.out.println(player.getName() + "카드: " + cardsFormat); + public static void printPlayerCards(User user) { + String cardsFormat = getCardsFormat(user); + System.out.println(user.getName() + "카드: " + cardsFormat); } public static void printCantHit() { @@ -54,32 +51,28 @@ public static void printDealerHit() { } public static void printHandStatus(Users users) { - Dealer dealer = users.getDealer(); - List players = users.getPlayers(); + User dealer = users.getDealer(); + List players = users.getPlayers(); StringBuilder sb = new StringBuilder(); createHandStatusFormat(sb, dealer); - for (Player player : players) { + for (User player : players) { createHandStatusFormat(sb, player); } System.out.println(sb); } - public static void printGameResult(PlayersGameResult playersGameResult, Users users) { + public static void printGameResult(ProfitResult profitResult, Users users) { System.out.println(); - System.out.println("## 최종 승패"); + System.out.println("## 최종 수익"); - Dealer dealer = users.getDealer(); - EnumMap dealerGameResult = dealer.getGameResults(); - System.out.println( - dealer.getName() + ": " + dealerGameResult.getOrDefault(GameResult.WIN, 0) + "승 " + - dealerGameResult.getOrDefault(GameResult.DRAW, 0) + "무 " + dealerGameResult.getOrDefault( - GameResult.LOSE, 0) + "패"); + User dealer = users.getDealer(); + System.out.println(dealer.getName() + ": " + profitResult.dealerProfit()); - Map result = playersGameResult.result(); - for (Player player : users.getPlayers()) { + Map playersProfit = profitResult.playersProfit(); + for (User player : users.getPlayers()) { System.out.print(player.getName() + ": "); - System.out.println(result.get(player).getFormat()); + System.out.println(playersProfit.get(player)); } } diff --git a/src/test/java/blackjack/model/bet/BetAmountTest.java b/src/test/java/blackjack/model/bet/BetAmountTest.java new file mode 100644 index 00000000000..647ee4236ad --- /dev/null +++ b/src/test/java/blackjack/model/bet/BetAmountTest.java @@ -0,0 +1,50 @@ +package blackjack.model.bet; + +import static blackjack.model.bet.BetAmount.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class BetAmountTest { + + @ParameterizedTest + @ValueSource(strings = {"1", "10"}) + @DisplayName("배팅 금액 정상 생성") + void createBetAmount_success(String input) { + //when & then + assertDoesNotThrow(() -> new BetAmount(input)); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + @DisplayName("입력값이 공백일 경우 예외 발생") + void createBetAmount_fail_when_input_is_empty(String emptyInput) { + //when & then + Assertions.assertThatThrownBy(() -> new BetAmount(emptyInput)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ERROR_EMPTY_INPUT); + } + + @ParameterizedTest + @ValueSource(strings = {"a", "1a", "123*"}) + @DisplayName("입력값이 숫자 형태가 아닐 경우 예외 발생") + void createBetAmount_fail_when_input_is_not_number(String stringInput) { + //when & then + Assertions.assertThatThrownBy(() -> new BetAmount(stringInput)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ERROR_BET_AMOUNT_NOT_INTEGER); + } + + @ParameterizedTest + @ValueSource(strings = {"0", "-1"}) + @DisplayName("입력값이 0원 이하일 경우 예외 발생") + void createBetAmount_fail_when_input_is_not_positive(String negativeInput) { + //when & then + Assertions.assertThatThrownBy(() -> new BetAmount(negativeInput)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(ERROR_BET_AMOUNT_NOT_POSITIVE); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/model/CardProviderTest.java b/src/test/java/blackjack/model/card/CardProviderTest.java similarity index 84% rename from src/test/java/blackjack/model/CardProviderTest.java rename to src/test/java/blackjack/model/card/CardProviderTest.java index 6f086bf4530..8eb0ed67b24 100644 --- a/src/test/java/blackjack/model/CardProviderTest.java +++ b/src/test/java/blackjack/model/card/CardProviderTest.java @@ -1,8 +1,7 @@ -package blackjack.model; +package blackjack.model.card; import static org.assertj.core.api.Assertions.assertThat; -import blackjack.model.card.CardProvider; import blackjack.model.user.Dealer; import blackjack.model.user.Player; import blackjack.model.user.Users; @@ -15,17 +14,19 @@ class CardProviderTest { @Test @DisplayName("딜러 및 플레이어에게 초기 카드 2장 정상 배분") void test_provide_init_cards() { + // given CardProvider cardProvider = new CardProvider(); List players = List.of(new Player("pobi"), new Player("james")); Dealer dealer = new Dealer(); Users users = new Users(players, dealer); + // when cardProvider.provideInitCards(users); - for (Player player : players) { - assertThat(player.cards().size()).isEqualTo(2); - } + // then + assertThat(players).allSatisfy(player -> + assertThat(player.cards().size()).isEqualTo(2)); assertThat(dealer.cards().size()).isEqualTo(2); } diff --git a/src/test/java/blackjack/model/HandTest.java b/src/test/java/blackjack/model/card/HandTest.java similarity index 91% rename from src/test/java/blackjack/model/HandTest.java rename to src/test/java/blackjack/model/card/HandTest.java index 0fa5ef4c795..a84cf065793 100644 --- a/src/test/java/blackjack/model/HandTest.java +++ b/src/test/java/blackjack/model/card/HandTest.java @@ -1,11 +1,7 @@ -package blackjack.model; +package blackjack.model.card; import static org.assertj.core.api.Assertions.assertThat; -import blackjack.model.card.Card; -import blackjack.model.card.Hand; -import blackjack.model.card.Rank; -import blackjack.model.card.Suit; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/blackjack/model/HitCommandTest.java b/src/test/java/blackjack/model/card/HitCommandTest.java similarity index 56% rename from src/test/java/blackjack/model/HitCommandTest.java rename to src/test/java/blackjack/model/card/HitCommandTest.java index 930073a4ddd..56c732e32c7 100644 --- a/src/test/java/blackjack/model/HitCommandTest.java +++ b/src/test/java/blackjack/model/card/HitCommandTest.java @@ -1,11 +1,10 @@ -package blackjack.model; +package blackjack.model.card; -import static blackjack.model.constant.ErrorMessage.ERROR_EMPTY_INPUT; -import static blackjack.model.constant.ErrorMessage.ERROR_NOT_Y_N_INPUT; +import static blackjack.model.card.HitCommand.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import blackjack.model.card.HitCommand; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -20,7 +19,6 @@ void create_hitCommand_success(String hitCommand) { assertDoesNotThrow(() -> new HitCommand(hitCommand)); } - @ParameterizedTest @ValueSource(strings = {"", " "}) @DisplayName("공백이 입력될 경우 예외 발생") @@ -28,7 +26,7 @@ void create_hitCommand_fail_when_empty_input(String emptyInput) { //when & then assertThatThrownBy(() -> new HitCommand(emptyInput)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ERROR_EMPTY_INPUT.getErrorMessage()); + .hasMessage(ERROR_EMPTY_INPUT); } @ParameterizedTest @@ -38,6 +36,34 @@ void create_hitCommand_fail_when_invalid_input(String invalidInput) { //when & then assertThatThrownBy(() -> new HitCommand(invalidInput)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ERROR_NOT_Y_N_INPUT.getErrorMessage()); + .hasMessage(ERROR_NOT_Y_N_INPUT); + } + + @ParameterizedTest + @ValueSource(strings = {"y", "Y"}) + @DisplayName("입력값이 y 또는 Y일 경우 true 반환") + void isY_return_true_when_y_Y(String hitCommandInput) { + // given + HitCommand hitCommand = new HitCommand(hitCommandInput); + + //when + boolean result = hitCommand.isY(); + + // then + assertThat(result).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = {"n", "N"}) + @DisplayName("입력값이 n 또는 N일 경우 false 반환") + void isY_return_false_when_n_N(String hitCommandInput) { + // given + HitCommand hitCommand = new HitCommand(hitCommandInput); + + //when + boolean result = hitCommand.isY(); + + // then + assertThat(result).isFalse(); } } \ No newline at end of file diff --git a/src/test/java/blackjack/model/user/PlayerParserTest.java b/src/test/java/blackjack/model/user/PlayerParserTest.java new file mode 100644 index 00000000000..063320841b9 --- /dev/null +++ b/src/test/java/blackjack/model/user/PlayerParserTest.java @@ -0,0 +1,23 @@ +package blackjack.model.user; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayerParserTest { + + @Test + @DisplayName("플레이어 이름 파싱 및 플레이어 객체 생성") + void test_parse_success() { + String input = "pobi,james"; + + List players = PlayerParser.parse(input); + + assertThat(players.size()).isEqualTo(2); + assertThat(players) + .extracting("name") + .contains("pobi", "james"); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/model/user/UsernameTest.java b/src/test/java/blackjack/model/user/UsernameTest.java index 0473f81c347..52fb44b53a4 100644 --- a/src/test/java/blackjack/model/user/UsernameTest.java +++ b/src/test/java/blackjack/model/user/UsernameTest.java @@ -1,7 +1,6 @@ package blackjack.model.user; -import static blackjack.model.constant.ErrorMessage.ERROR_EMPTY_INPUT; -import static blackjack.model.constant.ErrorMessage.ERROR_INVALID_PLAYER_NAME; +import static blackjack.model.user.Username.*; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -26,7 +25,7 @@ void create_username_fail_when_empty_name(String emptyName) { //when & then assertThatThrownBy(() -> new Username(emptyName)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ERROR_EMPTY_INPUT.getErrorMessage()); + .hasMessage(ERROR_EMPTY_INPUT); } @ParameterizedTest @@ -36,6 +35,6 @@ void create_username_fail_when_invalid_name(String invalidName) { //when & then assertThatThrownBy(() -> new Username(invalidName)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage(ERROR_INVALID_PLAYER_NAME.getErrorMessage()); + .hasMessage(ERROR_INVALID_PLAYER_NAME); } } \ No newline at end of file