diff --git a/README.md b/README.md index 556099c4de3..dc5ba957975 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,67 @@ -# java-blackjack +# 블랙잭 -블랙잭 미션 저장소 -## 우아한테크코스 코드리뷰 +## 기능 요구 사항 -- [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) +### 플레이어 이름 + +### 플레이어 + +- [X] 플레이어는 이름을 갖는다 +- [X] 플레이어는 카드들을 갖는다 +- [X] 플레이어는 카드를 저장한다 +- [X] 죽었는지 안 죽었는지 여부를 반환한다 (21을 초과하는지) + + +### 카드 + +- [X] 문자와 모양을 상태로 갖는다 + + +### Hand(카드들) + +- [X] 여러개의 카드를 갖는다 +- [X] 카드의 총합 계산한다 +- [X] 카드 추가한다 + + +### 카드 게임 + +- [X] 각 플레이어에게 카드를 2장씩 지급한다 +- [X] 플레이어마다 추가 지급한다 + + +### 덱 + +- [X] 모든 카드를 1장씩 갖고 있다. +- [X] 랜덤으로 카드를 뽑는다. + + +### 게임 승패 결정 + +- [X] 딜러와 모든 플레이어의 승패 여부를 결정한다. + - [X] 딜러와 플레이어 둘다 21을 초과할 경우, 플레이어가 패배한다. + - [X] 카드 합계가 딜러는 21 이하, 플레이어는 21 초과인 경우, 플레이어가 패배한다. + - [X] 카드 합계가 딜러는 21 초과, 플레이어는 21 이하인 경우, 플레이어가 승리한다. + - [X] 카드 합계가 딜러와 플레이어 모두 21 이하인 경우, 숫자가 큰 사람이 승리한다. + - [X] 카드 합계가 딜러와 플레이어 모두 21 이하이고 동일한 경우, 무승부다. + + +### 인풋 뷰 + +- [X] 참가자의 이름을 입력받는다. + - [X] 이름은 쉼표 기준으로 분리한다. +- [X] 카드 추가 여부를 입력받는다. + - [X] y 또는 n이 아닌 경우, 예외를 발생한다. + - [X] 사용자 카드 합이 21을 초과하면, 카드 추가 여부를 묻지 않는다. + - [X] 플레이어가 n을 입력할 때까지 카드 추가 여부를 묻는다. + + +### 아웃풋 뷰 + +- [X] 딜러와 플레이어 전원에게 초기에 분배한 카드 출력한다. + - [X] 딜러의 카드는 1장만 공개한다. +- [X] 플레이어가 보유한 모든 카드를 출력한다. +- [X] 딜러가 추가 카드를 발급 받았는지 여부 출력한다. +- [X] 보유한 모든 카드의 합을 출력한다. +- [X] 최종 승패를 출력한다. diff --git a/src/main/java/blackjack/BlackjackApplication.java b/src/main/java/blackjack/BlackjackApplication.java new file mode 100644 index 00000000000..ff783cf3c42 --- /dev/null +++ b/src/main/java/blackjack/BlackjackApplication.java @@ -0,0 +1,15 @@ +package blackjack; + +import blackjack.view.InputView; +import blackjack.view.MessageResolver; +import blackjack.view.OutputView; + +public class BlackjackApplication { + public static void main(String[] args) { + final InputView inputView = new InputView(); + final OutputView outputView = new OutputView(new MessageResolver()); + + final BlackjackController blackjackController = new BlackjackController(inputView, outputView); + blackjackController.run(); + } +} diff --git a/src/main/java/blackjack/BlackjackController.java b/src/main/java/blackjack/BlackjackController.java new file mode 100644 index 00000000000..8c929b60c8e --- /dev/null +++ b/src/main/java/blackjack/BlackjackController.java @@ -0,0 +1,71 @@ +package blackjack; + +import blackjack.domain.cardgame.CardDeck; +import blackjack.domain.cardgame.CardGameResult; +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; +import blackjack.view.InputView; +import blackjack.view.OutputView; + +import java.util.List; + +public class BlackjackController { + private final InputView inputView; + private final OutputView outputView; + + BlackjackController(final InputView inputView, final OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + final List names = inputView.askPlayerNames(); + final Dealer dealer = new Dealer(); + final List players = names.stream().map(Player::new).toList(); + final CardDeck deck = CardDeck.createShuffledDeck(); + + initializeHand(deck, dealer, players); + outputView.printInitialHandOfEachPlayer(dealer, players); + + for (final Player player : players) { + givePlayerMoreCardsIfWanted(deck, player); + } + giveDealerMoreCardsIfNeeded(deck, dealer); + + printHandStatusOfEachPlayer(dealer, players); + printCardGameResult(dealer, players); + } + + private void initializeHand(final CardDeck deck, final Dealer dealer, final List players) { + deck.giveInitialCards(dealer); + for (final Player player : players) { + deck.giveInitialCards(player); + } + } + + private void givePlayerMoreCardsIfWanted(final CardDeck deck, final Player player) { + final String playerName = player.getName(); + while (player.isAlive() && inputView.askForMoreCard(playerName)) { + deck.giveCard(player); + outputView.printPlayerCard(player); + } + } + + private void giveDealerMoreCardsIfNeeded(final CardDeck deck, final Dealer dealer) { + while (dealer.isMoreCardNeeded()) { + deck.giveCard(dealer); + outputView.printDealerHitMessage(dealer); + } + } + + private void printHandStatusOfEachPlayer(final Dealer dealer, final List players) { + outputView.printPlayerCardWithScore(dealer); + for (final Player player : players) { + outputView.printPlayerCardWithScore(player); + } + } + + private void printCardGameResult(final Dealer dealer, final List players) { + outputView.printResult(CardGameResult.of(dealer, players)); + } +} diff --git a/src/main/java/blackjack/domain/card/Card.java b/src/main/java/blackjack/domain/card/Card.java new file mode 100644 index 00000000000..ddcc69bfb2b --- /dev/null +++ b/src/main/java/blackjack/domain/card/Card.java @@ -0,0 +1,27 @@ +package blackjack.domain.card; + +public class Card { + private final CardNumber cardNumber; + private final CardShape cardShape; + + public Card(final CardNumber cardNumber, final CardShape cardShape) { + this.cardNumber = cardNumber; + this.cardShape = cardShape; + } + + public boolean isAceCard() { + return cardNumber == CardNumber.ACE; + } + + public int getScore() { + return cardNumber.getValue(); + } + + public CardNumber getNumber() { + return cardNumber; + } + + public CardShape getShape() { + return cardShape; + } +} diff --git a/src/main/java/blackjack/domain/card/CardNumber.java b/src/main/java/blackjack/domain/card/CardNumber.java new file mode 100644 index 00000000000..c99818bc6f8 --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardNumber.java @@ -0,0 +1,27 @@ +package blackjack.domain.card; + +public enum CardNumber { + 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 value; + + CardNumber(final int value) { + this.value = value; + } + + int getValue() { + return value; + } +} diff --git a/src/main/java/blackjack/domain/card/CardShape.java b/src/main/java/blackjack/domain/card/CardShape.java new file mode 100644 index 00000000000..a3f9a873009 --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardShape.java @@ -0,0 +1,8 @@ +package blackjack.domain.card; + +public enum CardShape { + SPADE, + HEART, + DIAMOND, + CLOVER +} diff --git a/src/main/java/blackjack/domain/cardgame/CardDeck.java b/src/main/java/blackjack/domain/cardgame/CardDeck.java new file mode 100644 index 00000000000..e1137d35606 --- /dev/null +++ b/src/main/java/blackjack/domain/cardgame/CardDeck.java @@ -0,0 +1,57 @@ +package blackjack.domain.cardgame; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardNumber; +import blackjack.domain.card.CardShape; +import blackjack.domain.player.Player; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CardDeck { + private static final int INITIAL_CARD_NUMBER = 2; + + private final Deque deck; + + private CardDeck(Deque deck) { + this.deck = deck; + } + + public static CardDeck createShuffledDeck() { + final List allKindOfCards = Arrays.stream(CardShape.values()) + .flatMap(CardDeck::createEachNumber) + .collect(Collectors.toList()); + + Collections.shuffle(allKindOfCards); + + return new CardDeck(new LinkedList<>(allKindOfCards)); + } + + private static Stream createEachNumber(final CardShape cardShape) { + return Arrays.stream(CardNumber.values()) + .map(cardNumber -> new Card(cardNumber, cardShape)); + } + + public void giveInitialCards(Player player) { + for (int i = 0; i < INITIAL_CARD_NUMBER; i++) { + giveCard(player); + } + } + + public void giveCard(final Player player) { + player.addCard(this.draw()); + } + + private Card draw() { + if (deck.size() == 0) { + throw new IllegalStateException("카드가 존재하지 않습니다."); + } + + return deck.pop(); + } +} diff --git a/src/main/java/blackjack/domain/cardgame/CardGameResult.java b/src/main/java/blackjack/domain/cardgame/CardGameResult.java new file mode 100644 index 00000000000..db68cb3adfb --- /dev/null +++ b/src/main/java/blackjack/domain/cardgame/CardGameResult.java @@ -0,0 +1,44 @@ +package blackjack.domain.cardgame; + +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static blackjack.domain.cardgame.WinningStatus.LOSE; +import static blackjack.domain.cardgame.WinningStatus.WIN; + +public record CardGameResult(Map totalResult) { + public static CardGameResult of(final Dealer dealer, final List players) { + return new CardGameResult( + players.stream() + .collect(Collectors.toMap( + player -> player, + player -> WinningStatus.doesPlayerWin(dealer, player), + (key, value) -> key, + LinkedHashMap::new + ))); + } + + public Map totalResult() { + return Collections.unmodifiableMap(totalResult); + } + + public int getDealerWinCount() { + return (int) totalResult.values() + .stream() + .filter(playerWinningStatus -> playerWinningStatus.equals(LOSE)) + .count(); + } + + public int getDealerLoseCount() { + return (int) totalResult.values() + .stream() + .filter(status -> status.equals(WIN)) + .count(); + } +} diff --git a/src/main/java/blackjack/domain/cardgame/WinningStatus.java b/src/main/java/blackjack/domain/cardgame/WinningStatus.java new file mode 100644 index 00000000000..3bfe525c198 --- /dev/null +++ b/src/main/java/blackjack/domain/cardgame/WinningStatus.java @@ -0,0 +1,26 @@ +package blackjack.domain.cardgame; + +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; + +public enum WinningStatus { + WIN, + PUSH, + LOSE; + + public static WinningStatus doesPlayerWin(final Dealer dealer, final Player player) { + if (!player.isAlive()) { + return WinningStatus.LOSE; + } + if (!dealer.isAlive()) { + return WinningStatus.WIN; + } + if (dealer.getScore() == player.getScore()) { + return WinningStatus.PUSH; + } + if (dealer.getScore() < player.getScore()) { + return WinningStatus.WIN; + } + return WinningStatus.LOSE; + } +} diff --git a/src/main/java/blackjack/domain/player/Dealer.java b/src/main/java/blackjack/domain/player/Dealer.java new file mode 100644 index 00000000000..4638b67ac61 --- /dev/null +++ b/src/main/java/blackjack/domain/player/Dealer.java @@ -0,0 +1,23 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; + +public class Dealer extends Player { + private static final int HIT_CONDITION = 16; + + public Dealer() { + super("딜러"); + } + + public boolean isMoreCardNeeded() { + return this.getScore() <= HIT_CONDITION; + } + + public Card getFirstCard() { + try { + return hand.getAllCards().get(0); + } catch (IndexOutOfBoundsException e) { + throw new RuntimeException("[ERROR] 딜러가 카드를 갖고 있지 않습니다."); + } + } +} diff --git a/src/main/java/blackjack/domain/player/Hand.java b/src/main/java/blackjack/domain/player/Hand.java new file mode 100644 index 00000000000..58049df2272 --- /dev/null +++ b/src/main/java/blackjack/domain/player/Hand.java @@ -0,0 +1,43 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static blackjack.domain.player.Player.BUST_CONDITION; + +public class Hand { + private static final int BONUS_SCORE = 10; + private static final int NON_SCORE = 0; + + private final List cards = new ArrayList<>(); + + int getScore() { + final int minimumScore = cards.stream() + .mapToInt(Card::getScore) + .sum(); + final int bonusScore = this.getBonusScore(); + + if (minimumScore + bonusScore <= BUST_CONDITION) { + return minimumScore + bonusScore; + } + return minimumScore; + } + + private int getBonusScore() { + if (this.cards.stream().anyMatch(Card::isAceCard)) { + return BONUS_SCORE; + } + return NON_SCORE; + } + + void add(final Card card) { + cards.add(card); + } + + List getAllCards() { + return Collections.unmodifiableList(cards); + } +} diff --git a/src/main/java/blackjack/domain/player/Name.java b/src/main/java/blackjack/domain/player/Name.java new file mode 100644 index 00000000000..e0ac8a7555d --- /dev/null +++ b/src/main/java/blackjack/domain/player/Name.java @@ -0,0 +1,27 @@ +package blackjack.domain.player; + +public class Name { + private static final int MINIMUM_LENGTH = 1; + private static final int MAXIMUM_LENGTH = 10; + + private final String value; + + public Name(final String value) { + validateLength(value); + this.value = value; + } + + private void validateLength(final String value) { + if (MINIMUM_LENGTH <= value.length() && value.length() <= MAXIMUM_LENGTH) { + return; + } + + final String errorMessage = String.format("[ERROR] 이름의 길이는 %s 이상, %s 이하여야 합니다.", + MINIMUM_LENGTH, MAXIMUM_LENGTH); + throw new IllegalArgumentException(errorMessage); + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/blackjack/domain/player/Player.java b/src/main/java/blackjack/domain/player/Player.java new file mode 100644 index 00000000000..154842bd580 --- /dev/null +++ b/src/main/java/blackjack/domain/player/Player.java @@ -0,0 +1,37 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; + +import java.util.List; + +public class Player { + static final int BUST_CONDITION = 21; + + protected final Hand hand; + private final Name name; + + public Player(final String name) { + this.hand = new Hand(); + this.name = new Name(name); + } + + public final void addCard(final Card card) { + hand.add(card); + } + + public final boolean isAlive() { + return hand.getScore() <= BUST_CONDITION; + } + + public final int getScore() { + return hand.getScore(); + } + + public final List getCards() { + return hand.getAllCards(); + } + + public final String getName() { + return name.getValue(); + } +} diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java new file mode 100644 index 00000000000..f48003cf66b --- /dev/null +++ b/src/main/java/blackjack/view/InputView.java @@ -0,0 +1,36 @@ +package blackjack.view; + +import java.util.List; +import java.util.Scanner; + +import static blackjack.view.PlayerChoice.*; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + public List askPlayerNames() { + printPlayerNamesInputMessage(); + String rawInput = scanner.nextLine(); + return List.of(rawInput.split(",", -1)); + } + + public boolean askForMoreCard(final String name) { + printAskingForAnotherCardMessage(name); + final String rawInput = scanner.nextLine(); + return PlayerChoice.isDrawable(rawInput); + } + + private void printPlayerNamesInputMessage() { + System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); + } + + private void printAskingForAnotherCardMessage(final String name) { + printLineSeparator(); + System.out.println(name + "는 한장의 카드를 더 받겠습니까?(예는 " + HIT.getMessage() + + ", 아니오는 " + STAND.getMessage() + ")"); + } + + private void printLineSeparator() { + System.out.println(); + } +} diff --git a/src/main/java/blackjack/view/MessageResolver.java b/src/main/java/blackjack/view/MessageResolver.java new file mode 100644 index 00000000000..6b718f98850 --- /dev/null +++ b/src/main/java/blackjack/view/MessageResolver.java @@ -0,0 +1,119 @@ +package blackjack.view; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardNumber; +import blackjack.domain.card.CardShape; +import blackjack.domain.cardgame.CardGameResult; +import blackjack.domain.cardgame.WinningStatus; +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static blackjack.domain.card.CardNumber.*; +import static blackjack.domain.card.CardShape.CLOVER; +import static blackjack.domain.card.CardShape.DIAMOND; +import static blackjack.domain.card.CardShape.HEART; +import static blackjack.domain.card.CardShape.SPADE; +import static blackjack.domain.cardgame.WinningStatus.LOSE; +import static blackjack.domain.cardgame.WinningStatus.PUSH; +import static blackjack.domain.cardgame.WinningStatus.WIN; + +public class MessageResolver { + private static final Map CARD_SHAPE_NAME_MAP = Map.of( + SPADE, "스페이드", + HEART, "하트", + DIAMOND, "다이아몬드", + CLOVER, "클로버" + ); + + private static final Map CARD_NUMBER_NAME_MAP = Map.ofEntries( + Map.entry(ACE, "A"), Map.entry(TWO, "2"), + Map.entry(THREE, "3"), Map.entry(FOUR, "4"), + Map.entry(FIVE, "5"), Map.entry(SIX, "6"), + Map.entry(SEVEN, "7"), Map.entry(EIGHT, "8"), + Map.entry(NINE, "9"), Map.entry(TEN, "10"), + Map.entry(JACK, "J"), Map.entry(QUEEN, "Q"), + Map.entry(KING, "K") + ); + + private static final Map WINNING_STATUS_NAME_MAP = Map.of( + WIN, "승", + PUSH, "무", + LOSE, "패" + ); + + private static final String LINE_SEPARATOR = System.lineSeparator(); + private static final String PLAYERS_NAME_DELIMITER = ", "; + + public String resolveInitialHandOfEachPlayer(final Dealer dealer, final List players) { + final String initialDistributionMessage = resolveInitialDistributionMessage(dealer, players); + final String dealerCardMessage = resolveDealerCard(dealer); + final String playersCardMessage = players.stream().map(this::resolvePlayerCard).collect(Collectors.joining(LINE_SEPARATOR)); + return String.join(LINE_SEPARATOR, initialDistributionMessage, dealerCardMessage, playersCardMessage); + } + + private String resolveInitialDistributionMessage(final Dealer dealer, final List players) { + final String playerNames = resolvePlayerNames(players); + final String message = String.format("%s와 %s에게 2장을 나누었습니다.", dealer.getName(), playerNames); + return String.join("", LINE_SEPARATOR, message); + } + + private String resolvePlayerNames(final List players) { + return players.stream() + .map(Player::getName) + .collect(Collectors.joining(PLAYERS_NAME_DELIMITER)); + } + + private String resolveDealerCard(final Dealer dealer) { + final Card card = dealer.getFirstCard(); + return String.join(": ", dealer.getName(), resolveCardInfo(card)); + } + + public String resolvePlayerCard(final Player player) { + return resolvePlayerCardInfo(player); + } + + public String resolveDealerHitMessage(final Dealer dealer) { + final String dealerHitMessage = String.format("%s는 16이하라 한장의 카드를 더 받았습니다.", dealer.getName()); + return String.join("", dealerHitMessage, LINE_SEPARATOR); + } + + public String resolvePlayerCardWithScore(final Player player) { + return String.format("%s - 결과: %d", resolvePlayerCardInfo(player), player.getScore()); + } + + public String resolveResult(final CardGameResult cardGameResult) { + final String resultOfDealer = resolveResultOfDealer(cardGameResult); + final String resultOfEachPlayer = resolveResultOfEachPlayer(cardGameResult); + return String.join("", resultOfDealer, LINE_SEPARATOR, resultOfEachPlayer); + } + + private String resolveResultOfDealer(final CardGameResult cardGameResult) { + final String prefix = String.join("", LINE_SEPARATOR, "## 최종 승패"); + final String message = String.format("딜러: %d승 %d패", cardGameResult.getDealerWinCount(), cardGameResult.getDealerLoseCount()); + return String.join("", prefix, LINE_SEPARATOR, message); + } + + private String resolveResultOfEachPlayer(final CardGameResult cardGameResult) { + return cardGameResult.totalResult() + .entrySet() + .stream() + .map(result -> result.getKey().getName() + ": " + WINNING_STATUS_NAME_MAP.get(result.getValue())) + .collect(Collectors.joining(LINE_SEPARATOR)); + } + + private String resolvePlayerCardInfo(final Player player) { + final String cardsInfo = player.getCards() + .stream() + .map(this::resolveCardInfo) + .collect(Collectors.joining(", ")); + return String.format("%s카드: %s", player.getName(), cardsInfo); + } + + private String resolveCardInfo(final Card card) { + return CARD_NUMBER_NAME_MAP.get(card.getNumber()) + CARD_SHAPE_NAME_MAP.get(card.getShape()); + } +} diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java new file mode 100644 index 00000000000..edbd1e3dffc --- /dev/null +++ b/src/main/java/blackjack/view/OutputView.java @@ -0,0 +1,35 @@ +package blackjack.view; + +import blackjack.domain.cardgame.CardGameResult; +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; + +import java.util.List; + +public class OutputView { + private final MessageResolver messageResolver; + + public OutputView(final MessageResolver messageResolver) { + this.messageResolver = messageResolver; + } + + public void printInitialHandOfEachPlayer(final Dealer dealer, final List players) { + System.out.println(messageResolver.resolveInitialHandOfEachPlayer(dealer, players)); + } + + public void printPlayerCard(final Player player) { + System.out.println(messageResolver.resolvePlayerCard(player)); + } + + public void printDealerHitMessage(final Dealer dealer) { + System.out.println(messageResolver.resolveDealerHitMessage(dealer)); + } + + public void printPlayerCardWithScore(final Player player) { + System.out.println(messageResolver.resolvePlayerCardWithScore(player)); + } + + public void printResult(final CardGameResult cardGameResult) { + System.out.println(messageResolver.resolveResult(cardGameResult)); + } +} diff --git a/src/main/java/blackjack/view/PlayerChoice.java b/src/main/java/blackjack/view/PlayerChoice.java new file mode 100644 index 00000000000..cab73a8229c --- /dev/null +++ b/src/main/java/blackjack/view/PlayerChoice.java @@ -0,0 +1,28 @@ +package blackjack.view; + +public enum PlayerChoice { + HIT("y"), + STAND("n"); + + private final String message; + + PlayerChoice(final String message) { + this.message = message; + } + + static boolean isDrawable(final String choice) { + if (HIT.message.equals(choice)) { + return true; + } + if (STAND.message.equals(choice)) { + return false; + } + + final String errorMessage = String.format("%s 또는 %s 만 입력할 수 있습니다.", HIT.message, STAND.message); + throw new IllegalArgumentException(errorMessage); + } + + public String getMessage() { + return message; + } +} diff --git a/src/test/java/blackjack/domain/card/CardTest.java b/src/test/java/blackjack/domain/card/CardTest.java new file mode 100644 index 00000000000..4ee68e37d4b --- /dev/null +++ b/src/test/java/blackjack/domain/card/CardTest.java @@ -0,0 +1,24 @@ +package blackjack.domain.card; + +import org.junit.jupiter.api.Test; + +import static blackjack.domain.card.CardNumber.ACE; +import static blackjack.domain.card.CardNumber.TWO; +import static blackjack.domain.card.CardShape.HEART; +import static org.assertj.core.api.Assertions.assertThat; + +public class CardTest { + @Test + void 에이스_카드인지_확인한다() { + Card aceCard = new Card(ACE, HEART); + + assertThat(aceCard.isAceCard()).isTrue(); + } + + @Test + void 에이스_카드가_아닌지_확인한다() { + Card notAceCard = new Card(TWO, HEART); + + assertThat(notAceCard.isAceCard()).isFalse(); + } +} diff --git a/src/test/java/blackjack/domain/cardgame/CardDeckTest.java b/src/test/java/blackjack/domain/cardgame/CardDeckTest.java new file mode 100644 index 00000000000..2b736166086 --- /dev/null +++ b/src/test/java/blackjack/domain/cardgame/CardDeckTest.java @@ -0,0 +1,44 @@ +package blackjack.domain.cardgame; + +import blackjack.domain.player.Player; +import org.junit.jupiter.api.Test; + +import static blackjack.fixture.PlayerFixture.player; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +public class CardDeckTest { + @Test + void 덱에_정확히_52장의_카드가_존재한다() { + CardDeck deck = CardDeck.createShuffledDeck(); + Player player = player(); + + for (int ignored = 0; ignored < 52; ignored++) { + deck.giveCard(player); + } + + assertThatThrownBy(() -> deck.giveCard(player)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("카드가 존재하지 않습니다."); + } + + @Test + void 카드_한_장을_플레이어에게_지급한다() { + CardDeck deck = CardDeck.createShuffledDeck(); + Player player = player(); + + deck.giveCard(player); + + assertThat(player.getCards().size()).isEqualTo(1); + } + + @Test + void 플레이어에게_최초_2개의_카드를_지급한다() { + CardDeck deck = CardDeck.createShuffledDeck(); + Player player = player(); + + deck.giveInitialCards(player); + + assertThat(player.getCards().size()).isEqualTo(2); + } +} diff --git a/src/test/java/blackjack/domain/cardgame/CardGameResultTest.java b/src/test/java/blackjack/domain/cardgame/CardGameResultTest.java new file mode 100644 index 00000000000..87b5a405172 --- /dev/null +++ b/src/test/java/blackjack/domain/cardgame/CardGameResultTest.java @@ -0,0 +1,134 @@ +package blackjack.domain.cardgame; + +import blackjack.domain.card.Card; +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; +import org.junit.jupiter.api.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static blackjack.domain.card.CardNumber.ACE; +import static blackjack.domain.card.CardNumber.KING; +import static blackjack.domain.card.CardNumber.QUEEN; +import static blackjack.domain.card.CardNumber.TWO; +import static blackjack.domain.card.CardShape.CLOVER; +import static blackjack.domain.card.CardShape.DIAMOND; +import static blackjack.domain.card.CardShape.HEART; +import static blackjack.domain.card.CardShape.SPADE; +import static blackjack.domain.cardgame.WinningStatus.LOSE; +import static blackjack.domain.cardgame.WinningStatus.WIN; +import static blackjack.fixture.PlayerFixture.dealer; +import static blackjack.fixture.PlayerFixture.player; +import static org.assertj.core.api.Assertions.assertThat; + +class CardGameResultTest { + @Test + void 딜러가_승패_횟수를_계산할_수_있다() { + Map result = new LinkedHashMap<>(); + result.put(player(), WIN); + result.put(player(), WIN); + result.put(player(), LOSE); + + CardGameResult cardGameResult = new CardGameResult(result); + + int dealerWinCount = cardGameResult.getDealerWinCount(); + int dealerLoseCount = cardGameResult.getDealerLoseCount(); + + assertThat(dealerWinCount).isEqualTo(1); + assertThat(dealerLoseCount).isEqualTo(2); + } + + @Test + void 딜러와_플레이어_둘다_21을_초과할_경우에_플레이어가_패배한다() { + Player player = player( + new Card(KING, CLOVER), + new Card(KING, HEART), + new Card(KING, SPADE)); + Dealer dealer = dealer( + new Card(QUEEN, CLOVER), + new Card(QUEEN, HEART), + new Card(QUEEN, SPADE)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.LOSE); + } + + @Test + void 딜러와_여러_플레이어의_숫자가_21_이하인_경우_숫자가_큰_사람이_이긴다() { + Player player = player(new Card(KING, SPADE)); + Dealer dealer = dealer(new Card(TWO, SPADE)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.WIN); + } + + @Test + void 카드_합계가_딜러는_21_이하_플레이어는_21_초과인_경우_플레이어가_패배한다() { + Player player = player( + new Card(KING, CLOVER), + new Card(KING, HEART), + new Card(KING, DIAMOND)); + Dealer dealer = dealer(new Card(TWO, HEART)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.LOSE); + } + + @Test + void 카드_합계가_딜러는_21_초과_플레이어는_21_이하인_경우_플레이어가_승리한다() { + Player player = player(new Card(TWO, HEART)); + Dealer dealer = dealer( + new Card(KING, CLOVER), + new Card(KING, HEART), + new Card(KING, SPADE)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.WIN); + } + + @Test + void 카드_합계가_딜러와_플레이어_모두_21_이하인_경우_숫자가_큰_사람이_승리한다() { + Player player = player(new Card(ACE, HEART)); + Dealer dealer = dealer(new Card(KING, SPADE)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.WIN); + } + + @Test + void 카드_합계가_딜러와_플레이어_모두_21_이하이고_동일한_경우_무승부다() { + Player player = player(new Card(ACE, HEART)); + Dealer dealer = dealer(new Card(ACE, HEART)); + + var result = CardGameResult.of(dealer, List.of(player)) + .totalResult(); + + assertThat(result.get(player)).isEqualTo(WinningStatus.PUSH); + } + + @Test + void 게임_결과에서_플레이어가_순서를_유지하고_있다() { + Player playerA = player(new Card(TWO, HEART)); + Player playerB = player(new Card(TWO, SPADE)); + Player playerC = player(new Card(TWO, CLOVER)); + Player playerD = player(new Card(TWO, DIAMOND)); + Dealer dealer = dealer(new Card(ACE, HEART)); + + var result = CardGameResult.of(dealer, List.of(playerA, playerB, playerC, playerD)) + .totalResult(); + + assertThat(result.keySet()).containsExactly(playerA, playerB, playerC, playerD); + } +} diff --git a/src/test/java/blackjack/domain/player/DealerTest.java b/src/test/java/blackjack/domain/player/DealerTest.java new file mode 100644 index 00000000000..1b5865971f7 --- /dev/null +++ b/src/test/java/blackjack/domain/player/DealerTest.java @@ -0,0 +1,49 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; +import org.junit.jupiter.api.Test; + +import static blackjack.domain.card.CardNumber.ACE; +import static blackjack.domain.card.CardNumber.KING; +import static blackjack.domain.card.CardNumber.SEVEN; +import static blackjack.domain.card.CardNumber.SIX; +import static blackjack.domain.card.CardShape.SPADE; +import static blackjack.fixture.PlayerFixture.dealer; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class DealerTest { + @Test + void 합계가_16이하라면_카드가_더_필요하다() { + Dealer dealer = dealer( + new Card(KING, SPADE), + new Card(SIX, SPADE)); + + assertThat(dealer.isMoreCardNeeded()).isTrue(); + } + + @Test + void 합계가_16보다_크다면_카드가_더_필요하다() { + Dealer dealer = dealer( + new Card(KING, SPADE), + new Card(SEVEN, SPADE)); + + assertThat(dealer.isMoreCardNeeded()).isFalse(); + } + + @Test + void 딜러에게_카드가_있는_경우에_첫_카드를_요청하면_정상적으로_돌려준다() { + Dealer dealer = dealer(new Card(ACE, SPADE)); + + assertThat(dealer.getFirstCard()).isNotNull(); + } + + @Test + void 딜러에게_카드가_없는_경우에_첫_카드를_요청하면_예외가_발생한다() { + Dealer dealer = dealer(); + + assertThatThrownBy(dealer::getFirstCard) + .isInstanceOf(RuntimeException.class) + .hasMessage("[ERROR] 딜러가 카드를 갖고 있지 않습니다."); + } +} diff --git a/src/test/java/blackjack/domain/player/HandTest.java b/src/test/java/blackjack/domain/player/HandTest.java new file mode 100644 index 00000000000..ee47036d679 --- /dev/null +++ b/src/test/java/blackjack/domain/player/HandTest.java @@ -0,0 +1,49 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; +import org.junit.jupiter.api.Test; + +import static blackjack.domain.card.CardNumber.ACE; +import static blackjack.domain.card.CardNumber.JACK; +import static blackjack.domain.card.CardNumber.KING; +import static blackjack.domain.card.CardNumber.QUEEN; +import static blackjack.domain.card.CardShape.SPADE; +import static org.assertj.core.api.Assertions.assertThat; + +public class HandTest { + @Test + void 가진_패의_숫자의_합계를_구할_수_있다() { + Hand hand = new Hand(); + hand.add(new Card(JACK, SPADE)); + hand.add(new Card(QUEEN, SPADE)); + hand.add(new Card(KING, SPADE)); + + int sum = hand.getScore(); + + assertThat(sum).isEqualTo(30); + } + + @Test + void 에이스_카드가_4개인_경우에_합계를_구할_수_있다() { + Hand hand = new Hand(); + hand.add(new Card(ACE, SPADE)); + hand.add(new Card(ACE, SPADE)); + hand.add(new Card(ACE, SPADE)); + hand.add(new Card(ACE, SPADE)); + + int sum = hand.getScore(); + + assertThat(sum).isEqualTo(14); + } + + @Test + void 에이스_카드를_포함해서_합계_21인_경우에_정확하게_계산할_수_있다() { + Hand hand = new Hand(); + hand.add(new Card(ACE, SPADE)); + hand.add(new Card(KING, SPADE)); + + int sum = hand.getScore(); + + assertThat(sum).isEqualTo(21); + } +} diff --git a/src/test/java/blackjack/domain/player/NameTest.java b/src/test/java/blackjack/domain/player/NameTest.java new file mode 100644 index 00000000000..f19063006a6 --- /dev/null +++ b/src/test/java/blackjack/domain/player/NameTest.java @@ -0,0 +1,15 @@ +package blackjack.domain.player; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class NameTest { + @ParameterizedTest + @ValueSource(strings = {"", "01234567890"}) + void 이름의_길이는_1이상_10이하여야_한다(String value) { + assertThatThrownBy(() -> new Name(value)) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/blackjack/domain/player/PlayerTest.java b/src/test/java/blackjack/domain/player/PlayerTest.java new file mode 100644 index 00000000000..cfa54ae0953 --- /dev/null +++ b/src/test/java/blackjack/domain/player/PlayerTest.java @@ -0,0 +1,25 @@ +package blackjack.domain.player; + +import blackjack.domain.card.Card; +import org.junit.jupiter.api.Test; + +import static blackjack.domain.card.CardNumber.KING; +import static blackjack.domain.card.CardShape.CLOVER; +import static blackjack.domain.card.CardShape.HEART; +import static blackjack.domain.card.CardShape.SPADE; +import static blackjack.fixture.PlayerFixture.player; +import static org.assertj.core.api.Assertions.assertThat; + +public class PlayerTest { + @Test + void 플레이어는_죽었는지_여부를_반환한다() { + Player player = player( + new Card(KING, CLOVER), + new Card(KING, SPADE), + new Card(KING, HEART)); + + boolean isAlive = player.isAlive(); + + assertThat(isAlive).isFalse(); + } +} diff --git a/src/test/java/blackjack/fixture/PlayerFixture.java b/src/test/java/blackjack/fixture/PlayerFixture.java new file mode 100644 index 00000000000..a5b541e8ee7 --- /dev/null +++ b/src/test/java/blackjack/fixture/PlayerFixture.java @@ -0,0 +1,23 @@ +package blackjack.fixture; + +import blackjack.domain.card.Card; +import blackjack.domain.player.Dealer; +import blackjack.domain.player.Player; + +public class PlayerFixture { + public static Player player(Card... cards) { + Player player = new Player("player"); + for (Card card : cards) { + player.addCard(card); + } + return player; + } + + public static Dealer dealer(Card... cards) { + Dealer dealer = new Dealer(); + for (Card card : cards) { + dealer.addCard(card); + } + return dealer; + } +}