diff --git a/README.md b/README.md index 3bcfc257847..8556219d51a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,35 @@ # java-blackjack 블랙잭 게임 미션 저장소 +## 요구사항 + + [x] 플레이어 이름 입력 + + [x] , 기준으로 분리 + + [x] 미입력, 공백 입력 등 예외처리 + + [x] 배팅 금액 입력 + + [x] 모든 플레이어의 배팅 금액 입력 + + [x] 미입력, 문자입력, 0 이하 입력 등 예외처리 + + [x] 딜러, 플레이어 모두에게 무작위의 2장의 카드 지급 + + [x] 나누어준 카드 출력 + + [x] 딜러는 1장만 출력, 플레이어는 2장 모두 출력 + + [x] 각 플레이어의 카드 합이 21이하인 경우 카드 더 받을지 질문 + + [x] 플레이어 마다 진행, n 입력 혹은 합이 21초과인경우 중지 + + [x] y(Y),n(N) 외의 입력 예외처리 + + [x] y 입력한 경우 무작위의 카드 장한 더 지 + + [x] 카드를 받은 플레이어의 모든 카드 출력 + + [x] 딜러의 경우 16 이하인 경우 카드 한 장 더 지급 + + [x] 딜러와 플레이어의 카드, 점수 합 출력 + + [x] 카드 계산 구현 + + [x] 카드의 숫자 계산은 카드 숫자를 기본으로 함. + + [x] 예외로 Ace는 1 또는 11로 계산 가능 + + [x] ace 제외 카드 합 <= 10 인 경우 ace 는 11로 계산 + + [x] King, Queen, Jack은 각각 10 + + [x] 수익 금액 구현 + + [x] 플레이어의 카드 합만 21 인 경우(블랙잭) 수익은 배팅금액 * 1.5 + + [x] 딜러와 플레이어 모두 블랙잭인 경우 수익은 배팅금액 * 1 + + [x] 합이 21 초과, 지는 경우면 수익은 배팅금액 * -1 + + [x] 21 이하이면서 21에 가까운 경우 수익은 배팅금액 * 1 + + [x] 플레이어와 딜러 무승부인 경우(모두 21 초과, 동점)인 경우 수익은 0 + + ## 우아한테크코스 코드리뷰 * [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) \ No newline at end of file diff --git a/src/main/java/BlackJackApplication.java b/src/main/java/BlackJackApplication.java new file mode 100644 index 00000000000..164edf1333a --- /dev/null +++ b/src/main/java/BlackJackApplication.java @@ -0,0 +1,11 @@ +import controller.Controller; +import view.InputView; +import view.OutputView; + +public class BlackJackApplication { + + public static void main(String[] args) { + Controller controller = new Controller(new InputView(), new OutputView()); + controller.run(); + } +} diff --git a/src/main/java/controller/Controller.java b/src/main/java/controller/Controller.java new file mode 100644 index 00000000000..01d0ced29c4 --- /dev/null +++ b/src/main/java/controller/Controller.java @@ -0,0 +1,78 @@ +package controller; + +import domain.Game; +import domain.card.Deck; +import domain.money.EarningMoney; +import domain.user.*; +import view.InputView; +import view.OutputView; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class Controller { + + private final InputView inputView; + private final OutputView outputView; + private Players players; + private Dealer dealer; + + public Controller(final InputView inputView, final OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + Game game = createGame(); + play(game); + calculateResult(); + } + + private Game createGame() { + List names = inputView.inputNames() + .stream() + .map(Name::new) + .collect(Collectors.toList()); + Map inputs = inputView.inputBettingMonies(names); + players = PlayerFactory.createPlayers(inputs); + dealer = new Dealer(); + return new Game(new Deck()); + } + + private void play(Game game) { + game.drawFirst(players, dealer); + outputView.printFirstCards(dealer, players.getPlayers()); + playPlayers(game); + playDealer(game); + } + + private void playPlayers(Game game) { + for (Player player : players.getPlayers()) { + playPlayer(player, game); + } + } + + private void playPlayer(Player player, Game game) { + while (player.canDrawCard() && inputView.inputWantMoreCard(player)) { + game.draw(player); + outputView.printCards(player); + } + if (player.canDrawCard()) { + player.stay(); + } + } + + private void playDealer(Game game) { + if (dealer.canDrawCard()) { + outputView.printDealerHit(); + game.draw(dealer); + } + } + + private void calculateResult() { + EarningMoney earningMoney = new EarningMoney(); + outputView.printResultCards(dealer, players); + outputView.printEarningMoney(EarningMoney.calculateMoney(dealer, players)); + } +} diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java new file mode 100644 index 00000000000..64d70b622af --- /dev/null +++ b/src/main/java/domain/Game.java @@ -0,0 +1,33 @@ +package domain; + +import domain.card.Card; +import domain.card.Deck; +import domain.user.Dealer; +import domain.user.Participant; +import domain.user.Players; + +import java.util.ArrayList; +import java.util.List; + +public class Game { + public static final int FIRST_DRAW_NUMBER = 2; + + private Deck deck; + + public Game(Deck deck) { + this.deck = deck; + } + + public void drawFirst(Players players, Dealer dealer) { + List drawCards = new ArrayList<>(); + for (int index = 0; index < players.size() * FIRST_DRAW_NUMBER; index++) { + drawCards.add(deck.deal()); + } + players.firstDraw(drawCards); + dealer.drawFirst(deck.deal(), deck.deal()); + } + + public void draw(Participant participant) { + participant.draw(deck.deal()); + } +} diff --git a/src/main/java/domain/card/Card.java b/src/main/java/domain/card/Card.java new file mode 100644 index 00000000000..c80bfd1da48 --- /dev/null +++ b/src/main/java/domain/card/Card.java @@ -0,0 +1,41 @@ +package domain.card; + +import java.util.Objects; + +public class Card { + private final Symbol symbol; + + private final Type type; + + public Card(Symbol symbol, Type type) { + this.symbol = symbol; + this.type = type; + } + + public boolean isAce() { + return symbol == Symbol.ACE; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Card card = (Card) o; + return symbol == card.symbol && + type == card.type; + } + + @Override + public int hashCode() { + return Objects.hash(symbol, type); + } + + @Override + public String toString() { + return symbol + " " + type; + } + + public int getScore() { + return symbol.getScore(); + } +} \ No newline at end of file diff --git a/src/main/java/domain/card/CardFactory.java b/src/main/java/domain/card/CardFactory.java new file mode 100644 index 00000000000..a8bcd2d4f3c --- /dev/null +++ b/src/main/java/domain/card/CardFactory.java @@ -0,0 +1,23 @@ +package domain.card; + +import java.util.ArrayList; +import java.util.List; + +public class CardFactory { + + public static List create() { + List cards = new ArrayList<>(); + Symbol[] symbols = Symbol.values(); + for (Symbol symbol : symbols) { + createByType(cards, symbol); + } + return cards; + } + + private static void createByType(final List cards, final Symbol symbol) { + Type[] types = Type.values(); + for (Type type : types) { + cards.add(new Card(symbol, type)); + } + } +} \ No newline at end of file diff --git a/src/main/java/domain/card/Deck.java b/src/main/java/domain/card/Deck.java new file mode 100644 index 00000000000..eac522f268d --- /dev/null +++ b/src/main/java/domain/card/Deck.java @@ -0,0 +1,17 @@ +package domain.card; + +import java.util.Collections; +import java.util.List; + +public class Deck { + private final List cards; + + public Deck() { + cards = CardFactory.create(); + Collections.shuffle(cards); + } + + public Card deal() { + return cards.remove(0); + } +} \ No newline at end of file diff --git a/src/main/java/domain/card/Hands.java b/src/main/java/domain/card/Hands.java new file mode 100644 index 00000000000..ba4ad592bdd --- /dev/null +++ b/src/main/java/domain/card/Hands.java @@ -0,0 +1,71 @@ +package domain.card; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static domain.Game.FIRST_DRAW_NUMBER; + +public class Hands { + public static final int TEN = 10; + public static final int ELEVEN = 11; + public static final int BLACKJACK = 21; + public static final int DEALER_BUST_NUMBER = 17; + + private final List cards; + + public Hands() { + this.cards = new ArrayList<>(); + } + + public List getCards() { + return Collections.unmodifiableList(cards); + } + + public void add(final Card card) { + cards.add(card); + } + + public boolean isBust() { + return sum() > BLACKJACK; + } + + public int sum() { + int sum = cards.stream() + .mapToInt(Card::getScore) + .sum(); + if (isAceNotExist() || isAceOne(sum)) { + return sum; + } + return sum + TEN; + } + + private boolean isAceOne(int sum) { + return isAceExist() && sum > ELEVEN; + } + + private boolean isAceExist() { + return cards.stream() + .anyMatch(Card::isAce); + } + + private boolean isAceNotExist() { + return !isAceExist(); + } + + public boolean isBlackjack() { + return cards.size() == FIRST_DRAW_NUMBER && sum() == BLACKJACK; + } + + public boolean isDealerBust() { + return sum() >= DEALER_BUST_NUMBER; + } + + public boolean isBiggerThan(Hands hands) { + return sum() > hands.sum(); + } + + public boolean isSame(Hands hands) { + return sum() == hands.sum(); + } +} diff --git a/src/main/java/domain/card/Symbol.java b/src/main/java/domain/card/Symbol.java new file mode 100644 index 00000000000..f99f5eaf9a0 --- /dev/null +++ b/src/main/java/domain/card/Symbol.java @@ -0,0 +1,27 @@ +package domain.card; + +public enum Symbol { + ACE(1), + TWO(2), + THREE(3), + FOUR(4), + FIVE(5), + SIX(6), + SEVEN(7), + EIGHT(8), + NINE(9), + TEN(10), + JACK(10), + QUEEN(10), + KING(10); + + private final int score; + + Symbol(final int score) { + this.score = score; + } + + public int getScore() { + return score; + } +} \ No newline at end of file diff --git a/src/main/java/domain/card/Type.java b/src/main/java/domain/card/Type.java new file mode 100644 index 00000000000..566eca75d48 --- /dev/null +++ b/src/main/java/domain/card/Type.java @@ -0,0 +1,8 @@ +package domain.card; + +public enum Type { + SPADE, + DIAMOND, + HEART, + CLUB +} \ No newline at end of file diff --git a/src/main/java/domain/money/BettingMoney.java b/src/main/java/domain/money/BettingMoney.java new file mode 100644 index 00000000000..e9b8f743e16 --- /dev/null +++ b/src/main/java/domain/money/BettingMoney.java @@ -0,0 +1,23 @@ +package domain.money; + +public class BettingMoney { + + public static final int MIN_BETTING_MONEY = 1; + + private final int bettingMoney; + + public BettingMoney(final int bettingMoney) { + validate(bettingMoney); + this.bettingMoney = bettingMoney; + } + + private void validate(final int bettingMoney) { + if (bettingMoney < MIN_BETTING_MONEY) { + throw new IllegalArgumentException("배팅금액은 1원 이상이어야 합니다. 입력금액 : " + bettingMoney); + } + } + + public int getBettingMoney() { + return bettingMoney; + } +} diff --git a/src/main/java/domain/money/EarningMoney.java b/src/main/java/domain/money/EarningMoney.java new file mode 100644 index 00000000000..5799a5e48d8 --- /dev/null +++ b/src/main/java/domain/money/EarningMoney.java @@ -0,0 +1,57 @@ +package domain.money; + +import domain.card.Hands; +import domain.user.Dealer; +import domain.user.Player; +import domain.user.Players; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class EarningMoney { + + public static Map calculateMoney(Dealer dealer, Players players) { + Map earningMonies = new LinkedHashMap<>(); + for (Player player : players.getPlayers()) { + earningMonies.put(player.getName(), calculate(dealer, player)); + } + return earningMonies; + } + + private static double calculate(Dealer dealer, Player player) { + Hands dealerHands = dealer.getHands(); + Hands playerHands = player.getHands(); + if (playerHands.isBust()) { + calculateWhenBust(dealerHands, player); + return player.getState().profit(player.getBettingMoney()); + } + if (playerHands.isBlackjack()) { + calculateWhenBlackjack(dealerHands, player); + return player.getState().profit(player.getBettingMoney()); + } + calculateWhenStay(dealerHands, player); + return player.getState().profit(player.getBettingMoney()); + } + + private static void calculateWhenStay(Hands dealerHands, Player player) { + if (dealerHands.isBust() || player.getHands().isBiggerThan(dealerHands)) { + player.stay(); + } + if (dealerHands.isBiggerThan(player.getHands())) { + player.bust(); + } + } + + private static void calculateWhenBlackjack(Hands dealerHands, Player player) { + if (dealerHands.isBlackjack()) { + player.stay(); + } + } + + private static void calculateWhenBust(Hands dealerHands, Player player) { + if (dealerHands.isBust()) { + player.stay(); + } + } + +} diff --git a/src/main/java/domain/state/Blackjack.java b/src/main/java/domain/state/Blackjack.java new file mode 100644 index 00000000000..adfe6b53d82 --- /dev/null +++ b/src/main/java/domain/state/Blackjack.java @@ -0,0 +1,15 @@ +package domain.state; + +import domain.card.Hands; + +public class Blackjack extends Finished { + + public Blackjack(Hands hands) { + super(hands); + } + + @Override + public double earningRate() { + return 1.5; + } +} diff --git a/src/main/java/domain/state/Bust.java b/src/main/java/domain/state/Bust.java new file mode 100644 index 00000000000..c6a87787802 --- /dev/null +++ b/src/main/java/domain/state/Bust.java @@ -0,0 +1,15 @@ +package domain.state; + +import domain.card.Hands; + +public class Bust extends Finished { + + public Bust(Hands hands) { + super(hands); + } + + @Override + public double earningRate() { + return -1; + } +} diff --git a/src/main/java/domain/state/Finished.java b/src/main/java/domain/state/Finished.java new file mode 100644 index 00000000000..9354e49d8d0 --- /dev/null +++ b/src/main/java/domain/state/Finished.java @@ -0,0 +1,33 @@ +package domain.state; + +import domain.card.Card; +import domain.card.Hands; + +public abstract class Finished extends Started { + + public Finished(Hands hands) { + super(hands); + } + + public abstract double earningRate(); + + @Override + public State draw(Card card) { + throw new UnsupportedOperationException(); + } + + @Override + public State stay() { + throw new UnsupportedOperationException(); + } + + @Override + public double profit(int money) { + return earningRate() * money; + } + + @Override + public boolean isFinished() { + return true; + } +} diff --git a/src/main/java/domain/state/Hit.java b/src/main/java/domain/state/Hit.java new file mode 100644 index 00000000000..8411ac51b5d --- /dev/null +++ b/src/main/java/domain/state/Hit.java @@ -0,0 +1,25 @@ +package domain.state; + +import domain.card.Card; +import domain.card.Hands; + +public class Hit extends Running { + + public Hit(Hands hands) { + super(hands); + } + + @Override + public State draw(Card card) { + hands.add(card); + if (hands.isBust()) { + return new Bust(hands); + } + return new Hit(hands); + } + + @Override + public State stay() { + return new Stay(hands); + } +} diff --git a/src/main/java/domain/state/Running.java b/src/main/java/domain/state/Running.java new file mode 100644 index 00000000000..613b258cbbf --- /dev/null +++ b/src/main/java/domain/state/Running.java @@ -0,0 +1,20 @@ +package domain.state; + +import domain.card.Hands; + +public abstract class Running extends Started { + + public Running(Hands hands) { + super(hands); + } + + @Override + public double profit(int money) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isFinished() { + return false; + } +} diff --git a/src/main/java/domain/state/Started.java b/src/main/java/domain/state/Started.java new file mode 100644 index 00000000000..0a6b1bf7a92 --- /dev/null +++ b/src/main/java/domain/state/Started.java @@ -0,0 +1,16 @@ +package domain.state; + +import domain.card.Hands; + +public abstract class Started implements State { + + protected final Hands hands; + + public Started(Hands hands) { + this.hands = hands; + } + + public Hands hands() { + return this.hands; + } +} diff --git a/src/main/java/domain/state/State.java b/src/main/java/domain/state/State.java new file mode 100644 index 00000000000..e6ddee1ea3b --- /dev/null +++ b/src/main/java/domain/state/State.java @@ -0,0 +1,17 @@ +package domain.state; + +import domain.card.Card; +import domain.card.Hands; + +public interface State { + + State draw(Card card); + + State stay(); + + Hands hands(); + + boolean isFinished(); + + double profit(int money); +} diff --git a/src/main/java/domain/state/StateFactory.java b/src/main/java/domain/state/StateFactory.java new file mode 100644 index 00000000000..e3ebddac42c --- /dev/null +++ b/src/main/java/domain/state/StateFactory.java @@ -0,0 +1,25 @@ +package domain.state; + +import domain.card.Card; +import domain.card.Hands; + +public class StateFactory { + + public static State drawFirst(Card firstCard, Card secondCard) { + Hands hands = new Hands(); + hands.add(firstCard); + hands.add(secondCard); + if (hands.isBlackjack()) { + return new Blackjack(hands); + } + return new Hit(hands); + } + + public static State bust(State state) { + return new Bust(state.hands()); + } + + public static State stay(State state) { + return new Stay(state.hands()); + } +} diff --git a/src/main/java/domain/state/Stay.java b/src/main/java/domain/state/Stay.java new file mode 100644 index 00000000000..692889d628b --- /dev/null +++ b/src/main/java/domain/state/Stay.java @@ -0,0 +1,15 @@ +package domain.state; + +import domain.card.Hands; + +public class Stay extends Finished { + + public Stay(Hands hands) { + super(hands); + } + + @Override + public double earningRate() { + return 1; + } +} diff --git a/src/main/java/domain/user/Dealer.java b/src/main/java/domain/user/Dealer.java new file mode 100644 index 00000000000..85d657b9fd9 --- /dev/null +++ b/src/main/java/domain/user/Dealer.java @@ -0,0 +1,12 @@ +package domain.user; + +public class Dealer extends Participant { + + public Dealer() { + } + + @Override + public boolean canDrawCard() { + return !state.hands().isDealerBust(); + } +} diff --git a/src/main/java/domain/user/Name.java b/src/main/java/domain/user/Name.java new file mode 100644 index 00000000000..92263784018 --- /dev/null +++ b/src/main/java/domain/user/Name.java @@ -0,0 +1,21 @@ +package domain.user; + +public class Name { + + private final String name; + + public Name(final String name) { + validate(name); + this.name = name; + } + + private void validate(final String name) { + if (name == null || name.isEmpty()) { + throw new IllegalArgumentException("이름은 최소 한 글자 이상이어야 합니다."); + } + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/domain/user/Participant.java b/src/main/java/domain/user/Participant.java new file mode 100644 index 00000000000..4cca952f688 --- /dev/null +++ b/src/main/java/domain/user/Participant.java @@ -0,0 +1,37 @@ +package domain.user; + +import domain.card.Card; +import domain.card.Hands; +import domain.state.State; +import domain.state.StateFactory; + +public abstract class Participant { + + protected State state; + + public void draw(final Card card) { + state = state.draw(card); + } + + public void drawFirst(Card firstCard, Card secondCard) { + state = StateFactory.drawFirst(firstCard, secondCard); + } + + public void stay() { + state = StateFactory.stay(state); + } + + public void bust() { + state = StateFactory.bust(state); + } + + public abstract boolean canDrawCard(); + + public State getState() { + return state; + } + + public Hands getHands() { + return state.hands(); + } +} diff --git a/src/main/java/domain/user/Player.java b/src/main/java/domain/user/Player.java new file mode 100644 index 00000000000..9b3e712e0fe --- /dev/null +++ b/src/main/java/domain/user/Player.java @@ -0,0 +1,31 @@ +package domain.user; + +import domain.money.BettingMoney; + +import java.util.Objects; + +public class Player extends Participant { + + private final BettingMoney bettingMoney; + private final Name name; + + public Player(final Name name, final BettingMoney bettingMoney) { + Objects.requireNonNull(name, "null 비허용"); + Objects.requireNonNull(bettingMoney, "null 비허용"); + this.name = name; + this.bettingMoney = bettingMoney; + } + + @Override + public boolean canDrawCard() { + return !state.hands().isBust(); + } + + public String getName() { + return name.getName(); + } + + public int getBettingMoney() { + return bettingMoney.getBettingMoney(); + } +} \ No newline at end of file diff --git a/src/main/java/domain/user/PlayerFactory.java b/src/main/java/domain/user/PlayerFactory.java new file mode 100644 index 00000000000..9c3485dc846 --- /dev/null +++ b/src/main/java/domain/user/PlayerFactory.java @@ -0,0 +1,26 @@ +package domain.user; + +import domain.money.BettingMoney; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class PlayerFactory { + + public static Players createPlayers(final Map inputs) { + validate(inputs); + List players = new ArrayList<>(); + for (Map.Entry input : inputs.entrySet()) { + players.add(new Player( + input.getKey(), new BettingMoney(input.getValue()))); + } + return new Players(players); + } + + private static void validate(final Map inputs) { + if (inputs == null || inputs.isEmpty()) { + throw new IllegalArgumentException("null / empty 값입니다."); + } + } +} diff --git a/src/main/java/domain/user/Players.java b/src/main/java/domain/user/Players.java new file mode 100644 index 00000000000..a18e4694af6 --- /dev/null +++ b/src/main/java/domain/user/Players.java @@ -0,0 +1,43 @@ +package domain.user; + +import domain.card.Card; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Players { + private final List players; + + Players(final List players) { + validate(players); + this.players = players; + } + + private void validate(final List players) { + Objects.requireNonNull(players, "플레이어는 1명 이상이어야 합니다. 입력값 : null"); + if (players.isEmpty()) { + throw new IllegalArgumentException("플레이어는 1명 이상이어야 합니다. 입력값 : empty"); + } + } + + public void firstDraw(List drawCards) { + for (Player player : players) { + player.drawFirst(drawCards.remove(0), drawCards.remove(0)); + } + } + + public void draw(List drawCards) { + for (Player player : players) { + player.draw(drawCards.remove(0)); + } + } + + public int size() { + return players.size(); + } + + public List getPlayers() { + return Collections.unmodifiableList(players); + } +} diff --git a/src/main/java/empty.txt b/src/main/java/empty.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000000..378f8e35b9c --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,34 @@ +package view; + +import domain.user.Name; +import domain.user.Player; + +import java.util.*; + +import static view.YesOrNo.YES; + +public class InputView { + + private final Scanner SCANNER = new Scanner(System.in); + + public List inputNames() { + System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); + return Arrays.asList(SCANNER.nextLine() + .replace(" ", "") + .split(",")); + } + + public Map inputBettingMonies(final List names) { + Map bettingMonies = new LinkedHashMap<>(); + for (Name name : names) { + System.out.println(name.getName() + " 의 배팅 금액은?"); + bettingMonies.put(name, Integer.parseInt(SCANNER.nextLine())); + } + return Collections.unmodifiableMap(bettingMonies); + } + + public boolean inputWantMoreCard(Player player) { + System.out.println(player.getName() + " 은 한 장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)"); + return YES.equals(YesOrNo.of(SCANNER.nextLine())); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000000..e311d566e54 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,70 @@ +package view; + +import domain.card.Card; +import domain.user.Dealer; +import domain.user.Player; +import domain.user.Players; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputView { + + public void printFirstCards(Dealer dealer, List players) { + String names = players.stream() + .map(Player::getName) + .collect(Collectors.joining(", ")); + System.out.println("딜러와 " + names + "에게 2장의 카드를 나누어주었습니다."); + System.out.println("딜러 : " + dealer.getHands().getCards().get(0).toString()); + for (Player player : players) { + String cards = player.getHands() + .getCards() + .stream().map(Card::toString) + .collect(Collectors.joining(", ")); + System.out.println(player.getName() + " : " + cards); + } + System.out.println(); + } + + public void printCards(Player player) { + String cards = player.getHands() + .getCards() + .stream().map(Card::toString) + .collect(Collectors.joining(", ")); + System.out.println(player.getName() + " : " + cards); + } + + public void printDealerHit() { + System.out.println("딜러는 16이하라 한장의 카드를 더 받았습니다.\n"); + } + + public void printResultCards(Dealer dealer, Players players) { + String dealerCards = dealer.getHands() + .getCards() + .stream().map(Card::toString) + .collect(Collectors.joining(", ")); + System.out.println("딜러 : " + dealerCards + " - 결과 : " + dealer.getHands().sum()); + for (Player player : players.getPlayers()) { + String cards = player.getHands() + .getCards() + .stream().map(Card::toString) + .collect(Collectors.joining(", ")); + System.out.println(player.getName() + " : " + cards + " - 결과 : " + player.getHands().sum()); + } + System.out.println(); + } + + public void printEarningMoney(Map earningMonies) { + int dealerEarningMoney = earningMonies.values() + .stream() + .mapToInt(Double::intValue) + .sum() * -1; + System.out.println("## 최종 수익"); + System.out.println("딜러 : " + dealerEarningMoney); + for (Map.Entry money : earningMonies.entrySet()) { + System.out.println(money.getKey() + " : " + money.getValue().intValue()); + } + + } +} diff --git a/src/main/java/view/YesOrNo.java b/src/main/java/view/YesOrNo.java new file mode 100644 index 00000000000..323f72418c1 --- /dev/null +++ b/src/main/java/view/YesOrNo.java @@ -0,0 +1,22 @@ +package view; + +import java.util.Arrays; + +public enum YesOrNo { + + YES("y"), + NO("n"); + + private final String response; + + YesOrNo(final String response) { + this.response = response; + } + + public static YesOrNo of(String response) { + return Arrays.stream(YesOrNo.values()) + .filter(yesOrNo -> yesOrNo.response.equals(response)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("유효하지 않은 답변입니다. 답변 : " + response)); + } +} diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java new file mode 100644 index 00000000000..fcd1c8423fc --- /dev/null +++ b/src/test/java/domain/GameTest.java @@ -0,0 +1,56 @@ +package domain; + +import domain.card.Deck; +import domain.user.Dealer; +import domain.user.Name; +import domain.user.PlayerFactory; +import domain.user.Players; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class GameTest { + + private Deck deck; + private Players players; + private Dealer dealer; + private Game game; + + @BeforeEach + void setUp() { + Map inputs = new HashMap<>(); + inputs.put(new Name("yerin"), 10000); + inputs.put(new Name("orange"), 20000); + inputs.put(new Name("dasom"), 30000); + players = PlayerFactory.createPlayers(inputs); + dealer = new Dealer(); + deck = new Deck(); + game = new Game(deck); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2}) + @DisplayName("게임 시작 시 참가자들에게 카드 두 장씩 부여") + void firstDraw(int index) { + game.drawFirst(players, dealer); + assertThat(dealer.getHands().getCards()).hasSize(2); + assertThat(players.getPlayers().get(index).getHands().getCards()).hasSize(2); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2}) + @DisplayName("한 장의 카드 지급") + void draw(int index) { + firstDraw(index); + game.draw(players.getPlayers().get(index)); + assertThat(players.getPlayers().get(index).getHands().getCards()).hasSize(3); + } + + +} \ No newline at end of file diff --git a/src/test/java/domain/YesOrNoTest.java b/src/test/java/domain/YesOrNoTest.java new file mode 100644 index 00000000000..67ddc4a8698 --- /dev/null +++ b/src/test/java/domain/YesOrNoTest.java @@ -0,0 +1,29 @@ +package domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import view.YesOrNo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class YesOrNoTest { + + @ParameterizedTest + @CsvSource(value = {"y,YES", "n,NO"}) + @DisplayName("정상 반환 확인") + void of(String input, YesOrNo expected) { + assertThat(YesOrNo.of(input)).isEqualTo(expected); + } + + @Test + @DisplayName("비정상 입력 예외처리") + void validateOf() { + assertThatThrownBy(() -> YesOrNo.of("r")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("유효하지 않은 답변입니다. 답변 : r"); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/card/CardFactoryTest.java b/src/test/java/domain/card/CardFactoryTest.java new file mode 100644 index 00000000000..3ae4b2df673 --- /dev/null +++ b/src/test/java/domain/card/CardFactoryTest.java @@ -0,0 +1,20 @@ +package 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; + +class CardFactoryTest { + + @Test + @DisplayName("52장 카드생성 확인") + void create() { + List cards = CardFactory.create(); + assertThat(cards).hasSize(52); + System.out.println(cards); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/card/CardTest.java b/src/test/java/domain/card/CardTest.java new file mode 100644 index 00000000000..a7b9e0778a0 --- /dev/null +++ b/src/test/java/domain/card/CardTest.java @@ -0,0 +1,26 @@ +package domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CardTest { + + @Test + @DisplayName("카드의 점수 반환") + void getScore() { + Card card = new Card(Symbol.EIGHT, Type.DIAMOND); + assertThat(card.getScore()).isEqualTo(8); + } + + @Test + @DisplayName("에이스 카드인지 확인") + void isAce() { + Card cardA = new Card(Symbol.ACE, Type.DIAMOND); + Card card8 = new Card(Symbol.EIGHT, Type.DIAMOND); + assertThat(cardA.isAce()).isTrue(); + assertThat(card8.isAce()).isFalse(); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/card/DeckTest.java b/src/test/java/domain/card/DeckTest.java new file mode 100644 index 00000000000..b7102948a5d --- /dev/null +++ b/src/test/java/domain/card/DeckTest.java @@ -0,0 +1,18 @@ +package domain.card; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class DeckTest { + + @Test + @DisplayName("카드 한 장 나눠주기") + void deal() { + Deck deck = new Deck(); + Card card = deck.deal(); + assertThat(card).isNotNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/card/HandsTest.java b/src/test/java/domain/card/HandsTest.java new file mode 100644 index 00000000000..be9372123ff --- /dev/null +++ b/src/test/java/domain/card/HandsTest.java @@ -0,0 +1,106 @@ +package domain.card; + +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.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class HandsTest { + + private Hands hands; + + @BeforeEach + void setUp() { + hands = new Hands(); + } + + @Test + @DisplayName("카드 한 장 추가") + void add() { + hands.add(new Card(Symbol.ACE, Type.CLUB)); + assertThat(hands.getCards()).hasSize(1); + } + + @Test + @DisplayName("에이스 없는 경우 카드합(17) 구하기") + void sum() { + hands.add(new Card(Symbol.EIGHT, Type.DIAMOND)); + hands.add(new Card(Symbol.NINE, Type.DIAMOND)); + assertThat(hands.sum()).isEqualTo(17); + } + + @Test + @DisplayName("에이스 있는 경우(1로 계산되는 경우) 카드합 구하기") + void sumWhenAceOne() { + hands.add(new Card(Symbol.ACE, Type.DIAMOND)); + hands.add(new Card(Symbol.EIGHT, Type.DIAMOND)); + hands.add(new Card(Symbol.NINE, Type.DIAMOND)); + assertThat(hands.sum()).isEqualTo(18); + } + + @Test + @DisplayName("에이스 있는 경우(11로 계산되는 경우) 카드합 구하기") + void sumWhenAceEleven() { + hands.add(new Card(Symbol.ACE, Type.DIAMOND)); + hands.add(new Card(Symbol.TEN, Type.DIAMOND)); + assertThat(hands.sum()).isEqualTo(21); + } + + @ParameterizedTest + @CsvSource(value = {"TWO,false", "NINE,true"}) + @DisplayName("Bust인지 확인") + void isBust(Symbol symbol, boolean expected) { + hands.add(new Card(symbol, Type.DIAMOND)); + hands.add(new Card(symbol, Type.DIAMOND)); + hands.add(new Card(symbol, Type.DIAMOND)); + assertThat(hands.isBust()).isEqualTo(expected); + } + + @ParameterizedTest + @CsvSource(value = {"JACK,ACE,true", "TWO,THREE,false"}) + @DisplayName("두 장 받았을 때 블랙잭인지 확인") + void isBlackJack(Symbol symbol1, Symbol symbol2, boolean expected) { + hands.add(new Card(symbol1, Type.DIAMOND)); + hands.add(new Card(symbol2, Type.DIAMOND)); + assertThat(hands.isBlackjack()).isEqualTo(expected); + } + + @Test + @DisplayName("세장 이상 받았을 때 합은 21이지만 블랙잭이 아님 확인") + void isBlackjack() { + hands.add(new Card(Symbol.JACK, Type.DIAMOND)); + hands.add(new Card(Symbol.JACK, Type.DIAMOND)); + hands.add(new Card(Symbol.ACE, Type.DIAMOND)); + assertThat(hands.isBlackjack()).isFalse(); + } + + @ParameterizedTest + @CsvSource(value = {"SEVEN, true", "SIX, false"}) + @DisplayName("딜러 버스트 확인") + void isDealerBust(Symbol symbol, boolean expected) { + hands.add(new Card(Symbol.JACK, Type.DIAMOND)); + hands.add(new Card(symbol, Type.DIAMOND)); + assertThat(hands.isDealerBust()).isEqualTo(expected); + } + + @Test + @DisplayName("합이 더 큰지 확인") + void isBiggerThan() { + hands.add(new Card(Symbol.JACK, Type.DIAMOND)); + Hands newHands = new Hands(); + newHands.add(new Card(Symbol.EIGHT, Type.DIAMOND)); + assertThat(newHands.isBiggerThan(hands)).isFalse(); + } + + @Test + @DisplayName("합이 같은지 확인") + void isSame() { + hands.add(new Card(Symbol.JACK, Type.DIAMOND)); + Hands newHands = new Hands(); + newHands.add(new Card(Symbol.JACK, Type.DIAMOND)); + assertThat(newHands.isSame(hands)).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/domain/money/EarningMoneyTest.java b/src/test/java/domain/money/EarningMoneyTest.java new file mode 100644 index 00000000000..a16b1d98abb --- /dev/null +++ b/src/test/java/domain/money/EarningMoneyTest.java @@ -0,0 +1,7 @@ +package domain.money; + +class EarningMoneyTest { + + //Todo 넘 빡쳐서 테스트를 안만듬... + +} \ No newline at end of file diff --git a/src/test/java/domain/state/StateFactoryTest.java b/src/test/java/domain/state/StateFactoryTest.java new file mode 100644 index 00000000000..2a4d75aeb0a --- /dev/null +++ b/src/test/java/domain/state/StateFactoryTest.java @@ -0,0 +1,33 @@ +package domain.state; + +import domain.card.Card; +import domain.card.Hands; +import domain.card.Symbol; +import domain.card.Type; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +class StateFactoryTest { + + @Test + @DisplayName("처음 두장 받은 후 블랙잭인 경우, 블랙잭 반환") + void blackjack() { + Card card1 = new Card(Symbol.ACE, Type.DIAMOND); + Card card2 = new Card(Symbol.JACK, Type.DIAMOND); + assertThat(Objects.requireNonNull(StateFactory.drawFirst(card1, card2)).getClass()).isEqualTo(Blackjack.class); + } + + @Test + @DisplayName("처음 두장 받은 후 블랙잭이 아니면 Hit 반환") + void hit() { + Hands hands = new Hands(); + Card card1 = new Card(Symbol.JACK, Type.DIAMOND); + Card card2 = new Card(Symbol.TWO, Type.DIAMOND); + assertThat(Objects.requireNonNull(StateFactory.drawFirst(card1, card2)).getClass()).isEqualTo(Hit.class); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/user/BettingMoneyTest.java b/src/test/java/domain/user/BettingMoneyTest.java new file mode 100644 index 00000000000..92cd71f4f12 --- /dev/null +++ b/src/test/java/domain/user/BettingMoneyTest.java @@ -0,0 +1,30 @@ +package domain.user; + +import domain.money.BettingMoney; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BettingMoneyTest { + + @Test + @DisplayName("정상 생성 테스트") + void create() { + BettingMoney bettingMoney = new BettingMoney(300); + assertThat(bettingMoney).isNotNull(); + } + + @ParameterizedTest + @ValueSource(ints = {0, -1}) + @DisplayName("유효성 검사") + void validate(int input) { + assertThatThrownBy(() -> new BettingMoney(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("배팅금액은 1원 이상이어야 합니다. 입력금액 : " + input); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/user/DealerTest.java b/src/test/java/domain/user/DealerTest.java new file mode 100644 index 00000000000..68a2610e646 --- /dev/null +++ b/src/test/java/domain/user/DealerTest.java @@ -0,0 +1,22 @@ +package domain.user; + +import domain.card.Card; +import domain.card.Symbol; +import domain.card.Type; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class DealerTest { + + @ParameterizedTest + @CsvSource(value = {"SEVEN, false", "SIX,true"}) + @DisplayName("카드를 더 받을 수 있는지 확인") + void canReceiveCard(Symbol symbol, boolean expected) { + Dealer dealer = new Dealer(); + dealer.drawFirst(new Card(Symbol.TEN, Type.DIAMOND), new Card(symbol, Type.DIAMOND)); + assertThat(dealer.canDrawCard()).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/src/test/java/domain/user/NameTest.java b/src/test/java/domain/user/NameTest.java new file mode 100644 index 00000000000..32c08ee00bf --- /dev/null +++ b/src/test/java/domain/user/NameTest.java @@ -0,0 +1,29 @@ +package domain.user; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class NameTest { + + @Test + @DisplayName("정상 생성 테스트") + void create() { + Name name = new Name("name"); + assertThat(name).isNotNull(); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("이름 입력에 대한 유효성 검사") + void validate(String name) { + assertThatThrownBy(() -> new Name(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이름은 최소 한 글자 이상이어야 합니다."); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/user/PlayerFactoryTest.java b/src/test/java/domain/user/PlayerFactoryTest.java new file mode 100644 index 00000000000..3ea75377ab0 --- /dev/null +++ b/src/test/java/domain/user/PlayerFactoryTest.java @@ -0,0 +1,38 @@ +package domain.user; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PlayerFactoryTest { + + + @Test + @DisplayName("String name 입력받고, Players 정상 생성 확인") + void create() { + Map inputs = new HashMap<>(); + inputs.put(new Name("yerin"), 10000); + inputs.put(new Name("orange"), 10000); + inputs.put(new Name("dasom"), 10000); + Players players = PlayerFactory.createPlayers(inputs); + assertThat(players).isNotNull(); + assertThat(players.getPlayers()).hasSize(3); + } + + @ParameterizedTest + @NullAndEmptySource + @DisplayName("null, 공백 유효성 테스트") + void validate(Map input) { + assertThatThrownBy(() -> PlayerFactory.createPlayers(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("null / empty 값입니다."); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/user/PlayerTest.java b/src/test/java/domain/user/PlayerTest.java new file mode 100644 index 00000000000..008124f747a --- /dev/null +++ b/src/test/java/domain/user/PlayerTest.java @@ -0,0 +1,44 @@ +package domain.user; + +import domain.card.Card; +import domain.card.Symbol; +import domain.card.Type; +import domain.money.BettingMoney; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.NullSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PlayerTest { + + @Test + @DisplayName("정상 생성 테스트") + void create() { + Player player = new Player(new Name("name"), new BettingMoney(30)); + assertThat(player).isNotNull(); + } + + @ParameterizedTest + @NullSource + @DisplayName("생성에 대한 유효성 검사") + void validate(Object object) { + assertThatThrownBy(() -> new Player((Name) object, (BettingMoney) object)) + .isInstanceOf(NullPointerException.class) + .hasMessage("null 비허용"); + } + + @ParameterizedTest + @CsvSource(value = {"ACE, true", "FIVE,false"}) + @DisplayName("카드를 더 받을 수 있는지 확인") + void canReceiveCard(Symbol symbol, boolean expected) { + Player player = new Player(new Name("name"), new BettingMoney(30)); + player.drawFirst(new Card(Symbol.TEN, Type.DIAMOND), new Card(Symbol.TEN, Type.DIAMOND)); + player.draw(new Card(symbol, Type.DIAMOND)); + assertThat(player.canDrawCard()).isEqualTo(expected); + } + +} \ No newline at end of file diff --git a/src/test/java/domain/user/PlayersTest.java b/src/test/java/domain/user/PlayersTest.java new file mode 100644 index 00000000000..48bce972c5d --- /dev/null +++ b/src/test/java/domain/user/PlayersTest.java @@ -0,0 +1,72 @@ +package domain.user; + +import domain.card.Card; +import domain.card.Symbol; +import domain.card.Type; +import domain.money.BettingMoney; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PlayersTest { + + private Players players; + + @Test + @DisplayName("정상생성 테스트") + void create() { + List playerList = Arrays.asList( + new Player(new Name("Dasom"), new BettingMoney(1000)), + new Player(new Name("yerin"), new BettingMoney(1))); + players = new Players(playerList); + assertThat(players).isNotNull(); + } + + @ParameterizedTest + @NullSource + @DisplayName("null 유효성 검사") + void validateNull(List input) { + assertThatThrownBy(() -> new Players(input)) + .isInstanceOf(NullPointerException.class) + .hasMessage("플레이어는 1명 이상이어야 합니다. 입력값 : " + input); + } + + @ParameterizedTest + @EmptySource + @DisplayName("empty 유효성 검사") + void validateEmpty(List input) { + assertThatThrownBy(() -> new Players(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("플레이어는 1명 이상이어야 합니다. 입력값 : empty"); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1}) + @DisplayName("모든 플레이어에게 첫 카드 부여") + void drawFirst(int index) { + create(); + List cards = new ArrayList<>( + Arrays.asList(new Card(Symbol.ACE, Type.CLUB), new Card(Symbol.ACE, Type.DIAMOND), + new Card(Symbol.ACE, Type.CLUB), new Card(Symbol.ACE, Type.CLUB))); + players.firstDraw(cards); + assertThat(players.getPlayers().get(index).getHands().getCards()).hasSize(2); + } + + @Test + @DisplayName("플레이어의 수 구하기") + void size() { + create(); + assertThat(players.size()).isEqualTo(2); + } + +} \ 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 e69de29bb2d..00000000000