diff --git a/README.md b/README.md index a05a3c0ce7b..ce68b6ffbb9 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,41 @@ 블랙잭 미션 저장소 ## [미션 리드미](https://github.com/talmood/private-mission-README/tree/main/%EB%AF%B8%EC%85%98%204%20-%20%EB%B8%94%EB%9E%99%EC%9E%AD) + + +## 기능 명세서 +- 카드 + - [ ] 카드는 조커를 제외한 52장의 카드로 구성된다. + - [ ] King, Queen, Jack은 각각 10점으로 계산한다. + - [ ] Ace는 1점 또는 11점으로 계산한다. + +- 딜러 + - [ ] 딜러는 처음에 받은 카드 2장의 합이 16점 이하이면 반드시 1장의 카드를 더 받아야 한다. + - [ ] 딜러는 17점 이상이면 추가로 카드를 받을 수 없다. + +- 플레이어 + - [ ] 플레이어는 카드의 합이 21점을 초과하면 패배한다. + - [ ] 플레이어는 카드의 합이 21점을 넘지 않으면 원하는 만큼 카드를 추가로 더 받을 수 있다. + - [ ] 플레이어는 게임 종료 시 딜러와 자신의 카드를 비교하여 승패를 가린다. + +- 게임 + - [ ] 딜러와 플레이어 중 카드의 합이 21을 초과하지 않으면서 21에 가장 가까운 쪽이 승리한다. + - [ ] 딜러와 플레이어의 카드의 합이 같으면 딜러가 승리한다. + - [ ] 딜러와 플레이어가 블랙잭인 경우 무승부로 처리한다. + - [ ] 게임을 시작하면 딜러와 플레이어에게 각각 2장의 카드를 나눠준다. + - [ ] 게임을 종료하면 승패를 가린다. +- 결과 + - [ ] 게임 종료 후 승패 결과를 보여준다. + - 계산 + - 플레이어가 이기는 경우 + - [ ] 플레이어가 블랙잭이면서 딜러가 블랙잭이 아닌 경우 + - [ ] 플레이어가 버스트가 아니면서 딜러가 버스트인 경우 + - [ ] 플레이어, 딜러 모두 버스트가 아니면서 플레이어가 딜러보다 점수가 높은 경우 + - 플레이어가 지는 경우 + - [ ] 딜러가 블랙잭이면서 플레이어가 블랙잭이 아닌 경우 + - [ ] 딜러가 버스트가 아니면서 플레이어가 버스트인 경우 + - [ ] 플레이어, 딜러 모두 버스트가 아니면서 플레이어가 딜러보다 점수가 낮은 경우 + - 무승부 + - [ ] 플레이어, 딜러 모두 블랙잭인 경우 + - [ ] 플레이어, 딜러 모두 버스트인 경우 + - [ ] 플레이어, 딜러 모두 버스트가 아니면서 플레이어와 딜러의 점수가 같은 경우 diff --git a/src/main/java/blackjack/Application.java b/src/main/java/blackjack/Application.java new file mode 100644 index 00000000000..4c4ae07f742 --- /dev/null +++ b/src/main/java/blackjack/Application.java @@ -0,0 +1,12 @@ +package blackjack; + + +import blackjack.controller.BlackJackGameController; + +public class Application { + + public static void main(String[] args) { + final BlackJackGameController blackJackGameController = new BlackJackGameController(); + blackJackGameController.run(); + } +} diff --git a/src/main/java/blackjack/controller/BlackJackGameController.java b/src/main/java/blackjack/controller/BlackJackGameController.java new file mode 100644 index 00000000000..9dfa218163b --- /dev/null +++ b/src/main/java/blackjack/controller/BlackJackGameController.java @@ -0,0 +1,35 @@ +package blackjack.controller; + +import blackjack.domain.card.CardDeck; +import blackjack.domain.game.BlackJackGame; +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Name; +import blackjack.domain.participant.Player; +import blackjack.domain.participant.Players; +import blackjack.view.InputView; + +import java.util.List; +import java.util.stream.Collectors; + +public class BlackJackGameController { + + public void run() { + final CardDeck cardDeck = CardDeck.create(); + cardDeck.shuffle(); + + final Dealer dealer = Dealer.create(); + final Players players = initPlayers(); + + final BlackJackGame blackJackGame = new BlackJackGame(cardDeck, players, dealer); + blackJackGame.play(); + } + + private Players initPlayers() { + final List playerNames = InputView.inputPlayerNames(); + final List players = playerNames.stream() + .map(name -> Player.of(Name.of(name))) + .collect(Collectors.toList()); + + return Players.of(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..e936d0a0c18 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Card.java @@ -0,0 +1,45 @@ +package blackjack.domain.card; + +import java.util.Objects; + +public class Card { + private final Suit suit; + private final Rank rank; + + private Card(final Suit suit, final Rank rank) { + this.suit = suit; + this.rank = rank; + } + + public static Card of(final String suit, final String rank) { + return new Card(Suit.of(suit), Rank.of(rank)); + } + + public static Card of(final Suit suit, final Rank rank) { + return new Card(suit, rank); + } + + public Suit getSuit() { + return suit; + } + + public Rank getRank() { + return rank; + } + + public String showCardInfo() { + return rank.getName() + suit.getName(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Card card)) return false; + return suit == card.suit && rank == card.rank; + } + + @Override + public int hashCode() { + return Objects.hash(suit, rank); + } +} diff --git a/src/main/java/blackjack/domain/card/CardDeck.java b/src/main/java/blackjack/domain/card/CardDeck.java new file mode 100644 index 00000000000..83eefee0f9c --- /dev/null +++ b/src/main/java/blackjack/domain/card/CardDeck.java @@ -0,0 +1,73 @@ +package blackjack.domain.card; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class CardDeck { + private static final int DECK_SIZE = 52; + private static final String DECK_EMPTY_MESSAGE = "덱이 비었습니다."; + private static final String DECK_SIZE_MESSAGE = String.format("덱의 크기는 %d 입니다.", DECK_SIZE); + private static final String DUPLICATE_CARD_MESSAGE = "중복된 카드가 존재합니다."; + + private final List cards; + + private CardDeck() { + final List initializedCards = initialize(); + validate(initializedCards); + this.cards = initializedCards; + } + + public static CardDeck create() { + return new CardDeck(); + } + + private static List initialize() { + return Arrays.stream(Suit.values()) + .flatMap(suit -> Arrays.stream(Rank.values()) + .map(rank -> Card.of(suit, rank))) + .collect(Collectors.toList()); + } + + private static void validate(final List cards) { + if (cards.isEmpty()) { + throw new IllegalArgumentException(DECK_EMPTY_MESSAGE); + } + if (cards.size() != DECK_SIZE) { + throw new IllegalArgumentException(DECK_SIZE_MESSAGE); + } + if (cards.stream().distinct().count() != DECK_SIZE) { + throw new IllegalArgumentException(DUPLICATE_CARD_MESSAGE); + } + } + + public void shuffle() { + Collections.shuffle(cards); + } + + public List getCards() { + return List.copyOf(cards); + } + + + /** + * 초기 카드 2장을 뽑고, 덱에서 제거 + * @return List 초기 카드 2장 + */ + public List drawInitialCards() { + return List.of(draw(), draw()); + } + + /** + * 카드를 뽑고, 덱에서 제거 + * @return Card 뽑은 카드 + */ + public Card draw() { + if (cards.isEmpty()) { + throw new IllegalArgumentException(DECK_EMPTY_MESSAGE); + } + // 마지막 카드를 뽑아내고, 덱에서 제거 + return cards.remove(cards.size() - 1); + } +} diff --git a/src/main/java/blackjack/domain/card/Cards.java b/src/main/java/blackjack/domain/card/Cards.java new file mode 100644 index 00000000000..46c83af584e --- /dev/null +++ b/src/main/java/blackjack/domain/card/Cards.java @@ -0,0 +1,54 @@ +package blackjack.domain.card; + +import java.util.List; + +public class Cards { + private final List cards; + + private Cards(final List cards) { + this.cards = cards; + } + + public static Cards of(final List cards) { + return new Cards(cards); + } + + public void add(final Card card) { + cards.add(card); + } + + public List showCardsInfo() { + return cards.stream() + .map(Card::showCardInfo) + .toList(); + } + + public boolean isBlackjack() { + return calculateScore() == 21 && cards.size() == 2; + } + + public int calculateScore() { + + int totalScore = cards.stream() + .mapToInt(card -> card.getRank().getScore()) + .sum(); + + int aceCount = (int) cards.stream() + .map(Card::getRank) + .filter(Rank.ACE::equals) + .count(); + + return calculateAceScore(totalScore, aceCount); + } + + private int calculateAceScore(final int totalScore, int aceCount) { + int score = totalScore; + + while (aceCount > 0 && score + 10 <= 21) { + score += 10; + aceCount--; + } + + return score; + } +} diff --git a/src/main/java/blackjack/domain/card/Rank.java b/src/main/java/blackjack/domain/card/Rank.java new file mode 100644 index 00000000000..9417c0282b0 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Rank.java @@ -0,0 +1,41 @@ +package blackjack.domain.card; + +public enum Rank { + ACE("A", 1), + 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("J", 10), + QUEEN("Q", 10), + KING("K", 10); + + private final String name; + private final int score; + + Rank(final String name, final int score) { + this.name = name; + this.score = score; + } + + public static Rank of(final String name) { + return Rank.valueOf(name); + } + + public String getName() { + return name; + } + + public int getScore() { + return score; + } + + public boolean isAce() { + return this == ACE; + } +} diff --git a/src/main/java/blackjack/domain/card/Suit.java b/src/main/java/blackjack/domain/card/Suit.java new file mode 100644 index 00000000000..67860ac2e0c --- /dev/null +++ b/src/main/java/blackjack/domain/card/Suit.java @@ -0,0 +1,27 @@ +package blackjack.domain.card; + +import java.util.Arrays; + +public enum Suit { + SPADE("스페이드"), + HEART("하트"), + DIAMOND("다이아몬드"), + CLOVER("클로버"); + + private final String name; + + Suit(final String name) { + this.name = name; + } + + public static Suit of(final String name) { + return Arrays.stream(Suit.values()) + .filter(suit -> suit.name.equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 카드 모양입니다.")); + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/blackjack/domain/game/BlackJackGame.java b/src/main/java/blackjack/domain/game/BlackJackGame.java new file mode 100644 index 00000000000..cfe00a2b91e --- /dev/null +++ b/src/main/java/blackjack/domain/game/BlackJackGame.java @@ -0,0 +1,80 @@ +package blackjack.domain.game; + +import blackjack.domain.card.CardDeck; +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Player; +import blackjack.domain.participant.Players; +import blackjack.domain.result.DealerResult; +import blackjack.domain.result.PlayerResult; +import blackjack.view.InputView; +import blackjack.view.ResultView; + +import java.util.List; + +import static blackjack.view.ResultView.printBlankLine; + +public class BlackJackGame { + private final CardDeck cardDeck; + private final Players players; + private final Dealer dealer; + + public BlackJackGame(final CardDeck cardDeck, final Players players, final Dealer dealer) { + this.cardDeck = cardDeck; + this.players = players; + this.dealer = dealer; + } + + public void play() { + drawInitialCards(cardDeck); + if (!isGameOver()) { + playersTurn(); + dealerTurn(); + } + printResult(); + judge(); + } + + private boolean isGameOver() { + return dealer.isBlackjack() || players.isAllBlackjack(); + } + + private void drawInitialCards(final CardDeck cardDeck) { + dealer.drawInitialCards(cardDeck); + players.drawInitialCards(cardDeck); + ResultView.printShareInitialCards(players, dealer); + } + + private void playersTurn() { + for (final Player player : players.getPlayers()) { + playerTurn(player); + } + } + + private void playerTurn(final Player player) { + printBlankLine(); + while (player.canDrawCard() && InputView.printOrDrawOrStop(player.getName())) { + player.drawCard(cardDeck); + ResultView.printCardInfo(player); + } + } + + private void dealerTurn() { + printBlankLine(); + while (dealer.canDrawCard()) { + dealer.drawCard(cardDeck); + ResultView.printDealerDrawCard(); + } + } + + private void printResult() { + printBlankLine(); + ResultView.printCardInfoWithScore(dealer); + players.getPlayers().forEach(ResultView::printCardInfoWithScore); + } + + private void judge() { + final List playersResults = players.createResults(dealer); + final DealerResult dealerResult = dealer.createResult(playersResults); + ResultView.printResult(playersResults, dealerResult); + } +} diff --git a/src/main/java/blackjack/domain/participant/Dealer.java b/src/main/java/blackjack/domain/participant/Dealer.java new file mode 100644 index 00000000000..8507fb4dab7 --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Dealer.java @@ -0,0 +1,28 @@ +package blackjack.domain.participant; + +import blackjack.domain.result.DealerResult; +import blackjack.domain.result.PlayerResult; + +import java.util.List; + +public class Dealer extends Participant { + + private static final int DEALER_DRAW_THRESHOLD = 17; + + private Dealer(final Name name) { + super(name); + } + + @Override + public boolean canDrawCard() { + return cards.calculateScore() < DEALER_DRAW_THRESHOLD; + } + + public static Dealer create() { + return new Dealer(Name.of("딜러")); + } + + public DealerResult createResult(final List playerResults) { + return new DealerResult(playerResults); + } +} diff --git a/src/main/java/blackjack/domain/participant/Name.java b/src/main/java/blackjack/domain/participant/Name.java new file mode 100644 index 00000000000..a733bfbcdff --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Name.java @@ -0,0 +1,38 @@ +package blackjack.domain.participant; + +import java.util.Objects; + +public class Name { + private final String name; + + private Name(final String name) { + validate(name); + this.name = name; + } + + public static Name of(final String name) { + return new Name(name); + } + + private static void validate(final String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("이름은 null 이거나 빈 문자열일 수 없습니다."); + } + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Name name1)) return false; + return Objects.equals(getName(), name1.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(getName()); + } +} diff --git a/src/main/java/blackjack/domain/participant/Participant.java b/src/main/java/blackjack/domain/participant/Participant.java new file mode 100644 index 00000000000..e043aa280bb --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Participant.java @@ -0,0 +1,45 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.CardDeck; +import blackjack.domain.card.Cards; + +import java.util.ArrayList; + +public abstract class Participant { + protected Name name; + protected Cards cards; + + protected Participant(final Name name) { + this.name = name; + this.cards = Cards.of(new ArrayList<>()); + } + + public void drawInitialCards(final CardDeck cardDeck) { + cardDeck.drawInitialCards().forEach(cards::add); + } + + public void drawCard(final CardDeck cardDeck) { + cards.add(cardDeck.draw()); + } + + public boolean isBlackjack() { + return cards.isBlackjack(); + } + + public boolean isBust() { + return cards.calculateScore() > 21; + } + + public int calculateScore() { + return cards.calculateScore(); + } + public abstract boolean canDrawCard(); + + public String getName() { + return name.getName(); + } + + public Cards getCards() { + return cards; + } +} diff --git a/src/main/java/blackjack/domain/participant/Player.java b/src/main/java/blackjack/domain/participant/Player.java new file mode 100644 index 00000000000..f06578ef0b5 --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Player.java @@ -0,0 +1,27 @@ +package blackjack.domain.participant; + +import blackjack.domain.result.PlayerResult; +import blackjack.domain.result.Result; + +public class Player extends Participant { + + private static final int PLAYER_DRAW_THRESHOLD = 21; + + private Player(final Name name) { + super(name); + } + + @Override + public boolean canDrawCard() { + return cards.calculateScore() < PLAYER_DRAW_THRESHOLD; + } + + public static Player of(final Name name) { + return new Player(name); + } + + public PlayerResult createResult(final Dealer dealer) { + final Result result = Result.checkResult(this, dealer); + return PlayerResult.of(name.getName(), result); + } +} diff --git a/src/main/java/blackjack/domain/participant/Players.java b/src/main/java/blackjack/domain/participant/Players.java new file mode 100644 index 00000000000..6c28eca5eb8 --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Players.java @@ -0,0 +1,46 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.CardDeck; +import blackjack.domain.result.PlayerResult; + +import java.util.List; + +public class Players { + private final List players; + + private Players(final List players) { + if (players.isEmpty()) { + throw new IllegalArgumentException("플레이어는 최소 한 명 이상이어야 합니다."); + } + this.players = players; + } + + public static Players of(final List players) { + return new Players(players); + } + + public void drawInitialCards(final CardDeck cardDeck) { + players.forEach(player -> player.drawInitialCards(cardDeck)); + } + + public List getPlayers() { + return players; + } + + public List getPlayerNames() { + return players.stream() + .map(Player::getName) + .toList(); + } + + public boolean isAllBlackjack() { + return players.stream() + .allMatch(Player::isBlackjack); + } + + public List createResults(final Dealer dealer) { + return players.stream() + .map(player -> player.createResult(dealer)) + .toList(); + } +} diff --git a/src/main/java/blackjack/domain/result/DealerResult.java b/src/main/java/blackjack/domain/result/DealerResult.java new file mode 100644 index 00000000000..5c83f0fa35a --- /dev/null +++ b/src/main/java/blackjack/domain/result/DealerResult.java @@ -0,0 +1,30 @@ +package blackjack.domain.result; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public class DealerResult { + private final Map result; + + public DealerResult(final List playerResults) { + this.result = new EnumMap<>(Result.class); + calculateResultCount(playerResults); + } + + private void calculateResultCount(final List playerResults) { + for (final PlayerResult playerResult : playerResults) { + addCount(Result.reverse(playerResult.getResult())); + } + } + + private void addCount(final Result result) { + this.result.put(result, this.result.getOrDefault(result, 0) + 1); + } + + public Map getResult() { + return Collections.unmodifiableMap(result); + } + +} diff --git a/src/main/java/blackjack/domain/result/ParticipantResult.java b/src/main/java/blackjack/domain/result/ParticipantResult.java new file mode 100644 index 00000000000..e9ae3c9db8e --- /dev/null +++ b/src/main/java/blackjack/domain/result/ParticipantResult.java @@ -0,0 +1,21 @@ +package blackjack.domain.result; + +public class ParticipantResult { + + private final String name; + private final Result result; + + public ParticipantResult(final String name, final Result result) { + this.name = name; + this.result = result; + } + + public String getName() { + return name; + } + + public Result getResult() { + return result; + } + +} diff --git a/src/main/java/blackjack/domain/result/PlayerResult.java b/src/main/java/blackjack/domain/result/PlayerResult.java new file mode 100644 index 00000000000..c085e343bd5 --- /dev/null +++ b/src/main/java/blackjack/domain/result/PlayerResult.java @@ -0,0 +1,23 @@ +package blackjack.domain.result; + +public class PlayerResult { + private final String name; + private final Result result; + + private PlayerResult(final String name, final Result result) { + this.name = name; + this.result = result; + } + + public static PlayerResult of(final String name, final Result result) { + return new PlayerResult(name, result); + } + + public String getName() { + return name; + } + + public Result getResult() { + return result; + } +} diff --git a/src/main/java/blackjack/domain/result/Result.java b/src/main/java/blackjack/domain/result/Result.java new file mode 100644 index 00000000000..06842327780 --- /dev/null +++ b/src/main/java/blackjack/domain/result/Result.java @@ -0,0 +1,41 @@ +package blackjack.domain.result; + +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Player; + +import java.util.Arrays; + +public enum Result { + WIN("승", (player, dealer) -> (player.isBlackjack() && !dealer.isBlackjack()) || (player.isBust() && !dealer.isBust()) || (player.calculateScore() > dealer.calculateScore() && !dealer.isBust())), + LOSE("패", (player, dealer) -> (!player.isBlackjack() && dealer.isBlackjack()) || (!player.isBust() && dealer.isBust() && player.calculateScore() < dealer.calculateScore())), + DRAW("무", (player, dealer) -> (player.calculateScore() == dealer.calculateScore()) || (player.isBust() && dealer.isBust()) || (player.isBlackjack() && dealer.isBlackjack())); + + private final String name; + private final ResultDeterminer determiner; + + Result(final String name, final ResultDeterminer determiner) { + this.name = name; + this.determiner = determiner; + } + + public static Result checkResult(final Player player, final Dealer dealer) { + return Arrays.stream(values()) + .filter(result -> result.determiner.compare(player, dealer)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("결과를 판단할 수 없습니다.")); + } + + public static Result reverse(final Result result) { + if (result == WIN) { + return LOSE; + } + if (result == LOSE) { + return WIN; + } + return DRAW; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/blackjack/domain/result/ResultDeterminer.java b/src/main/java/blackjack/domain/result/ResultDeterminer.java new file mode 100644 index 00000000000..88dd5628dc0 --- /dev/null +++ b/src/main/java/blackjack/domain/result/ResultDeterminer.java @@ -0,0 +1,8 @@ +package blackjack.domain.result; + +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Player; + +public interface ResultDeterminer { + boolean compare(Player player, Dealer dealer); +} \ No newline at end of file diff --git a/src/main/java/blackjack/utils/CommaStringSplitter.java b/src/main/java/blackjack/utils/CommaStringSplitter.java new file mode 100644 index 00000000000..2311912b078 --- /dev/null +++ b/src/main/java/blackjack/utils/CommaStringSplitter.java @@ -0,0 +1,21 @@ +package blackjack.utils; + +import java.util.Arrays; +import java.util.List; + +public class CommaStringSplitter { + private static final String DELIMITER = ","; + + private CommaStringSplitter() { + } + + public static List split(final String str) { + if (StringUtil.isBlank(str)) { + throw new IllegalArgumentException("split string must not be blank"); + } + + return Arrays.stream(str.split(DELIMITER)) + .map(String::trim) + .toList(); + } +} diff --git a/src/main/java/blackjack/utils/Console.java b/src/main/java/blackjack/utils/Console.java new file mode 100644 index 00000000000..2a2e57f0229 --- /dev/null +++ b/src/main/java/blackjack/utils/Console.java @@ -0,0 +1,29 @@ +package blackjack.utils; + +import java.util.Scanner; + +public class Console { + + private static Scanner scanner; + + private Console() { + } + + public static String readLine() { + return getInstance().nextLine(); + } + + public static void close() { + if (scanner != null) { + scanner.close(); + scanner = null; + } + } + + private static Scanner getInstance() { + if (scanner == null) { + scanner = new Scanner(System.in); + } + return scanner; + } +} diff --git a/src/main/java/blackjack/utils/StringUtil.java b/src/main/java/blackjack/utils/StringUtil.java new file mode 100644 index 00000000000..80289805e1f --- /dev/null +++ b/src/main/java/blackjack/utils/StringUtil.java @@ -0,0 +1,13 @@ +package blackjack.utils; + +import java.util.Objects; + +public abstract class StringUtil { + + private StringUtil() { + } + + public static boolean isBlank(final String str) { + return Objects.isNull(str) || str.trim().isEmpty(); + } +} diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java new file mode 100644 index 00000000000..0b5f4c416c8 --- /dev/null +++ b/src/main/java/blackjack/view/InputView.java @@ -0,0 +1,35 @@ +package blackjack.view; + +import blackjack.utils.CommaStringSplitter; +import blackjack.utils.Console; + +import java.util.List; + +public class InputView { + private static final String INPUT_PLAYER_NAME = "게임에 참여할 사람의 이름을 입력하세요. (쉼표 기준으로 분리)"; + private static final String ANSWER_YES = "y"; + private static final String ANSWER_NO = "n"; + private static final String PLAYER_ADDITIONAL_DRAW_CARD = "%s는 한장의 카드를 더 받겠습니까? (예는 y, 아니오는 n)"; + private static final String NOT_YES_OR_NO = "y 또는 n 중 하나를 입력해주세요."; + + public static List inputPlayerNames() { + System.out.println(INPUT_PLAYER_NAME); + final List names = CommaStringSplitter.split(Console.readLine()); + System.out.println(); + return names; + } + + public static boolean printOrDrawOrStop(final String playerName) { + System.out.println(PLAYER_ADDITIONAL_DRAW_CARD.formatted(playerName)); + final String answer = Console.readLine(); + if (!isYesOrNo(answer)) { + System.out.println(NOT_YES_OR_NO); + return printOrDrawOrStop(playerName); + } + return ANSWER_YES.equalsIgnoreCase(answer); + } + + private static boolean isYesOrNo(final String answer) { + return ANSWER_YES.equalsIgnoreCase(answer) || ANSWER_NO.equalsIgnoreCase(answer); + } +} diff --git a/src/main/java/blackjack/view/ResultView.java b/src/main/java/blackjack/view/ResultView.java new file mode 100644 index 00000000000..457b2bd4a09 --- /dev/null +++ b/src/main/java/blackjack/view/ResultView.java @@ -0,0 +1,92 @@ +package blackjack.view; + +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Participant; +import blackjack.domain.participant.Players; +import blackjack.domain.result.DealerResult; +import blackjack.domain.result.PlayerResult; +import blackjack.domain.result.Result; + +import java.util.List; +import java.util.Map; + +public class ResultView { + private static final String SHARE_INITIAL_CARDS = "딜러와 %s에게 2장을 나누었습니다."; + private static final String DEALER_DRAW_CARD = "딜러는 16이하라 한장의 카드를 더 받았습니다."; + private static final String CARD_INFO = "%s 카드: %s"; + private static final String CARD_INFO_WITH_SCORE = "%s 카드: %s - 결과: %d"; + private static final String FINAL_RESULT = "## 최종 승패"; + private static final String PARTICIPANT_RESULT = "%s: %s"; + + + public static void printShareInitialCards(final Players players, final Dealer dealer) { + final String playerNames = String.join(", ", players.getPlayerNames()); + System.out.println(String.format(SHARE_INITIAL_CARDS, playerNames)); + + printDealerInitialCards(dealer); + printPlayerInitialCards(players); + } + private static void printDealerInitialCards(final Dealer dealer) { + final String cardsInfo = dealer.getCards().showCardsInfo().get(0); + System.out.println(String.format(CARD_INFO, dealer.getName(), cardsInfo)); + } + + private static void printPlayerInitialCards(final Players players) { + for (Participant player : players.getPlayers()) { + printCardInfo(player); + } + } + + public static void printDealerDrawCard() { + System.out.println(DEALER_DRAW_CARD); + } + + public static void printCardInfo(final Participant participant) { + final String cardsInfo = String.join(", ", participant.getCards().showCardsInfo()); + System.out.println(String.format(CARD_INFO, participant.getName(), cardsInfo)); + } + + public static void printCardInfoWithScore(final Participant participant) { + final String cardsInfo = String.join(", ", participant.getCards().showCardsInfo()); + System.out.println(String.format(CARD_INFO_WITH_SCORE, participant.getName(), cardsInfo, participant.getCards().calculateScore())); + } + + public static void printResult(final List playerResults, final DealerResult dealerResult) { + printBlankLine(); + System.out.println(FINAL_RESULT); + printDealerResult(dealerResult); + printPlayerResults(playerResults); + } + + private static void printDealerResult(final DealerResult dealerResult) { + final Map result = dealerResult.getResult(); + final StringBuilder resultString = new StringBuilder(); + + int winCount = result.getOrDefault(Result.WIN, 0); + int loseCount = result.getOrDefault(Result.LOSE, 0); + int drawCount = result.getOrDefault(Result.DRAW, 0); + + if (winCount > 0) { + resultString.append(winCount).append("승 "); + } + if (loseCount > 0) { + resultString.append(loseCount).append("패 "); + } + if (drawCount > 0) { + resultString.append(drawCount).append("무"); + } + + System.out.println(String.format(PARTICIPANT_RESULT, "딜러", resultString)); + } + + private static void printPlayerResults(final List playerResults) { + for (final PlayerResult playerResult : playerResults) { + System.out.println(String.format(PARTICIPANT_RESULT, playerResult.getName(), playerResult.getResult().getName())); + } + } + + + public static void printBlankLine() { + System.out.println(); + } +} diff --git a/src/test/java/blackjack/domain/card/CardDeckTest.java b/src/test/java/blackjack/domain/card/CardDeckTest.java new file mode 100644 index 00000000000..ce89a301b41 --- /dev/null +++ b/src/test/java/blackjack/domain/card/CardDeckTest.java @@ -0,0 +1,34 @@ +package blackjack.domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CardDeckTest { + + @DisplayName("덱을 생성한다.") + @Test + void create() { + final CardDeck cardDeck = CardDeck.create(); + System.out.println(cardDeck); + assertEquals(52, cardDeck.getCards().size()); + } + + @DisplayName("초기 카드 2장을 뽑고, 덱에서 제거") + @Test + void drawInitialCards() { + final CardDeck cardDeck = CardDeck.create(); + assertEquals(2, cardDeck.drawInitialCards().size()); + } + + @DisplayName("카드를 뽑고, 덱에서 제거") + @Test + void draw() { + final CardDeck cardDeck = CardDeck.create(); + final Card card = cardDeck.draw(); + assertEquals(Suit.CLOVER, card.getSuit()); + assertEquals(Rank.KING, card.getRank()); + assertEquals(51, cardDeck.getCards().size()); + } +} \ No newline at end of file diff --git a/src/test/java/blackjack/view/InputViewTest.java b/src/test/java/blackjack/view/InputViewTest.java new file mode 100644 index 00000000000..e3d1efa7568 --- /dev/null +++ b/src/test/java/blackjack/view/InputViewTest.java @@ -0,0 +1,88 @@ +package blackjack.view; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class InputViewTest { + + private final InputView inputView = new InputView(); + private InputStream originalIn; + + @BeforeEach + void setUp() { + // 현재 입력 스트림을 originalIn에 보관한다. + originalIn = System.in; + } + + @AfterEach + void tearDown() { + // 테스트가 끝나면 System.in을 원래의 originalIn으로 복구한다. + System.setIn(originalIn); + } + + @DisplayName("게임에 참여할 사람의 이름을 입력받는다.") + @Test + void inputPlayerNames() { + // Given + String input = "은정, 금정"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + // When + List names = inputView.inputPlayerNames(); + + // Then + assertEquals(2, names.size()); + assertEquals("은정", names.get(0)); + assertEquals("금정", names.get(1)); + } + + @Test + void inputMoreCard_yes() { + // Given + String input = "y"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + // When + boolean result = inputView.printOrDrawOrStop("은정"); + + // Then + assertTrue(result); + } + + @Test + void inputMoreCard_no() { + + // Given + String input = "n"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + // When + boolean result = inputView.printOrDrawOrStop("은정"); + + // Then + assertFalse(result); + } + + @Test + void inputMoreCard_invalidInput() { + String input = "invalid"; + System.setIn(new ByteArrayInputStream(input.getBytes())); + + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + inputView.printOrDrawOrStop("은정"); + }); + + assertEquals("y 또는 n 중 하나를 입력해주세요.", exception.getMessage()); + } +}