diff --git a/README.md b/README.md index 3bcfc25784..4965fcb8e5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ # java-blackjack 블랙잭 게임 미션 저장소 -## 우아한테크코스 코드리뷰 -* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) \ No newline at end of file +## 도메인 기능 요구사항 +- [x] 딜러가 게임 참가자들에게 카드를 2장씩 나눈다. +- [x] 딜러와 플레이어들은 카드를 가질 수 있다. +- [x] 각 플레이어들의 카드 합을 구한다. + - [x] Ace는 1 또는 11로 계산하고, King, Queen, Jack은 10으로 계산한다. +- [x] 플레이어들의 카드 합이 21을 넘는지 확인한다. +- [x] 딜러의 카드 합이 16 이하인 경우 카드를 계속해서 더 받는다. + +## 입출력 기능 요구사항 +- [x] 게임에 참여할 사람의 이름을 입력받는다. + - [x] (예외) 공백이 입력될 경우 +- [x] 딜러와 플레이어의 카드를 출력한다. + - [x] 딜러는 2장 중 1장만 출력한다. +- [x] 플레이어들을 순회하며 카드를 더 받을지 물어본다. +- [x] 딜러와 플레이어들의 카드 목록과 합을 출력한다. +- [x] 최종 승패를 출력한다. \ No newline at end of file diff --git a/src/main/java/blackjack/BlackJackApplication.java b/src/main/java/blackjack/BlackJackApplication.java new file mode 100644 index 0000000000..3ecbd79b31 --- /dev/null +++ b/src/main/java/blackjack/BlackJackApplication.java @@ -0,0 +1,28 @@ +package blackjack; + +import blackjack.domain.card.CardFactory; +import blackjack.domain.deck.Deck; +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Players; +import blackjack.dto.GamersResultAssembler; +import blackjack.dto.GamersResultDto; +import blackjack.view.InputView; +import blackjack.view.OutputView; + +public class BlackJackApplication { + + public static void main(String[] args) { + Deck deck = new Deck(CardFactory.generate()); + Dealer dealer = new Dealer(); + Players players = Players.of(InputView.askPlayerNames().get()); + + BlackJackController.initializeCard(dealer, players, deck); + OutputView.printInitialCards(dealer, players); + + BlackJackController.drawMoreCard(dealer, players, deck); + OutputView.printGamerScore(dealer, players); + + GamersResultDto gamersResultDto = GamersResultAssembler.of(dealer, players); + OutputView.printGamersResult(gamersResultDto); + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/BlackJackController.java b/src/main/java/blackjack/BlackJackController.java new file mode 100644 index 0000000000..ec406f07ea --- /dev/null +++ b/src/main/java/blackjack/BlackJackController.java @@ -0,0 +1,47 @@ +package blackjack; + +import blackjack.domain.deck.Deck; +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Player; +import blackjack.domain.gamer.Players; +import blackjack.domain.rule.HandInitializer; +import blackjack.domain.rule.PlayerAnswer; +import blackjack.view.InputView; +import blackjack.view.OutputView; + +public class BlackJackController { + + public static void initializeCard(Dealer dealer, Players players, Deck deck) { + HandInitializer.initialize(dealer, players, deck); + } + + public static void drawMoreCard(Dealer dealer, Players players, Deck deck) { + hitOrStandForPlayers(players, deck); + drawCardForDealer(dealer, deck); + } + + private static void hitOrStandForPlayers(Players players, Deck deck) { + for (Player player : players) { + hitOrStand(player, deck); + } + } + + private static void drawCardForDealer(Dealer dealer, Deck deck) { + while (dealer.canDrawCard()) { + dealer.draw(deck.pick()); + OutputView.printDealerDrewCard(); + } + } + + private static void hitOrStand(Player player, Deck deck) { + while (player.canDrawCard() && wantMoreCard(player)) { + player.draw(deck.pick()); + OutputView.printPlayerHand(player); + } + } + + private static boolean wantMoreCard(Player player) { + PlayerAnswer answer = PlayerAnswer.of(InputView.askHitOrStand(player)); + return answer.isYes(); + } +} 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 0000000000..2552320e6c --- /dev/null +++ b/src/main/java/blackjack/domain/card/Card.java @@ -0,0 +1,29 @@ +package blackjack.domain.card; + +public class Card { + + private CardSymbol cardSymbol; + private CardType cardType; + + public Card(CardSymbol cardSymbol, CardType cardType) { + this.cardSymbol = cardSymbol; + this.cardType = cardType; + } + + public boolean isAce() { + return cardSymbol.isAce(); + } + + public int getCardNumber() { + return cardSymbol.getCardNumber(); + } + + public CardType getCardType() { + return cardType; + } + + @Override + public String toString() { + return cardSymbol.getCardSymbol() + cardType.getKoreanName(); + } +} diff --git a/src/main/java/blackjack/domain/card/CardFactory.java b/src/main/java/blackjack/domain/card/CardFactory.java new file mode 100644 index 0000000000..e407d85979 --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardFactory.java @@ -0,0 +1,23 @@ +package blackjack.domain.card; + +import java.util.ArrayList; +import java.util.List; + +public class CardFactory { + + public static List generate() { + + List cards = new ArrayList<>(); + + for (CardSymbol symbol : CardSymbol.values()) { + createByType(cards, symbol); + } + return cards; + } + + private static void createByType(List cards, CardSymbol symbol) { + for (CardType type : CardType.values()) { + cards.add(new Card(symbol, type)); + } + } +} diff --git a/src/main/java/blackjack/domain/card/CardSymbol.java b/src/main/java/blackjack/domain/card/CardSymbol.java new file mode 100644 index 0000000000..3596a6d1b6 --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardSymbol.java @@ -0,0 +1,38 @@ +package blackjack.domain.card; + +public enum CardSymbol { + + ACE(11, "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"); + + private final int cardNumber; + private final String cardSymbol; + + CardSymbol(int cardNumber, String cardSymbol) { + this.cardNumber = cardNumber; + this.cardSymbol = cardSymbol; + } + + public boolean isAce() { + return this == ACE; + } + + public int getCardNumber() { + return cardNumber; + } + + public String getCardSymbol() { + return cardSymbol; + } +} diff --git a/src/main/java/blackjack/domain/card/CardType.java b/src/main/java/blackjack/domain/card/CardType.java new file mode 100644 index 0000000000..df03c59d3b --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardType.java @@ -0,0 +1,20 @@ +package blackjack.domain.card; + +public enum CardType { + + SPADE("스페이드"), + HEART("하트"), + CLOVER("클로버"), + DIAMOND("다이아몬드"); + + private final String koreanName; + + CardType(String koreanName) { + this.koreanName = koreanName; + } + + public String getKoreanName() { + return koreanName; + } + +} diff --git a/src/main/java/blackjack/domain/card/Hand.java b/src/main/java/blackjack/domain/card/Hand.java new file mode 100644 index 0000000000..e58c186d92 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Hand.java @@ -0,0 +1,27 @@ +package blackjack.domain.card; + +import blackjack.domain.rule.Score; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Hand { + + private final List hand = new ArrayList<>(); + + public void add(Card card) { + if (Objects.isNull(card)) { + throw new IllegalArgumentException("카드를 추가할 수 없습니다."); + } + hand.add(card); + } + + public Score getScore() { + return Score.calculateScore(hand); + } + + public List getCardStatus() { + return hand; + } +} diff --git a/src/main/java/blackjack/domain/deck/Deck.java b/src/main/java/blackjack/domain/deck/Deck.java new file mode 100644 index 0000000000..11695b9cb7 --- /dev/null +++ b/src/main/java/blackjack/domain/deck/Deck.java @@ -0,0 +1,28 @@ +package blackjack.domain.deck; + +import blackjack.domain.card.Card; + +import java.util.Collections; +import java.util.EmptyStackException; +import java.util.List; +import java.util.Stack; + +public class Deck { + + private final Stack cards = new Stack<>(); + + public Deck(List cards) { + if (cards.isEmpty() || cards == null) { + throw new IllegalArgumentException("카드 덱을 생성할 수 없습니다."); + } + Collections.shuffle(cards); + this.cards.addAll(cards); + } + + public Card pick() { + if (cards.empty()) { + throw new EmptyStackException(); + } + return cards.pop(); + } +} diff --git a/src/main/java/blackjack/domain/gamer/Dealer.java b/src/main/java/blackjack/domain/gamer/Dealer.java new file mode 100644 index 0000000000..ece1083ef0 --- /dev/null +++ b/src/main/java/blackjack/domain/gamer/Dealer.java @@ -0,0 +1,26 @@ +package blackjack.domain.gamer; + +import blackjack.domain.card.Card; +import static blackjack.domain.rule.Score.SCORES; + +public class Dealer extends Gamer { + + private static final int DRAW_THRESHOLD = 16; + + @Override + public boolean canDrawCard() { + if (handScore().compareTo(SCORES.get(DRAW_THRESHOLD)) > 0) { + return false; + } + return true; + } + + @Override + public String getName() { + return "딜러"; + } + + public Card getOpenCard() { + return hand.getCardStatus().get(0); + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/gamer/Gamer.java b/src/main/java/blackjack/domain/gamer/Gamer.java new file mode 100644 index 0000000000..3353e1dc2b --- /dev/null +++ b/src/main/java/blackjack/domain/gamer/Gamer.java @@ -0,0 +1,36 @@ +package blackjack.domain.gamer; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Hand; +import blackjack.domain.rule.Score; + +import java.util.List; + +public abstract class Gamer { + + protected Hand hand = new Hand(); + + public void draw(Card card) { + hand.add(card); + } + + public boolean isBusted() { + return handScore().isBusted(); + } + + public Score handScore() { + return hand.getScore(); + } + + public abstract boolean canDrawCard(); + + public abstract String getName(); + + public List getHand() { + return hand.getCardStatus(); + } + + public int getHandScore() { + return handScore().getNumber(); + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/gamer/Player.java b/src/main/java/blackjack/domain/gamer/Player.java new file mode 100644 index 0000000000..176123bab7 --- /dev/null +++ b/src/main/java/blackjack/domain/gamer/Player.java @@ -0,0 +1,26 @@ +package blackjack.domain.gamer; + +import blackjack.domain.rule.HandCalculator; + +import static blackjack.domain.rule.Score.SCORES; + +import java.util.Objects; + +public class Player extends Gamer { + + private final String name; + + public Player(String name) { + this.name = Objects.requireNonNull(name); + } + + @Override + public boolean canDrawCard() { + return !handScore().isBusted(); + } + + @Override + public String getName() { + return name; + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/gamer/Players.java b/src/main/java/blackjack/domain/gamer/Players.java new file mode 100644 index 0000000000..958fc17174 --- /dev/null +++ b/src/main/java/blackjack/domain/gamer/Players.java @@ -0,0 +1,32 @@ +package blackjack.domain.gamer; + +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +public class Players implements Iterable { + + private final List players; + + private Players(List players) { + this.players = players; + } + + public static Players of(List names) { + List players = names.stream() + .map(Player::new) + .collect(Collectors.toList()); + return new Players(players); + } + + public List getNames() { + return players.stream() + .map(Player::getName) + .collect(Collectors.toList()); + } + + @Override + public Iterator iterator() { + return players.iterator(); + } +} diff --git a/src/main/java/blackjack/domain/result/BlackJackResult.java b/src/main/java/blackjack/domain/result/BlackJackResult.java new file mode 100644 index 0000000000..c7e4ac0eea --- /dev/null +++ b/src/main/java/blackjack/domain/result/BlackJackResult.java @@ -0,0 +1,28 @@ +package blackjack.domain.result; + +public enum BlackJackResult { + + WIN("승"), + DRAW("무"), + LOSE("패"); + + private final String koreanName; + + BlackJackResult(String koreanName) { + this.koreanName = koreanName; + } + + public BlackJackResult reversed() { + if (this == WIN) { + return LOSE; + } + if (this == LOSE) { + return WIN; + } + return DRAW; + } + + public String getKoreanName() { + return koreanName; + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/result/PlayerResultMatcher.java b/src/main/java/blackjack/domain/result/PlayerResultMatcher.java new file mode 100644 index 0000000000..f00521a3a7 --- /dev/null +++ b/src/main/java/blackjack/domain/result/PlayerResultMatcher.java @@ -0,0 +1,33 @@ +package blackjack.domain.result; + +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Player; +import blackjack.domain.rule.Score; + +public class PlayerResultMatcher { + + public static BlackJackResult match(Dealer dealer, Player player) { + if (player.isBusted()) { + return BlackJackResult.LOSE; + } + if (dealer.isBusted()) { + return BlackJackResult.WIN; + } + return findWinner(dealer, player); + } + + private static BlackJackResult findWinner(Dealer dealer, Player player) { + Score dealerScore = dealer.handScore(); + Score playerScore = player.handScore(); + + int matchResult = playerScore.compareTo(dealerScore); + + if (matchResult > 0) { + return BlackJackResult.WIN; + } + if (matchResult < 0) { + return BlackJackResult.LOSE; + } + return BlackJackResult.DRAW; + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/rule/HandCalculator.java b/src/main/java/blackjack/domain/rule/HandCalculator.java new file mode 100644 index 0000000000..f51734dcf8 --- /dev/null +++ b/src/main/java/blackjack/domain/rule/HandCalculator.java @@ -0,0 +1,31 @@ +package blackjack.domain.rule; + +import blackjack.domain.card.Card; + +import java.util.List; + +public class HandCalculator { + + public static final int BUST_THRESHOLD = 21; + private static final int ACE_NUMBER_GAP = 10; + + public static int calculate(List cards) { + int result = sumAll(cards); + return subtractIfContainingAce(cards, result); + } + + private static int sumAll(List cards) { + return cards.stream() + .mapToInt(Card::getCardNumber) + .sum(); + } + + private static int subtractIfContainingAce(List cards, int result) { + for (Card card : cards) { + if (result > BUST_THRESHOLD && card.isAce()) { + result -= ACE_NUMBER_GAP; + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/domain/rule/HandInitializer.java b/src/main/java/blackjack/domain/rule/HandInitializer.java new file mode 100644 index 0000000000..0ef017376a --- /dev/null +++ b/src/main/java/blackjack/domain/rule/HandInitializer.java @@ -0,0 +1,24 @@ +package blackjack.domain.rule; + +import blackjack.domain.deck.Deck; +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Player; +import blackjack.domain.gamer.Players; + +public class HandInitializer { + + private static final int INITIAL_HAND_COUNT = 2; + + public static void initialize(Dealer dealer, Players players, Deck deck) { + for (int i = 0; i < INITIAL_HAND_COUNT; i++) { + drawCard(dealer, players, deck); + } + } + + private static void drawCard(Dealer dealer, Players players, Deck deck) { + dealer.draw(deck.pick()); + for (Player player : players) { + player.draw(deck.pick()); + } + } +} diff --git a/src/main/java/blackjack/domain/rule/PlayerAnswer.java b/src/main/java/blackjack/domain/rule/PlayerAnswer.java new file mode 100644 index 0000000000..bb89b62f14 --- /dev/null +++ b/src/main/java/blackjack/domain/rule/PlayerAnswer.java @@ -0,0 +1,16 @@ +package blackjack.domain.rule; + +public enum PlayerAnswer { + + Y, + N; + + public static PlayerAnswer of(String answer) { + return valueOf(answer.toUpperCase()); + } + + public boolean isYes() { + return this == Y; + } +} + diff --git a/src/main/java/blackjack/domain/rule/Score.java b/src/main/java/blackjack/domain/rule/Score.java new file mode 100644 index 0000000000..df42cdc411 --- /dev/null +++ b/src/main/java/blackjack/domain/rule/Score.java @@ -0,0 +1,51 @@ +package blackjack.domain.rule; + +import blackjack.domain.card.Card; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Score implements Comparable { + + private static final int BUSTED = 0; + private static final int BUST_THRESHOLD = 21; + + public static final Map SCORES; + + private int score; + + static { + SCORES = new HashMap<>(); + + for (int i = 0; i <= BUST_THRESHOLD; i++) { + SCORES.put(i, new Score(i)); + } + } + + private Score(int score) { + this.score = score; + } + + public static Score calculateScore(List cards) { + int score = HandCalculator.calculate(cards); + + if (score > BUST_THRESHOLD) { + return SCORES.get(BUSTED); + } + return SCORES.get(score); + } + + public boolean isBusted() { + return score == BUSTED; + } + + public int getNumber() { + return score; + } + + @Override + public int compareTo(Score target) { + return Integer.compare(this.score, target.score); + } +} diff --git a/src/main/java/blackjack/dto/GamersResultAssembler.java b/src/main/java/blackjack/dto/GamersResultAssembler.java new file mode 100644 index 0000000000..21069e32b8 --- /dev/null +++ b/src/main/java/blackjack/dto/GamersResultAssembler.java @@ -0,0 +1,28 @@ +package blackjack.dto; + +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Player; +import blackjack.domain.gamer.Players; +import blackjack.domain.result.BlackJackResult; +import blackjack.domain.result.PlayerResultMatcher; + +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class GamersResultAssembler { + + public static GamersResultDto of(Dealer dealer, Players players) { + EnumMap dealerResult = new EnumMap<>(BlackJackResult.class); + Map playersResult = new LinkedHashMap<>(); + + for (Player player : players) { + BlackJackResult result = PlayerResultMatcher.match(dealer, player); + playersResult.put(player, result); + dealerResult.computeIfPresent(result.reversed(), (key, value) -> ++value); + dealerResult.putIfAbsent(result.reversed(), 1); + } + + return new GamersResultDto(dealerResult, playersResult); + } +} diff --git a/src/main/java/blackjack/dto/GamersResultDto.java b/src/main/java/blackjack/dto/GamersResultDto.java new file mode 100644 index 0000000000..322bdda33e --- /dev/null +++ b/src/main/java/blackjack/dto/GamersResultDto.java @@ -0,0 +1,31 @@ +package blackjack.dto; + +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Player; +import blackjack.domain.gamer.Players; +import blackjack.domain.result.BlackJackResult; +import blackjack.domain.result.PlayerResultMatcher; + +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public final class GamersResultDto { + + private final Map dealerResult; + private final Map playersResult; + + protected GamersResultDto(Map dealerResult, Map playersResult) { + this.dealerResult = dealerResult; + this.playersResult = playersResult; + } + + public Map getDealerResult() { + return dealerResult; + } + + public Map getPlayersResult() { + return playersResult; + } +} \ No newline at end of file diff --git a/src/main/java/blackjack/dto/NamesDto.java b/src/main/java/blackjack/dto/NamesDto.java new file mode 100644 index 0000000000..d877eec8df --- /dev/null +++ b/src/main/java/blackjack/dto/NamesDto.java @@ -0,0 +1,28 @@ +package blackjack.dto; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class NamesDto { + private final List names; + + public NamesDto(String names) { + this.names = validate(names); + } + + private List validate(String names) { + List splittedNames = Arrays.stream(names.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + + if (names == null || splittedNames.isEmpty()) + throw new NullPointerException("비어있는 값을 입력하셨습니다"); + + return splittedNames; + } + + public List get() { + return names; + } +} diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java new file mode 100644 index 0000000000..e764e93829 --- /dev/null +++ b/src/main/java/blackjack/view/InputView.java @@ -0,0 +1,23 @@ +package blackjack.view; + +import blackjack.domain.gamer.Player; +import blackjack.dto.NamesDto; + +import java.util.Scanner; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + private static final String ASK_PLAYER_NAMES_MESSAGE = "게임에 참여할 사람의 이름을 입력하세요. (쉼표 기준으로 분리)"; + private static final String ASK_HIT_OR_STAND_MESSAGE = "%s는 한 장의 카드를 더 받겠습니까?(에는 y, 아니오는 n)"; + + public static NamesDto askPlayerNames() { + System.out.println(ASK_PLAYER_NAMES_MESSAGE); + return new NamesDto(SCANNER.nextLine()); + } + + public static String askHitOrStand(Player player) { + System.out.println(String.format(ASK_HIT_OR_STAND_MESSAGE, player.getName())); + return SCANNER.nextLine(); + } +} diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java new file mode 100644 index 0000000000..c6ccb50276 --- /dev/null +++ b/src/main/java/blackjack/view/OutputView.java @@ -0,0 +1,85 @@ +package blackjack.view; + +import blackjack.domain.card.Card; +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Gamer; +import blackjack.domain.gamer.Player; +import blackjack.domain.gamer.Players; +import blackjack.domain.result.BlackJackResult; +import blackjack.dto.GamersResultDto; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputView { + + private static final String INITIAL_CARD_FORMAT = "딜러와 %s에게 2장의 카드를 나누었습니다."; + private static final String CARD_STATUS_FORMAT = "%s 카드 : %s"; + private static final String DEALER_DREW_A_CARD_MESSAGE = "딜러는 16이하라 1장의 카드를 더 받았습니다."; + private static final String CARD_STATUS_AND_RESULT_FORMAT = "%s 카드 : %s - 결과 : %d"; + private static final String RESULT_MESSAGE = "## 최종 승패"; + private static final String GAMERS_RESULT_FORMAT = "%s : %s"; + private static final String CARD_FORMAT = "%s%s"; + private static final String DELIMITER = ", "; + + public static void printInitialCards(Dealer dealer, Players players) { + System.out.print(System.lineSeparator()); + System.out.println(String.format(INITIAL_CARD_FORMAT, players.getNames().stream().collect(Collectors.joining(DELIMITER)))); + printInitialHand(dealer, players); + System.out.print(System.lineSeparator()); + } + + public static void printPlayerHand(Player player) { + System.out.println(String.format(CARD_STATUS_FORMAT, player.getName(), makeHandResult(player.getHand()))); + } + + public static void printDealerDrewCard() { + System.out.print(System.lineSeparator() + DEALER_DREW_A_CARD_MESSAGE); + } + + public static void printGamerScore(Dealer dealer, Players players) { + System.out.println(System.lineSeparator()); + printCardStatusAndResult(dealer); + for (Player player : players) { + printCardStatusAndResult(player); + } + } + + public static void printGamersResult(GamersResultDto gamersResultDto) { + System.out.println(System.lineSeparator() + RESULT_MESSAGE); + System.out.println(String.format(GAMERS_RESULT_FORMAT, "딜러", makeDealerResult(gamersResultDto.getDealerResult()))); + System.out.println(makePlayersResult(gamersResultDto.getPlayersResult())); + } + + private static void printInitialHand(Dealer dealer, Players players) { + System.out.println(String.format(CARD_STATUS_FORMAT, dealer.getName(), dealer.getOpenCard().toString())); + for (Player player : players) { + printPlayerHand(player); + } + } + + private static void printCardStatusAndResult(Gamer gamer) { + System.out.println(String.format(CARD_STATUS_AND_RESULT_FORMAT, gamer.getName(), makeHandResult(gamer.getHand()), gamer.getHandScore())); + } + + private static String makeHandResult(List cards) { + return cards.stream() + .map(card -> String.format(CARD_FORMAT, card.getCardNumber(), card.getCardType().getKoreanName())) + .collect(Collectors.joining(DELIMITER)); + } + + private static String makeDealerResult(Map dealerResult) { + StringBuilder result = new StringBuilder(); + for (Map.Entry entry : dealerResult.entrySet()) { + result.append(entry.getValue() + entry.getKey().getKoreanName()); + } + return result.toString(); + } + + private static String makePlayersResult(Map playersResult) { + return playersResult.entrySet().stream() + .map(entry -> String.format(GAMERS_RESULT_FORMAT, entry.getKey().getName(), entry.getValue().getKoreanName())) + .collect(Collectors.joining(System.lineSeparator())); + } +} \ No newline at end of file diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/interfacepractice/Avante.java b/src/main/java/interfacepractice/Avante.java new file mode 100644 index 0000000000..f8509a9430 --- /dev/null +++ b/src/main/java/interfacepractice/Avante.java @@ -0,0 +1,15 @@ +package interfacepractice; + +public class Avante extends Car { + + private static int FUEL_EFFICIENCY = 15; + private static String NAME = "Avante"; + + public Avante(int distance) { + super(FUEL_EFFICIENCY, distance); + } + + public String getName() { + return NAME; + } +} diff --git a/src/main/java/interfacepractice/Car.java b/src/main/java/interfacepractice/Car.java new file mode 100644 index 0000000000..3f1fd9520f --- /dev/null +++ b/src/main/java/interfacepractice/Car.java @@ -0,0 +1,18 @@ +package interfacepractice; + +public abstract class Car { + + private int fuelEfficiency; + private int distance; + + public Car(int fuelEfficiency, int distance) { + this.fuelEfficiency = fuelEfficiency; + this.distance = distance; + } + + public int getNeededFuel() { + return distance / fuelEfficiency; + } + + public abstract String getName(); +} diff --git a/src/main/java/interfacepractice/K5.java b/src/main/java/interfacepractice/K5.java new file mode 100644 index 0000000000..a81fce1ac0 --- /dev/null +++ b/src/main/java/interfacepractice/K5.java @@ -0,0 +1,15 @@ +package interfacepractice; + +public class K5 extends Car { + + private static int FUEL_EFFICIENCY = 13; + private static String NAME = "K5"; + + public K5(int distance) { + super(FUEL_EFFICIENCY, distance); + } + + public String getName() { + return NAME; + } +} diff --git a/src/main/java/interfacepractice/RentCompany.java b/src/main/java/interfacepractice/RentCompany.java new file mode 100644 index 0000000000..984d707aa7 --- /dev/null +++ b/src/main/java/interfacepractice/RentCompany.java @@ -0,0 +1,30 @@ +package interfacepractice; + +import java.util.ArrayList; +import java.util.List; + +public class RentCompany { + + public static final String NEWLINE = System.getProperty("line.separator"); + + private List cars = new ArrayList<>(); + + public static RentCompany create() { + return new RentCompany(); + } + + public void addCar(Car car) { + cars.add(car); + } + + public String generateReport() { + StringBuilder stringBuilder = new StringBuilder(); + for (Car car : cars) { + stringBuilder.append(car.getName()); + stringBuilder.append(" : "); + stringBuilder.append(car.getNeededFuel() + "리터"); + stringBuilder.append(NEWLINE); + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/interfacepractice/Sonata.java b/src/main/java/interfacepractice/Sonata.java new file mode 100644 index 0000000000..26b3759840 --- /dev/null +++ b/src/main/java/interfacepractice/Sonata.java @@ -0,0 +1,15 @@ +package interfacepractice; + +public class Sonata extends Car { + + private static int FUEL_EFFICIENCY = 10; + private static String NAME = "Sonata"; + + public Sonata(int distance) { + super(FUEL_EFFICIENCY, distance); + } + + public String getName() { + return NAME; + } +} diff --git a/src/test/java/blackjack/domain/card/CardFactoryTest.java b/src/test/java/blackjack/domain/card/CardFactoryTest.java new file mode 100644 index 0000000000..1632a98ac1 --- /dev/null +++ b/src/test/java/blackjack/domain/card/CardFactoryTest.java @@ -0,0 +1,18 @@ +package blackjack.domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CardFactoryTest { + + @Test + @DisplayName("카드 리스트 생성") + void generate() { + List cards = CardFactory.generate(); + assertThat(cards.size()).isEqualTo(52); + } +} 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 0000000000..fc437ef961 --- /dev/null +++ b/src/test/java/blackjack/domain/card/CardTest.java @@ -0,0 +1,16 @@ +package blackjack.domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CardTest { + + @Test + @DisplayName("카드 생성") + void cardTest() { + Card card = new Card(CardSymbol.ACE, CardType.SPADE); + assertThat(card.toString()).isEqualTo("A스페이드"); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/card/HandTest.java b/src/test/java/blackjack/domain/card/HandTest.java new file mode 100644 index 0000000000..792fd92c1e --- /dev/null +++ b/src/test/java/blackjack/domain/card/HandTest.java @@ -0,0 +1,18 @@ +package blackjack.domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class HandTest { + @Test + @DisplayName("카드 리스트 출력") + void getCardStatus() { + Hand hand = new Hand(); + hand.add(new Card(CardSymbol.ACE, CardType.SPADE)); + hand.add(new Card(CardSymbol.FIVE, CardType.HEART)); + + assertThat(hand.getCardStatus().size()).isEqualTo(2); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/deck/DeckTest.java b/src/test/java/blackjack/domain/deck/DeckTest.java new file mode 100644 index 0000000000..d412995e29 --- /dev/null +++ b/src/test/java/blackjack/domain/deck/DeckTest.java @@ -0,0 +1,35 @@ +package blackjack.domain.deck; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardFactory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class DeckTest { + + @Test + @DisplayName("카드 꺼내기") + void pick() { + List cards = CardFactory.generate(); + Deck deck = new Deck(cards); + assertThat(deck.pick()).isInstanceOf(Card.class); + } + + @Test + @DisplayName("카드 꺼내기") + void pickThrowException() { + List cards = CardFactory.generate(); + Deck deck = new Deck(cards); + for (int i = 0; i < 52; i++) { + deck.pick(); + } + assertThatThrownBy(() -> deck.pick()) + .isInstanceOf(NullPointerException.class) + .hasMessage("카드를 모두 사용하셨습니다."); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/gamer/DealerTest.java b/src/test/java/blackjack/domain/gamer/DealerTest.java new file mode 100644 index 0000000000..df252c7c5f --- /dev/null +++ b/src/test/java/blackjack/domain/gamer/DealerTest.java @@ -0,0 +1,47 @@ +package blackjack.domain.gamer; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardSymbol; +import blackjack.domain.card.CardType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DealerTest { + + private Dealer dealer; + + @BeforeEach + void setUp() { + dealer = new Dealer(); + } + + @ParameterizedTest + @MethodSource("createCards") + @DisplayName("딜러가 갖고 있는 카드의 합이 16 이하인지 확인") + void shouldReceiveCard1(List cards, boolean shouldDrawCard) { + for (Card card : cards) { + dealer.draw(card); + } + assertThat(dealer.canDrawCard()).isEqualTo(shouldDrawCard); + } + + private static Stream createCards() { + Card aceSpade = new Card(CardSymbol.ACE, CardType.SPADE); + Card queenClover = new Card(CardSymbol.QUEEN, CardType.CLOVER); + Card kingClover = new Card(CardSymbol.KING, CardType.CLOVER); + List cardSumUnderSixteen = Arrays.asList(aceSpade); + List cardSumOverSixteen = Arrays.asList(aceSpade, queenClover, kingClover); + return Stream.of( + Arguments.of(cardSumUnderSixteen, true), + Arguments.of(cardSumOverSixteen, false)); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/gamer/PlayerTest.java b/src/test/java/blackjack/domain/gamer/PlayerTest.java new file mode 100644 index 0000000000..c2fb1adbc6 --- /dev/null +++ b/src/test/java/blackjack/domain/gamer/PlayerTest.java @@ -0,0 +1,55 @@ +package blackjack.domain.gamer; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardSymbol; +import blackjack.domain.card.CardType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlayerTest { + + private Player player; + + @BeforeEach + void setUp() { + player = new Player("엘리"); + } + + @Test + @DisplayName("플레이어는 이름을 입력받아 생성") + void player() { + assertThat(player.getName()).isEqualTo("엘리"); + } + + @ParameterizedTest + @MethodSource("createCards") + @DisplayName("bust되었는지 확인") + void isBusted(List cards, boolean isBusted) { + for (Card card : cards) { + player.draw(card); + } + + assertThat(player.isBusted()).isEqualTo(isBusted); + } + + private static Stream createCards() { + Card aceSpade = new Card(CardSymbol.ACE, CardType.SPADE); + Card queenClover = new Card(CardSymbol.QUEEN, CardType.CLOVER); + Card kingClover = new Card(CardSymbol.KING, CardType.CLOVER); + List notBustedCards = Arrays.asList(aceSpade, queenClover, kingClover); + List bustedCards = Arrays.asList(kingClover, kingClover, kingClover); + return Stream.of( + Arguments.of(notBustedCards, false), + Arguments.of(bustedCards, true)); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/result/BlackJackResultTest.java b/src/test/java/blackjack/domain/result/BlackJackResultTest.java new file mode 100644 index 0000000000..ea4d0a5275 --- /dev/null +++ b/src/test/java/blackjack/domain/result/BlackJackResultTest.java @@ -0,0 +1,17 @@ +package blackjack.domain.result; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class BlackJackResultTest { + + @Test + @DisplayName("승/무/패의 반대를 반환하는 메소드") + void reversed() { + assertThat(BlackJackResult.LOSE.reversed()).isEqualTo(BlackJackResult.WIN); + assertThat(BlackJackResult.WIN.reversed()).isEqualTo(BlackJackResult.LOSE); + assertThat(BlackJackResult.DRAW.reversed()).isEqualTo(BlackJackResult.DRAW); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/result/PlayerResultMatcherTest.java b/src/test/java/blackjack/domain/result/PlayerResultMatcherTest.java new file mode 100644 index 0000000000..5b1a603b10 --- /dev/null +++ b/src/test/java/blackjack/domain/result/PlayerResultMatcherTest.java @@ -0,0 +1,70 @@ +package blackjack.domain.result; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardSymbol; +import blackjack.domain.card.CardType; +import blackjack.domain.gamer.Dealer; +import blackjack.domain.gamer.Gamer; +import blackjack.domain.gamer.Player; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlayerResultMatcherTest { + + private Dealer dealer; + private Player player; + + @BeforeEach + void setUp() { + dealer = new Dealer(); + player = new Player("pobi"); + } + + @Test + @DisplayName("player가 버스트 되면 무조건 패배") + void playerBusted() { + makeGamerBusted(player); + assertThat(PlayerResultMatcher.match(dealer, player)).isEqualTo(BlackJackResult.LOSE); + } + + @Test + @DisplayName("player 버스트 x, dealer 버스트 o") + void dealerBusted() { + makeGamerBusted(dealer); + assertThat(PlayerResultMatcher.match(dealer, player)).isEqualTo(BlackJackResult.WIN); + } + + @Test + @DisplayName("player 버스트 x, dealer 버스트 x, 플레이어 승") + void playerWin() { + dealer.draw(new Card(CardSymbol.KING, CardType.CLOVER)); + player.draw(new Card(CardSymbol.ACE, CardType.CLOVER)); + assertThat(PlayerResultMatcher.match(dealer, player)).isEqualTo(BlackJackResult.WIN); + } + + @Test + @DisplayName("player 버스트 x, dealer 버스트 x, 플레이어 무") + void playerDraw() { + dealer.draw(new Card(CardSymbol.KING, CardType.CLOVER)); + player.draw(new Card(CardSymbol.KING, CardType.DIAMOND)); + assertThat(PlayerResultMatcher.match(dealer, player)).isEqualTo(BlackJackResult.DRAW); + } + + @Test + @DisplayName("player 버스트 x, dealer 버스트 x, 플레이어 패") + void playerLose() { + dealer.draw(new Card(CardSymbol.ACE, CardType.CLOVER)); + player.draw(new Card(CardSymbol.KING, CardType.DIAMOND)); + assertThat(PlayerResultMatcher.match(dealer, player)).isEqualTo(BlackJackResult.LOSE); + } + + private void makeGamerBusted(Gamer gamer) { + gamer.draw(new Card(CardSymbol.KING, CardType.SPADE)); + gamer.draw(new Card(CardSymbol.KING, CardType.CLOVER)); + gamer.draw(new Card(CardSymbol.KING, CardType.DIAMOND)); + } + +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/rule/HandCalculatorTest.java b/src/test/java/blackjack/domain/rule/HandCalculatorTest.java new file mode 100644 index 0000000000..d0fc94ce5b --- /dev/null +++ b/src/test/java/blackjack/domain/rule/HandCalculatorTest.java @@ -0,0 +1,39 @@ +package blackjack.domain.rule; + +import blackjack.domain.card.Card; +import blackjack.domain.card.CardSymbol; +import blackjack.domain.card.CardType; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class HandCalculatorTest { + + @ParameterizedTest + @MethodSource("createCards") + @DisplayName("카드의 합을 구하는 테스트") + void calculateSum(List cards, int result) { + assertThat(HandCalculator.calculate(cards)).isEqualTo(result); + } + + private static Stream createCards() { + Card aceSpade = new Card(CardSymbol.ACE, CardType.SPADE); + Card fiveClover = new Card(CardSymbol.FIVE, CardType.CLOVER); + Card queenClover = new Card(CardSymbol.QUEEN, CardType.CLOVER); + Card kingClover = new Card(CardSymbol.KING, CardType.CLOVER); + List cardSumSixteen = Arrays.asList(aceSpade, fiveClover); + List cardSumTwentyOne = Arrays.asList(aceSpade, queenClover, kingClover); + List cardSumThirteen = Arrays.asList(aceSpade, aceSpade, aceSpade); + return Stream.of( + Arguments.of(cardSumSixteen, 16), + Arguments.of(cardSumTwentyOne, 21), + Arguments.of(cardSumThirteen, 13)); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/domain/rule/PlayerAnswerTest.java b/src/test/java/blackjack/domain/rule/PlayerAnswerTest.java new file mode 100644 index 0000000000..57cbcd4d17 --- /dev/null +++ b/src/test/java/blackjack/domain/rule/PlayerAnswerTest.java @@ -0,0 +1,16 @@ +package blackjack.domain.rule; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class PlayerAnswerTest { + + @Test + @DisplayName("y와 n을 PlayerAnswer 객체로 포장") + void yesOrNo() { + assertThat(PlayerAnswer.of("y")).isEqualTo(PlayerAnswer.Y); + assertThat(PlayerAnswer.of("n")).isEqualTo(PlayerAnswer.N); + } +} \ No newline at end of file diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/test/java/interfacepractice/RentCompanyTest.java b/src/test/java/interfacepractice/RentCompanyTest.java new file mode 100644 index 0000000000..a3c7d9496c --- /dev/null +++ b/src/test/java/interfacepractice/RentCompanyTest.java @@ -0,0 +1,29 @@ +package interfacepractice; + +import org.junit.jupiter.api.Test; + +import static interfacepractice.RentCompany.NEWLINE; +import static org.assertj.core.api.Assertions.assertThat; + +public class RentCompanyTest { + + @Test + public void report() { + RentCompany company = RentCompany.create(); // factory method를 사용해 생성 + company.addCar(new Sonata(150)); + company.addCar(new K5(260)); + company.addCar(new Sonata(120)); + company.addCar(new Avante(300)); + company.addCar(new K5(390)); + + String report = company.generateReport(); + assertThat(report).isEqualTo( + "Sonata : 15리터" + NEWLINE + + "K5 : 20리터" + NEWLINE + + "Sonata : 12리터" + NEWLINE + + "Avante : 20리터" + NEWLINE + + "K5 : 30리터" + NEWLINE + ); + } + +} \ No newline at end of file